Compare commits

..

1 Commits
pool ... v2.0.4

Author SHA1 Message Date
Matthias Bertschy
2156677e04 add kubectl plugin with krew 2023-01-16 11:37:26 +01:00
500 changed files with 11007 additions and 220443 deletions

BIN
.DS_Store vendored Normal file

Binary file not shown.

View File

@@ -41,4 +41,7 @@ put an [x] in the box to get it checked
- [ ] If it is a core feature, I have added thorough tests.
- [ ] New and existing unit tests pass locally with my changes
-->
**Please open the PR against the `dev` branch (Unless the PR contains only documentation changes)**
-->

View File

@@ -1,44 +0,0 @@
name: 'Tag validator and retag'
description: 'This action will check if the tag is rc and create a new tag for release'
inputs:
ORIGINAL_TAG: # id of input
description: 'Original tag'
required: true
default: ${{ github.ref_name }}
SUB_STRING:
description: 'Sub string for rc tag'
required: true
default: "-rc"
outputs:
NEW_TAG:
description: "The new tag for release"
value: ${{ steps.retag.outputs.NEW_TAG }}
runs:
using: "composite"
steps:
- run: |
if [[ -z "${{ inputs.ORIGINAL_TAG }}" ]]; then
echo "The value of ORIGINAL_TAG is ${{ inputs.ORIGINAL_TAG }}"
echo "Setting the value of ORIGINAL_TAG to ${{ github.ref_name }}"
echo ORIGINAL_TAG="${{ github.ref_name }}" >> $GITHUB_ENV
fi
shell: bash
- run: |
if [[ "${{ inputs.ORIGINAL_TAG }}" == *"${{ inputs.SUB_STRING }}"* ]]; then
echo "Release candidate tag found."
else
echo "Release candidate tag not found."
exit 1
fi
shell: bash
- id: retag
run: |
NEW_TAG=
echo "Original tag: ${{ inputs.ORIGINAL_TAG }}"
NEW_TAG=$(echo ${{ inputs.ORIGINAL_TAG }} | awk -F '-rc' '{print $1}')
echo "New tag: $NEW_TAG"
echo "NEW_TAG=$NEW_TAG" >> $GITHUB_OUTPUT
shell: bash

View File

@@ -1,11 +0,0 @@
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
version: 2
updates:
- package-ecosystem: "" # See documentation for possible values
directory: "/" # Location of package manifests
schedule:
interval: "weekly"

View File

@@ -1,71 +0,0 @@
name: 00-pr_scanner
permissions: read-all
on:
pull_request:
types: [opened, reopened, synchronize, ready_for_review]
paths-ignore:
- '**.yaml'
- '**.yml'
- '**.md'
- '**.sh'
- 'website/*'
- 'examples/*'
- 'docs/*'
- 'build/*'
- '.github/*'
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
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
GO111MODULE: ""
GO_VERSION: "1.23"
RELEASE: "latest"
CLIENT: test
secrets: inherit

129
.github/workflows/00-test.yaml vendored Normal file
View File

@@ -0,0 +1,129 @@
name: 00-test
on:
workflow_call:
inputs:
release:
description: 'release'
required: true
type: string
client:
description: 'Client name'
required: true
type: string
jobs:
basic-tests:
name: Create cross-platform build
runs-on: ${{ matrix.os }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
strategy:
matrix:
os: [ubuntu-20.04, macos-latest, windows-latest]
steps:
- uses: actions/checkout@v3
with:
submodules: recursive
- name: Cache Go modules (Linux)
if: matrix.os == 'ubuntu-20.04'
uses: actions/cache@v3
with:
path: |
~/.cache/go-build
~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-
- name: Cache Go modules (macOS)
if: matrix.os == 'macos-latest'
uses: actions/cache@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@v3
with:
path: |
~\AppData\Local\go-build
~\go\pkg\mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-
- name: Set up Go
uses: actions/setup-go@v3
with:
go-version: 1.19
- name: Install MSYS2 & libgit2 (Windows)
shell: cmd
run: .\build.bat all
if: matrix.os == 'windows-latest'
- name: Install libgit2 (Linux/macOS)
run: make libgit2
if: matrix.os != 'windows-latest'
- name: Test core pkg
run: go test "-tags=static,gitenabled" -v ./...
- name: Test httphandler pkg
run: cd httphandler && go test "-tags=static,gitenabled" -v ./...
- name: Build
env:
RELEASE: ${{ inputs.release }}
CLIENT: test
CGO_ENABLED: 1
run: python3 --version && python3 build.py
- name: Smoke Testing (Windows / MacOS)
env:
RELEASE: ${{ inputs.release }}
KUBESCAPE_SKIP_UPDATE_CHECK: "true"
run: python3 smoke_testing/init.py ${PWD}/build/${{ matrix.os }}/kubescape-${{ matrix.os }}
if: matrix.os != 'ubuntu-20.04'
- name: Smoke Testing (Linux)
env:
RELEASE: ${{ inputs.release }}
KUBESCAPE_SKIP_UPDATE_CHECK: "true"
run: python3 smoke_testing/init.py ${PWD}/build/ubuntu-latest/kubescape-ubuntu-latest
if: matrix.os == 'ubuntu-20.04'
- name: golangci-lint
if: matrix.os == 'ubuntu-20.04'
continue-on-error: true
uses: golangci/golangci-lint-action@v3
with:
# Optional: version of golangci-lint to use in form of v1.2 or v1.2.3 or `latest` to use the latest version
version: latest
# Optional: working directory, useful for monorepos
# working-directory: somedir
# Optional: golangci-lint command line arguments.
# args: --issues-exit-code=0
args: --timeout 10m --build-tags=static
#--new-from-rev dev
# Optional: show only new issues if it's a pull request. The default value is `false`.
only-new-issues: true
# Optional: if set to true then the all caching functionality will be complete disabled,
# takes precedence over all other caching options.
# skip-cache: true
# Optional: if set to true then the action don't cache or restore ~/go/pkg.
# skip-pkg-cache: true
# Optional: if set to true then the action don't cache or restore ~/.cache/go-build.
# skip-build-cache: true

View File

@@ -0,0 +1,41 @@
name: 01-create-release
on:
workflow_call:
inputs:
release_name:
description: 'release'
required: true
type: string
tag:
description: 'tag'
required: true
type: string
draft:
description: 'create draft release'
required: false
type: boolean
default: false
outputs:
upload_url:
description: "The first output string"
value: ${{ jobs.release.outputs.upload_url }}
jobs:
release:
name: Create release
runs-on: ubuntu-latest
outputs:
upload_url: ${{ steps.create_release.outputs.upload_url }}
steps:
- name: Create a release
id: create_release
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
release_name: ${{ inputs.release_name }}
tag_name: ${{ inputs.tag }}
draft: ${{ inputs.draft }}
prerelease: false

View File

@@ -0,0 +1,71 @@
name: publish-artifacts
on:
workflow_call:
inputs:
upload_url:
description: 'upload url'
required: true
type: string
release:
description: 'release tag'
required: true
type: string
jobs:
publish-artifacts:
name: Build and publish artifacts
runs-on: ${{ matrix.os }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
strategy:
matrix:
os: [ubuntu-20.04, macos-latest, windows-latest]
steps:
- uses: actions/checkout@v3
with:
submodules: recursive
- name: Set up Go
uses: actions/setup-go@v3
with:
go-version: 1.19
- name: Install MSYS2 & libgit2 (Windows)
shell: cmd
run: .\build.bat all
if: matrix.os == 'windows-latest'
- name: Install libgit2 (Linux/macOS)
run: make libgit2
if: matrix.os != 'windows-latest'
- name: Build
env:
RELEASE: ${{ inputs.release }}
CLIENT: release
CGO_ENABLED: 1
run: python3 --version && python3 build.py
- name: Upload release assets (Windows / MacOS)
id: upload-release-asset-win-macos
uses: shogo82148/actions-upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ inputs.upload_url }}
asset_path: build/${{ matrix.os }}/*
if: matrix.os != 'ubuntu-20.04'
- name: Upload release assets (Linux)
id: upload-release-asset-linux
uses: shogo82148/actions-upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ inputs.upload_url }}
asset_path: build/ubuntu-latest/*
if: matrix.os == 'ubuntu-20.04'
- name: Update new version in krew-index
uses: rajatjindal/krew-release-bot@v0.0.43

View File

@@ -1,112 +0,0 @@
name: 02-create_release
permissions: read-all
on:
push:
tags:
- 'v*.*.*-rc.*'
jobs:
retag:
outputs:
NEW_TAG: ${{ steps.tag-calculator.outputs.NEW_TAG }}
runs-on: ubuntu22-core4-mem16-ssd150
steps:
- uses: actions/checkout@v4
- 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
GO111MODULE: ""
GO_VERSION: "1.23"
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:
RELEASE_NAME: "Release ${{ needs.retag.outputs.NEW_TAG }}"
TAG: ${{ needs.retag.outputs.NEW_TAG }}
DRAFT: false
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
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_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

89
.github/workflows/03-publish-image.yaml vendored Normal file
View File

@@ -0,0 +1,89 @@
name: 03-publish-image
on:
workflow_call:
inputs:
client:
description: 'client name'
required: true
type: string
image_tag:
description: 'image tag'
required: true
type: string
image_name:
description: 'image registry and name'
required: true
type: string
cosign:
required: false
default: false
type: boolean
description: 'run cosign on released image'
support_platforms:
required: false
default: true
type: boolean
description: 'support amd64/arm64'
jobs:
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 whether unity activation requests should be done
id: check-secret-set
env:
QUAYIO_REGISTRY_USERNAME: ${{ secrets.QUAYIO_REGISTRY_USERNAME }}
QUAYIO_REGISTRY_PASSWORD: ${{ secrets.QUAYIO_REGISTRY_PASSWORD }}
run: |
echo "is-secret-set=${{ env.QUAYIO_REGISTRY_USERNAME != '' && env.QUAYIO_REGISTRY_PASSWORD != '' }}" >> $GITHUB_OUTPUT
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
permissions:
id-token: write
packages: write
contents: read
steps:
- uses: actions/checkout@v3
with:
submodules: recursive
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
- name: Set up Docker Buildx
uses: 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
- 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
with:
cosign-release: 'v1.12.0'
- name: sign kubescape container image
if: ${{ inputs.cosign }}
env:
COSIGN_EXPERIMENTAL: "true"
run: |
cosign sign --force ${{ inputs.image_name }}

24
.github/workflows/a-pr-check.yaml vendored Normal file
View File

@@ -0,0 +1,24 @@
name: pr-checks
on:
pull_request:
types: [ edited, opened, synchronize, reopened ]
branches:
- 'master'
- 'main'
- 'dev'
paths-ignore:
- '**.yaml'
- '**.md'
- '**.sh'
- 'website/*'
- 'examples/*'
- 'docs/*'
- 'build/*'
- '.github/*'
jobs:
test:
uses: ./.github/workflows/00-test.yaml
with:
release: ${{ github.ref_name}}
client: test

View File

@@ -1,151 +0,0 @@
name: a-pr-scanner
permissions: read-all
on:
workflow_call:
inputs:
RELEASE:
description: 'release'
required: true
type: string
CLIENT:
description: 'Client name'
required: true
type: string
UNIT_TESTS_PATH:
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
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
submodules: recursive
- uses: actions/setup-go@v4
name: Installing go
with:
go-version: "1.23"
- name: Scanning - Forbidden Licenses (go-licenses)
id: licenses-scan
continue-on-error: true
run: |
echo "## Installing go-licenses tool"
go install github.com/google/go-licenses@latest
echo "## Scanning for forbiden licenses ##"
go-licenses check .
- name: Scanning - Credentials (GitGuardian)
if: ${{ env.GITGUARDIAN_API_KEY }}
continue-on-error: true
id: credentials-scan
uses: GitGuardian/ggshield-action@master
with:
args: -v --all-policies
env:
GITHUB_PUSH_BEFORE_SHA: ${{ github.event.before }}
GITHUB_PUSH_BASE_SHA: ${{ github.event.base }}
GITHUB_PULL_BASE_SHA: ${{ github.event.pull_request.base.sha }}
GITHUB_DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
GITGUARDIAN_API_KEY: ${{ secrets.GITGUARDIAN_API_KEY }}
- name: Scanning - Vulnerabilities (Snyk)
if: ${{ env.SNYK_TOKEN }}
id: vulnerabilities-scan
continue-on-error: true
uses: snyk/actions/golang@master
with:
command: test --all-projects
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
- name: Test coverage
id: unit-test
run: go test -v ${{ inputs.UNIT_TESTS_PATH }} -covermode=count -coverprofile=coverage.out
- 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 }}
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
with:
issue-number: ${{ github.event.pull_request.number }}
body: |
Scan results:
- License scan: ${{ steps.licenses-scan.outcome }}
- Credentials scan: ${{ steps.credentials-scan.outcome }}
- Vulnerabilities scan: ${{ steps.vulnerabilities-scan.outcome }}
reactions: 'eyes'

View File

@@ -1,359 +0,0 @@
name: b-binary-build-and-e2e-tests
permissions: read-all
on:
workflow_dispatch:
inputs:
COMPONENT_NAME:
required: false
type: string
default: "kubescape"
RELEASE:
required: false
type: string
default: ""
CLIENT:
required: false
type: string
default: "test"
GO_VERSION:
required: false
type: string
default: "1.23"
GO111MODULE:
required: false
type: string
default: ""
CGO_ENABLED:
type: number
default: 1
required: false
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"
]'
workflow_call:
inputs:
COMPONENT_NAME:
required: true
type: string
RELEASE:
required: true
type: string
CLIENT:
required: true
type: string
GO_VERSION:
type: string
default: "1.23"
GO111MODULE:
required: true
type: string
CGO_ENABLED:
type: number
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"
]'
jobs:
wf-preparation:
name: secret-validator
runs-on: ubuntu-latest
outputs:
TEST_NAMES: ${{ steps.export_tests_to_env.outputs.TEST_NAMES }}
is-secret-set: ${{ steps.check-secret-set.outputs.is-secret-set }}
steps:
- name: check if the necessary secrets are set in github secrets
id: check-secret-set
env:
CUSTOMER: ${{ secrets.CUSTOMER }}
USERNAME: ${{ secrets.USERNAME }}
PASSWORD: ${{ secrets.PASSWORD }}
CLIENT_ID: ${{ secrets.CLIENT_ID_PROD }}
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"
- id: export_tests_to_env
name: set test name
run: |
echo "TEST_NAMES=$input" >> $GITHUB_OUTPUT
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 }}
run: |
echo "is-secret-set=${{ env.QUAYIO_REGISTRY_USERNAME != '' && env.QUAYIO_REGISTRY_PASSWORD != '' }}" >> $GITHUB_OUTPUT
binary-build:
name: Create cross-platform build
needs: wf-preparation
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
runs-on: ubuntu-large
steps:
- name: (debug) Step 1 - Check disk space before checkout
run: df -h
- uses: actions/checkout@v4
with:
fetch-depth: 0
submodules: recursive
- name: (debug) Step 2 - Check disk space before installing Go
run: df -h
- uses: actions/setup-go@v4
name: Installing go
with:
go-version: ${{ inputs.GO_VERSION }}
- name: (debug) Step 3 - Check disk space before build
run: df -h
- 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
- name: Test httphandler pkg
run: ${{ env.DOCKER_CMD }} sh -c 'cd httphandler && go test -v ./...'
if: startsWith(github.ref, 'refs/tags')
- 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
env:
RELEASE: ${{ inputs.RELEASE }}
CLIENT: ${{ inputs.CLIENT }}
CGO_ENABLED: ${{ inputs.CGO_ENABLED }}
- name: (debug) Step 7 - Check disk space before smoke testing
run: df -h
- 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: (debug) Step 8 - Check disk space before golangci-lint
run: df -h
- name: golangci-lint
continue-on-error: true
uses: golangci/golangci-lint-action@v3
with:
version: latest
args: --timeout 10m
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
with:
name: kubescape
path: dist/kubescape*
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
run-tests:
strategy:
fail-fast: false
matrix:
TEST: ${{ fromJson(needs.wf-preparation.outputs.TEST_NAMES) }}
needs: [wf-preparation, binary-build]
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
id: download-artifact
with:
name: kubescape
path: "~"
- run: ls -laR
- name: chmod +x
run: chmod +x -R ${{steps.download-artifact.outputs.download-path}}/kubescape-ubuntu-latest
- name: Checkout systests repo
uses: actions/checkout@v4
with:
repository: armosec/system-tests
path: .
- uses: actions/setup-python@v4
with:
python-version: '3.8.13'
cache: 'pip'
- name: create env
run: ./create_env.sh
- name: Generate uuid
id: uuid
run: |
echo "RANDOM_UUID=$(uuidgen)" >> $GITHUB_OUTPUT
- name: Create k8s Kind Cluster
id: kind-cluster-install
uses: helm/kind-action@v1.10.0
with:
cluster_name: ${{ steps.uuid.outputs.RANDOM_UUID }}
- name: run-tests-on-local-built-kubescape
env:
CUSTOMER: ${{ secrets.CUSTOMER }}
USERNAME: ${{ secrets.USERNAME }}
PASSWORD: ${{ secrets.PASSWORD }}
CLIENT_ID: ${{ secrets.CLIENT_ID_PROD }}
SECRET_KEY: ${{ secrets.SECRET_KEY_PROD }}
REGISTRY_USERNAME: ${{ secrets.REGISTRY_USERNAME }}
REGISTRY_PASSWORD: ${{ secrets.REGISTRY_PASSWORD }}
run: |
echo "Test history:"
echo " ${{ matrix.TEST }} " >/tmp/testhistory
cat /tmp/testhistory
source systests_python_env/bin/activate
python3 systest-cli.py \
-t ${{ matrix.TEST }} \
-b production \
-c CyberArmorTests \
--duration 3 \
--logger DEBUG \
--kwargs kubescape=${{steps.download-artifact.outputs.download-path}}/kubescape-ubuntu-latest
deactivate
- name: Test Report
uses: mikepenz/action-junit-report@v5
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,41 +0,0 @@
name: build-image
permissions: read-all
on:
workflow_dispatch:
inputs:
CLIENT:
required: false
type: string
default: "test"
IMAGE_TAG:
required: true
type: string
CO_SIGN:
type: boolean
required: false
default: false
PLATFORMS:
type: boolean
required: false
default: false
jobs:
build-http-image:
permissions:
id-token: write
packages: write
contents: write
pull-requests: read
uses: kubescape/workflows/.github/workflows/incluster-comp-pr-merged.yaml@main
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

View File

@@ -1,92 +0,0 @@
name: c-create_release
permissions: read-all
on:
workflow_call:
inputs:
RELEASE_NAME:
description: 'Release name'
required: true
type: string
TAG:
description: 'Tag name'
required: true
type: string
DRAFT:
description: 'Create draft release'
required: false
type: boolean
default: false
jobs:
create-release:
name: create-release
runs-on: ubuntu-latest
env:
MAC_OS: macos-latest
UBUNTU_OS: ubuntu-latest
WINDOWS_OS: windows-latest
permissions:
contents: write
steps:
- uses: actions/download-artifact@v4
id: download-artifact
with:
name: kubescape
path: .
# TODO: kubescape-windows-latest is deprecated and should be removed
- name: Get kubescape.exe from kubescape-windows-latest.exe
run: cp ${{steps.download-artifact.outputs.download-path}}/kubescape-${{ env.WINDOWS_OS }}.exe ${{steps.download-artifact.outputs.download-path}}/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;
else
echo "token=${{ secrets.GITHUB_TOKEN }}" >> $GITHUB_OUTPUT;
fi
- name: List artifacts
run: |
find . -type f -print
- name: Release
uses: softprops/action-gh-release@v2
with:
token: ${{ steps.set-token.outputs.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
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

47
.github/workflows/c-release.yaml vendored Normal file
View File

@@ -0,0 +1,47 @@
name: release
on:
push:
tags:
# - 'v*.*.*-rc.*' # Comment out since the re-tagging process is not yet implemented
- 'v*.*.*'
jobs:
test:
uses: ./.github/workflows/00-test.yaml
with:
release: ${{ github.ref_name}}
client: test
# integration-test:
# if: ${{ label == e2e-tests }}
# re-tag:
# # if tests passed, create new tag without `rc`
create-release:
uses: ./.github/workflows/01-create-release.yaml
needs: test
with:
release_name: "Release ${{ github.ref_name}}"
tag: ${{ github.ref_name}}
secrets: inherit
publish-artifacts:
uses: ./.github/workflows/02-publish-artifacts.yaml
needs: create-release
with:
upload_url: ${{ needs.create-release.outputs.upload_url }}
release: "${{ github.ref_name}}"
secrets: inherit
publish-image:
uses: ./.github/workflows/03-publish-image.yaml
needs: create-release
with:
client: "image-release"
image_name: "quay.io/${{ github.repository_owner }}/kubescape"
image_tag: "${{ github.ref_name}}"
support_platforms: true
cosign: true
secrets: inherit

View File

@@ -1,20 +0,0 @@
name: pr-agent
permissions: read-all
on:
issue_comment:
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:
- name: PR Agent action step
continue-on-error: true
id: pragent
uses: Codium-ai/pr-agent@main
env:
OPENAI_KEY: ${{ secrets.OPENAI_KEY }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

19
.github/workflows/d-post-release.yaml vendored Normal file
View File

@@ -0,0 +1,19 @@
name: create release digests
on:
release:
types: [ published]
branches:
- 'master'
- 'main'
jobs:
once:
name: Creating digests
runs-on: ubuntu-latest
steps:
- name: Digest
uses: MCJack123/ghaction-generate-release-hashes@v1
with:
hash-type: sha1
file-name: kubescape-release-digests

View File

@@ -1,107 +0,0 @@
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:
client:
description: 'client name'
required: true
type: string
image_tag:
description: 'image tag'
required: true
type: string
image_name:
description: 'image registry and name'
required: true
type: string
cosign:
required: false
default: false
type: boolean
description: 'run cosign on released image'
support_platforms:
required: false
default: true
type: boolean
description: 'support amd64/arm64'
jobs:
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 }}
run: |
echo "is-secret-set=${{ env.QUAYIO_REGISTRY_USERNAME != '' && env.QUAYIO_REGISTRY_PASSWORD != '' }}" >> $GITHUB_OUTPUT
build-cli-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
with:
submodules: recursive
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to Quay.io
env:
QUAY_PASSWORD: ${{ secrets.QUAYIO_REGISTRY_PASSWORD }}
QUAY_USERNAME: ${{ secrets.QUAYIO_REGISTRY_USERNAME }}
run: docker login -u="${QUAY_USERNAME}" -p="${QUAY_PASSWORD}" quay.io
- uses: actions/download-artifact@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: Install cosign
uses: sigstore/cosign-installer@main
with:
cosign-release: 'v2.2.2'
- name: sign kubescape container image
if: ${{ inputs.cosign }}
env:
COSIGN_EXPERIMENTAL: "true"
COSIGN_PRIVATE_KEY: ${{ secrets.COSIGN_PRIVATE_KEY_V1 }}
COSIGN_PRIVATE_KEY_PASSWORD: ${{ secrets.COSIGN_PRIVATE_KEY_V1_PASSWORD }}
COSIGN_PUBLIC_KEY: ${{ secrets.COSIGN_PUBLIC_KEY_V1 }}
run: |
# 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 }}

View File

@@ -1,46 +0,0 @@
name: e-post_release
permissions: read-all
on:
workflow_call:
inputs:
TAG:
description: 'Tag name'
required: true
type: string
jobs:
post_release:
name: Post release jobs
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
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 }}
- name: Invoke workflow to update packaging
uses: benc-uk/workflow-dispatch@v1
if: github.repository_owner == 'kubescape'
with:
workflow: release.yml
repo: kubescape/packaging
ref: refs/heads/main
token: ${{ secrets.GH_PERSONAL_ACCESS_TOKEN }}
- name: Invoke workflow to update homebrew tap
uses: benc-uk/workflow-dispatch@v1
if: github.repository_owner == 'kubescape'
with:
workflow: release.yml
repo: kubescape/homebrew-tap
ref: refs/heads/main
token: ${{ secrets.GH_PERSONAL_ACCESS_TOKEN }}
- name: Invoke workflow to update github action
uses: benc-uk/workflow-dispatch@v1
if: github.repository_owner == 'kubescape'
with:
workflow: release.yaml
repo: kubescape/github-action
ref: refs/heads/main
token: ${{ secrets.GH_PERSONAL_ACCESS_TOKEN }}

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,7 +1,7 @@
permissions: read-all
on:
issues:
types: [opened, labeled]
jobs:
open_PR_message:
if: github.event.label.name == 'typo'
@@ -11,6 +11,9 @@ jobs:
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:

4
.gitignore vendored
View File

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

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

@@ -3,40 +3,34 @@ kind: Plugin
metadata:
name: kubescape
spec:
homepage: https://github.com/kubescape/kubescape/
shortDescription: Scan resources and cluster configs against security frameworks.
homepage: https://kubescape.io/
shortDescription: An open-source Kubernetes security platform for your IDE, CI/CD pipelines, and clusters
version: {{ .TagName }}
description: |
It includes risk analysis, security compliance, and misconfiguration scanning
with an easy-to-use CLI interface, flexible output formats, and automated scanning capabilities.
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 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/).
platforms:
- selector:
matchLabels:
os: darwin
arch: amd64
{{ addURIAndSha "https://github.com/kubescape/kubescape/releases/download/{{ .TagName }}/kubescape-macos-latest.tar.gz" .TagName }}
bin: kubescape
- selector:
matchLabels:
os: darwin
arch: arm64
{{ addURIAndSha "https://github.com/kubescape/kubescape/releases/download/{{ .TagName }}/kubescape-arm64-macos-latest.tar.gz" .TagName }}
bin: kubescape
{{ addURIAndSha "https://github.com/kubescape/kubescape/releases/download/{{ .TagName }}/kubescape-macos-latest" .TagName }}
bin: kubectl-kubescape
- selector:
matchLabels:
os: linux
arch: amd64
{{ addURIAndSha "https://github.com/kubescape/kubescape/releases/download/{{ .TagName }}/kubescape-ubuntu-latest.tar.gz" .TagName }}
bin: kubescape
- selector:
matchLabels:
os: linux
arch: arm64
{{ addURIAndSha "https://github.com/kubescape/kubescape/releases/download/{{ .TagName }}/kubescape-arm64-ubuntu-latest.tar.gz" .TagName }}
bin: kubescape
{{ addURIAndSha "https://github.com/kubescape/kubescape/releases/download/{{ .TagName }}/kubescape-ubuntu-latest" .TagName }}
bin: kubectl-kubescape
- selector:
matchLabels:
os: windows
arch: amd64
{{ addURIAndSha "https://github.com/kubescape/kubescape/releases/download/{{ .TagName }}/kubescape-windows-latest.tar.gz" .TagName }}
bin: kubescape.exe
{{ addURIAndSha "https://github.com/kubescape/kubescape/releases/download/{{ .TagName }}/kubescape-windows-latest" .TagName }}
bin: kubectl-kubescape.exe

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,64 @@
# 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.
## 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 `dev` branch - we test the component before merging into 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](#f)
### 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
```
## 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,20 @@
.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
# 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

@@ -1,16 +1,11 @@
[![Version](https://img.shields.io/github/v/release/kubescape/kubescape)](https://github.com/kubescape/kubescape/releases)
[![build](https://github.com/kubescape/kubescape/actions/workflows/02-release.yaml/badge.svg)](https://github.com/kubescape/kubescape/actions/workflows/02-release.yaml)
[![Version](https://img.shields.io/github/v/release/kubescape/kubescape)](releases)
[![build](https://github.com/kubescape/kubescape/actions/workflows/build.yaml/badge.svg)](https://github.com/kubescape/kubescape/actions/workflows/build.yaml)
[![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 +15,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 +36,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/getting-started.md#install-kubescape)
* [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](docs/building.md)
_Did you know you can use Kubescape in all these places?_
@@ -58,47 +50,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://us02web.zoom.us/j/84020231442) on Zoom, on the first Tuesday of every month, at 14:00 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 +84,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)

51
build.bat Normal file
View File

@@ -0,0 +1,51 @@
@ECHO OFF
IF "%1"=="install" goto Install
IF "%1"=="build" goto Build
IF "%1"=="all" goto All
IF "%1"=="" goto Error ELSE goto Error
:Install
if exist C:\MSYS64\ (
echo "MSYS2 already installed"
) else (
mkdir temp_install & cd temp_install
echo "Downloading MSYS2..."
curl -L https://github.com/msys2/msys2-installer/releases/download/2022-06-03/msys2-x86_64-20220603.exe > msys2-x86_64-20220603.exe
echo "Installing MSYS2..."
msys2-x86_64-20220603.exe install --root C:\MSYS64 --confirm-command
cd .. && rmdir /s /q temp_install
)
echo "Adding MSYS2 to path..."
SET "PATH=C:\MSYS64\mingw64\bin;C:\MSYS64\usr\bin;%PATH%"
echo %PATH%
echo "Installing MSYS2 packages..."
pacman -S --needed --noconfirm make
pacman -S --needed --noconfirm mingw-w64-x86_64-cmake
pacman -S --needed --noconfirm mingw-w64-x86_64-gcc
pacman -S --needed --noconfirm mingw-w64-x86_64-pkg-config
pacman -S --needed --noconfirm msys2-w32api-runtime
IF "%1"=="all" GOTO Build
GOTO End
:Build
SET "PATH=C:\MSYS2\mingw64\bin;C:\MSYS2\usr\bin;%PATH%"
make libgit2
GOTO End
:All
GOTO Install
:Error
echo "Error: Unknown option"
GOTO End
:End

89
build.py Normal file
View File

@@ -0,0 +1,89 @@
import os
import sys
import hashlib
import platform
import subprocess
import tarfile
BASE_GETTER_CONST = "github.com/kubescape/kubescape/v2/core/cautils/getter"
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():
current_platform = platform.system()
if current_platform not in platformSuffixes: raise OSError("Platform %s is not supported!" % (current_platform))
return os.path.join("build", platformSuffixes[current_platform])
def get_package_name():
current_platform = platform.system()
if current_platform not in platformSuffixes: raise OSError("Platform %s is not supported!" % (current_platform))
return "kubescape-" + 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:
archive.add(ks_file, "kubescape")
print("Build Done")
if __name__ == "__main__":
main()

View File

@@ -1,27 +1,51 @@
FROM --platform=$BUILDPLATFORM golang:1.23-bookworm AS builder
FROM golang:1.19-alpine as builder
ARG image_version
ARG client
ENV RELEASE=$image_version
ENV CLIENT=$client
ENV GO111MODULE=
ENV CGO_ENABLED=1
# Install required python/pip
ENV PYTHONUNBUFFERED=1
RUN apk add --update --no-cache python3 gcc make git libc-dev binutils-gold cmake pkgconfig && ln -sf python3 /usr/bin/python
RUN python3 -m ensurepip
RUN pip3 install --no-cache --upgrade pip setuptools
ENV GO111MODULE=on CGO_ENABLED=0
WORKDIR /work
ARG TARGETOS TARGETARCH
ADD . .
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
# install libgit2
RUN rm -rf git2go && make libgit2
FROM gcr.io/distroless/static-debian12:nonroot
# build kubescape server
WORKDIR /work/httphandler
RUN python build.py
RUN ls -ltr build/ubuntu-latest
USER nonroot
WORKDIR /home/nonroot/
# build kubescape cmd
WORKDIR /work
RUN python build.py
COPY --from=builder /out/ksserver /usr/bin/ksserver
COPY --from=builder /root/.kubescape /home/nonroot/.kubescape
RUN /work/build/ubuntu-latest/kubescape-ubuntu-latest download artifacts -o /work/artifacts
ARG image_version client
ENV RELEASE=$image_version CLIENT=$client
FROM alpine:3.16.2
RUN addgroup -S ks && adduser -S ks -G ks
COPY --from=builder /work/artifacts/ /home/ks/.kubescape
RUN chown -R ks:ks /home/ks/.kubescape
USER ks
WORKDIR /home/ks
COPY --from=builder /work/httphandler/build/ubuntu-latest/kubescape-ubuntu-latest /usr/bin/ksserver
COPY --from=builder /work/build/ubuntu-latest/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"
)
@@ -15,8 +15,8 @@ var completionCmdExamples = fmt.Sprintf(`
$ echo 'source <(%[1]s completion bash)' >> ~/.bashrc
# Enable ZSH shell autocompletion
$ source <(%[1]s completion zsh)
$ echo 'source <(%[1]s completion zsh)' >> "${fpath[1]}/_%[1]s"
$ source <(kubectl completion zsh)
$ echo 'source <(kubectl completion zsh)' >> "${fpath[1]}/_kubectl"
`, cautils.ExecName())
func GetCompletionCmd() *cobra.Command {
@@ -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"
)
@@ -23,8 +23,14 @@ var (
# Set account id
%[1]s config set accountID <account id>
# Set cloud report URL
%[1]s config set cloudReportURL <cloud Report URL>
# Set client id
%[1]s config set clientID <client id>
# Set access key
%[1]s config set secretKey <access key>
# Set cloudAPIURL
%[1]s config set cloudAPIURL <cloud API URL>
`, cautils.ExecName())
)

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,9 @@
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"
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 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,20 @@ 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 },
"clientID": func(s *metav1.SetConfig, clientID string) { s.ClientID = clientID },
"secretKey": func(s *metav1.SetConfig, secretKey string) { s.SecretKey = secretKey },
"cloudAPIURL": func(s *metav1.SetConfig, cloudAPIURL string) { s.CloudAPIURL = cloudAPIURL },
"cloudAuthURL": func(s *metav1.SetConfig, cloudAuthURL string) { s.CloudAuthURL = cloudAuthURL },
"cloudReportURL": func(s *metav1.SetConfig, cloudReportURL string) { s.CloudReportURL = cloudReportURL },
"cloudUIURL": func(s *metav1.SetConfig, cloudUIURL string) { s.CloudUIURL = cloudUIURL },
}
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)
}

37
cmd/delete/delete.go Normal file
View File

@@ -0,0 +1,37 @@
package delete
import (
"fmt"
"github.com/kubescape/kubescape/v2/core/cautils"
"github.com/kubescape/kubescape/v2/core/meta"
v1 "github.com/kubescape/kubescape/v2/core/meta/datastructures/v1"
"github.com/spf13/cobra"
)
var deleteExceptionsExamples = fmt.Sprintf(`
# Delete single exception
%[1]s delete exceptions "exception name"
# Delete multiple exceptions
%[1]s delete exceptions "first exception;second exception;third exception"
`, cautils.ExecName())
func GetDeleteCmd(ks meta.IKubescape) *cobra.Command {
var deleteInfo v1.Delete
var deleteCmd = &cobra.Command{
Use: "delete <command>",
Short: "Delete configurations in Kubescape SaaS version",
Long: ``,
Run: func(cmd *cobra.Command, args []string) {
},
}
deleteCmd.PersistentFlags().StringVarP(&deleteInfo.Credentials.Account, "account", "", "", "Kubescape SaaS account ID. Default will load account ID from cache")
deleteCmd.PersistentFlags().StringVarP(&deleteInfo.Credentials.ClientID, "client-id", "", "", "Kubescape SaaS client ID. Default will load client ID from cache, read more - https://hub.armosec.io/docs/authentication")
deleteCmd.PersistentFlags().StringVarP(&deleteInfo.Credentials.SecretKey, "secret-key", "", "", "Kubescape SaaS secret key. Default will load secret key from cache, read more - https://hub.armosec.io/docs/authentication")
deleteCmd.AddCommand(getExceptionsCmd(ks, &deleteInfo))
return deleteCmd
}

47
cmd/delete/exceptions.go Normal file
View File

@@ -0,0 +1,47 @@
package delete
import (
"fmt"
"strings"
logger "github.com/kubescape/go-logger"
"github.com/kubescape/kubescape/v2/core/cautils"
"github.com/kubescape/kubescape/v2/core/meta"
v1 "github.com/kubescape/kubescape/v2/core/meta/datastructures/v1"
"github.com/spf13/cobra"
)
func getExceptionsCmd(ks meta.IKubescape, deleteInfo *v1.Delete) *cobra.Command {
return &cobra.Command{
Use: "exceptions <exception name>",
Short: fmt.Sprintf("Delete exceptions from Kubescape SaaS version. Run '%[1]s list exceptions' for all exceptions names", cautils.ExecName()),
Example: deleteExceptionsExamples,
Args: func(cmd *cobra.Command, args []string) error {
if len(args) != 1 {
return fmt.Errorf("missing exceptions names")
}
return nil
},
Run: func(cmd *cobra.Command, args []string) {
if err := flagValidationDelete(deleteInfo); err != nil {
logger.L().Fatal(err.Error())
}
exceptionsNames := strings.Split(args[0], ";")
if len(exceptionsNames) == 0 {
logger.L().Fatal("missing exceptions names")
}
if err := ks.DeleteExceptions(&v1.DeleteExceptions{Credentials: deleteInfo.Credentials, Exceptions: exceptionsNames}); err != nil {
logger.L().Fatal(err.Error())
}
},
}
}
// Check if the flag entered are valid
func flagValidationDelete(deleteInfo *v1.Delete) error {
// Validate the user's credentials
return deleteInfo.Credentials.Validate()
}

View File

@@ -3,14 +3,13 @@ package download
import (
"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"
)
@@ -36,10 +35,13 @@ var (
# Download the configured controls-inputs
%[1]s download controls-inputs
# Download the attack tracks
%[1]s download attack-tracks
`, cautils.ExecName())
)
func GetDownloadCmd(ks meta.IKubescape) *cobra.Command {
func GeDownloadCmd(ks meta.IKubescape) *cobra.Command {
var downloadInfo = v1.DownloadInfo{}
downloadCmd := &cobra.Command{
@@ -52,7 +54,7 @@ func GetDownloadCmd(ks meta.IKubescape) *cobra.Command {
if len(args) < 1 {
return fmt.Errorf("policy type required, supported: %v", supported)
}
if !slices.Contains(core.DownloadSupportCommands(), args[0]) {
if cautils.StringInSlice(core.DownloadSupportCommands(), args[0]) == cautils.ValueNotFound {
return fmt.Errorf("invalid parameter '%s'. Supported parameters: %s", args[0], supported)
}
return nil
@@ -66,14 +68,11 @@ func GetDownloadCmd(ks meta.IKubescape) *cobra.Command {
if filepath.Ext(downloadInfo.Path) == ".json" {
downloadInfo.Path, downloadInfo.FileName = filepath.Split(downloadInfo.Path)
}
if len(args) == 0 {
return fmt.Errorf("no arguements provided")
}
downloadInfo.Target = args[0]
if len(args) >= 2 {
downloadInfo.Identifier = args[1]
}
if err := ks.Download(&downloadInfo); err != nil {
logger.L().Fatal(err.Error())
@@ -82,8 +81,9 @@ 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().StringVarP(&downloadInfo.Credentials.Account, "account", "", "", "Kubescape SaaS account ID. Default will load account ID from cache")
downloadCmd.PersistentFlags().StringVarP(&downloadInfo.Credentials.ClientID, "client-id", "", "", "Kubescape SaaS client ID. Default will load client ID from cache, read more - https://hub.armosec.io/docs/authentication")
downloadCmd.PersistentFlags().StringVarP(&downloadInfo.Credentials.SecretKey, "secret-key", "", "", "Kubescape SaaS secret key. Default will load secret key from cache, read more - https://hub.armosec.io/docs/authentication")
downloadCmd.Flags().StringVarP(&downloadInfo.Path, "output", "o", "", "Output file. If not specified, will save in `~/.kubescape/<policy name>.json`")
return downloadCmd
@@ -93,5 +93,5 @@ func GetDownloadCmd(ks meta.IKubescape) *cobra.Command {
func flagValidationDownload(downloadInfo *v1.DownloadInfo) error {
// Validate the user's credentials
return cautils.ValidateAccountID(downloadInfo.AccountID)
return downloadInfo.Credentials.Validate()
}

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

@@ -2,30 +2,29 @@ package fix
import (
"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/meta"
metav1 "github.com/kubescape/kubescape/v2/core/meta/datastructures/v1"
"github.com/spf13/cobra"
)
var fixCmdExamples = fmt.Sprintf(`
var fixCmdExamples = `
Fix command is for fixing kubernetes manifest files based on a scan command output.
Use with caution, this command will change your files in-place.
# Fix kubernetes YAML manifest files based on a scan command output (output.json)
1) %[1]s scan . --format json --output output.json
2) %[1]s fix output.json
1) kubescape scan --format json --format-version v2 --output output.json
2) kubescape fix output.json
`, cautils.ExecName())
`
func GetFixCmd(ks meta.IKubescape) *cobra.Command {
var fixInfo metav1.FixInfo
fixCmd := &cobra.Command{
Use: "fix <report output file>",
Short: "Propose a fix for the misconfiguration found when scanning Kubernetes manifest files",
Short: "Fix misconfiguration in files",
Long: ``,
Example: fixCmdExamples,
RunE: func(cmd *cobra.Command, args []string) error {

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,16 +1,14 @@
package list
import (
"errors"
"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"
)
@@ -44,7 +42,7 @@ func GetListCmd(ks meta.IKubescape) *cobra.Command {
if len(args) < 1 {
return fmt.Errorf("policy type requeued, supported: %s", supported)
}
if !slices.Contains(core.ListSupportActions(), args[0]) {
if cautils.StringInSlice(core.ListSupportActions(), args[0]) == cautils.ValueNotFound {
return fmt.Errorf("invalid parameter '%s'. Supported parameters: %s", args[0], supported)
}
return nil
@@ -55,10 +53,6 @@ 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 {
@@ -67,8 +61,9 @@ func GetListCmd(ks meta.IKubescape) *cobra.Command {
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().StringVarP(&listPolicies.Credentials.Account, "account", "", "", "Kubescape SaaS account ID. Default will load account ID from cache")
listCmd.PersistentFlags().StringVarP(&listPolicies.Credentials.ClientID, "client-id", "", "", "Kubescape SaaS client ID. Default will load client ID from cache, read more - https://hub.armosec.io/docs/authentication")
listCmd.PersistentFlags().StringVarP(&listPolicies.Credentials.SecretKey, "secret-key", "", "", "Kubescape SaaS secret key. Default will load secret key from cache, read more - https://hub.armosec.io/docs/authentication")
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")
@@ -79,5 +74,5 @@ func GetListCmd(ks meta.IKubescape) *cobra.Command {
func flagValidationList(listPolicies *v1.ListPolicies) error {
// Validate the user's credentials
return cautils.ValidateAccountID(listPolicies.AccountID)
return listPolicies.Credentials.Validate()
}

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,142 +0,0 @@
# 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).
## Usage
```bash
kubescape patch --image <image-name> [flags]
```
The patch command can be run in 2 ways:
1. **With sudo privileges**
You will need to start `buildkitd` if it is not already running
```bash
sudo buildkitd &
sudo kubescape patch --image <image-name>
```
2. **Without sudo privileges**
```bash
export BUILDKIT_VERSION=v0.11.4
export BUILDKIT_PORT=8888
docker run \
--detach \
--rm \
--privileged \
-p 127.0.0.1:$BUILDKIT_PORT:$BUILDKIT_PORT/tcp \
--name buildkitd \
--entrypoint buildkitd \
"moby/buildkit:$BUILDKIT_VERSION" \
--addr tcp://0.0.0.0:$BUILDKIT_PORT
kubescape patch \
-i <image-name> \
-a tcp://0.0.0.0:$BUILDKIT_PORT
```
### Flags
| 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 | |
| -o, --output | Output file. Print output to file and not stdout | No | |
| -v, --verbose | Display full report. Default to false | No | |
| -h, --help | help for patch | No | |
## Example
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:
```bash
sudo buildkitd
```
2. In a seperate terminal, run the `kubescape patch` command:
```bash
sudo kubescape patch --image docker.io/library/nginx:1.22
```
3. You will get an output like below:
```bash
✅ Successfully scanned image: docker.io/library/nginx:1.22
✅ Patched image successfully. Loaded image: nginx:1.22-patched
✅ Successfully re-scanned image: nginx:1.22-patched
| 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 - 161 vulnerabilities found:
Image: nginx:1.22-patched
* 3 Critical
* 24 High
* 31 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
* libxml2 (2.9.10+dfsg-6.7+deb11u4) - 1 High, 2 Medium
* perl-base (5.32.1-4+deb11u2) - 2 High, 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-cloud-operator
```
## Limitations
- The patch command can only fix OS-level vulnerability. It cannot fix application-level vulnerabilities. This is a limitation of copa. The reason behind this is that application level vulnerabilities are best suited to be fixed by the developers of the application.
Hence, this is not really a limitation but a design decision.
- No support for windows containers given the dependency on buildkit.

View File

@@ -1,141 +0,0 @@
package patch
import (
"errors"
"fmt"
"strings"
"time"
"github.com/docker/distribution/reference"
"github.com/kubescape/go-logger"
"github.com/kubescape/kubescape/v3/cmd/shared"
"github.com/kubescape/kubescape/v3/core/cautils"
"github.com/kubescape/kubescape/v3/core/meta"
metav1 "github.com/kubescape/kubescape/v3/core/meta/datastructures/v1"
"github.com/kubescape/kubescape/v3/pkg/imagescan"
"github.com/spf13/cobra"
)
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
# The patch command can also be run without sudo privileges
# Documentation: https://github.com/kubescape/kubescape/tree/master/cmd/patch
`, cautils.ExecName())
func GetPatchCmd(ks meta.IKubescape) *cobra.Command {
var patchInfo metav1.PatchInfo
var scanInfo cautils.ScanInfo
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.`,
Example: patchCmdExamples,
Args: func(cmd *cobra.Command, args []string) error {
if len(args) != 0 {
return fmt.Errorf("the command takes no arguments")
}
return nil
},
RunE: func(cmd *cobra.Command, args []string) error {
if err := shared.ValidateImageScanInfo(&scanInfo); err != nil {
return err
}
if err := validateImagePatchInfo(&patchInfo); err != nil {
return err
}
results, err := ks.Patch(&patchInfo, &scanInfo)
if err != nil {
return err
}
if imagescan.ExceedsSeverityThreshold(results, imagescan.ParseSeverity(scanInfo.FailThresholdSeverity)) {
shared.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
}
// validateImagePatchInfo validates the image patch info for the `patch` command
func validateImagePatchInfo(patchInfo *metav1.PatchInfo) error {
if patchInfo.Image == "" {
return errors.New("image tag is required")
}
// Convert image to canonical format (required by copacetic for patching images)
patchInfoImage, err := cautils.NormalizeImageName(patchInfo.Image)
if err != nil {
return nil
}
// Parse the image full name to get image name and tag
named, err := reference.ParseNamed(patchInfoImage)
if err != nil {
return err
}
// If no tag or digest is provided, default to 'latest'
if reference.IsNameOnly(named) {
logger.L().Warning("Image name has no tag or digest, using latest as tag")
named = reference.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)
if !ok {
return errors.New("unexpected error while parsing image tag")
}
patchInfo.ImageTag = taggedName.Tag()
if patchInfo.ImageTag == "" {
logger.L().Warning("No tag provided, defaulting to 'patched'")
patchInfo.PatchedImageTag = "patched"
} else {
patchInfo.PatchedImageTag = fmt.Sprintf("%s-%s", patchInfo.ImageTag, "patched")
}
}
// Extract the "image" name from the canonical Image URL
// If it's an official docker image, we store just the "image-name". Else if a docker repo then we store as "repo/image". Else complete URL
ref, _ := reference.ParseNormalizedNamed(patchInfo.Image)
imageName := named.Name()
if strings.Contains(imageName, "docker.io/library/") {
imageName = reference.Path(ref)
imageName = imageName[strings.LastIndex(imageName, "/")+1:]
} else if strings.Contains(imageName, "docker.io/") {
imageName = reference.Path(ref)
}
patchInfo.ImageName = imageName
return nil
}

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,40 +1,37 @@
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/delete"
"github.com/kubescape/kubescape/v2/cmd/download"
"github.com/kubescape/kubescape/v2/cmd/fix"
"github.com/kubescape/kubescape/v2/cmd/list"
"github.com/kubescape/kubescape/v2/cmd/scan"
"github.com/kubescape/kubescape/v2/cmd/submit"
"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"
)
var rootInfo cautils.RootInfo
var ksExamples = fmt.Sprintf(`
# Scan a Kubernetes cluster or YAML files for image vulnerabilities and misconfigurations
# Scan command
%[1]s scan
# List supported controls
%[1]s list controls
# List supported frameworks
%[1]s list frameworks
# Download artifacts (air-gapped environment support)
%[1]s download artifacts
@@ -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)
}
@@ -54,13 +51,6 @@ func getRootCmd(ks meta.IKubescape) *cobra.Command {
Use: "kubescape",
Short: "Kubescape is a tool for testing Kubernetes security posture. Docs: https://hub.armosec.io/docs",
Example: ksExamples,
PersistentPreRun: func(cmd *cobra.Command, args []string) {
k8sinterface.SetClusterContextName(rootInfo.KubeContext)
initLogger()
initLoggerLevel()
initEnvironment()
initCacheDir()
},
}
if cautils.IsKrewPlugin() {
@@ -73,10 +63,9 @@ func getRootCmd(ks meta.IKubescape) *cobra.Command {
rootCmd.SetUsageTemplate(newUsageTemplate)
}
rootCmd.PersistentFlags().StringVar(&rootInfo.DiscoveryServerURL, "server", "", "Backend discovery server URL")
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.")
rootCmd.PersistentFlags().StringVar(&rootInfo.KSCloudBEURLsDep, "environment", "", envFlagUsage)
rootCmd.PersistentFlags().StringVar(&rootInfo.KSCloudBEURLs, "env", "", envFlagUsage)
rootCmd.PersistentFlags().MarkDeprecated("environment", "use 'env' instead")
rootCmd.PersistentFlags().MarkHidden("environment")
rootCmd.PersistentFlags().MarkHidden("env")
@@ -85,36 +74,27 @@ 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")
cobra.OnInitialize(initLogger, initLoggerLevel, initEnvironment, initCacheDir)
rootCmd.PersistentFlags().StringVarP(&rootInfo.KubeContext, "kube-context", "", "", "Kube context. Default will use the current-context")
// Supported commands
rootCmd.AddCommand(scan.GetScanCommand(ks))
rootCmd.AddCommand(download.GetDownloadCmd(ks))
rootCmd.AddCommand(download.GeDownloadCmd(ks))
rootCmd.AddCommand(delete.GetDeleteCmd(ks))
rootCmd.AddCommand(list.GetListCmd(ks))
rootCmd.AddCommand(submit.GetSubmitCmd(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))
// deprecated commands
rootCmd.AddCommand(&cobra.Command{
Use: "submit",
Deprecated: "This command is deprecated. Contact Kubescape maintainers for more information.",
})
rootCmd.AddCommand(&cobra.Command{
Use: "delete",
Deprecated: "This command is deprecated. Contact Kubescape maintainers for more information.",
})
return rootCmd
}
func Execute(ctx context.Context) error {
ks := NewDefaultKubescapeCommand(ctx)
func Execute() error {
ks := NewDefaultKubescapeCommand()
return ks.Execute()
}

View File

@@ -5,34 +5,34 @@ import (
"os"
"strings"
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"
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/getter"
"github.com/mattn/go-isatty"
)
const envFlagUsage = "Send report results to specific URL. Format:<ReportReceiver>,<Backend>,<Frontend>.\n\t\tExample:report.armo.cloud,api.armo.cloud,portal.armo.cloud"
func initLogger() {
logger.DisableColor(rootInfo.DisableColor)
logger.EnableColor(rootInfo.EnableColor)
if rootInfo.LoggerName == "" {
if l := os.Getenv("KS_LOGGER_NAME"); l != "" {
rootInfo.LoggerName = l
} else {
if isatty.IsTerminal(os.Stdout.Fd()) {
rootInfo.LoggerName = iconlogger.LoggerName
rootInfo.LoggerName = "pretty"
} else {
rootInfo.LoggerName = zaplogger.LoggerName
rootInfo.LoggerName = "zap"
}
}
}
logger.InitLogger(rootInfo.LoggerName)
}
}
func initLoggerLevel() {
if rootInfo.Logger == helpers.InfoLevel.String() {
} else if l := os.Getenv("KS_LOGGER"); l != "" {
@@ -56,51 +56,35 @@ func initCacheDir() {
logger.L().Debug("cache dir updated", helpers.String("path", getter.DefaultLocalStore))
}
func initEnvironment() {
if rootInfo.DiscoveryServerURL == "" {
return
if rootInfo.KSCloudBEURLs == "" {
rootInfo.KSCloudBEURLs = rootInfo.KSCloudBEURLsDep
}
logger.L().Debug("fetching URLs from service discovery server", helpers.String("server", rootInfo.DiscoveryServerURL))
client, err := sdClientV2.NewServiceDiscoveryClientV2(rootInfo.DiscoveryServerURL)
if err != nil {
logger.L().Fatal("failed to create service discovery client", helpers.Error(err), helpers.String("server", rootInfo.DiscoveryServerURL))
return
urlSlices := strings.Split(rootInfo.KSCloudBEURLs, ",")
if len(urlSlices) != 1 && len(urlSlices) < 3 {
logger.L().Fatal("expected at least 3 URLs (report, api, frontend, auth)")
}
services, err := servicediscovery.GetServices(
client,
)
if err != nil {
logger.L().Fatal("failed to get services from server", helpers.Error(err), helpers.String("server", rootInfo.DiscoveryServerURL))
return
switch len(urlSlices) {
case 1:
switch urlSlices[0] {
case "dev", "development":
getter.SetKSCloudAPIConnector(getter.NewKSCloudAPIDev())
case "stage", "staging":
getter.SetKSCloudAPIConnector(getter.NewKSCloudAPIStaging())
case "":
getter.SetKSCloudAPIConnector(getter.NewKSCloudAPIProd())
default:
logger.L().Fatal("--environment flag usage: " + envFlagUsage)
}
case 2:
logger.L().Fatal("--environment flag usage: " + envFlagUsage)
case 3, 4:
var ksAuthURL string
ksEventReceiverURL := urlSlices[0] // mandatory
ksBackendURL := urlSlices[1] // mandatory
ksFrontendURL := urlSlices[2] // mandatory
if len(urlSlices) >= 4 {
ksAuthURL = urlSlices[3]
}
getter.SetKSCloudAPIConnector(getter.NewKSCloudAPICustomized(ksEventReceiverURL, ksBackendURL, ksFrontendURL, ksAuthURL))
}
logger.L().Debug("configuring service discovery URLs", helpers.String("cloudAPIURL", services.GetApiServerUrl()), helpers.String("cloudReportURL", services.GetReportReceiverHttpUrl()))
tenant := cautils.GetTenantConfig("", "", "", "", nil)
if services.GetApiServerUrl() != "" {
tenant.GetConfigObj().CloudAPIURL = services.GetApiServerUrl()
}
if services.GetReportReceiverHttpUrl() != "" {
tenant.GetConfigObj().CloudReportURL = services.GetReportReceiverHttpUrl()
}
if err = tenant.UpdateCachedConfig(); err != nil {
logger.L().Error("failed to update cached config", helpers.Error(err))
}
ksCloud, err := v1.NewKSCloudAPI(
services.GetApiServerUrl(),
services.GetReportReceiverHttpUrl(),
"",
"",
)
if err != nil {
logger.L().Fatal("failed to create KS Cloud client", helpers.Error(err))
return
}
getter.SetKSCloudAPIConnector(ksCloud)
}

View File

@@ -6,12 +6,14 @@ import (
"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/core/cautils"
"github.com/kubescape/kubescape/v2/core/meta"
"github.com/enescakir/emoji"
"github.com/spf13/cobra"
)
@@ -89,7 +91,6 @@ func getControlCmd(ks meta.IKubescape, scanInfo *cautils.ScanInfo) *cobra.Comman
}
scanInfo.FrameworkScan = false
scanInfo.SetScanType(cautils.ScanTypeControl)
if err := validateControlScanInfo(scanInfo); err != nil {
return err
@@ -99,18 +100,15 @@ func getControlCmd(ks meta.IKubescape, scanInfo *cautils.ScanInfo) *cobra.Comman
if err != nil {
logger.L().Fatal(err.Error())
}
if err := results.HandleResults(ks.Context()); err != nil {
if err := results.HandleResults(); err != nil {
logger.L().Fatal(err.Error())
}
if !scanInfo.VerboseMode {
logger.L().Info("Run with '--verbose'/'-v' flag for detailed resources view\n")
cautils.SimpleDisplay(os.Stderr, "%s Run with '--verbose'/'-v' flag for detailed resources view\n\n", emoji.Detective)
}
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)))
}
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)
return nil
@@ -126,7 +124,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 := 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

@@ -5,18 +5,17 @@ import (
"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"
logger "github.com/kubescape/go-logger"
"github.com/kubescape/go-logger/helpers"
"github.com/kubescape/kubescape/v2/core/cautils"
"github.com/kubescape/kubescape/v2/core/meta"
"github.com/spf13/cobra"
)
@@ -40,10 +39,7 @@ var (
Run '%[1]s list frameworks' for the list of supported frameworks
`, cautils.ExecName())
ErrSecurityViewNotSupported = errors.New("security view is not supported for framework scan")
ErrBadThreshold = errors.New("bad argument: out of range threshold")
ErrKeepLocalOrSubmit = errors.New("you can use `keep-local` or `submit`, but not both")
ErrOmitRawResourcesOrSubmit = errors.New("you can use `omit-raw-resources` or `submit`, but not both")
ErrUnknownSeverity = errors.New("unknown severity")
)
func getFrameworkCmd(ks meta.IKubescape, scanInfo *cautils.ScanInfo) *cobra.Command {
@@ -75,25 +71,20 @@ func getFrameworkCmd(ks meta.IKubescape, scanInfo *cautils.ScanInfo) *cobra.Comm
}
scanInfo.FrameworkScan = true
// We do not scan all frameworks by default when triggering scan from the CLI
scanInfo.ScanAll = false
var frameworks []string
if len(args) == 0 {
if len(args) == 0 { // scan all frameworks
scanInfo.ScanAll = true
} else {
// Read frameworks from input args
frameworks = strings.Split(args[0], ",")
if slices.Contains(frameworks, "all") {
if cautils.StringInSlice(frameworks, "all") != cautils.ValueNotFound {
scanInfo.ScanAll = true
frameworks = getter.NativeFrameworks
frameworks = []string{}
}
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 !!
tempFile, err := os.CreateTemp(".", "tmp-kubescape*.yaml")
if err != nil {
@@ -108,7 +99,7 @@ func getFrameworkCmd(ks meta.IKubescape, scanInfo *cautils.ScanInfo) *cobra.Comm
}
}
}
scanInfo.SetScanType(cautils.ScanTypeFramework)
scanInfo.FrameworkScan = true
scanInfo.SetPolicyIdentifiers(frameworks, apisv1.KindFramework)
@@ -117,28 +108,26 @@ func getFrameworkCmd(ks meta.IKubescape, scanInfo *cautils.ScanInfo) *cobra.Comm
logger.L().Fatal(err.Error())
}
if err = results.HandleResults(ks.Context()); err != nil {
if err = results.HandleResults(); err != nil {
logger.L().Fatal(err.Error())
}
if !scanInfo.VerboseMode {
cautils.SimpleDisplay(os.Stderr, "Run with '--verbose'/'-v' flag for detailed resources view\n\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)))
}
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.GetData().Report.SummaryDetails.GetResourcesSeverityCounters(), scanInfo, 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 := validateSeverity(targetSeverity); err != nil {
return false, err
}
@@ -172,14 +161,14 @@ 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))
func terminateOnExceedingSeverity(scanInfo *cautils.ScanInfo, l logger.ILogger) {
l.Fatal("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
func enforceSeverityThresholds(severityCounters reportsummary.ISeverityCounters, scanInfo *cautils.ScanInfo, onExceed func(*cautils.ScanInfo, helpers.ILogger)) {
func enforceSeverityThresholds(severityCounters reportsummary.ISeverityCounters, scanInfo *cautils.ScanInfo, onExceed func(*cautils.ScanInfo, logger.ILogger)) {
// If a severity threshold is not set, we dont need to enforce it
if scanInfo.FailThresholdSeverity == "" {
return
@@ -192,29 +181,33 @@ func enforceSeverityThresholds(severityCounters reportsummary.ISeverityCounters,
}
}
// 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) {
return nil
}
}
return ErrUnknownSeverity
}
// 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)
}
if scanInfo.Submit && scanInfo.Local {
return ErrKeepLocalOrSubmit
}
if 100 < scanInfo.ComplianceThreshold || 0 > scanInfo.ComplianceThreshold {
return ErrBadThreshold
return fmt.Errorf("you can use `keep-local` or `submit`, but not both")
}
if 100 < scanInfo.FailThreshold || 0 > scanInfo.FailThreshold {
return ErrBadThreshold
return fmt.Errorf("bad argument: out of range threshold")
}
if scanInfo.Submit && scanInfo.OmitRawResources {
return ErrOmitRawResourcesOrSubmit
return fmt.Errorf("you can use `omit-raw-resources` or `submit`, but not both")
}
severity := scanInfo.FailThresholdSeverity
if err := shared.ValidateSeverity(severity); severity != "" && err != nil {
if err := validateSeverity(severity); severity != "" && err != nil {
return err
}
// Validate the user's credentials
return cautils.ValidateAccountID(scanInfo.AccountID)
return scanInfo.Credentials.Validate()
}

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

@@ -3,32 +3,29 @@ package scan
import (
"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"
v1 "github.com/kubescape/opa-utils/httpserver/apis/v1"
"github.com/kubescape/k8s-interface/k8sinterface"
"github.com/kubescape/kubescape/v2/core/cautils"
"github.com/kubescape/kubescape/v2/core/meta"
"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 with all frameworks
%[1]s scan --enable-host-scan --verbose
# Scan current cluster
%[1]s scan
# Scan kubernetes manifest files
# Scan kubernetes YAML manifest files
%[1]s scan .
# Scan and save the results in the JSON format
%[1]s scan --format json --output results.json
%[1]s scan --format json --output results.json --format-version=v2
# 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())
@@ -38,68 +35,69 @@ func GetScanCommand(ks meta.IKubescape) *cobra.Command {
// scanCmd represents the scan command
scanCmd := &cobra.Command{
Use: "scan",
Short: "Scan a Kubernetes cluster or YAML files for image vulnerabilities and misconfigurations",
Short: "Scan the current running cluster or yaml files",
Long: `The action you want to perform`,
Example: scanCmdExamples,
RunE: func(cmd *cobra.Command, args []string) error {
if scanInfo.View == string(cautils.SecurityViewType) {
setSecurityViewScanInfo(args, &scanInfo)
if err := securityScan(scanInfo, ks); err != nil {
logger.L().Fatal(err.Error())
Args: func(cmd *cobra.Command, args []string) error {
if len(args) > 0 {
if args[0] != "framework" && args[0] != "control" {
scanInfo.ScanAll = true
return getFrameworkCmd(ks, &scanInfo).RunE(cmd, append([]string{"all"}, args...))
}
} 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 nil
},
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) == 0 {
scanInfo.ScanAll = true
return getFrameworkCmd(ks, &scanInfo).RunE(cmd, []string{"all"})
}
return nil
},
PreRun: func(cmd *cobra.Command, args []string) {
k8sinterface.SetClusterContextName(scanInfo.KubeContext)
},
PostRun: func(cmd *cobra.Command, args []string) {
// TODO - revert context
},
}
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().StringVarP(&scanInfo.Credentials.Account, "account", "", "", "Kubescape SaaS account ID. Default will load account ID from cache")
scanCmd.PersistentFlags().BoolVar(&scanInfo.CreateAccount, "create-account", false, "Create a Kubescape SaaS account ID account ID is not found in cache. After creating the account, the account ID will be saved in cache. In addition, the scanning results will be uploaded to the Kubescape SaaS")
scanCmd.PersistentFlags().StringVarP(&scanInfo.Credentials.ClientID, "client-id", "", "", "Kubescape SaaS client ID. Default will load client ID from cache, read more - https://hub.armosec.io/docs/authentication")
scanCmd.PersistentFlags().StringVarP(&scanInfo.Credentials.SecretKey, "secret-key", "", "", "Kubescape SaaS secret key. Default will load secret key from cache, read more - https://hub.armosec.io/docs/authentication")
scanCmd.PersistentFlags().StringVarP(&scanInfo.KubeContext, "kube-context", "", "", "Kube context. Default will use the current-context")
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")
scanCmd.PersistentFlags().StringVarP(&scanInfo.ExcludedNamespaces, "exclude-namespaces", "e", "", "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.")
scanCmd.PersistentFlags().StringVarP(&scanInfo.ExcludedNamespaces, "exclude-namespaces", "e", "", "Namespaces to exclude from scanning. Notice, when running with `exclude-namespace` kubescape does not scan cluster-scoped objects.")
scanCmd.PersistentFlags().Float32VarP(&scanInfo.FailThreshold, "fail-threshold", "t", 100, "Failure threshold is the percent above which the command fails and returns exit code 1")
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. default is --view=%s", cautils.ResourceViewType, cautils.ControlViewType, 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")
scanCmd.PersistentFlags().StringVar(&scanInfo.FormatVersion, "format-version", "v2", "Output object can be different between versions, this is for maintaining backward and forward compatibility. Supported:'v1'/'v2'")
scanCmd.PersistentFlags().StringVar(&scanInfo.FormatVersion, "format-version", "v1", "Output object can be different between versions, this is for maintaining backward and forward compatibility. Supported:'v1'/'v2'")
scanCmd.PersistentFlags().StringVar(&scanInfo.CustomClusterName, "cluster-name", "", "Set the custom name of the cluster. Not same as the kube-context flag")
scanCmd.PersistentFlags().BoolVarP(&scanInfo.Submit, "submit", "", false, "Submit the scan results to Kubescape SaaS where you can see the results in a user-friendly UI, choose your preferred compliance framework, check risk results history and trends, manage exceptions, get remediation recommendations and much more. By default the results are not submitted")
scanCmd.PersistentFlags().BoolVarP(&scanInfo.OmitRawResources, "omit-raw-resources", "", false, "Omit raw resources from the output. By default the raw resources are included in the output")
scanCmd.PersistentFlags().BoolVarP(&scanInfo.PrintAttackTree, "print-attack-tree", "", false, "Print attack tree")
scanCmd.PersistentFlags().BoolVarP(&scanInfo.EnableRegoPrint, "enable-rego-prints", "", false, "Enable sending to rego prints to the logs (use with debug log level: -l debug)")
scanCmd.PersistentFlags().BoolVarP(&scanInfo.ScanImages, "scan-images", "", false, "Scan resources images")
scanCmd.PersistentFlags().MarkDeprecated("fail-threshold", "use '--compliance-threshold' flag instead. Flag will be removed at 1.Dec.2023")
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("silent", "use '--logger' flag instead. Flag will be removed at 1.May.2022")
// hidden flags
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().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"))
@@ -107,43 +105,9 @@ func GetScanCommand(ks meta.IKubescape) *cobra.Command {
hostF := scanCmd.PersistentFlags().VarPF(&scanInfo.HostSensorEnabled, "enable-host-scan", "", "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")
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().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.AddCommand(getControlCmd(ks, &scanInfo))
scanCmd.AddCommand(getFrameworkCmd(ks, &scanInfo))
scanCmd.AddCommand(getWorkloadCmd(ks, &scanInfo))
scanCmd.AddCommand(getImageCmd(ks, &scanInfo))
return scanCmd
}
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)
}
}
func securityScan(scanInfo cautils.ScanInfo, ks meta.IKubescape) error {
results, err := ks.Scan(&scanInfo)
if err != nil {
return err
}
if err = results.HandleResults(ks.Context()); err != nil {
return err
}
enforceSeverityThresholds(results.GetData().Report.SummaryDetails.GetResourcesSeverityCounters(), &scanInfo, terminateOnExceedingSeverity)
return nil
}

View File

@@ -1,19 +1,16 @@
package scan
import (
"context"
logger "github.com/kubescape/go-logger"
"github.com/kubescape/go-logger/helpers"
"github.com/kubescape/kubescape/v2/core/cautils"
"github.com/kubescape/opa-utils/reporthandling/apis"
"github.com/kubescape/opa-utils/reporthandling/results/v1/reportsummary"
"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"
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"
)
func TestExceedsSeverity(t *testing.T) {
@@ -113,7 +110,7 @@ func TestExceedsSeverity(t *testing.T) {
ScanInfo: &cautils.ScanInfo{FailThresholdSeverity: "unknown"},
SeverityCounters: &reportsummary.SeverityCounters{LowSeverityCounter: 1},
Want: false,
Error: shared.ErrUnknownSeverity,
Error: ErrUnknownSeverity,
},
}
@@ -163,7 +160,7 @@ func Test_enforceSeverityThresholds(t *testing.T) {
want := tc.Want
got := false
onExceed := func(*cautils.ScanInfo, helpers.ILogger) {
onExceed := func(*cautils.ScanInfo, logger.ILogger) {
got = true
}
@@ -186,20 +183,16 @@ 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) 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) Fatal(msg string, details ...helpers.IDetails) {
firstDetail := details[0]
@@ -214,7 +207,7 @@ func (l *spyLogger) GetSpiedItems() []spyLogMessage {
}
func Test_terminateOnExceedingSeverity(t *testing.T) {
expectedMessage := "compliance result exceeds severity threshold"
expectedMessage := "result exceeds severity threshold"
expectedKey := "set severity threshold"
testCases := []struct {
@@ -259,115 +252,3 @@ func Test_terminateOnExceedingSeverity(t *testing.T) {
)
}
}
func TestSetSecurityViewScanInfo(t *testing.T) {
tests := []struct {
name string
args []string
want *cautils.ScanInfo
}{
{
name: "no args",
args: []string{},
want: &cautils.ScanInfo{
InputPatterns: []string{},
ScanType: cautils.ScanTypeCluster,
PolicyIdentifier: []cautils.PolicyIdentifier{
{
Kind: v1.KindFramework,
Identifier: "clusterscan",
},
{
Kind: v1.KindFramework,
Identifier: "mitre",
},
{
Kind: v1.KindFramework,
Identifier: "nsa",
},
},
},
},
{
name: "with args",
args: []string{
"file.yaml",
"file2.yaml",
},
want: &cautils.ScanInfo{
ScanType: cautils.ScanTypeRepo,
InputPatterns: []string{
"file.yaml",
"file2.yaml",
},
PolicyIdentifier: []cautils.PolicyIdentifier{
{
Kind: v1.KindFramework,
Identifier: "workloadscan",
},
{
Kind: v1.KindFramework,
Identifier: "allcontrols",
},
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := &cautils.ScanInfo{
View: string(cautils.SecurityViewType),
}
setSecurityViewScanInfo(tt.args, got)
if len(tt.want.InputPatterns) != len(got.InputPatterns) {
t.Errorf("in test: %s, got: %v, want: %v", tt.name, got.InputPatterns, tt.want.InputPatterns)
}
if tt.want.ScanType != got.ScanType {
t.Errorf("in test: %s, got: %v, want: %v", tt.name, got.ScanType, tt.want.ScanType)
}
for i := range tt.want.InputPatterns {
found := false
for j := range tt.want.InputPatterns[i] {
if tt.want.InputPatterns[i][j] == got.InputPatterns[i][j] {
found = true
break
}
}
if !found {
t.Errorf("in test: %s, got: %v, want: %v", tt.name, got.InputPatterns, tt.want.InputPatterns)
}
}
for i := range tt.want.PolicyIdentifier {
found := false
for j := range got.PolicyIdentifier {
if tt.want.PolicyIdentifier[i].Kind == got.PolicyIdentifier[j].Kind && tt.want.PolicyIdentifier[i].Identifier == got.PolicyIdentifier[j].Identifier {
found = true
break
}
}
if !found {
t.Errorf("in test: %s, got: %v, want: %v", tt.name, got.PolicyIdentifier, tt.want.PolicyIdentifier)
}
}
})
}
}
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,7 @@ package scan
import (
"testing"
"github.com/kubescape/kubescape/v3/cmd/shared"
"github.com/kubescape/kubescape/v3/core/cautils"
"github.com/kubescape/kubescape/v2/core/cautils"
)
// Test_validateControlScanInfo tests how scan info is validated for the `scan control` command
@@ -27,7 +26,7 @@ func Test_validateControlScanInfo(t *testing.T) {
{
"Unknown severity should be invalid for scan info",
&cautils.ScanInfo{FailThresholdSeverity: "Unknown"},
shared.ErrUnknownSeverity,
ErrUnknownSeverity,
},
}
@@ -67,17 +66,7 @@ func Test_validateFrameworkScanInfo(t *testing.T) {
{
"Unknown severity should be invalid for scan info",
&cautils.ScanInfo{FailThresholdSeverity: "Unknown"},
shared.ErrUnknownSeverity,
},
{
"Security view should be invalid for scan info",
&cautils.ScanInfo{View: string(cautils.SecurityViewType)},
nil,
},
{
"Empty view should be valid for scan info",
&cautils.ScanInfo{},
nil,
ErrUnknownSeverity,
},
}
@@ -97,22 +86,27 @@ func Test_validateFrameworkScanInfo(t *testing.T) {
}
}
func Test_validateWorkloadIdentifier(t *testing.T) {
func Test_validateSeverity(t *testing.T) {
testCases := []struct {
Description string
Input string
Want error
}{
{"valid workload identifier should be valid", "deployment/test", nil},
{"invalid workload identifier missing kind", "deployment", ErrInvalidWorkloadIdentifier},
{"invalid workload identifier with namespace", "ns/deployment/name", ErrInvalidWorkloadIdentifier},
{"low should be a valid severity", "low", nil},
{"Low should be a valid severity", "Low", nil},
{"medium should be a valid severity", "medium", nil},
{"Medium should be a valid severity", "Medium", nil},
{"high should be a valid severity", "high", nil},
{"Critical should be a valid severity", "Critical", nil},
{"critical should be a valid severity", "critical", nil},
{"Unknown should be an invalid severity", "Unknown", ErrUnknownSeverity},
}
for _, testCase := range testCases {
t.Run(testCase.Description, func(t *testing.T) {
input := testCase.Input
want := testCase.Want
got := validateWorkloadIdentifier(input)
got := validateSeverity(input)
if got != want {
t.Errorf("got: %v, want: %v", got, want)

View File

@@ -1,124 +0,0 @@
package scan
import (
"errors"
"fmt"
"strings"
"github.com/kubescape/go-logger"
"github.com/kubescape/kubescape/v3/core/cautils"
"github.com/kubescape/kubescape/v3/core/meta"
v1 "github.com/kubescape/opa-utils/httpserver/apis/v1"
"github.com/kubescape/opa-utils/objectsenvelopes"
"github.com/spf13/cobra"
)
var (
workloadExample = fmt.Sprintf(`
Scan a workload for misconfigurations and image vulnerabilities.
# Scan an workload
%[1]s scan workload <kind>/<name>
# Scan an workload in a specific namespace
%[1]s scan workload <kind>/<name> --namespace <namespace>
# Scan an workload from a file path
%[1]s scan workload <kind>/<name> --file-path <file path>
# Scan an workload from a helm-chart template
%[1]s scan workload <kind>/<name> --chart-path <chart path> --file-path <file path>
`, cautils.ExecName())
ErrInvalidWorkloadIdentifier = errors.New("invalid workload identifier")
)
var namespace string
// controlCmd represents the control command
func getWorkloadCmd(ks meta.IKubescape, scanInfo *cautils.ScanInfo) *cobra.Command {
workloadCmd := &cobra.Command{
Use: "workload <kind>/<name> [`<glob pattern>`/`-`] [flags]",
Short: "Scan a workload for misconfigurations and image vulnerabilities",
Example: workloadExample,
Args: func(cmd *cobra.Command, args []string) error {
if len(args) != 1 {
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>")
}
return validateWorkloadIdentifier(args[0])
},
RunE: func(cmd *cobra.Command, args []string) error {
kind, name, err := parseWorkloadIdentifierString(args[0])
if err != nil {
return fmt.Errorf("invalid input: %s", err.Error())
}
setWorkloadScanInfo(scanInfo, kind, name)
// todo: add api version if provided
results, err := ks.Scan(scanInfo)
if err != nil {
logger.L().Fatal(err.Error())
}
if err = results.HandleResults(ks.Context()); err != nil {
logger.L().Fatal(err.Error())
}
enforceSeverityThresholds(results.GetData().Report.SummaryDetails.GetResourcesSeverityCounters(), scanInfo, terminateOnExceedingSeverity)
return nil
},
}
workloadCmd.PersistentFlags().StringVarP(&namespace, "namespace", "n", "", "Namespace of the workload. Default will be empty.")
workloadCmd.PersistentFlags().StringVar(&scanInfo.FilePath, "file-path", "", "Path to the workload file.")
workloadCmd.PersistentFlags().StringVar(&scanInfo.ChartPath, "chart-path", "", "Path to the helm chart the workload is part of. Must be used with --file-path.")
return workloadCmd
}
func setWorkloadScanInfo(scanInfo *cautils.ScanInfo, kind string, name string) {
scanInfo.SetScanType(cautils.ScanTypeWorkload)
scanInfo.ScanImages = true
scanInfo.ScanObject = &objectsenvelopes.ScanObject{}
scanInfo.ScanObject.SetNamespace(namespace)
scanInfo.ScanObject.SetKind(kind)
scanInfo.ScanObject.SetName(name)
scanInfo.SetPolicyIdentifiers([]string{"workloadscan"}, v1.KindFramework)
if scanInfo.FilePath != "" {
scanInfo.InputPatterns = []string{scanInfo.FilePath}
}
}
func validateWorkloadIdentifier(workloadIdentifier string) error {
// workloadIdentifier is in the form of kind/name
x := strings.Split(workloadIdentifier, "/")
if len(x) != 2 || x[0] == "" || x[1] == "" {
return ErrInvalidWorkloadIdentifier
}
return nil
}
func parseWorkloadIdentifierString(workloadIdentifier string) (kind, name string, err error) {
// workloadIdentifier is in the form of namespace/kind/name
// example: default/Deployment/nginx-deployment
x := strings.Split(workloadIdentifier, "/")
if len(x) != 2 {
return "", "", ErrInvalidWorkloadIdentifier
}
return x[0], x[1], nil
}

View File

@@ -1,96 +0,0 @@
package scan
import (
"testing"
"github.com/kubescape/kubescape/v3/core/cautils"
"github.com/kubescape/kubescape/v3/core/mocks"
v1 "github.com/kubescape/opa-utils/httpserver/apis/v1"
"github.com/kubescape/opa-utils/objectsenvelopes"
"github.com/spf13/cobra"
"github.com/stretchr/testify/assert"
)
func TestSetWorkloadScanInfo(t *testing.T) {
test := []struct {
Description string
kind string
name string
want *cautils.ScanInfo
}{
{
Description: "Set workload scan info",
kind: "Deployment",
name: "test",
want: &cautils.ScanInfo{
PolicyIdentifier: []cautils.PolicyIdentifier{
{
Identifier: "workloadscan",
Kind: v1.KindFramework,
},
},
ScanType: cautils.ScanTypeWorkload,
ScanObject: &objectsenvelopes.ScanObject{
Kind: "Deployment",
Metadata: objectsenvelopes.ScanObjectMetadata{
Name: "test",
},
},
},
},
}
for _, tc := range test {
t.Run(
tc.Description,
func(t *testing.T) {
scanInfo := &cautils.ScanInfo{}
setWorkloadScanInfo(scanInfo, tc.kind, tc.name)
if scanInfo.ScanType != tc.want.ScanType {
t.Errorf("got: %v, want: %v", scanInfo.ScanType, tc.want.ScanType)
}
if scanInfo.ScanObject.Kind != tc.want.ScanObject.Kind {
t.Errorf("got: %v, want: %v", scanInfo.ScanObject.Kind, tc.want.ScanObject.Kind)
}
if scanInfo.ScanObject.Metadata.Name != tc.want.ScanObject.Metadata.Name {
t.Errorf("got: %v, want: %v", scanInfo.ScanObject.Metadata.Name, tc.want.ScanObject.Metadata.Name)
}
if len(scanInfo.PolicyIdentifier) != 1 {
t.Errorf("got: %v, want: %v", len(scanInfo.PolicyIdentifier), 1)
}
if scanInfo.PolicyIdentifier[0].Identifier != tc.want.PolicyIdentifier[0].Identifier {
t.Errorf("got: %v, want: %v", scanInfo.PolicyIdentifier[0].Identifier, tc.want.PolicyIdentifier[0].Identifier)
}
},
)
}
}
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

@@ -1,28 +0,0 @@
package shared
import (
"fmt"
"strings"
"github.com/kubescape/go-logger/helpers"
"github.com/kubescape/kubescape/v3/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
func ValidateSeverity(severity string) error {
for _, val := range reporthandlingapis.GetSupportedSeverities() {
if strings.EqualFold(severity, val) {
return nil
}
}
return ErrUnknownSeverity
}
// 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,124 +0,0 @@
package shared
import (
"context"
"os"
"reflect"
"testing"
"github.com/kubescape/go-logger/helpers"
"github.com/kubescape/kubescape/v3/core/cautils"
"github.com/kubescape/opa-utils/reporthandling/apis"
)
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 TestTerminateOnExceedingSeverity(t *testing.T) {
expectedMessage := "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 TestValidateSeverity(t *testing.T) {
testCases := []struct {
Description string
Input string
Want error
}{
{"low should be a valid severity", "low", nil},
{"Low should be a valid severity", "Low", nil},
{"medium should be a valid severity", "medium", nil},
{"Medium should be a valid severity", "Medium", nil},
{"high should be a valid severity", "high", nil},
{"Critical should be a valid severity", "Critical", nil},
{"critical should be a valid severity", "critical", nil},
{"Unknown should be an invalid severity", "Unknown", ErrUnknownSeverity},
}
for _, testCase := range testCases {
t.Run(testCase.Description, func(t *testing.T) {
input := testCase.Input
want := testCase.Want
got := ValidateSeverity(input)
if got != want {
t.Errorf("got: %v, want: %v", got, want)
}
})
}
}

34
cmd/submit/exceptions.go Normal file
View File

@@ -0,0 +1,34 @@
package submit
import (
"fmt"
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"
)
func getExceptionsCmd(ks meta.IKubescape, submitInfo *metav1.Submit) *cobra.Command {
return &cobra.Command{
Use: "exceptions <full path to exceptions file>",
Short: "Submit exceptions to the Kubescape SaaS version",
Args: func(cmd *cobra.Command, args []string) error {
if len(args) != 1 {
return fmt.Errorf("missing full path to exceptions file")
}
return nil
},
Run: func(cmd *cobra.Command, args []string) {
if err := flagValidationSubmit(submitInfo); err != nil {
logger.L().Fatal(err.Error())
}
if err := ks.SubmitExceptions(&submitInfo.Credentials, args[0]); err != nil {
logger.L().Fatal(err.Error())
}
},
}
}

97
cmd/submit/rbac.go Normal file
View File

@@ -0,0 +1,97 @@
package submit
import (
"fmt"
"github.com/google/uuid"
logger "github.com/kubescape/go-logger"
"github.com/kubescape/go-logger/helpers"
"github.com/kubescape/k8s-interface/k8sinterface"
"github.com/kubescape/kubescape/v2/core/cautils"
"github.com/kubescape/kubescape/v2/core/cautils/getter"
"github.com/kubescape/kubescape/v2/core/meta"
"github.com/kubescape/kubescape/v2/core/meta/cliinterfaces"
v1 "github.com/kubescape/kubescape/v2/core/meta/datastructures/v1"
reporterv2 "github.com/kubescape/kubescape/v2/core/pkg/resultshandling/reporter/v2"
"github.com/kubescape/rbac-utils/rbacscanner"
"github.com/spf13/cobra"
)
var (
rbacExamples = fmt.Sprintf(`
# Submit cluster's Role-Based Access Control(RBAC)
%[1]s submit rbac
# Submit cluster's Role-Based Access Control(RBAC) with account ID
%[1]s submit rbac --account <account-id>
`, cautils.ExecName())
)
// getRBACCmd represents the RBAC command
func getRBACCmd(ks meta.IKubescape, submitInfo *v1.Submit) *cobra.Command {
return &cobra.Command{
Use: "rbac",
Deprecated: "This command is deprecated and will not be supported after 1/Jan/2023. Please use the 'scan' command instead.",
Example: rbacExamples,
Short: "Submit cluster's Role-Based Access Control(RBAC)",
Long: ``,
RunE: func(_ *cobra.Command, args []string) error {
if err := flagValidationSubmit(submitInfo); err != nil {
return err
}
k8s := k8sinterface.NewKubernetesApi()
// get config
clusterConfig := getTenantConfig(&submitInfo.Credentials, "", "", k8s)
if err := clusterConfig.SetTenant(); err != nil {
logger.L().Error("failed setting account ID", helpers.Error(err))
}
if clusterConfig.GetAccountID() == "" {
return fmt.Errorf("account ID is not set, run '%[1]s submit rbac --account <account-id>'", cautils.ExecName())
}
// list RBAC
rbacObjects := cautils.NewRBACObjects(rbacscanner.NewRbacScannerFromK8sAPI(k8s, clusterConfig.GetAccountID(), clusterConfig.GetContextName()))
// submit resources
r := reporterv2.NewReportEventReceiver(clusterConfig.GetConfigObj(), uuid.NewString(), reporterv2.SubmitContextRBAC)
submitInterfaces := cliinterfaces.SubmitInterfaces{
ClusterConfig: clusterConfig,
SubmitObjects: rbacObjects,
Reporter: r,
}
if err := ks.Submit(submitInterfaces); err != nil {
logger.L().Fatal(err.Error())
}
return nil
},
}
}
// getKubernetesApi
func getKubernetesApi() *k8sinterface.KubernetesApi {
if !k8sinterface.IsConnectedToCluster() {
return nil
}
return k8sinterface.NewKubernetesApi()
}
func getTenantConfig(credentials *cautils.Credentials, clusterName string, customClusterName string, k8s *k8sinterface.KubernetesApi) cautils.ITenantConfig {
if !k8sinterface.IsConnectedToCluster() || k8s == nil {
return cautils.NewLocalConfig(getter.GetKSCloudAPIConnector(), credentials, clusterName, customClusterName)
}
return cautils.NewClusterConfig(k8s, getter.GetKSCloudAPIConnector(), credentials, clusterName, customClusterName)
}
// Check if the flag entered are valid
func flagValidationSubmit(submitInfo *v1.Submit) error {
// Validate the user's credentials
return submitInfo.Credentials.Validate()
}

105
cmd/submit/results.go Normal file
View File

@@ -0,0 +1,105 @@
package submit
import (
"encoding/json"
"fmt"
"os"
"github.com/google/uuid"
"github.com/kubescape/kubescape/v2/core/cautils"
reporthandlingv2 "github.com/kubescape/opa-utils/reporthandling/v2"
logger "github.com/kubescape/go-logger"
"github.com/kubescape/go-logger/helpers"
"github.com/kubescape/k8s-interface/workloadinterface"
"github.com/kubescape/kubescape/v2/core/meta"
"github.com/kubescape/kubescape/v2/core/meta/cliinterfaces"
v1 "github.com/kubescape/kubescape/v2/core/meta/datastructures/v1"
reporterv2 "github.com/kubescape/kubescape/v2/core/pkg/resultshandling/reporter/v2"
"github.com/spf13/cobra"
)
var formatVersion string
type ResultsObject struct {
filePath string
customerGUID string
clusterName string
}
func NewResultsObject(customerGUID, clusterName, filePath string) *ResultsObject {
return &ResultsObject{
filePath: filePath,
customerGUID: customerGUID,
clusterName: clusterName,
}
}
func (resultsObject *ResultsObject) SetResourcesReport() (*reporthandlingv2.PostureReport, error) {
// load framework results from json file
report, err := loadResultsFromFile(resultsObject.filePath)
if err != nil {
return nil, err
}
return report, nil
}
func (resultsObject *ResultsObject) ListAllResources() (map[string]workloadinterface.IMetadata, error) {
return map[string]workloadinterface.IMetadata{}, nil
}
func getResultsCmd(ks meta.IKubescape, submitInfo *v1.Submit) *cobra.Command {
var resultsCmd = &cobra.Command{
Use: fmt.Sprintf("results <json file>\nExample:\n$ %[1]s submit results path/to/results.json --format-version v2", cautils.ExecName()),
Short: "Submit a pre scanned results file. The file must be in json format",
Long: ``,
RunE: func(cmd *cobra.Command, args []string) error {
if err := flagValidationSubmit(submitInfo); err != nil {
return err
}
if len(args) == 0 {
return fmt.Errorf("missing results file")
}
k8s := getKubernetesApi()
// get config
clusterConfig := getTenantConfig(&submitInfo.Credentials, "", "", k8s)
if err := clusterConfig.SetTenant(); err != nil {
logger.L().Error("failed setting account ID", helpers.Error(err))
}
resultsObjects := NewResultsObject(clusterConfig.GetAccountID(), clusterConfig.GetContextName(), args[0])
r := reporterv2.NewReportEventReceiver(clusterConfig.GetConfigObj(), uuid.NewString(), reporterv2.SubmitContextScan)
submitInterfaces := cliinterfaces.SubmitInterfaces{
ClusterConfig: clusterConfig,
SubmitObjects: resultsObjects,
Reporter: r,
}
if err := ks.Submit(submitInterfaces); err != nil {
logger.L().Fatal(err.Error())
}
return nil
},
}
resultsCmd.PersistentFlags().StringVar(&formatVersion, "format-version", "v1", "Output object can be different between versions, this is for maintaining backward and forward compatibility. Supported:'v1'/'v2'")
return resultsCmd
}
func loadResultsFromFile(filePath string) (*reporthandlingv2.PostureReport, error) {
report := &reporthandlingv2.PostureReport{}
f, err := os.ReadFile(filePath)
if err != nil {
return nil, err
}
if err = json.Unmarshal(f, report); err != nil {
return report, fmt.Errorf("failed to unmarshal results file: %s, make sure you run kubescape with '--format=json --format-version=v2'", err.Error())
}
return report, nil
}

40
cmd/submit/submit.go Normal file
View File

@@ -0,0 +1,40 @@
package submit
import (
"fmt"
"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"
)
var submitCmdExamples = fmt.Sprintf(`
# Submit Kubescape scan results file
%[1]s submit results
# Submit exceptions file to Kubescape SaaS
%[1]s submit exceptions
`, cautils.ExecName())
func GetSubmitCmd(ks meta.IKubescape) *cobra.Command {
var submitInfo metav1.Submit
submitCmd := &cobra.Command{
Use: "submit <command>",
Short: "Submit an object to the Kubescape SaaS version",
Long: ``,
Example: submitCmdExamples,
Run: func(cmd *cobra.Command, args []string) {
},
}
submitCmd.PersistentFlags().StringVarP(&submitInfo.Credentials.Account, "account", "", "", "Kubescape SaaS account ID. Default will load account ID from cache")
submitCmd.PersistentFlags().StringVarP(&submitInfo.Credentials.ClientID, "client-id", "", "", "Kubescape SaaS client ID. Default will load client ID from cache, read more - https://hub.armosec.io/docs/authentication")
submitCmd.PersistentFlags().StringVarP(&submitInfo.Credentials.SecretKey, "secret-key", "", "", "Kubescape SaaS secret key. Default will load secret key from cache, read more - https://hub.armosec.io/docs/authentication")
submitCmd.AddCommand(getExceptionsCmd(ks, &submitInfo))
submitCmd.AddCommand(getResultsCmd(ks, &submitInfo))
submitCmd.AddCommand(getRBACCmd(ks, &submitInfo))
return submitCmd
}

View File

@@ -5,52 +5,52 @@ package update
// kubescape update
import (
"fmt"
"strings"
"os/exec"
"runtime"
"github.com/kubescape/kubescape/v3/core/meta"
"github.com/kubescape/backend/pkg/versioncheck"
"github.com/kubescape/go-logger"
"github.com/kubescape/go-logger/helpers"
"github.com/kubescape/kubescape/v3/core/cautils"
logger "github.com/kubescape/go-logger"
"github.com/kubescape/kubescape/v2/core/cautils"
"github.com/spf13/cobra"
)
const (
installationLink string = "https://kubescape.io/docs/install-cli/"
)
var updateCmdExamples = fmt.Sprintf(`
# Update to the latest kubescape release
%[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,
Use: "update",
Short: "Update your version",
Long: ``,
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(("You are in the latest version"))
} else {
fmt.Printf("Version %s is available. Please refer to our installation documentation: %s\n", versioncheck.LatestReleaseVersion, installationLink)
const OSTYPE string = runtime.GOOS
var ShellToUse string
switch OSTYPE {
case "windows":
cautils.StartSpinner()
//run the installation command for windows
ShellToUse = "powershell"
_, err := exec.Command(ShellToUse, "-c", "iwr -useb https://raw.githubusercontent.com/kubescape/kubescape/master/install.ps1 | iex").Output()
if err != nil {
logger.L().Fatal(err.Error())
}
cautils.StopSpinner()
default:
ShellToUse = "bash"
cautils.StartSpinner()
//run the installation command for linux and macOS
_, err := exec.Command(ShellToUse, "-c", "curl -s https://raw.githubusercontent.com/kubescape/kubescape/master/install.sh | /bin/bash").Output()
if err != nil {
logger.L().Fatal(err.Error())
}
cautils.StopSpinner()
}
}
return nil
},

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
}

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