Compare commits

..

2 Commits

Author SHA1 Message Date
Matthias Bertschy
c011b0e99c todo 2023-01-16 11:41:37 +01:00
Matthias Bertschy
2156677e04 add kubectl plugin with krew 2023-01-16 11:37:26 +01:00
281 changed files with 5159 additions and 164001 deletions

BIN
.DS_Store vendored Normal file

Binary file not shown.

View File

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

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,41 +0,0 @@
name: 00-pr_scanner
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:
pull-requests: write
uses: ./.github/workflows/a-pr-scanner.yaml
with:
RELEASE: ""
CLIENT: test
secrets: inherit
binary-build:
uses: ./.github/workflows/b-binary-build-and-e2e-tests.yaml
with:
COMPONENT_NAME: kubescape
CGO_ENABLED: 1
GO111MODULE: ""
GO_VERSION: "1.20"
RELEASE: ""
CLIENT: test
ARCH_MATRIX: '[ "" ]'
OS_MATRIX: '[ "ubuntu-20.04" ]'
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,51 +0,0 @@
name: 02-create_release
on:
push:
tags:
- 'v*.*.*-rc.*'
jobs:
retag:
outputs:
NEW_TAG: ${{ steps.tag-calculator.outputs.NEW_TAG }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # ratchet:actions/checkout@v3
- id: tag-calculator
uses: ./.github/actions/tag-action
with:
SUB_STRING: "-rc"
binary-build:
needs: [retag]
uses: ./.github/workflows/b-binary-build-and-e2e-tests.yaml
with:
COMPONENT_NAME: kubescape
CGO_ENABLED: 1
GO111MODULE: ""
GO_VERSION: "1.20"
RELEASE: ${{ needs.retag.outputs.NEW_TAG }}
CLIENT: release
secrets: inherit
create-release:
permissions:
contents: write
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:
id-token: write
packages: write
contents: read
uses: ./.github/workflows/d-publish-image.yaml
needs: [create-release, retag]
with:
client: "image-release"
image_name: "quay.io/${{ github.repository_owner }}/kubescape"
image_tag: ${{ needs.retag.outputs.NEW_TAG }}
support_platforms: true
cosign: true
secrets: inherit

View File

@@ -1,41 +0,0 @@
name: 03-post_release
on:
release:
types: [published]
branches:
- 'master'
- 'main'
jobs:
post_release:
name: Post release jobs
runs-on: ubuntu-latest
steps:
- name: Digest
uses: MCJack123/ghaction-generate-release-hashes@c03f3111b39432dde3edebe401c5a8d1ffbbf917 # ratchet:MCJack123/ghaction-generate-release-hashes@v1
with:
hash-type: sha1
file-name: kubescape-release-digests
- name: Invoke workflow to update packaging
uses: benc-uk/workflow-dispatch@v1
if: github.repository_owner == 'kubescape'
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,4 +1,5 @@
name: d-publish-image
name: 03-publish-image
on:
workflow_call:
inputs:
@@ -24,6 +25,7 @@ on:
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
@@ -31,39 +33,51 @@ jobs:
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
- 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 }}
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
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@ac593985615ec2ede58e132d2e21d2b1cbd6127c # ratchet:actions/checkout@v3
- uses: actions/checkout@v3
with:
submodules: recursive
- name: Set up QEMU
uses: docker/setup-qemu-action@e81a89b1732b9c48d79cd809d8d81d79c4647a18 # ratchet:docker/setup-qemu-action@v2
uses: docker/setup-qemu-action@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@f03ac48505955848960e80bbb68046aa35c7b9e7 # ratchet:docker/setup-buildx-action@v2
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
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@4079ad3567a89f68395480299c77e40170430341 # ratchet:sigstore/cosign-installer@main
uses: sigstore/cosign-installer@main
with:
cosign-release: 'v1.12.0'
- name: sign kubescape container image
@@ -71,4 +85,5 @@ jobs:
env:
COSIGN_EXPERIMENTAL: "true"
run: |
cosign sign --force ${{ inputs.image_name }}
cosign sign --force ${{ inputs.image_name }}

View File

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

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,89 +0,0 @@
name: a-pr-scanner
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: "./..."
jobs:
scanners:
env:
GITGUARDIAN_API_KEY: ${{ secrets.GITGUARDIAN_API_KEY }}
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
name: PR Scanner
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # ratchet:actions/checkout@v3
with:
fetch-depth: 0
submodules: recursive
- uses: actions/setup-go@6edd4406fa81c3da01a34fa6f6343087c207a568 # Install go because go-licenses use it ratchet:actions/setup-go@v3
name: Installing go
with:
go-version: '1.20'
cache: true
- 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@4ab2994172fadab959240525e6b833d9ae3aca61 # ratchet: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@806182742461562b67788a64410098c9d9b96adb # ratchet: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.GITHUB_TOKEN }}
path-to-lcov: coverage.lcov
- name: Comment results to PR
continue-on-error: true # Warning: This might break opening PRs from forks
uses: peter-evans/create-or-update-comment@5adcb0bb0f9fb3f95ef05400558bdb3f329ee808 # ratchet:peter-evans/create-or-update-comment@v2.1.0
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,346 +0,0 @@
name: b-binary-build-and-e2e-tests
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.20"
GO111MODULE:
required: false
type: string
default: ""
CGO_ENABLED:
type: number
default: 1
required: false
OS_MATRIX:
type: string
required: false
default: '[ "ubuntu-20.04", "macos-latest", "windows-latest"]'
ARCH_MATRIX:
type: string
required: false
default: '[ "", "arm64"]'
BINARY_TESTS:
type: string
required: false
default: '[ "scan_nsa", "scan_mitre", "scan_with_exceptions", "scan_repository", "scan_local_file", "scan_local_glob_files", "scan_local_list_of_files", "scan_nsa_and_submit_to_backend", "scan_mitre_and_submit_to_backend", "scan_local_repository_and_submit_to_backend", "scan_repository_from_url_and_submit_to_backend", "scan_with_exception_to_backend", "scan_with_custom_framework", "scan_customer_configuration", "host_scanner", "scan_compliance_score" ]'
workflow_call:
inputs:
COMPONENT_NAME:
required: true
type: string
RELEASE:
required: true
type: string
CLIENT:
required: true
type: string
GO_VERSION:
type: string
default: "1.20"
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_exception_to_backend", "scan_with_custom_framework", "scan_customer_configuration", "host_scanner", "scan_compliance_score", "scan_custom_framework_scanning_file_scope_testing", "scan_custom_framework_scanning_cluster_scope_testing", "scan_custom_framework_scanning_cluster_and_file_scope_testing", "unified_configuration_config_view", "unified_configuration_config_set", "unified_configuration_config_delete" ]'
OS_MATRIX:
type: string
required: false
default: '[ "ubuntu-20.04", "macos-latest", "windows-latest"]'
ARCH_MATRIX:
type: string
required: false
default: '[ "", "arm64"]'
jobs:
wf-preparation:
name: secret-validator
runs-on: ubuntu-latest
outputs:
TEST_NAMES: ${{ steps.export_tests_to_env.outputs.TEST_NAMES }}
OS_MATRIX: ${{ steps.export_os_to_env.outputs.OS_MATRIX }}
ARCH_MATRIX: ${{ steps.export_arch_to_env.outputs.ARCH_MATRIX }}
is-secret-set: ${{ steps.check-secret-set.outputs.is-secret-set }}
steps:
- 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 != '' && \n env.USERNAME != '' &&\n env.PASSWORD != '' &&\n env.CLIENT_ID != '' &&\n env.SECRET_KEY != '' &&\n env.REGISTRY_USERNAME != '' &&\n env.REGISTRY_PASSWORD != ''\n }}\" >> $GITHUB_OUTPUT\n"
- id: export_os_to_env
name: set test name
run: |
echo "OS_MATRIX=$input" >> $GITHUB_OUTPUT
env:
input: ${{ inputs.OS_MATRIX }}
- id: export_tests_to_env
name: set test name
run: |
echo "TEST_NAMES=$input" >> $GITHUB_OUTPUT
env:
input: ${{ inputs.BINARY_TESTS }}
- id: export_arch_to_env
name: set test name
run: |
echo "ARCH_MATRIX=$input" >> $GITHUB_OUTPUT
env:
input: ${{ inputs.ARCH_MATRIX }}
binary-build:
name: Create cross-platform build
needs: wf-preparation
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GOARCH: ${{ matrix.arch }}
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: ${{ fromJson(needs.wf-preparation.outputs.OS_MATRIX) }}
arch: ${{ fromJson(needs.wf-preparation.outputs.ARCH_MATRIX) }}
exclude:
- os: windows-latest
arch: arm64
steps:
- uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # ratchet:actions/checkout@v3
with:
fetch-depth: 0
submodules: recursive
- name: Cache Go modules (Linux)
if: matrix.os == 'ubuntu-20.04'
uses: actions/cache@69d9d449aced6a2ede0bc19182fadc3a0a42d2b0 # ratchet:actions/cache@v3
with:
path: |
~/.cache/go-build
~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-
- name: Cache Go modules (macOS)
if: matrix.os == 'macos-latest'
uses: actions/cache@69d9d449aced6a2ede0bc19182fadc3a0a42d2b0 # ratchet:actions/cache@v3
with:
path: |
~/Library/Caches/go-build
~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-
- name: Cache Go modules (Windows)
if: matrix.os == 'windows-latest'
uses: actions/cache@69d9d449aced6a2ede0bc19182fadc3a0a42d2b0 # ratchet:actions/cache@v3
with:
path: |
~\AppData\Local\go-build
~\go\pkg\mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-
- uses: actions/setup-go@6edd4406fa81c3da01a34fa6f6343087c207a568 # ratchet:actions/setup-go@v3
name: Installing go
with:
go-version: ${{ inputs.GO_VERSION }}
cache: true
- name: start ${{ matrix.arch }} environment in container
run: |
sudo apt-get update
sudo apt-get install -y binfmt-support qemu-user-static
sudo docker run --platform linux/${{ matrix.arch }} -e RELEASE=${{ inputs.RELEASE }} \
-e CLIENT=${{ inputs.CLIENT }} -e CGO_ENABLED=${{ inputs.CGO_ENABLED }} \
-e KUBESCAPE_SKIP_UPDATE_CHECK=true -e GOARCH=${{ matrix.arch }} -v ${PWD}:/work \
-w /work -v ~/go/pkg/mod:/root/go/pkg/mod -v ~/.cache/go-build:/root/.cache/go-build \
-d --name build golang:${{ inputs.GO_VERSION }}-bullseye sleep 21600
sudo docker ps
DOCKER_CMD="sudo docker exec build"
${DOCKER_CMD} apt update
${DOCKER_CMD} apt install -y cmake python3
${DOCKER_CMD} git config --global --add safe.directory '*'
echo "DOCKER_CMD=${DOCKER_CMD}" >> $GITHUB_ENV;
if: matrix.os == 'ubuntu-20.04' && matrix.arch != ''
- name: Install MSYS2 & libgit2 (Windows)
shell: pwsh
run: .\build.ps1 all
if: matrix.os == 'windows-latest'
- name: Install pkg-config (macOS)
run: brew install pkg-config
if: matrix.os == 'macos-latest'
- name: Install libgit2 (Linux/macOS)
run: ${{ env.DOCKER_CMD }} make libgit2${{ matrix.arch }}
if: matrix.os != 'windows-latest'
- name: Test core pkg
run: ${{ env.DOCKER_CMD }} go test "-tags=static,gitenabled" -v ./...
if: "!startsWith(github.ref, 'refs/tags') && matrix.os == 'ubuntu-20.04' && matrix.arch == '' || startsWith(github.ref, 'refs/tags') && (matrix.os != 'macos-latest' || matrix.arch != 'arm64')"
- name: Test httphandler pkg
run: ${{ env.DOCKER_CMD }} sh -c 'cd httphandler && go test "-tags=static,gitenabled" -v ./...'
if: "!startsWith(github.ref, 'refs/tags') && matrix.os == 'ubuntu-20.04' && matrix.arch == '' || startsWith(github.ref, 'refs/tags') && (matrix.os != 'macos-latest' || matrix.arch != 'arm64')"
- name: Build
env:
RELEASE: ${{ inputs.RELEASE }}
CLIENT: ${{ inputs.CLIENT }}
CGO_ENABLED: ${{ inputs.CGO_ENABLED }}
run: ${{ env.DOCKER_CMD }} python3 --version && ${{ env.DOCKER_CMD }} python3 build.py
- name: Smoke Testing (Windows / MacOS)
env:
RELEASE: ${{ inputs.RELEASE }}
KUBESCAPE_SKIP_UPDATE_CHECK: "true"
run: python3 smoke_testing/init.py ${PWD}/build/kubescape-${{ matrix.os }}
if: startsWith(github.ref, 'refs/tags') && matrix.os != 'ubuntu-20.04' && matrix.arch == ''
- name: Smoke Testing (Linux amd64)
env:
RELEASE: ${{ inputs.RELEASE }}
KUBESCAPE_SKIP_UPDATE_CHECK: "true"
run: ${{ env.DOCKER_CMD }} python3 smoke_testing/init.py ${PWD}/build/kubescape-ubuntu-latest
if: matrix.os == 'ubuntu-20.04' && matrix.arch == ''
- name: Smoke Testing (Linux ${{ matrix.arch }})
env:
RELEASE: ${{ inputs.RELEASE }}
KUBESCAPE_SKIP_UPDATE_CHECK: "true"
run: ${{ env.DOCKER_CMD }} python3 smoke_testing/init.py ./build/kubescape-${{ matrix.arch }}-ubuntu-latest
if: startsWith(github.ref, 'refs/tags') && matrix.os == 'ubuntu-20.04' && matrix.arch != ''
- name: golangci-lint
if: matrix.os == 'ubuntu-20.04'
continue-on-error: true
uses: golangci/golangci-lint-action@08e2f20817b15149a52b5b3ebe7de50aff2ba8c5 # ratchet:golangci/golangci-lint-action@v3
with:
version: latest
args: --timeout 10m --build-tags=static
only-new-issues: true
- uses: actions/upload-artifact@83fd05a356d7e2593de66fc9913b3002723633cb # ratchet:actions/upload-artifact@v3.1.1
name: Upload artifact (Linux)
if: matrix.os == 'ubuntu-20.04'
with:
name: kubescape${{ matrix.arch }}-ubuntu-latest
path: build/
if-no-files-found: error
- uses: actions/upload-artifact@83fd05a356d7e2593de66fc9913b3002723633cb # ratchet:actions/upload-artifact@v3.1.1
name: Upload artifact (MacOS, Win)
if: matrix.os != 'ubuntu-20.04'
with:
name: kubescape${{ matrix.arch }}-${{ matrix.os }}
path: build/
if-no-files-found: error
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@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # ratchet:actions/download-artifact@v3.0.2
id: download-artifact
with:
name: kubescape-ubuntu-latest
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@ac593985615ec2ede58e132d2e21d2b1cbd6127c # ratchet:actions/checkout@v3
with:
repository: armosec/system-tests
path: .
- uses: actions/setup-python@d27e3f3d7c64b4bbf8e4abfb9b63b83e846e0435 # ratchet: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@d08cf6ff1575077dee99962540d77ce91c62387d # ratchet:helm/kind-action@v1.3.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@6e9933f4a97f4d2b99acef4d7b97924466037882 # ratchet:mikepenz/action-junit-report@v3.6.1
if: always() # always run even if the previous step fails
with:
report_paths: '**/results_xml_format/**.xml'
commit: ${{github.event.workflow_run.head_sha}}

View File

@@ -1,34 +0,0 @@
name: build-image
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:
publish-image:
permissions:
id-token: write
packages: write
contents: read
uses: ./.github/workflows/d-publish-image.yaml
with:
client: ${{ inputs.CLIENT }}
image_name: "quay.io/${{ github.repository_owner }}/kubescape"
image_tag: ${{ inputs.IMAGE_TAG }}
support_platforms: ${{ inputs.PLATFORMS }}
cosign: ${{ inputs.CO_SIGN }}
secrets: inherit

View File

@@ -1,72 +0,0 @@
name: c-create_release
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@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # ratchet:actions/download-artifact@v3.0.2
id: download-artifact
with:
path: .
# TODO: kubescape-windows-latest is deprecated and should be removed
- name: Get kubescape.exe from kubescape-windows-latest
run: cp ./kubescape-${{ env.WINDOWS_OS }}/kubescape-${{ env.WINDOWS_OS }} ./kubescape-${{ env.WINDOWS_OS }}/kubescape.exe
- name: Set release token
run: |
if [ "${{ secrets.GH_PERSONAL_ACCESS_TOKEN }}" != "" ]; then
echo "TOKEN=${{ secrets.GH_PERSONAL_ACCESS_TOKEN }}" >> $GITHUB_ENV;
else
echo "TOKEN=${{ secrets.GITHUB_TOKEN }}" >> $GITHUB_ENV;
fi
- name: Release
uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 # ratchet:softprops/action-gh-release@v1
with:
token: ${{ env.TOKEN }}
name: ${{ inputs.RELEASE_NAME }}
tag_name: ${{ inputs.TAG }}
body: ${{ github.event.pull_request.body }}
draft: ${{ inputs.DRAFT }}
fail_on_unmatched_files: true
prerelease: false
# TODO: kubescape-windows-latest is deprecated and should be removed
files: |
./kubescape-${{ env.WINDOWS_OS }}/kubescape-${{ env.WINDOWS_OS }}
./kubescape-${{ env.MAC_OS }}/kubescape-${{ env.MAC_OS }}
./kubescape-${{ env.MAC_OS }}/kubescape-${{ env.MAC_OS }}.sha256
./kubescape-${{ env.MAC_OS }}/kubescape-${{ env.MAC_OS }}.tar.gz
./kubescape-${{ env.UBUNTU_OS }}/kubescape-${{ env.UBUNTU_OS }}
./kubescape-${{ env.UBUNTU_OS }}/kubescape-${{ env.UBUNTU_OS }}.sha256
./kubescape-${{ env.UBUNTU_OS }}/kubescape-${{ env.UBUNTU_OS }}.tar.gz
./kubescape-${{ env.WINDOWS_OS }}/kubescape.exe
./kubescape-${{ env.WINDOWS_OS }}/kubescape-${{ env.WINDOWS_OS }}.sha256
./kubescape-${{ env.WINDOWS_OS }}/kubescape-${{ env.WINDOWS_OS }}.tar.gz
./kubescapearm64-${{ env.MAC_OS }}/kubescape-arm64-${{ env.MAC_OS }}
./kubescapearm64-${{ env.MAC_OS }}/kubescape-arm64-${{ env.MAC_OS }}.sha256
./kubescapearm64-${{ env.MAC_OS }}/kubescape-arm64-${{ env.MAC_OS }}.tar.gz
./kubescapearm64-${{ env.UBUNTU_OS }}/kubescape-arm64-${{ env.UBUNTU_OS }}
./kubescapearm64-${{ env.UBUNTU_OS }}/kubescape-arm64-${{ env.UBUNTU_OS }}.sha256
./kubescapearm64-${{ env.UBUNTU_OS }}/kubescape-arm64-${{ env.UBUNTU_OS }}.tar.gz

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

View File

@@ -1,23 +0,0 @@
name: pr-agent
on:
issue_comment:
permissions:
issues: write
pull-requests: write
jobs:
pr_agent:
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,19 +1,23 @@
on:
issues:
types: [opened, labeled]
jobs:
open_PR_message:
if: github.event.label.name == 'typo'
runs-on: ubuntu-latest
steps:
- uses: ben-z/actions-comment-on-issue@10be23f9c43ac792663043420fda29dde07e2f0f # ratchet:ben-z/actions-comment-on-issue@1.0.2
- uses: ben-z/actions-comment-on-issue@1.0.2
with:
message: "Hello! :wave:\n\nThis issue is being automatically closed, Please open a PR with a relevant fix."
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
auto_close_issues:
runs-on: ubuntu-latest
steps:
- uses: lee-dohm/close-matching-issues@e9e43aad2fa6f06a058cedfd8fb975fd93b56d8f # ratchet:lee-dohm/close-matching-issues@v2
- uses: lee-dohm/close-matching-issues@v2
with:
query: 'label:typo'
token: ${{ secrets.GITHUB_TOKEN }}

1
.gitignore vendored
View File

@@ -7,4 +7,3 @@
.history
ca.srl
*.out
ks

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

@@ -15,17 +15,13 @@ so the maintainers are able to help guide you and let you know if you are going
Please follow our [code of conduct](CODE_OF_CONDUCT.md) in all of your interactions within the project.
## Build and test locally
Please follow the [instructions here](https://github.com/kubescape/kubescape/wiki/Building).
## Pull Request Process
1. Ensure any install or build dependencies are removed before the end of the layer when doing a
build.
2. Update the README.md with details of changes to the interface, this includes new environment
variables, exposed ports, useful file locations and container parameters.
3. Open Pull Request to the `master` branch.
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
@@ -51,7 +47,7 @@ Add [`-s`](https://git-scm.com/docs/git-commit#Documentation/git-commit.txt--s)
```git commit -s -m "Fix issue 64738"```
This is tedious, and if you forget, you'll have to [amend your commit](#fixing-a-commit-where-the-dco-failed).
This is tedious, and if you forget, you'll have to [amend your commit](#f)
### Configure a repository to always include sign off
@@ -63,36 +59,6 @@ curl -Ls https://gist.githubusercontent.com/dixudx/7d7edea35b4d91e1a2a8fbf41d095
chmod +x .git/hooks/prepare-commit-msg
```
### Use semantic commit messages (optional)
When contributing, you could consider using [conventional commits](https://www.conventionalcommits.org/en/v1.0.0/), in order to improve logs readability and help us to automatically generate `CHANGELOG`s.
Format: `<type>(<scope>): <subject>`
`<scope>` is optional
#### Example
```
feat(cmd): add kubectl plugin
^--^ ^-^ ^----------------^
| | |
| | +-> subject: summary in present tense.
| |
| +-------> scope: point of interest
|
+-------> type: chore, docs, feat, fix, refactor, style, or test.
```
More Examples:
* `feat`: new feature for the user, not a new feature for build script
* `fix`: bug fix for the user, not a fix to a build script
* `docs`: changes to the documentation
* `style`: formatting, missing semi colons, etc; no production code change
* `refactor`: refactoring production code, eg. renaming a variable
* `test`: adding missing tests, refactoring tests; no production code change
* `chore`: updating grunt tasks etc; no production code change
## Fixing a commit where the DCO failed
Check out [this guide](https://github.com/src-d/guide/blob/master/developer-community/fix-DCO.md).

View File

@@ -10,14 +10,6 @@ libgit2:
-git submodule update --init --recursive
cd git2go; make install-static
# build and install libgit2 for macOS m1
libgit2arm64:
git submodule update --init --recursive
if [ "$(shell uname -s)" = "Darwin" ]; then \
sed -i '' 's/cmake -D/cmake -DCMAKE_OSX_ARCHITECTURES="arm64" -D/' git2go/script/build-libgit2.sh; \
fi
cd git2go; make install-static
# go build tags
TAGS = "gitenabled,static"

View File

@@ -1,5 +1,5 @@
[![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)
@@ -37,11 +37,11 @@ curl -s https://raw.githubusercontent.com/kubescape/kubescape/master/install.sh
Learn more about:
* [Installing Kubescape](docs/installation.md)
* [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](https://github.com/kubescape/kubescape/wiki/Building)
* [Building Kubescape from source](docs/building.md)
_Did you know you can use Kubescape in all these places?_
@@ -65,7 +65,7 @@ It retrieves Kubernetes objects from the API server and runs a set of [Rego snip
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.
We hold [community meetings](https://zoom.us/j/95174063585) on Zoom, on the first Tuesday of every month, at 14:00 GMT. ([See that in your local time zone](https://time.is/compare/1400_in_GMT)).
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).

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

View File

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

View File

@@ -6,7 +6,6 @@ import subprocess
import tarfile
BASE_GETTER_CONST = "github.com/kubescape/kubescape/v2/core/cautils/getter"
CURRENT_PLATFORM = platform.system()
platformSuffixes = {
"Windows": "windows-latest",
@@ -21,19 +20,19 @@ def check_status(status, msg):
def get_build_dir():
return "build"
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():
if CURRENT_PLATFORM not in platformSuffixes: raise OSError("Platform %s is not supported!" % (CURRENT_PLATFORM))
current_platform = platform.system()
# # TODO: kubescape-windows-latest is deprecated and should be removed
# if CURRENT_PLATFORM == "Windows": return "kubescape.exe"
if current_platform not in platformSuffixes: raise OSError("Platform %s is not supported!" % (current_platform))
package_name = "kubescape-"
if os.getenv("GOARCH"):
package_name += os.getenv("GOARCH") + "-"
return package_name + platformSuffixes[CURRENT_PLATFORM]
return "kubescape-" + platformSuffixes[current_platform]
def main():
@@ -81,11 +80,7 @@ def main():
kube_sha.write(sha256.hexdigest())
with tarfile.open(tar_file, 'w:gz') as archive:
name = "kubescape"
if CURRENT_PLATFORM == "Windows":
name += ".exe"
archive.add(ks_file, name)
archive.add("LICENSE", "LICENSE")
archive.add(ks_file, "kubescape")
print("Build Done")

View File

@@ -1,4 +1,4 @@
FROM golang:1.20-alpine as builder
FROM golang:1.19-alpine as builder
ARG image_version
ARG client
@@ -25,13 +25,13 @@ RUN rm -rf git2go && make libgit2
# build kubescape server
WORKDIR /work/httphandler
RUN python build.py
RUN ls -ltr build/
RUN ls -ltr build/ubuntu-latest
# build kubescape cmd
WORKDIR /work
RUN python build.py
RUN /work/build/kubescape-ubuntu-latest download artifacts -o /work/artifacts
RUN /work/build/ubuntu-latest/kubescape-ubuntu-latest download artifacts -o /work/artifacts
FROM alpine:3.16.2
@@ -45,7 +45,7 @@ USER ks
WORKDIR /home/ks
COPY --from=builder /work/httphandler/build/kubescape-ubuntu-latest /usr/bin/ksserver
COPY --from=builder /work/build/kubescape-ubuntu-latest /usr/bin/kubescape
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

@@ -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 {

View File

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

View File

@@ -1,7 +1,6 @@
package download
import (
"context"
"fmt"
"path/filepath"
"strings"
@@ -12,7 +11,6 @@ import (
"github.com/kubescape/kubescape/v2/core/meta"
v1 "github.com/kubescape/kubescape/v2/core/meta/datastructures/v1"
"github.com/spf13/cobra"
"golang.org/x/exp/slices"
)
var (
@@ -43,7 +41,7 @@ var (
`, cautils.ExecName())
)
func GetDownloadCmd(ks meta.IKubescape) *cobra.Command {
func GeDownloadCmd(ks meta.IKubescape) *cobra.Command {
var downloadInfo = v1.DownloadInfo{}
downloadCmd := &cobra.Command{
@@ -56,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
@@ -76,7 +74,7 @@ func GetDownloadCmd(ks meta.IKubescape) *cobra.Command {
downloadInfo.Identifier = args[1]
}
if err := ks.Download(context.TODO(), &downloadInfo); err != nil {
if err := ks.Download(&downloadInfo); err != nil {
logger.L().Fatal(err.Error())
}
return nil

View File

@@ -1,26 +1,23 @@
package fix
import (
"context"
"errors"
"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 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
@@ -36,7 +33,7 @@ func GetFixCmd(ks meta.IKubescape) *cobra.Command {
}
fixInfo.ReportFile = args[0]
return ks.Fix(context.TODO(), &fixInfo)
return ks.Fix(&fixInfo)
},
}

View File

@@ -1,7 +1,6 @@
package list
import (
"context"
"fmt"
"strings"
@@ -11,7 +10,6 @@ import (
"github.com/kubescape/kubescape/v2/core/meta"
v1 "github.com/kubescape/kubescape/v2/core/meta/datastructures/v1"
"github.com/spf13/cobra"
"golang.org/x/exp/slices"
)
var (
@@ -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
@@ -57,13 +55,15 @@ func GetListCmd(ks meta.IKubescape) *cobra.Command {
listPolicies.Target = args[0]
if err := ks.List(context.TODO(), &listPolicies); err != nil {
if err := ks.List(&listPolicies); err != nil {
logger.L().Fatal(err.Error())
}
return nil
},
}
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")

View File

@@ -81,7 +81,7 @@ func getRootCmd(ks meta.IKubescape) *cobra.Command {
// 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))

View File

@@ -85,11 +85,6 @@ func initEnvironment() {
if len(urlSlices) >= 4 {
ksAuthURL = urlSlices[3]
}
getter.SetKSCloudAPIConnector(getter.NewKSCloudAPICustomized(
ksBackendURL, ksAuthURL,
getter.WithReportURL(ksEventReceiverURL),
getter.WithFrontendURL(ksFrontendURL),
))
getter.SetKSCloudAPIConnector(getter.NewKSCloudAPICustomized(ksEventReceiverURL, ksBackendURL, ksFrontendURL, ksAuthURL))
}
}

View File

@@ -1,7 +1,6 @@
package scan
import (
"context"
"fmt"
"io"
"os"
@@ -14,6 +13,7 @@ import (
"github.com/kubescape/kubescape/v2/core/cautils"
"github.com/kubescape/kubescape/v2/core/meta"
"github.com/enescakir/emoji"
"github.com/spf13/cobra"
)
@@ -96,23 +96,19 @@ func getControlCmd(ks meta.IKubescape, scanInfo *cautils.ScanInfo) *cobra.Comman
return err
}
ctx := context.TODO()
results, err := ks.Scan(ctx, scanInfo)
results, err := ks.Scan(scanInfo)
if err != nil {
logger.L().Fatal(err.Error())
}
if err := results.HandleResults(ctx); err != nil {
if err := results.HandleResults(); 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

View File

@@ -1,7 +1,6 @@
package scan
import (
"context"
"errors"
"fmt"
"io"
@@ -11,12 +10,10 @@ import (
apisv1 "github.com/kubescape/opa-utils/httpserver/apis/v1"
reporthandlingapis "github.com/kubescape/opa-utils/reporthandling/apis"
"github.com/kubescape/opa-utils/reporthandling/results/v1/reportsummary"
"golang.org/x/exp/slices"
logger "github.com/kubescape/go-logger"
"github.com/kubescape/go-logger/helpers"
"github.com/kubescape/kubescape/v2/core/cautils"
"github.com/kubescape/kubescape/v2/core/cautils/getter"
"github.com/kubescape/kubescape/v2/core/meta"
"github.com/spf13/cobra"
@@ -74,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 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 {
@@ -107,30 +99,24 @@ func getFrameworkCmd(ks meta.IKubescape, scanInfo *cautils.ScanInfo) *cobra.Comm
}
}
}
scanInfo.SetScanType(cautils.ScanTypeFramework)
scanInfo.FrameworkScan = true
scanInfo.SetPolicyIdentifiers(frameworks, apisv1.KindFramework)
ctx := context.TODO()
results, err := ks.Scan(ctx, scanInfo)
results, err := ks.Scan(scanInfo)
if err != nil {
logger.L().Fatal(err.Error())
}
if err = results.HandleResults(ctx); err != nil {
if err = results.HandleResults(); err != nil {
logger.L().Fatal(err.Error())
}
if !scanInfo.VerboseMode && scanInfo.ScanType == cautils.ScanTypeFramework {
logger.L().Info("Run with '--verbose'/'-v' flag for detailed resources view\n")
if !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
@@ -175,14 +161,14 @@ func countersExceedSeverityThreshold(severityCounters reportsummary.ISeverityCou
}
// terminateOnExceedingSeverity terminates the application on exceeding severity
func terminateOnExceedingSeverity(scanInfo *cautils.ScanInfo, l helpers.ILogger) {
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
@@ -211,9 +197,6 @@ func validateFrameworkScanInfo(scanInfo *cautils.ScanInfo) error {
if scanInfo.Submit && scanInfo.Local {
return fmt.Errorf("you can use `keep-local` or `submit`, but not both")
}
if 100 < scanInfo.ComplianceThreshold || 0 > scanInfo.ComplianceThreshold {
return fmt.Errorf("bad argument: out of range threshold")
}
if 100 < scanInfo.FailThreshold || 0 > scanInfo.FailThreshold {
return fmt.Errorf("bad argument: out of range threshold")
}

View File

@@ -1,117 +0,0 @@
package scan
import (
"context"
"fmt"
logger "github.com/kubescape/go-logger"
"github.com/kubescape/go-logger/iconlogger"
"github.com/kubescape/kubescape/v2/core/cautils"
"github.com/kubescape/kubescape/v2/core/core"
"github.com/kubescape/kubescape/v2/core/meta"
"github.com/kubescape/kubescape/v2/core/pkg/resultshandling"
"github.com/kubescape/kubescape/v2/pkg/imagescan"
"github.com/spf13/cobra"
)
type imageScanInfo struct {
Username string
Password string
}
// TODO(vladklokun): document image scanning on the Kubescape Docs Hub?
var (
imageExample = fmt.Sprintf(`
This command is still in BETA. Feel free to contact the kubescape maintainers for more information.
Scan an image for vulnerabilities.
# Scan the 'nginx' image
%[1]s scan image "nginx"
# Image scan documentation:
# https://hub.armosec.io/docs/images
`, cautils.ExecName())
)
// imageCmd represents the image command
func getImageCmd(ks meta.IKubescape, scanInfo *cautils.ScanInfo, imgScanInfo *imageScanInfo) *cobra.Command {
cmd := &cobra.Command{
Use: "image <IMAGE_NAME>",
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 err := validateImageScanInfo(scanInfo); err != nil {
return err
}
failOnSeverity := imagescan.ParseSeverity(scanInfo.FailThresholdSeverity)
ctx := context.Background()
logger.InitLogger(iconlogger.LoggerName)
dbCfg, _ := imagescan.NewDefaultDBConfig()
svc := imagescan.NewScanService(dbCfg)
creds := imagescan.RegistryCredentials{
Username: imgScanInfo.Username,
Password: imgScanInfo.Password,
}
userInput := args[0]
logger.L().Start(fmt.Sprintf("Scanning image: %s", userInput))
scanResults, err := svc.Scan(ctx, userInput, creds)
if err != nil {
logger.L().StopError(fmt.Sprintf("Failed to scan image: %s", userInput))
return err
}
logger.L().StopSuccess(fmt.Sprintf("Successfully scanned image: %s", userInput))
scanInfo.SetScanType(cautils.ScanTypeImage)
outputPrinters := core.GetOutputPrinters(scanInfo, ctx)
uiPrinter := core.GetUIPrinter(ctx, scanInfo)
resultsHandler := resultshandling.NewResultsHandler(nil, outputPrinters, uiPrinter)
resultsHandler.ImageScanData = []cautils.ImageScanData{
{
PresenterConfig: scanResults,
Image: userInput,
},
}
resultsHandler.HandleResults(ctx)
if imagescan.ExceedsSeverityThreshold(scanResults, failOnSeverity) {
terminateOnExceedingSeverity(scanInfo, logger.L())
}
return err
},
}
cmd.PersistentFlags().StringVarP(&imgScanInfo.Username, "username", "u", "", "Username for registry login")
cmd.PersistentFlags().StringVarP(&imgScanInfo.Password, "password", "p", "", "Password for registry login")
return cmd
}
// validateImageScanInfo validates the ScanInfo struct for the `image` command
func validateImageScanInfo(scanInfo *cautils.ScanInfo) error {
severity := scanInfo.FailThresholdSeverity
if err := validateSeverity(severity); severity != "" && err != nil {
return err
}
return nil
}

View File

@@ -1,16 +1,12 @@
package scan
import (
"context"
"flag"
"fmt"
"strings"
"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"
v1 "github.com/kubescape/opa-utils/httpserver/apis/v1"
"github.com/spf13/cobra"
)
@@ -18,7 +14,7 @@ var scanCmdExamples = fmt.Sprintf(`
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
%[1]s scan --enable-host-scan --verbose
# Scan kubernetes YAML manifest files
%[1]s scan .
@@ -43,23 +39,19 @@ func GetScanCommand(ks meta.IKubescape) *cobra.Command {
Long: `The action you want to perform`,
Example: scanCmdExamples,
Args: func(cmd *cobra.Command, args []string) error {
// setting input patterns for framework scan is only relevancy for non-security view
if len(args) > 0 && scanInfo.View != string(cautils.SecurityViewType) {
if len(args) > 0 {
if args[0] != "framework" && args[0] != "control" {
return getFrameworkCmd(ks, &scanInfo).RunE(cmd, append([]string{strings.Join(getter.NativeFrameworks, ",")}, args...))
scanInfo.ScanAll = true
return getFrameworkCmd(ks, &scanInfo).RunE(cmd, append([]string{"all"}, args...))
}
}
return nil
},
RunE: func(cmd *cobra.Command, args []string) error {
if scanInfo.View == string(cautils.SecurityViewType) {
setSecurityViewScanInfo(args, &scanInfo)
return securityScan(scanInfo, ks)
}
if len(args) == 0 {
return getFrameworkCmd(ks, &scanInfo).RunE(cmd, []string{strings.Join(getter.NativeFrameworks, ",")})
scanInfo.ScanAll = true
return getFrameworkCmd(ks, &scanInfo).RunE(cmd, []string{"all"})
}
return nil
},
@@ -74,14 +66,15 @@ func GetScanCommand(ks meta.IKubescape) *cobra.Command {
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", "", `Output file format. Supported formats: "pretty-printer", "json", "junit", "prometheus", "pdf", "html", "sarif"`)
@@ -89,26 +82,20 @@ func GetScanCommand(ks meta.IKubescape) *cobra.Command {
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.ResourceViewType), fmt.Sprintf("View results based on the %s/%s/%s. default is --view=%s", cautils.ResourceViewType, cautils.ControlViewType, cautils.SecurityViewType, cautils.ResourceViewType))
scanCmd.PersistentFlags().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.ScanImages, "scan-images", "", false, "Scan resources images")
scanCmd.PersistentFlags().MarkDeprecated("silent", "use '--logger' flag instead. Flag will be removed at 1.May.2022")
scanCmd.PersistentFlags().MarkDeprecated("fail-threshold", "use '--compliance-threshold' flag instead. Flag will be removed at 1.Dec.2023")
scanCmd.PersistentFlags().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().MarkDeprecated("client-id", "login to Kubescape SaaS will be unsupported, please contact the Kubescape maintainers for more information")
scanCmd.PersistentFlags().MarkDeprecated("secret-key", "login to Kubescape SaaS will be unsupported, please contact the Kubescape maintainers for more information")
// 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")
@@ -118,46 +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-cloud-operator. The flag will be removed at 1.Dec.2023")
scanCmd.PersistentFlags().MarkHidden("host-scan-yaml") // this flag should be used very cautiously. We prefer users will not use it at all unless the DaemonSet can not run pods on the nodes
scanCmd.PersistentFlags().MarkDeprecated("host-scan-yaml", "To activate the host scanner capability, proceed with the installation of the kubescape operator chart found here: https://github.com/kubescape/helm-charts/tree/main/charts/kubescape-cloud-operator. The flag will be removed at 1.Dec.2023")
scanCmd.AddCommand(getControlCmd(ks, &scanInfo))
scanCmd.AddCommand(getFrameworkCmd(ks, &scanInfo))
scanCmd.AddCommand(getWorkloadCmd(ks, &scanInfo))
isi := &imageScanInfo{}
scanCmd.AddCommand(getImageCmd(ks, &scanInfo, isi))
return scanCmd
}
func setSecurityViewScanInfo(args []string, scanInfo *cautils.ScanInfo) {
if len(args) > 0 {
scanInfo.SetScanType(cautils.ScanTypeRepo)
scanInfo.InputPatterns = args
} else {
scanInfo.SetScanType(cautils.ScanTypeCluster)
}
scanInfo.SetPolicyIdentifiers([]string{"clusterscan", "mitre", "nsa"}, v1.KindFramework)
}
func securityScan(scanInfo cautils.ScanInfo, ks meta.IKubescape) error {
ctx := context.TODO()
results, err := ks.Scan(ctx, &scanInfo)
if err != nil {
return err
}
if err = results.HandleResults(ctx); err != nil {
return err
}
enforceSeverityThresholds(results.GetData().Report.SummaryDetails.GetResourcesSeverityCounters(), &scanInfo, terminateOnExceedingSeverity)
return nil
}

View File

@@ -1,12 +1,10 @@
package scan
import (
"context"
logger "github.com/kubescape/go-logger"
"github.com/kubescape/go-logger/helpers"
"github.com/kubescape/kubescape/v2/core/cautils"
v1 "github.com/kubescape/opa-utils/httpserver/apis/v1"
"github.com/kubescape/opa-utils/reporthandling/apis"
"github.com/kubescape/opa-utils/reporthandling/results/v1/reportsummary"
@@ -162,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
}
@@ -185,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]
@@ -258,106 +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: "clusterscan",
},
{
Kind: v1.KindFramework,
Identifier: "mitre",
},
{
Kind: v1.KindFramework,
Identifier: "nsa",
},
},
},
},
}
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)
}
}
})
}
}

View File

@@ -114,27 +114,3 @@ func Test_validateSeverity(t *testing.T) {
})
}
}
func Test_validateWorkloadIdentifier(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},
}
for _, testCase := range testCases {
t.Run(testCase.Description, func(t *testing.T) {
input := testCase.Input
want := testCase.Want
got := validateWorkloadIdentifier(input)
if got != want {
t.Errorf("got: %v, want: %v", got, want)
}
})
}
}

View File

@@ -1,126 +0,0 @@
package scan
import (
"context"
"errors"
"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/opa-utils/httpserver/apis/v1"
"github.com/kubescape/opa-utils/objectsenvelopes"
"github.com/spf13/cobra"
)
var (
workloadExample = fmt.Sprintf(`
This command is still in BETA. Feel free to contact the kubescape maintainers for more information.
Scan a workload for misconfigurations and image vulnerabilities.
# Scan an workload
%[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]")
}
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
ctx := context.TODO()
results, err := ks.Scan(ctx, scanInfo)
if err != nil {
logger.L().Fatal(err.Error())
}
if err = results.HandleResults(ctx); err != nil {
logger.L().Fatal(err.Error())
}
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,69 +0,0 @@
package scan
import (
"testing"
"github.com/kubescape/kubescape/v2/core/cautils"
v1 "github.com/kubescape/opa-utils/httpserver/apis/v1"
"github.com/kubescape/opa-utils/objectsenvelopes"
)
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)
}
},
)
}
}

View File

@@ -1,7 +1,6 @@
package submit
import (
"context"
"fmt"
logger "github.com/kubescape/go-logger"
@@ -27,7 +26,7 @@ func getExceptionsCmd(ks meta.IKubescape, submitInfo *metav1.Submit) *cobra.Comm
logger.L().Fatal(err.Error())
}
if err := ks.SubmitExceptions(context.TODO(), &submitInfo.Credentials, args[0]); err != nil {
if err := ks.SubmitExceptions(&submitInfo.Credentials, args[0]); err != nil {
logger.L().Fatal(err.Error())
}
},

View File

@@ -1,7 +1,6 @@
package submit
import (
"context"
"fmt"
"github.com/google/uuid"
@@ -67,7 +66,7 @@ func getRBACCmd(ks meta.IKubescape, submitInfo *v1.Submit) *cobra.Command {
Reporter: r,
}
if err := ks.Submit(context.TODO(), submitInterfaces); err != nil {
if err := ks.Submit(submitInterfaces); err != nil {
logger.L().Fatal(err.Error())
}
return nil

View File

@@ -1,7 +1,6 @@
package submit
import (
"context"
"encoding/json"
"fmt"
"os"
@@ -83,13 +82,13 @@ func getResultsCmd(ks meta.IKubescape, submitInfo *v1.Submit) *cobra.Command {
Reporter: r,
}
if err := ks.Submit(context.TODO(), submitInterfaces); err != nil {
if err := ks.Submit(submitInterfaces); err != nil {
logger.L().Fatal(err.Error())
}
return nil
},
}
resultsCmd.PersistentFlags().StringVar(&formatVersion, "format-version", "v2", "Output object can be different between versions, this is for maintaining backward and forward compatibility. Supported:'v1'/'v2'")
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
}

View File

@@ -5,35 +5,52 @@ package update
// kubescape update
import (
"fmt"
"os/exec"
"runtime"
logger "github.com/kubescape/go-logger"
"github.com/kubescape/kubescape/v2/core/cautils"
"github.com/spf13/cobra"
)
const (
installationLink string = "https://github.com/kubescape/kubescape/blob/master/docs/installation.md"
)
var updateCmdExamples = fmt.Sprintf(`
# Update to the latest kubescape release
%[1]s update
`, cautils.ExecName())
func GetUpdateCmd() *cobra.Command {
updateCmd := &cobra.Command{
Use: "update",
Short: "Update your version",
Long: ``,
Example: updateCmdExamples,
Use: "update",
Short: "Update your version",
Long: ``,
RunE: func(_ *cobra.Command, args []string) error {
//Checking the user's version of kubescape to the latest release
if cautils.BuildNumber == cautils.LatestReleaseVersion {
//your version == latest version
logger.L().Info(("You are in the latest version"))
} else {
fmt.Printf("please refer to our installation docs in the following link: %s", 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,7 +1,6 @@
package version
import (
"context"
"fmt"
"os"
@@ -15,9 +14,8 @@ func GetVersionCmd() *cobra.Command {
Short: "Get current version",
Long: ``,
RunE: func(cmd *cobra.Command, args []string) error {
ctx := context.TODO()
v := cautils.NewIVersionCheckHandler(ctx)
v.CheckLatestVersion(ctx, cautils.NewVersionCheckRequest(cautils.BuildNumber, "", "", "version"))
v := cautils.NewIVersionCheckHandler()
v.CheckLatestVersion(cautils.NewVersionCheckRequest(cautils.BuildNumber, "", "", "version"))
fmt.Fprintf(os.Stdout,
"Your current version is: %s [git enabled in build: %t]\n",
cautils.BuildNumber,

View File

@@ -17,11 +17,7 @@ import (
corev1 "k8s.io/api/core/v1"
)
const (
configFileName string = "config"
kubescapeNamespace string = "kubescape"
kubescapeConfigMapName string = "kubescape-config"
)
const configFileName = "config"
func ConfigFileFullPath() string { return getter.GetDefaultPath(configFileName + ".json") }
@@ -33,6 +29,7 @@ type ConfigObj struct {
AccountID string `json:"accountID,omitempty"`
ClientID string `json:"clientID,omitempty"`
SecretKey string `json:"secretKey,omitempty"`
CustomerGUID string `json:"customerGUID,omitempty"` // Deprecated
Token string `json:"invitationParam,omitempty"`
CustomerAdminEMail string `json:"adminMail,omitempty"`
ClusterName string `json:"clusterName,omitempty"`
@@ -66,35 +63,6 @@ func (co *ConfigObj) Config() []byte {
return []byte{}
}
func (co *ConfigObj) updateEmptyFields(inCO *ConfigObj) error {
if inCO.AccountID != "" {
co.AccountID = inCO.AccountID
}
if inCO.CloudAPIURL != "" {
co.CloudAPIURL = inCO.CloudAPIURL
}
if inCO.CloudAuthURL != "" {
co.CloudAuthURL = inCO.CloudAuthURL
}
if inCO.CloudReportURL != "" {
co.CloudReportURL = inCO.CloudReportURL
}
if inCO.CloudUIURL != "" {
co.CloudUIURL = inCO.CloudUIURL
}
if inCO.ClusterName != "" {
co.ClusterName = inCO.ClusterName
}
if inCO.CustomerAdminEMail != "" {
co.CustomerAdminEMail = inCO.CustomerAdminEMail
}
if inCO.Token != "" {
co.Token = inCO.Token
}
return nil
}
// ======================================================================================
// =============================== interface ============================================
// ======================================================================================
@@ -102,7 +70,7 @@ type ITenantConfig interface {
// set
SetTenant() error
UpdateCachedConfig() error
DeleteCachedConfig(ctx context.Context) error
DeleteCachedConfig() error
// getters
GetContextName() string
@@ -126,9 +94,6 @@ type ITenantConfig interface {
// ============================ Local Config ============================================
// ======================================================================================
// Config when scanning YAML files or URL but not a Kubernetes cluster
var _ ITenantConfig = &LocalConfig{}
type LocalConfig struct {
backendAPI getter.IBackend
configObj *ConfigObj
@@ -181,8 +146,6 @@ func NewLocalConfig(
}
logger.L().Debug("Kubescape Cloud URLs", helpers.String("api", lc.backendAPI.GetCloudAPIURL()), helpers.String("auth", lc.backendAPI.GetCloudAuthURL()), helpers.String("report", lc.backendAPI.GetCloudReportURL()), helpers.String("UI", lc.backendAPI.GetCloudUIURL()))
initializeCloudAPI(lc)
return lc
}
@@ -212,9 +175,9 @@ func (lc *LocalConfig) UpdateCachedConfig() error {
return updateConfigFile(lc.configObj)
}
func (lc *LocalConfig) DeleteCachedConfig(ctx context.Context) error {
func (lc *LocalConfig) DeleteCachedConfig() error {
if err := DeleteConfigFile(); err != nil {
logger.L().Ctx(ctx).Warning(err.Error())
logger.L().Warning(err.Error())
}
return nil
}
@@ -257,8 +220,6 @@ KS_SECRET_KEY
TODO - support:
KS_CACHE // path to cached files
*/
var _ ITenantConfig = &ClusterConfig{}
type ClusterConfig struct {
backendAPI getter.IBackend
k8s *k8sinterface.KubernetesApi
@@ -274,19 +235,18 @@ func NewClusterConfig(k8s *k8sinterface.KubernetesApi, backendAPI getter.IBacken
backendAPI: backendAPI,
configObj: &ConfigObj{},
configMapName: getConfigMapName(),
configMapNamespace: GetConfigMapNamespace(),
configMapNamespace: getConfigMapNamespace(),
}
// first, load from file
// first, load from configMap
if c.existsConfigMap() {
c.loadConfigFromConfigMap()
}
// second, load from file
if existsConfigFile() { // get from file
loadConfigFromFile(c.configObj)
}
// second, load from configMap
if c.existsConfigMap() {
c.updateConfigEmptyFieldsFromConfigMap()
}
updateCredentials(c.configObj, credentials)
updateCloudURLs(c.configObj)
@@ -328,8 +288,6 @@ func NewClusterConfig(k8s *k8sinterface.KubernetesApi, backendAPI getter.IBacken
}
logger.L().Debug("Kubescape Cloud URLs", helpers.String("api", c.backendAPI.GetCloudAPIURL()), helpers.String("auth", c.backendAPI.GetCloudAuthURL()), helpers.String("report", c.backendAPI.GetCloudReportURL()), helpers.String("UI", c.backendAPI.GetCloudUIURL()))
initializeCloudAPI(c)
return c
}
@@ -372,12 +330,12 @@ func (c *ClusterConfig) UpdateCachedConfig() error {
return updateConfigFile(c.configObj)
}
func (c *ClusterConfig) DeleteCachedConfig(ctx context.Context) error {
func (c *ClusterConfig) DeleteCachedConfig() error {
if err := c.deleteConfigMap(); err != nil {
logger.L().Ctx(ctx).Warning(err.Error())
logger.L().Warning(err.Error())
}
if err := DeleteConfigFile(); err != nil {
logger.L().Ctx(ctx).Warning(err.Error())
logger.L().Warning(err.Error())
}
return nil
}
@@ -392,22 +350,6 @@ func (c *ClusterConfig) ToMapString() map[string]interface{} {
}
return m
}
func (c *ClusterConfig) updateConfigEmptyFieldsFromConfigMap() error {
configMap, err := c.k8s.KubernetesClient.CoreV1().ConfigMaps(c.configMapNamespace).Get(context.Background(), c.configMapName, metav1.GetOptions{})
if err != nil {
return err
}
tempCO := ConfigObj{}
if jsonConf, ok := configMap.Data["config.json"]; ok {
json.Unmarshal([]byte(jsonConf), &tempCO)
return c.configObj.updateEmptyFields(&tempCO)
}
return err
}
func (c *ClusterConfig) loadConfigFromConfigMap() error {
configMap, err := c.k8s.KubernetesClient.CoreV1().ConfigMaps(c.configMapNamespace).Get(context.Background(), c.configMapName, metav1.GetOptions{})
if err != nil {
@@ -558,6 +500,10 @@ func readConfig(dat []byte, configObj *ConfigObj) error {
if err := json.Unmarshal(dat, configObj); err != nil {
return err
}
if configObj.AccountID == "" {
configObj.AccountID = configObj.CustomerGUID
}
configObj.CustomerGUID = ""
return nil
}
@@ -599,15 +545,14 @@ func getConfigMapName() string {
if n := os.Getenv("KS_DEFAULT_CONFIGMAP_NAME"); n != "" {
return n
}
return kubescapeConfigMapName
return "kubescape"
}
// GetConfigMapNamespace returns the namespace of the cluster config, which is the same for all in-cluster components
func GetConfigMapNamespace() string {
func getConfigMapNamespace() string {
if n := os.Getenv("KS_DEFAULT_CONFIGMAP_NAMESPACE"); n != "" {
return n
}
return kubescapeNamespace
return "default"
}
func getAccountFromEnv(credentials *Credentials) {
@@ -677,15 +622,3 @@ func updateCloudURLs(configObj *ConfigObj) {
}
}
func initializeCloudAPI(c ITenantConfig) {
cloud := getter.GetKSCloudAPIConnector()
cloud.SetAccountID(c.GetAccountID())
cloud.SetClientID(c.GetClientID())
cloud.SetSecretKey(c.GetSecretKey())
cloud.SetCloudAuthURL(c.GetCloudAuthURL())
cloud.SetCloudReportURL(c.GetCloudReportURL())
cloud.SetCloudUIURL(c.GetCloudUIURL())
cloud.SetCloudAPIURL(c.GetCloudAPIURL())
getter.SetKSCloudAPIConnector(cloud)
}

View File

@@ -5,7 +5,6 @@ import (
"os"
"testing"
"github.com/kubescape/kubescape/v2/core/cautils/getter"
"github.com/stretchr/testify/assert"
corev1 "k8s.io/api/core/v1"
)
@@ -269,189 +268,3 @@ func TestUpdateCloudURLs(t *testing.T) {
updateCloudURLs(co)
assert.Equal(t, co.CloudAPIURL, mockCloudAPIURL)
}
func Test_initializeCloudAPI(t *testing.T) {
type args struct {
c ITenantConfig
}
tests := []struct {
name string
args args
}{
{
name: "test",
args: args{
c: mockClusterConfig(),
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
initializeCloudAPI(tt.args.c)
cloud := getter.GetKSCloudAPIConnector()
assert.Equal(t, tt.args.c.GetCloudAPIURL(), cloud.GetCloudAPIURL())
assert.Equal(t, tt.args.c.GetCloudAuthURL(), cloud.GetCloudAuthURL())
assert.Equal(t, tt.args.c.GetCloudUIURL(), cloud.GetCloudUIURL())
assert.Equal(t, tt.args.c.GetCloudReportURL(), cloud.GetCloudReportURL())
assert.Equal(t, tt.args.c.GetAccountID(), cloud.GetAccountID())
assert.Equal(t, tt.args.c.GetClientID(), cloud.GetClientID())
assert.Equal(t, tt.args.c.GetSecretKey(), cloud.GetSecretKey())
})
}
}
func TestGetConfigMapNamespace(t *testing.T) {
tests := []struct {
name string
env string
want string
}{
{
name: "no env",
want: kubescapeNamespace,
},
{
name: "default ns",
env: kubescapeNamespace,
want: kubescapeNamespace,
},
{
name: "custom ns",
env: "my-ns",
want: "my-ns",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.env != "" {
_ = os.Setenv("KS_DEFAULT_CONFIGMAP_NAMESPACE", tt.env)
}
assert.Equalf(t, tt.want, GetConfigMapNamespace(), "GetConfigMapNamespace()")
})
}
}
const (
anyString string = "anyString"
shouldNotUpdate string = "shouldNotUpdate"
shouldUpdate string = "shouldUpdate"
)
func checkIsUpdateCorrectly(t *testing.T, beforeField string, afterField string) {
switch beforeField {
case anyString:
assert.Equal(t, anyString, afterField)
case "":
assert.Equal(t, shouldUpdate, afterField)
}
}
func TestUpdateEmptyFields(t *testing.T) {
tests := []struct {
inCo *ConfigObj
outCo *ConfigObj
}{
{
outCo: &ConfigObj{
AccountID: "",
Token: "",
CustomerAdminEMail: "",
ClusterName: "",
CloudReportURL: "",
CloudAPIURL: "",
CloudUIURL: "",
CloudAuthURL: "",
},
inCo: &ConfigObj{
AccountID: shouldUpdate,
Token: shouldUpdate,
CustomerAdminEMail: shouldUpdate,
ClusterName: shouldUpdate,
CloudReportURL: shouldUpdate,
CloudAPIURL: shouldUpdate,
CloudUIURL: shouldUpdate,
CloudAuthURL: shouldUpdate,
},
},
{
outCo: &ConfigObj{
AccountID: anyString,
Token: anyString,
CustomerAdminEMail: "",
ClusterName: "",
CloudReportURL: "",
CloudAPIURL: "",
CloudUIURL: "",
CloudAuthURL: "",
},
inCo: &ConfigObj{
AccountID: shouldNotUpdate,
Token: shouldNotUpdate,
CustomerAdminEMail: shouldUpdate,
ClusterName: shouldUpdate,
CloudReportURL: shouldUpdate,
CloudAPIURL: shouldUpdate,
CloudUIURL: shouldUpdate,
CloudAuthURL: shouldUpdate,
},
},
{
outCo: &ConfigObj{
AccountID: "",
Token: "",
CustomerAdminEMail: anyString,
ClusterName: anyString,
CloudReportURL: anyString,
CloudAPIURL: anyString,
CloudUIURL: anyString,
CloudAuthURL: anyString,
},
inCo: &ConfigObj{
AccountID: shouldUpdate,
Token: shouldUpdate,
CustomerAdminEMail: shouldNotUpdate,
ClusterName: shouldNotUpdate,
CloudReportURL: shouldNotUpdate,
CloudAPIURL: shouldNotUpdate,
CloudUIURL: shouldNotUpdate,
CloudAuthURL: shouldNotUpdate,
},
},
{
outCo: &ConfigObj{
AccountID: anyString,
Token: anyString,
CustomerAdminEMail: "",
ClusterName: anyString,
CloudReportURL: "",
CloudAPIURL: anyString,
CloudUIURL: "",
CloudAuthURL: anyString,
},
inCo: &ConfigObj{
AccountID: shouldNotUpdate,
Token: shouldNotUpdate,
CustomerAdminEMail: shouldUpdate,
ClusterName: shouldNotUpdate,
CloudReportURL: shouldUpdate,
CloudAPIURL: shouldNotUpdate,
CloudUIURL: shouldUpdate,
CloudAuthURL: shouldNotUpdate,
},
},
}
for i := range tests {
beforeChangesOutCO := tests[i].outCo
tests[i].outCo.updateEmptyFields(tests[i].inCo)
checkIsUpdateCorrectly(t, beforeChangesOutCO.AccountID, tests[i].outCo.AccountID)
checkIsUpdateCorrectly(t, beforeChangesOutCO.CloudAPIURL, tests[i].outCo.CloudAPIURL)
checkIsUpdateCorrectly(t, beforeChangesOutCO.CloudAuthURL, tests[i].outCo.CloudAuthURL)
checkIsUpdateCorrectly(t, beforeChangesOutCO.CloudReportURL, tests[i].outCo.CloudReportURL)
checkIsUpdateCorrectly(t, beforeChangesOutCO.CloudUIURL, tests[i].outCo.CloudUIURL)
checkIsUpdateCorrectly(t, beforeChangesOutCO.ClusterName, tests[i].outCo.ClusterName)
checkIsUpdateCorrectly(t, beforeChangesOutCO.CustomerAdminEMail, tests[i].outCo.CustomerAdminEMail)
checkIsUpdateCorrectly(t, beforeChangesOutCO.Token, tests[i].outCo.Token)
}
}

View File

@@ -1,10 +1,6 @@
package cautils
import (
"context"
"sort"
"github.com/anchore/grype/grype/presenter/models"
"github.com/armosec/armoapi-go/armotypes"
"github.com/kubescape/k8s-interface/workloadinterface"
"github.com/kubescape/opa-utils/reporthandling"
@@ -17,29 +13,12 @@ import (
// K8SResources map[<api group>/<api version>/<resource>][]<resourceID>
type K8SResources map[string][]string
type ExternalResources map[string][]string
type ImageScanData struct {
PresenterConfig *models.PresenterConfig
Image string
}
type ScanTypes string
const (
TopWorkloadsNumber = 5
ScanTypeCluster ScanTypes = "cluster"
ScanTypeRepo ScanTypes = "repo"
ScanTypeImage ScanTypes = "image"
ScanTypeWorkload ScanTypes = "workload"
ScanTypeFramework ScanTypes = "framework"
)
type KSResources map[string][]string
type OPASessionObj struct {
K8SResources K8SResources // input k8s objects
ExternalResources ExternalResources // input non-k8s objects (external resources)
K8SResources *K8SResources // input k8s objects
ArmoResource *KSResources // input ARMO objects
AllPolicies *Policies // list of all frameworks
ExcludedRules map[string]bool // rules to exclude map[rule name>]X
AllResources map[string]workloadinterface.IMetadata // all scanned resources, map[<resource ID>]<resource>
ResourcesResult map[string]resourcesresults.Result // resources scan results, map[<resource ID>]<resource result>
ResourceSource map[string]reporthandling.Source // resources sources, map[<resource ID>]<resource result>
@@ -55,10 +34,9 @@ type OPASessionObj struct {
Policies []reporthandling.Framework // list of frameworks to scan
Exceptions []armotypes.PostureExceptionPolicy // list of exceptions to apply on scan results
OmitRawResources bool // omit raw resources from output
SingleResourceScan workloadinterface.IWorkload // single resource scan
}
func NewOPASessionObj(ctx context.Context, frameworks []reporthandling.Framework, k8sResources K8SResources, scanInfo *ScanInfo) *OPASessionObj {
func NewOPASessionObj(frameworks []reporthandling.Framework, k8sResources *K8SResources, scanInfo *ScanInfo) *OPASessionObj {
return &OPASessionObj{
Report: &reporthandlingv2.PostureReport{},
Policies: frameworks,
@@ -70,50 +48,11 @@ func NewOPASessionObj(ctx context.Context, frameworks []reporthandling.Framework
ResourceToControlsMap: make(map[string][]string),
ResourceSource: make(map[string]reporthandling.Source),
SessionID: scanInfo.ScanID,
Metadata: scanInfoToScanMetadata(ctx, scanInfo),
Metadata: scanInfoToScanMetadata(scanInfo),
OmitRawResources: scanInfo.OmitRawResources,
}
}
// SetTopWorkloads sets the top workloads by score
func (sessionObj *OPASessionObj) SetTopWorkloads() {
count := 0
topWorkloadsSorted := make([]prioritization.PrioritizedResource, 0)
// create list in order to sort
for _, wl := range sessionObj.ResourcesPrioritized {
topWorkloadsSorted = append(topWorkloadsSorted, wl)
}
// sort by score. If scores are equal, sort by resource ID
sort.Slice(topWorkloadsSorted, func(i, j int) bool {
if topWorkloadsSorted[i].Score == topWorkloadsSorted[j].Score {
return topWorkloadsSorted[i].ResourceID < topWorkloadsSorted[j].ResourceID
}
return topWorkloadsSorted[i].Score > topWorkloadsSorted[j].Score
})
if sessionObj.Report == nil {
sessionObj.Report = &reporthandlingv2.PostureReport{}
}
// set top workloads according to number of top workloads
for i := 0; i < TopWorkloadsNumber; i++ {
if i >= len(topWorkloadsSorted) {
break
}
source := sessionObj.ResourceSource[topWorkloadsSorted[i].ResourceID]
wlObj := &reporthandling.Resource{
IMetadata: sessionObj.AllResources[topWorkloadsSorted[i].ResourceID],
Source: &source,
}
sessionObj.Report.SummaryDetails.TopWorkloadsByScore = append(sessionObj.Report.SummaryDetails.TopWorkloadsByScore, wlObj)
count++
}
}
func (sessionObj *OPASessionObj) SetMapNamespaceToNumberOfResources(mapNamespaceToNumberOfResources map[string]int) {
if sessionObj.Metadata.ContextMetadata.ClusterContextMetadata == nil {
sessionObj.Metadata.ContextMetadata.ClusterContextMetadata = &reporthandlingv2.ClusterMetadata{}

View File

@@ -4,10 +4,7 @@ import (
"golang.org/x/mod/semver"
"github.com/armosec/utils-go/boolutils"
cloudsupport "github.com/kubescape/k8s-interface/cloudsupport/v1"
"github.com/kubescape/k8s-interface/k8sinterface"
"github.com/kubescape/opa-utils/reporthandling"
"github.com/kubescape/opa-utils/reporthandling/apis"
)
func NewPolicies() *Policies {
@@ -17,41 +14,22 @@ func NewPolicies() *Policies {
}
}
func (policies *Policies) Set(frameworks []reporthandling.Framework, version string, excludedRules map[string]bool, scanningScope reporthandling.ScanningScopeType) {
func (policies *Policies) Set(frameworks []reporthandling.Framework, version string) {
for i := range frameworks {
if !isFrameworkFitToScanScope(frameworks[i], scanningScope) {
continue
}
if frameworks[i].Name != "" && len(frameworks[i].Controls) > 0 {
policies.Frameworks = append(policies.Frameworks, frameworks[i].Name)
}
for j := range frameworks[i].Controls {
compatibleRules := []reporthandling.PolicyRule{}
for r := range frameworks[i].Controls[j].Rules {
if excludedRules != nil {
ruleName := frameworks[i].Controls[j].Rules[r].Name
if _, exclude := excludedRules[ruleName]; exclude {
continue
}
}
if !ruleWithKSOpaDependency(frameworks[i].Controls[j].Rules[r].Attributes) && isRuleKubescapeVersionCompatible(frameworks[i].Controls[j].Rules[r].Attributes, version) && isControlFitToScanScope(frameworks[i].Controls[j], scanningScope) {
if !ruleWithKSOpaDependency(frameworks[i].Controls[j].Rules[r].Attributes) && isRuleKubescapeVersionCompatible(frameworks[i].Controls[j].Rules[r].Attributes, version) {
compatibleRules = append(compatibleRules, frameworks[i].Controls[j].Rules[r])
}
}
if len(compatibleRules) > 0 {
frameworks[i].Controls[j].Rules = compatibleRules
policies.Controls[frameworks[i].Controls[j].ControlID] = frameworks[i].Controls[j]
} else { // if the control type is manual review, add it to the list of controls
actionRequiredStr := frameworks[i].Controls[j].GetActionRequiredAttribute()
if actionRequiredStr == "" {
continue
}
if actionRequiredStr == string(apis.SubStatusManualReview) {
policies.Controls[frameworks[i].Controls[j].ControlID] = frameworks[i].Controls[j]
}
}
}
}
@@ -88,89 +66,3 @@ func isRuleKubescapeVersionCompatible(attributes map[string]interface{}, version
}
return true
}
func getCloudType(scanInfo *ScanInfo) (bool, reporthandling.ScanningScopeType) {
if cloudsupport.IsAKS() {
return true, reporthandling.ScopeCloudAKS
}
if cloudsupport.IsEKS(k8sinterface.GetConfig()) {
return true, reporthandling.ScopeCloudEKS
}
if cloudsupport.IsGKE(k8sinterface.GetConfig()) {
return true, reporthandling.ScopeCloudGKE
}
return false, ""
}
func GetScanningScope(scanInfo *ScanInfo) reporthandling.ScanningScopeType {
var result reporthandling.ScanningScopeType
switch scanInfo.GetScanningContext() {
case ContextCluster:
isCloud, cloudType := getCloudType(scanInfo)
if isCloud {
result = cloudType
} else {
result = reporthandling.ScopeCluster
}
default:
result = reporthandling.ScopeFile
}
return result
}
func isScanningScopeMatchToControlScope(scanScope reporthandling.ScanningScopeType, controlScope reporthandling.ScanningScopeType) bool {
result := false
switch controlScope {
case reporthandling.ScopeFile:
result = (reporthandling.ScopeFile == scanScope)
case reporthandling.ScopeCluster:
result = (reporthandling.ScopeCluster == scanScope) || (reporthandling.ScopeCloud == scanScope) || (reporthandling.ScopeCloudAKS == scanScope) || (reporthandling.ScopeCloudEKS == scanScope) || (reporthandling.ScopeCloudGKE == scanScope)
case reporthandling.ScopeCloud:
result = (reporthandling.ScopeCloud == scanScope) || (reporthandling.ScopeCloudAKS == scanScope) || (reporthandling.ScopeCloudEKS == scanScope) || (reporthandling.ScopeCloudGKE == scanScope)
case reporthandling.ScopeCloudAKS:
result = (reporthandling.ScopeCloudAKS == scanScope)
case reporthandling.ScopeCloudEKS:
result = (reporthandling.ScopeCloudEKS == scanScope)
case reporthandling.ScopeCloudGKE:
result = (reporthandling.ScopeCloudGKE == scanScope)
default:
result = true
}
return result
}
func isControlFitToScanScope(control reporthandling.Control, scanScopeMatches reporthandling.ScanningScopeType) bool {
// for backward compatibility - case: kubescape with scope(new one) and regolibrary without scope(old one)
if control.ScanningScope == nil {
return true
}
if len(control.ScanningScope.Matches) == 0 {
return true
}
for i := range control.ScanningScope.Matches {
if isScanningScopeMatchToControlScope(scanScopeMatches, control.ScanningScope.Matches[i]) {
return true
}
}
return false
}
func isFrameworkFitToScanScope(framework reporthandling.Framework, scanScopeMatches reporthandling.ScanningScopeType) bool {
// for backward compatibility - case: kubescape with scope(new one) and regolibrary without scope(old one)
if framework.ScanningScope == nil {
return true
}
if len(framework.ScanningScope.Matches) == 0 {
return true
}
for i := range framework.ScanningScope.Matches {
if isScanningScopeMatchToControlScope(scanScopeMatches, framework.ScanningScope.Matches[i]) {
return true
}
}
return false
}

View File

@@ -1,104 +0,0 @@
package cautils
import (
"fmt"
"testing"
"github.com/kubescape/opa-utils/reporthandling"
"github.com/stretchr/testify/assert"
)
func TestIsControlFitToScanScope(t *testing.T) {
tests := []struct {
scanInfo *ScanInfo
Control reporthandling.Control
expected_res bool
}{
{
scanInfo: &ScanInfo{
InputPatterns: []string{
"./testdata/any_file_for_test.json",
},
},
Control: reporthandling.Control{
ScanningScope: &reporthandling.ScanningScope{
Matches: []reporthandling.ScanningScopeType{
reporthandling.ScopeFile,
},
},
},
expected_res: true,
},
{
scanInfo: &ScanInfo{
InputPatterns: []string{
"./testdata/any_file_for_test.json",
},
},
Control: reporthandling.Control{
ScanningScope: &reporthandling.ScanningScope{
Matches: []reporthandling.ScanningScopeType{
reporthandling.ScopeCluster,
reporthandling.ScopeFile,
},
},
},
expected_res: true,
},
{
scanInfo: &ScanInfo{},
Control: reporthandling.Control{
ScanningScope: &reporthandling.ScanningScope{
Matches: []reporthandling.ScanningScopeType{
reporthandling.ScopeCluster,
},
},
},
expected_res: true,
},
{
scanInfo: &ScanInfo{
InputPatterns: []string{
"./testdata/any_file_for_test.json",
},
},
Control: reporthandling.Control{
ScanningScope: &reporthandling.ScanningScope{
Matches: []reporthandling.ScanningScopeType{
reporthandling.ScopeCloudGKE,
},
},
},
expected_res: false,
},
{
scanInfo: &ScanInfo{},
Control: reporthandling.Control{
ScanningScope: &reporthandling.ScanningScope{
Matches: []reporthandling.ScanningScopeType{
reporthandling.ScopeCloudEKS,
},
},
},
expected_res: false,
},
{
scanInfo: &ScanInfo{},
Control: reporthandling.Control{
ScanningScope: &reporthandling.ScanningScope{
Matches: []reporthandling.ScanningScopeType{
reporthandling.ScopeCloud,
},
},
},
expected_res: false,
}}
for i := range tests {
assert.Equal(t, isControlFitToScanScope(tests[i].Control, GetScanningScope(tests[i].scanInfo)), tests[i].expected_res, fmt.Sprintf("tests_true index %d", i))
}
}

View File

@@ -1,62 +1,26 @@
package cautils
import (
"fmt"
"io"
"os"
"time"
spinnerpkg "github.com/briandowns/spinner"
"github.com/jwalton/gchalk"
logger "github.com/kubescape/go-logger"
"github.com/kubescape/go-logger/helpers"
"github.com/fatih/color"
"github.com/mattn/go-isatty"
"github.com/schollz/progressbar/v3"
)
func FailureDisplay(w io.Writer, format string, a ...interface{}) {
fmt.Fprintf(w, gchalk.WithBrightRed().Bold(format), a...)
}
func WarningDisplay(w io.Writer, format string, a ...interface{}) {
fmt.Fprintf(w, gchalk.WithBrightYellow().Bold(format), a...)
}
func FailureTextDisplay(w io.Writer, format string, a ...interface{}) {
fmt.Fprintf(w, gchalk.WithBrightRed().Dim(format), a...)
}
func InfoDisplay(w io.Writer, format string, a ...interface{}) {
fmt.Fprintf(w, gchalk.WithCyan().Bold(format), a...)
}
func InfoTextDisplay(w io.Writer, format string, a ...interface{}) {
fmt.Fprintf(w, gchalk.WithBrightYellow().Bold(format), a...)
}
func SimpleDisplay(w io.Writer, format string, a ...interface{}) {
fmt.Fprintf(w, gchalk.White(format), a...)
}
func SuccessDisplay(w io.Writer, format string, a ...interface{}) {
fmt.Fprintf(w, gchalk.WithBlue().Bold(format), a...)
}
func DescriptionDisplay(w io.Writer, format string, a ...interface{}) {
fmt.Fprintf(w, gchalk.WithWhite().Dim(format), a...)
}
func BoldDisplay(w io.Writer, format string, a ...interface{}) {
fmt.Fprintf(w, gchalk.Bold(format), a...)
}
var FailureDisplay = color.New(color.Bold, color.FgHiRed).FprintfFunc()
var WarningDisplay = color.New(color.Bold, color.FgHiYellow).FprintfFunc()
var FailureTextDisplay = color.New(color.Faint, color.FgHiRed).FprintfFunc()
var InfoDisplay = color.New(color.Bold, color.FgCyan).FprintfFunc()
var InfoTextDisplay = color.New(color.Bold, color.FgHiYellow).FprintfFunc()
var SimpleDisplay = color.New().FprintfFunc()
var SuccessDisplay = color.New(color.Bold, color.FgHiGreen).FprintfFunc()
var DescriptionDisplay = color.New(color.Faint, color.FgWhite).FprintfFunc()
var spinner *spinnerpkg.Spinner
func StartSpinner() {
if helpers.ToLevel(logger.L().GetLevel()) >= helpers.WarningLevel {
return
}
if spinner != nil {
if !spinner.Active() {
spinner.Start()
@@ -75,28 +39,3 @@ func StopSpinner() {
}
spinner.Stop()
}
type ProgressHandler struct {
pb *progressbar.ProgressBar
title string
}
func NewProgressHandler(title string) *ProgressHandler {
return &ProgressHandler{title: title}
}
func (p *ProgressHandler) Start(allSteps int) {
if !isatty.IsTerminal(os.Stderr.Fd()) || helpers.ToLevel(logger.L().GetLevel()) >= helpers.WarningLevel {
p.pb = progressbar.DefaultSilent(int64(allSteps), p.title)
return
}
p.pb = progressbar.Default(int64(allSteps), p.title)
}
func (p *ProgressHandler) ProgressJob(step int, message string) {
p.pb.Add(step)
p.pb.Describe(message)
}
func (p *ProgressHandler) Stop() {
}

View File

@@ -1,32 +0,0 @@
package cautils
import (
"testing"
"github.com/kubescape/go-logger"
)
func TestStartSpinner(t *testing.T) {
tests := []struct {
name string
loggerLevel string
enabled bool
}{
{
name: "TestStartSpinner - disabled",
loggerLevel: "warning",
enabled: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
logger.L().SetLevel(tt.loggerLevel)
StartSpinner()
if !tt.enabled {
if spinner != nil {
t.Errorf("spinner should be nil")
}
}
})
}
}

View File

@@ -2,7 +2,6 @@ package cautils
import (
"bytes"
"context"
"encoding/json"
"fmt"
"os"
@@ -11,7 +10,6 @@ import (
"github.com/kubescape/go-logger/helpers"
"github.com/kubescape/k8s-interface/workloadinterface"
"golang.org/x/exp/slices"
logger "github.com/kubescape/go-logger"
"github.com/kubescape/opa-utils/objectsenvelopes"
@@ -32,13 +30,8 @@ const (
JSON_FILE_FORMAT FileFormat = "json"
)
type Chart struct {
Name string
Path string
}
// LoadResourcesFromHelmCharts scans a given path (recursively) for helm charts, renders the templates and returns a map of workloads and a map of chart names
func LoadResourcesFromHelmCharts(ctx context.Context, basePath string) (map[string][]workloadinterface.IMetadata, map[string]Chart) {
func LoadResourcesFromHelmCharts(basePath string) (map[string][]workloadinterface.IMetadata, map[string]string) {
directories, _ := listDirs(basePath)
helmDirectories := make([]string, 0)
for _, dir := range directories {
@@ -48,32 +41,29 @@ func LoadResourcesFromHelmCharts(ctx context.Context, basePath string) (map[stri
}
sourceToWorkloads := map[string][]workloadinterface.IMetadata{}
sourceToChart := make(map[string]Chart, 0)
sourceToChartName := map[string]string{}
for _, helmDir := range helmDirectories {
chart, err := NewHelmChart(helmDir)
if err == nil {
wls, errs := chart.GetWorkloadsWithDefaultValues()
if len(errs) > 0 {
logger.L().Ctx(ctx).Warning(fmt.Sprintf("Rendering of Helm chart template '%s', failed: %v", chart.GetName(), errs))
logger.L().Error(fmt.Sprintf("Rendering of Helm chart template '%s', failed: %v", chart.GetName(), errs))
continue
}
chartName := chart.GetName()
for k, v := range wls {
sourceToWorkloads[k] = v
sourceToChart[k] = Chart{
Name: chartName,
Path: helmDir,
}
sourceToChartName[k] = chartName
}
}
}
return sourceToWorkloads, sourceToChart
return sourceToWorkloads, sourceToChartName
}
// If the contents at given path is a Kustomize Directory, LoadResourcesFromKustomizeDirectory will
// generate yaml files using "Kustomize" & renders a map of workloads from those yaml files
func LoadResourcesFromKustomizeDirectory(ctx context.Context, basePath string) (map[string][]workloadinterface.IMetadata, string) {
func LoadResourcesFromKustomizeDirectory(basePath string) (map[string][]workloadinterface.IMetadata, string) {
isKustomizeDirectory := IsKustomizeDirectory(basePath)
isKustomizeFile := IsKustomizeFile(basePath)
if ok := isKustomizeDirectory || isKustomizeFile; !ok {
@@ -97,7 +87,7 @@ func LoadResourcesFromKustomizeDirectory(ctx context.Context, basePath string) (
kustomizeDirectoryName := GetKustomizeDirectoryName(newBasePath)
if len(errs) > 0 {
logger.L().Ctx(ctx).Warning(fmt.Sprintf("Rendering yaml from Kustomize failed: %v", errs))
logger.L().Error(fmt.Sprintf("Rendering yaml from Kustomize failed: %v", errs))
}
for k, v := range wls {
@@ -106,19 +96,18 @@ func LoadResourcesFromKustomizeDirectory(ctx context.Context, basePath string) (
return sourceToWorkloads, kustomizeDirectoryName
}
func LoadResourcesFromFiles(ctx context.Context, input, rootPath string) map[string][]workloadinterface.IMetadata {
func LoadResourcesFromFiles(input, rootPath string) map[string][]workloadinterface.IMetadata {
files, errs := listFiles(input)
if len(errs) > 0 {
logger.L().Ctx(ctx).Warning(fmt.Sprintf("%v", errs))
logger.L().Error(fmt.Sprintf("%v", errs))
}
if len(files) == 0 {
logger.L().Ctx(ctx).Error("no files found to scan", helpers.String("input", input))
return nil
}
workloads, errs := loadFiles(rootPath, files)
if len(errs) > 0 {
logger.L().Ctx(ctx).Warning(fmt.Sprintf("%v", errs))
logger.L().Error(fmt.Sprintf("%v", errs))
}
return workloads
@@ -293,11 +282,11 @@ func convertYamlToJson(i interface{}) interface{} {
}
func IsYaml(filePath string) bool {
return slices.Contains(YAML_PREFIX, strings.ReplaceAll(filepath.Ext(filePath), ".", ""))
return StringInSlice(YAML_PREFIX, strings.ReplaceAll(filepath.Ext(filePath), ".", "")) != ValueNotFound
}
func IsJson(filePath string) bool {
return slices.Contains(JSON_PREFIX, strings.ReplaceAll(filepath.Ext(filePath), ".", ""))
return StringInSlice(JSON_PREFIX, strings.ReplaceAll(filepath.Ext(filePath), ".", "")) != ValueNotFound
}
func glob(root, pattern string, onlyDirectories bool) ([]string, error) {

View File

@@ -1,7 +1,6 @@
package cautils
import (
"context"
"os"
"path/filepath"
"strings"
@@ -31,7 +30,7 @@ func TestListFiles(t *testing.T) {
}
func TestLoadResourcesFromFiles(t *testing.T) {
workloads := LoadResourcesFromFiles(context.TODO(), onlineBoutiquePath(), "")
workloads := LoadResourcesFromFiles(onlineBoutiquePath(), "")
assert.Equal(t, 12, len(workloads))
for i, w := range workloads {
@@ -45,7 +44,7 @@ func TestLoadResourcesFromFiles(t *testing.T) {
}
func TestLoadResourcesFromHelmCharts(t *testing.T) {
sourceToWorkloads, sourceToChartName := LoadResourcesFromHelmCharts(context.TODO(), helmChartPath())
sourceToWorkloads, sourceToChartName := LoadResourcesFromHelmCharts(helmChartPath())
assert.Equal(t, 6, len(sourceToWorkloads))
for file, workloads := range sourceToWorkloads {
@@ -53,8 +52,7 @@ func TestLoadResourcesFromHelmCharts(t *testing.T) {
w := workloads[0]
assert.True(t, localworkload.IsTypeLocalWorkload(w.GetObject()), "Expected localworkload as object type")
assert.Equal(t, "kubescape", sourceToChartName[file].Name)
assert.Equal(t, helmChartPath(), sourceToChartName[file].Path)
assert.Equal(t, "kubescape", sourceToChartName[file])
switch filepath.Base(file) {
case "serviceaccount.yaml":

View File

@@ -1,61 +1,24 @@
package getter
import (
"github.com/armosec/armoapi-go/armotypes"
"github.com/kubescape/opa-utils/reporthandling"
"github.com/kubescape/opa-utils/reporthandling/attacktrack/v1alpha1"
reporthandlingv2 "github.com/kubescape/opa-utils/reporthandling/v2"
)
type FeLoginData struct {
Secret string `json:"secret"`
ClientId string `json:"clientId"`
}
// NativeFrameworks identifies all pre-built, native frameworks.
var NativeFrameworks = []string{"allcontrols", "nsa", "mitre"}
type FeLoginResponse struct {
Token string `json:"accessToken"`
RefreshToken string `json:"refreshToken"`
Expires string `json:"expires"`
ExpiresIn int32 `json:"expiresIn"`
}
type (
// TenantResponse holds the credentials for a tenant.
TenantResponse struct {
TenantID string `json:"tenantId"`
Token string `json:"token"`
Expires string `json:"expires"`
AdminMail string `json:"adminMail,omitempty"`
}
type KSCloudSelectCustomer struct {
SelectedCustomerGuid string `json:"selectedCustomer"`
}
// AttackTrack is an alias to the API type definition for attack tracks.
AttackTrack = v1alpha1.AttackTrack
// Framework is an alias to the API type definition for a framework.
Framework = reporthandling.Framework
// Control is an alias to the API type definition for a control.
Control = reporthandling.Control
// PostureExceptionPolicy is an alias to the API type definition for posture exception policy.
PostureExceptionPolicy = armotypes.PostureExceptionPolicy
// CustomerConfig is an alias to the API type definition for a customer configuration.
CustomerConfig = armotypes.CustomerConfig
// PostureReport is an alias to the API type definition for a posture report.
PostureReport = reporthandlingv2.PostureReport
)
type (
// internal data descriptors
// feLoginData describes the input to a login challenge.
feLoginData struct {
Secret string `json:"secret"`
ClientId string `json:"clientId"`
}
// feLoginResponse describes the response to a login challenge.
feLoginResponse struct {
Token string `json:"accessToken"`
RefreshToken string `json:"refreshToken"`
Expires string `json:"expires"`
ExpiresIn int32 `json:"expiresIn"`
}
ksCloudSelectCustomer struct {
SelectedCustomerGuid string `json:"selectedCustomer"`
}
)
type TenantResponse struct {
TenantID string `json:"tenantId"`
Token string `json:"token"`
Expires string `json:"expires"`
AdminMail string `json:"adminMail,omitempty"`
}

View File

@@ -1,8 +0,0 @@
// Package getter provides functionality to retrieve policy objects.
//
// It comes with 3 implementations:
//
// * KSCloudAPI is a client for the KS Cloud SaaS API
// * LoadPolicy exposes policy objects stored in a local repository
// * DownloadReleasedPolicy downloads policy objects from the policy library released on github: https://github.com/kubescape/regolibrary
package getter

View File

@@ -5,21 +5,14 @@ import (
"strings"
"github.com/armosec/armoapi-go/armotypes"
"github.com/kubescape/opa-utils/gitregostore"
"github.com/kubescape/opa-utils/reporthandling"
"github.com/kubescape/opa-utils/reporthandling/attacktrack/v1alpha1"
"github.com/kubescape/regolibrary/gitregostore"
)
// =======================================================================================================================
// ======================================== DownloadReleasedPolicy =======================================================
// =======================================================================================================================
var (
_ IPolicyGetter = &DownloadReleasedPolicy{}
_ IExceptionsGetter = &DownloadReleasedPolicy{}
_ IAttackTracksGetter = &DownloadReleasedPolicy{}
_ IControlsInputsGetter = &DownloadReleasedPolicy{}
)
// Use gitregostore to get policies from github release
type DownloadReleasedPolicy struct {
@@ -78,12 +71,12 @@ func (drp *DownloadReleasedPolicy) ListControls() ([]string, error) {
}
var controlsFrameworksList [][]string
for _, control := range controls {
controlsFrameworksList = append(controlsFrameworksList, drp.gs.GetOpaFrameworkListByControlID(control.ControlID))
controlsFrameworksList = append(controlsFrameworksList, control.FrameworkNames)
}
controlsNamesWithIDsandFrameworksList := make([]string, len(controlsIDsList))
// by design all slices have the same lengt
for i := range controlsIDsList {
controlsNamesWithIDsandFrameworksList[i] = fmt.Sprintf("%v|%v|%v", controlsIDsList[i], controlsNamesList[i], strings.Join(controlsFrameworksList[i], ", "))
controlsNamesWithIDsandFrameworksList[i] = fmt.Sprintf("%v|%v|%v", controlsIDsList[i], controlsNamesList[i], strings.Join(controlsFrameworksList[i], ","))
}
return controlsNamesWithIDsandFrameworksList, nil
}
@@ -112,6 +105,19 @@ func (drp *DownloadReleasedPolicy) SetRegoObjects() error {
return drp.gs.SetRegoObjects()
}
func isNativeFramework(framework string) bool {
return contains(NativeFrameworks, framework)
}
func contains(s []string, str string) bool {
for _, v := range s {
if strings.EqualFold(v, str) {
return true
}
}
return false
}
func (drp *DownloadReleasedPolicy) GetExceptions(clusterName string) ([]armotypes.PostureExceptionPolicy, error) {
exceptions, err := drp.gs.GetSystemPostureExceptionPolicies()
if err != nil {

View File

@@ -1,164 +0,0 @@
package getter
import (
"errors"
"fmt"
"io/fs"
"os"
"path/filepath"
"strings"
"testing"
"github.com/kubescape/kubescape/v2/internal/testutils"
jsoniter "github.com/json-iterator/go"
"github.com/stretchr/testify/require"
)
func TestReleasedPolicy(t *testing.T) {
t.Parallel()
p := NewDownloadReleasedPolicy()
t.Run("should initialize objects", func(t *testing.T) {
t.Parallel()
// acquire from github or from local fixture
hydrateReleasedPolicyFromMock(t, p)
require.NoError(t, p.SetRegoObjects())
t.Run("with ListControls", func(t *testing.T) {
t.Parallel()
controlIDs, err := p.ListControls()
require.NoError(t, err)
require.NotEmpty(t, controlIDs)
sampleSize := int(min(int64(len(controlIDs)), 10))
for _, toPin := range controlIDs[:sampleSize] {
// Example of a returned "ID": `C-0154|Ensure_that_the_--client-cert-auth_argument_is_set_to_true|`
controlString := toPin
parts := strings.Split(controlString, "|")
controlID := parts[0]
t.Run(fmt.Sprintf("with GetControl(%q)", controlID), func(t *testing.T) {
t.Parallel()
ctrl, err := p.GetControl(controlID)
require.NoError(t, err)
require.NotEmpty(t, ctrl)
require.Equal(t, controlID, ctrl.ControlID)
})
}
t.Run("with unknown GetControl()", func(t *testing.T) {
t.Parallel()
ctrl, err := p.GetControl("zork")
require.Error(t, err)
require.Nil(t, ctrl)
})
})
t.Run("with GetFrameworks", func(t *testing.T) {
t.Parallel()
frameworks, err := p.GetFrameworks()
require.NoError(t, err)
require.NotEmpty(t, frameworks)
for _, toPin := range frameworks {
framework := toPin
require.NotEmpty(t, framework)
require.NotEmpty(t, framework.Name)
t.Run(fmt.Sprintf("with GetFramework(%q)", framework.Name), func(t *testing.T) {
t.Parallel()
fw, err := p.GetFramework(framework.Name)
require.NoError(t, err)
require.NotNil(t, fw)
require.EqualValues(t, framework, *fw)
})
}
t.Run("with unknown GetFramework()", func(t *testing.T) {
t.Parallel()
ctrl, err := p.GetFramework("zork")
require.Error(t, err)
require.Nil(t, ctrl)
})
t.Run("with ListFrameworks", func(t *testing.T) {
t.Parallel()
frameworkIDs, err := p.ListFrameworks()
require.NoError(t, err)
require.NotEmpty(t, frameworkIDs)
require.Len(t, frameworkIDs, len(frameworks))
})
})
t.Run("with GetControlsInput", func(t *testing.T) {
t.Parallel()
controlInputs, err := p.GetControlsInputs("") // NOTE: cluster name currently unused
require.NoError(t, err)
require.NotEmpty(t, controlInputs)
})
t.Run("with GetAttackTracks", func(t *testing.T) {
t.Parallel()
attackTracks, err := p.GetAttackTracks()
require.NoError(t, err)
require.NotEmpty(t, attackTracks)
})
t.Run("with GetExceptions", func(t *testing.T) {
t.Parallel()
exceptions, err := p.GetExceptions("") // NOTE: cluster name currently unused
require.NoError(t, err)
require.NotEmpty(t, exceptions)
})
})
}
func hydrateReleasedPolicyFromMock(t testing.TB, p *DownloadReleasedPolicy) {
regoFile := testRegoFile("policy")
if _, err := os.Stat(regoFile); errors.Is(err, fs.ErrNotExist) {
// retrieve fixture from latest released policy from github.
//
// NOTE: to update the mock, just delete the testdata/policy.json file and run the tests again.
t.Logf("updating fixture file %q from github", regoFile)
require.NoError(t, p.SetRegoObjects())
require.NotNil(t, p.gs)
require.NoError(t,
SaveInFile(p.gs, regoFile),
)
return
}
// we have a mock fixture: load this rather than calling github
t.Logf("populating rego policy from fixture file %q", regoFile)
buf, err := os.ReadFile(regoFile)
require.NoError(t, err)
require.NoError(t,
jsoniter.Unmarshal(buf, p.gs),
)
}
func testRegoFile(framework string) string {
return filepath.Join(testutils.CurrentDir(), "testdata", fmt.Sprintf("%s.json", framework))
}

View File

@@ -0,0 +1,47 @@
package getter
import (
"github.com/armosec/armoapi-go/armotypes"
"github.com/kubescape/opa-utils/reporthandling"
"github.com/kubescape/opa-utils/reporthandling/attacktrack/v1alpha1"
)
type IPolicyGetter interface {
GetFramework(name string) (*reporthandling.Framework, error)
GetFrameworks() ([]reporthandling.Framework, error)
GetControl(ID string) (*reporthandling.Control, error)
ListFrameworks() ([]string, error)
ListControls() ([]string, error)
}
type IExceptionsGetter interface {
GetExceptions(clusterName string) ([]armotypes.PostureExceptionPolicy, error)
}
type IBackend interface {
GetAccountID() string
GetClientID() string
GetSecretKey() string
GetCloudReportURL() string
GetCloudAPIURL() string
GetCloudUIURL() string
GetCloudAuthURL() string
SetAccountID(accountID string)
SetClientID(clientID string)
SetSecretKey(secretKey string)
SetCloudReportURL(cloudReportURL string)
SetCloudAPIURL(cloudAPIURL string)
SetCloudUIURL(cloudUIURL string)
SetCloudAuthURL(cloudAuthURL string)
GetTenant() (*TenantResponse, error)
}
type IControlsInputsGetter interface {
GetControlsInputs(clusterName string) (map[string][]string, error)
}
type IAttackTracksGetter interface {
GetAttackTracks() ([]v1alpha1.AttackTrack, error)
}

View File

@@ -6,27 +6,24 @@ import (
"io"
"net/http"
"os"
"path"
"path/filepath"
"strings"
)
// GetDefaultPath returns a location under the local dot files for kubescape.
//
// This is typically located under $HOME/.kubescape
func GetDefaultPath(name string) string {
return filepath.Join(DefaultLocalStore, name)
}
// SaveInFile serializes any object as a JSON file.
func SaveInFile(object interface{}, targetFile string) error {
encodedData, err := json.MarshalIndent(object, "", " ")
func SaveInFile(policy interface{}, pathStr string) error {
encodedData, err := json.MarshalIndent(policy, "", " ")
if err != nil {
return err
}
err = os.WriteFile(targetFile, encodedData, 0644) //nolint:gosec
err = os.WriteFile(pathStr, encodedData, 0644) //nolint:gosec
if err != nil {
if os.IsNotExist(err) {
pathDir := filepath.Dir(targetFile)
pathDir := path.Dir(pathStr)
// pathDir could contain subdirectories
if erm := os.MkdirAll(pathDir, 0755); erm != nil {
return erm
@@ -35,7 +32,7 @@ func SaveInFile(object interface{}, targetFile string) error {
return err
}
err = os.WriteFile(targetFile, encodedData, 0644) //nolint:gosec
err = os.WriteFile(pathStr, encodedData, 0644) //nolint:gosec
if err != nil {
return err
}
@@ -43,9 +40,6 @@ func SaveInFile(object interface{}, targetFile string) error {
return nil
}
// HttpDelete provides a low-level capability to send a HTTP DELETE request and serialize the response as a string.
//
// Deprecated: use methods of the KSCloudAPI client instead.
func HttpDelete(httpClient *http.Client, fullURL string, headers map[string]string) (string, error) {
req, err := http.NewRequest("DELETE", fullURL, nil)
@@ -65,10 +59,8 @@ func HttpDelete(httpClient *http.Client, fullURL string, headers map[string]stri
return respStr, nil
}
// HttpGetter provides a low-level capability to send a HTTP GET request and serialize the response as a string.
//
// Deprecated: use methods of the KSCloudAPI client instead.
func HttpGetter(httpClient *http.Client, fullURL string, headers map[string]string) (string, error) {
req, err := http.NewRequest("GET", fullURL, nil)
if err != nil {
return "", err
@@ -86,10 +78,8 @@ func HttpGetter(httpClient *http.Client, fullURL string, headers map[string]stri
return respStr, nil
}
// HttpPost provides a low-level capability to send a HTTP POST request and serialize the response as a string.
//
// Deprecated: use methods of the KSCloudAPI client instead.
func HttpPost(httpClient *http.Client, fullURL string, headers map[string]string, body []byte) (string, error) {
req, err := http.NewRequest("POST", fullURL, bytes.NewReader(body))
if err != nil {
return "", err
@@ -114,7 +104,7 @@ func setHeaders(req *http.Request, headers map[string]string) {
}
}
// httpRespToString parses the body as string and checks the HTTP status code, it closes the body reader at the end
// HTTPRespToString parses the body as string and checks the HTTP status code, it closes the body reader at the end
func httpRespToString(resp *http.Response) (string, error) {
if resp == nil || resp.Body == nil {
return "", nil
@@ -124,7 +114,6 @@ func httpRespToString(resp *http.Response) (string, error) {
if resp.ContentLength > 0 {
strBuilder.Grow(int(resp.ContentLength))
}
_, err := io.Copy(&strBuilder, resp.Body)
respStr := strBuilder.String()
if err != nil {

View File

@@ -1,97 +0,0 @@
package getter
import (
"net/http"
"os"
"path/filepath"
"testing"
"github.com/stretchr/testify/require"
)
func TestGetDefaultPath(t *testing.T) {
t.Parallel()
const name = "mine"
pth := GetDefaultPath(name)
require.Equal(t, name, filepath.Base(pth))
require.Equal(t, ".kubescape", filepath.Base(filepath.Dir(pth)))
}
func TestSaveInFile(t *testing.T) {
t.Parallel()
dir, err := os.MkdirTemp(".", "test")
require.NoError(t, err)
defer func() {
_ = os.RemoveAll(dir)
}()
policy := map[string]interface{}{
"key": "value",
"number": 1.00,
}
t.Run("should save data as JSON (target folder exists)", func(t *testing.T) {
target := filepath.Join(dir, "target.json")
require.NoError(t, SaveInFile(policy, target))
buf, err := os.ReadFile(target)
require.NoError(t, err)
var retrieved interface{}
require.NoError(t, json.Unmarshal(buf, &retrieved))
require.EqualValues(t, policy, retrieved)
})
t.Run("should save data as JSON (new target folder)", func(t *testing.T) {
target := filepath.Join(dir, "subdir", "target.json")
require.NoError(t, SaveInFile(policy, target))
buf, err := os.ReadFile(target)
require.NoError(t, err)
var retrieved interface{}
require.NoError(t, json.Unmarshal(buf, &retrieved))
require.EqualValues(t, policy, retrieved)
})
t.Run("should error", func(t *testing.T) {
badPolicy := map[string]interface{}{
"key": "value",
"number": 1.00,
"err": func() {},
}
target := filepath.Join(dir, "error.json")
require.Error(t, SaveInFile(badPolicy, target))
})
}
func TestHttpMethods(t *testing.T) {
client := http.DefaultClient
hdrs := map[string]string{"key": "value"}
srv := mockAPIServer(t)
t.Cleanup(srv.Close)
t.Run("HttpGetter should GET", func(t *testing.T) {
resp, err := HttpGetter(client, srv.URL(pathTestGet), hdrs)
require.NoError(t, err)
require.EqualValues(t, "body-get", resp)
})
t.Run("HttpPost should POST", func(t *testing.T) {
body := []byte("body-post")
resp, err := HttpPost(client, srv.URL(pathTestPost), hdrs, body)
require.NoError(t, err)
require.EqualValues(t, string(body), resp)
})
t.Run("HttpDelete should DELETE", func(t *testing.T) {
resp, err := HttpDelete(client, srv.URL(pathTestDelete), hdrs)
require.NoError(t, err)
require.EqualValues(t, "body-delete", resp)
})
}

View File

@@ -1,55 +0,0 @@
package getter
import (
"github.com/armosec/armoapi-go/armotypes"
"github.com/kubescape/opa-utils/reporthandling"
"github.com/kubescape/opa-utils/reporthandling/attacktrack/v1alpha1"
)
type (
// IPolicyGetter knows how to retrieve policies, i.e. frameworks and their controls.
IPolicyGetter interface {
GetFramework(name string) (*reporthandling.Framework, error)
GetFrameworks() ([]reporthandling.Framework, error)
GetControl(ID string) (*reporthandling.Control, error)
ListFrameworks() ([]string, error)
ListControls() ([]string, error)
}
// IExceptionsGetter knows how to retrieve exceptions.
IExceptionsGetter interface {
GetExceptions(clusterName string) ([]armotypes.PostureExceptionPolicy, error)
}
// IControlsInputsGetter knows how to retrieve controls inputs.
IControlsInputsGetter interface {
GetControlsInputs(clusterName string) (map[string][]string, error)
}
// IAttackTracksGetter knows how to retrieve attack tracks.
IAttackTracksGetter interface {
GetAttackTracks() ([]v1alpha1.AttackTrack, error)
}
// IBackend knows how to configure a KS Cloud client
IBackend interface {
GetAccountID() string
GetClientID() string
GetSecretKey() string
GetCloudReportURL() string
GetCloudAPIURL() string
GetCloudUIURL() string
GetCloudAuthURL() string
SetAccountID(accountID string)
SetClientID(clientID string)
SetSecretKey(secretKey string)
SetCloudReportURL(cloudReportURL string)
SetCloudAPIURL(cloudAPIURL string)
SetCloudUIURL(cloudUIURL string)
SetCloudAuthURL(cloudAuthURL string)
GetTenant() (*TenantResponse, error)
}
)

View File

@@ -1,13 +1,16 @@
package getter
import (
"io"
"strings"
stdjson "encoding/json"
jsoniter "github.com/json-iterator/go"
)
var json jsoniter.API
var (
json jsoniter.API
)
func init() {
// NOTE(fredbi): attention, this configuration rounds floats down to 6 digits
@@ -15,24 +18,9 @@ func init() {
json = jsoniter.ConfigFastest
}
// JSONDecoder provides a low-level utility that returns a JSON decoder for given string.
//
// Deprecated: use higher level methods from the KSCloudAPI client instead.
func JSONDecoder(origin string) *jsoniter.Decoder {
dec := jsoniter.NewDecoder(strings.NewReader(origin))
// JSONDecoder returns JSON decoder for given string
func JSONDecoder(origin string) *stdjson.Decoder {
dec := stdjson.NewDecoder(strings.NewReader(origin))
dec.UseNumber()
return dec
}
func decode[T any](rdr io.Reader) (T, error) {
var receiver T
dec := newDecoder(rdr)
err := dec.Decode(&receiver)
return receiver, err
}
func newDecoder(rdr io.Reader) *jsoniter.Decoder {
return json.NewDecoder(rdr)
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,295 +0,0 @@
package getter
import (
"os"
"path/filepath"
"testing"
"github.com/armosec/armoapi-go/armotypes"
"github.com/armosec/armoapi-go/identifiers"
jsoniter "github.com/json-iterator/go"
"github.com/kubescape/kubescape/v2/internal/testutils"
"github.com/kubescape/opa-utils/reporthandling"
"github.com/kubescape/opa-utils/reporthandling/attacktrack/v1alpha1"
"github.com/stretchr/testify/require"
)
func mockAttackTracks() []v1alpha1.AttackTrack {
return []v1alpha1.AttackTrack{
{
ApiVersion: "v1",
Kind: "track",
Metadata: map[string]interface{}{"label": "name"},
Spec: v1alpha1.AttackTrackSpecification{
Version: "v2",
Description: "a mock",
Data: v1alpha1.AttackTrackStep{
Name: "track1",
Description: "mock-step",
SubSteps: []v1alpha1.AttackTrackStep{
{
Name: "track1",
Description: "mock-step",
Controls: []v1alpha1.IAttackTrackControl{
mockControlPtr("control-1"),
},
},
},
Controls: []v1alpha1.IAttackTrackControl{
mockControlPtr("control-2"),
mockControlPtr("control-3"),
},
},
},
},
{
ApiVersion: "v1",
Kind: "track",
Metadata: map[string]interface{}{"label": "stuff"},
Spec: v1alpha1.AttackTrackSpecification{
Version: "v1",
Description: "another mock",
Data: v1alpha1.AttackTrackStep{
Name: "track2",
Description: "mock-step2",
SubSteps: []v1alpha1.AttackTrackStep{
{
Name: "track3",
Description: "mock-step",
Controls: []v1alpha1.IAttackTrackControl{
mockControlPtr("control-4"),
},
},
},
Controls: []v1alpha1.IAttackTrackControl{
mockControlPtr("control-5"),
mockControlPtr("control-6"),
},
},
},
},
}
}
func mockFrameworks() []reporthandling.Framework {
id1s := []string{"control-1", "control-2"}
id2s := []string{"control-3", "control-4"}
id3s := []string{"control-5", "control-6"}
return []reporthandling.Framework{
{
PortalBase: armotypes.PortalBase{
Name: "mock-1",
},
CreationTime: "now",
Description: "mock-1",
Controls: []reporthandling.Control{
mockControl("control-1"),
mockControl("control-2"),
},
ControlsIDs: &id1s,
SubSections: map[string]*reporthandling.FrameworkSubSection{
"section1": {
ID: "section-id",
ControlIDs: id1s,
},
},
},
{
PortalBase: armotypes.PortalBase{
Name: "mock-2",
},
CreationTime: "then",
Description: "mock-2",
Controls: []reporthandling.Control{
mockControl("control-3"),
mockControl("control-4"),
},
ControlsIDs: &id2s,
SubSections: map[string]*reporthandling.FrameworkSubSection{
"section2": {
ID: "section-id",
ControlIDs: id2s,
},
},
},
{
PortalBase: armotypes.PortalBase{
Name: "nsa",
},
CreationTime: "tomorrow",
Description: "nsa mock",
Controls: []reporthandling.Control{
mockControl("control-5"),
mockControl("control-6"),
},
ControlsIDs: &id3s,
SubSections: map[string]*reporthandling.FrameworkSubSection{
"section2": {
ID: "section-id",
ControlIDs: id3s,
},
},
},
}
}
func mockControl(controlID string) reporthandling.Control {
return reporthandling.Control{
ControlID: controlID,
}
}
func mockControlPtr(controlID string) *reporthandling.Control {
val := mockControl(controlID)
return &val
}
func mockExceptions() []armotypes.PostureExceptionPolicy {
return []armotypes.PostureExceptionPolicy{
{
PolicyType: "postureExceptionPolicy",
CreationTime: "now",
Actions: []armotypes.PostureExceptionPolicyActions{
"alertOnly",
},
Resources: []identifiers.PortalDesignator{
{
DesignatorType: "Attributes",
Attributes: map[string]string{
"kind": "Pod",
"name": "coredns-[A-Za-z0-9]+-[A-Za-z0-9]+",
"namespace": "kube-system",
},
},
{
DesignatorType: "Attributes",
Attributes: map[string]string{
"kind": "Pod",
"name": "etcd-.*",
"namespace": "kube-system",
},
},
},
PosturePolicies: []armotypes.PosturePolicy{
{
FrameworkName: "MITRE",
ControlID: "C-.*",
},
{
FrameworkName: "another-framework",
ControlID: "a regexp",
},
},
},
{
PolicyType: "postureExceptionPolicy",
CreationTime: "then",
Actions: []armotypes.PostureExceptionPolicyActions{
"alertOnly",
},
Resources: []identifiers.PortalDesignator{
{
DesignatorType: "Attributes",
Attributes: map[string]string{
"kind": "Deployment",
"name": "my-regexp",
},
},
{
DesignatorType: "Attributes",
Attributes: map[string]string{
"kind": "Secret",
"name": "another-regexp",
},
},
},
PosturePolicies: []armotypes.PosturePolicy{
{
FrameworkName: "yet-another-framework",
ControlID: "a regexp",
},
},
},
}
}
func mockTenantResponse() *TenantResponse {
return &TenantResponse{
TenantID: "id",
Token: "token",
Expires: "expiry-time",
AdminMail: "admin@example.com",
}
}
func mockCustomerConfig(cluster, scope string) func() *armotypes.CustomerConfig {
if cluster == "" {
cluster = "my-cluster"
}
if scope == "" {
scope = "default"
}
return func() *armotypes.CustomerConfig {
return &armotypes.CustomerConfig{
Name: "user",
Attributes: map[string]interface{}{
"label": "value",
},
Scope: identifiers.PortalDesignator{
DesignatorType: "Attributes",
Attributes: map[string]string{
"kind": "Cluster",
"name": cluster,
"scope": scope,
},
},
Settings: armotypes.Settings{
PostureControlInputs: map[string][]string{
"inputs-1": {"x1", "y2"},
"inputs-2": {"x2", "y2"},
},
PostureScanConfig: armotypes.PostureScanConfig{
ScanFrequency: armotypes.ScanFrequency("weekly"),
},
VulnerabilityScanConfig: armotypes.VulnerabilityScanConfig{
ScanFrequency: armotypes.ScanFrequency("daily"),
CriticalPriorityThreshold: 1,
HighPriorityThreshold: 2,
MediumPriorityThreshold: 3,
ScanNewDeployment: true,
AllowlistRegistries: []string{"a", "b"},
BlocklistRegistries: []string{"c", "d"},
},
SlackConfigurations: armotypes.SlackSettings{
Token: "slack-token",
},
},
}
}
}
func mockLoginResponse() *feLoginResponse {
return &feLoginResponse{
Token: "access-token",
RefreshToken: "refresh-token",
Expires: "expiry-time",
ExpiresIn: 123,
}
}
func mockPostureReport(t testing.TB, reportID, cluster string) *PostureReport {
fixture := filepath.Join(testutils.CurrentDir(), "testdata", "mock_posture_report.json")
buf, err := os.ReadFile(fixture)
require.NoError(t, err)
var report PostureReport
require.NoError(t,
jsoniter.Unmarshal(buf, &report),
)
return &report
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,186 @@
package getter
import (
"bytes"
"fmt"
"net/http"
"net/url"
"strings"
)
var NativeFrameworks = []string{"nsa", "mitre", "armobest", "devopsbest"}
func (api *KSCloudAPI) getFrameworkURL(frameworkName string) string {
u := url.URL{}
u.Scheme, u.Host = parseHost(api.GetCloudAPIURL())
u.Path = "api/v1/armoFrameworks"
q := u.Query()
q.Add("customerGUID", api.getCustomerGUIDFallBack())
if isNativeFramework(frameworkName) {
q.Add("frameworkName", strings.ToUpper(frameworkName))
} else {
// For customer framework has to be the way it was added
q.Add("frameworkName", frameworkName)
}
u.RawQuery = q.Encode()
return u.String()
}
func (api *KSCloudAPI) getAttackTracksURL() string {
u := url.URL{}
u.Scheme, u.Host = parseHost(api.GetCloudAPIURL())
u.Path = "api/v1/attackTracks"
q := u.Query()
q.Add("customerGUID", api.getCustomerGUIDFallBack())
u.RawQuery = q.Encode()
return u.String()
}
func (api *KSCloudAPI) getListFrameworkURL() string {
u := url.URL{}
u.Scheme, u.Host = parseHost(api.GetCloudAPIURL())
u.Path = "api/v1/armoFrameworks"
q := u.Query()
q.Add("customerGUID", api.getCustomerGUIDFallBack())
u.RawQuery = q.Encode()
return u.String()
}
func (api *KSCloudAPI) getExceptionsURL(clusterName string) string {
u := url.URL{}
u.Scheme, u.Host = parseHost(api.GetCloudAPIURL())
u.Path = "api/v1/armoPostureExceptions"
q := u.Query()
q.Add("customerGUID", api.getCustomerGUIDFallBack())
// if clusterName != "" { // TODO - fix customer name support in Armo BE
// q.Add("clusterName", clusterName)
// }
u.RawQuery = q.Encode()
return u.String()
}
func (api *KSCloudAPI) exceptionsURL(exceptionsPolicyName string) string {
u := url.URL{}
u.Scheme, u.Host = parseHost(api.GetCloudAPIURL())
u.Path = "api/v1/postureExceptionPolicy"
q := u.Query()
q.Add("customerGUID", api.getCustomerGUIDFallBack())
if exceptionsPolicyName != "" { // for delete
q.Add("policyName", exceptionsPolicyName)
}
u.RawQuery = q.Encode()
return u.String()
}
func (api *KSCloudAPI) getAccountConfigDefault(clusterName string) string {
config := api.getAccountConfig(clusterName)
url := config + "&scope=customer"
return url
}
func (api *KSCloudAPI) getAccountConfig(clusterName string) string {
u := url.URL{}
u.Scheme, u.Host = parseHost(api.GetCloudAPIURL())
u.Path = "api/v1/armoCustomerConfiguration"
q := u.Query()
q.Add("customerGUID", api.getCustomerGUIDFallBack())
if clusterName != "" { // TODO - fix customer name support in Armo BE
q.Add("clusterName", clusterName)
}
u.RawQuery = q.Encode()
return u.String()
}
func (api *KSCloudAPI) getAccountURL() string {
u := url.URL{}
u.Scheme, u.Host = parseHost(api.GetCloudAPIURL())
u.Path = "api/v1/createTenant"
return u.String()
}
func (api *KSCloudAPI) getApiToken() string {
u := url.URL{}
u.Scheme, u.Host = parseHost(api.GetCloudAuthURL())
u.Path = "identity/resources/auth/v1/api-token"
return u.String()
}
func (api *KSCloudAPI) getOpenidCustomers() string {
u := url.URL{}
u.Scheme, u.Host = parseHost(api.GetCloudAPIURL())
u.Path = "api/v1/openid_customers"
return u.String()
}
func (api *KSCloudAPI) getAuthCookie() (string, error) {
selectCustomer := KSCloudSelectCustomer{SelectedCustomerGuid: api.accountID}
requestBody, _ := json.Marshal(selectCustomer)
client := &http.Client{}
httpRequest, err := http.NewRequest(http.MethodPost, api.getOpenidCustomers(), bytes.NewBuffer(requestBody))
if err != nil {
return "", err
}
httpRequest.Header.Set("Content-Type", "application/json")
httpRequest.Header.Set("Authorization", fmt.Sprintf("Bearer %s", api.feToken.Token))
httpResponse, err := client.Do(httpRequest)
if err != nil {
return "", err
}
defer httpResponse.Body.Close()
if httpResponse.StatusCode != http.StatusOK {
return "", fmt.Errorf("failed to get cookie from %s: status %d", api.getOpenidCustomers(), httpResponse.StatusCode)
}
cookies := httpResponse.Header.Get("set-cookie")
if len(cookies) == 0 {
return "", fmt.Errorf("no cookie field in response from %s", api.getOpenidCustomers())
}
authCookie := ""
for _, cookie := range strings.Split(cookies, ";") {
kv := strings.Split(cookie, "=")
if kv[0] == "auth" {
authCookie = kv[1]
}
}
if len(authCookie) == 0 {
return "", fmt.Errorf("no auth cookie field in response from %s", api.getOpenidCustomers())
}
return authCookie, nil
}
func (api *KSCloudAPI) appendAuthHeaders(headers map[string]string) {
if api.feToken.Token != "" {
headers["Authorization"] = fmt.Sprintf("Bearer %s", api.feToken.Token)
}
if api.authCookie != "" {
headers["Cookie"] = fmt.Sprintf("auth=%s", api.authCookie)
}
}
func (api *KSCloudAPI) getCustomerGUIDFallBack() string {
if api.accountID != "" {
return api.accountID
}
return "11111111-1111-1111-1111-111111111111"
}
func parseHost(host string) (string, string) {
if strings.HasPrefix(host, "http://") {
return "http", strings.Replace(host, "http://", "", 1)
}
// default scheme
return "https", strings.Replace(host, "https://", "", 1)
}

View File

@@ -1,202 +0,0 @@
package getter
import (
"context"
"fmt"
"log"
"net/http"
"net/http/httputil"
"time"
)
type (
// KSCloudOption allows to configure the behavior of the KS Cloud client.
KSCloudOption func(*ksCloudOptions)
// ksCloudOptions holds all the configurable parts of the KS Cloud client.
ksCloudOptions struct {
httpClient *http.Client
cloudReportURL string
cloudUIURL string
timeout *time.Duration
withTrace bool
}
// request option instructs post/get/delete to alter the outgoing request
requestOption func(*requestOptions)
// requestOptions knows how to enrich a request with headers
requestOptions struct {
withJSON bool
withToken string
withCookie *http.Cookie
withTrace bool
headers map[string]string
reqContext context.Context
}
)
// KS Cloud client options
// WithHTTPClient overrides the default http.Client used by the KS Cloud client.
func WithHTTPClient(client *http.Client) KSCloudOption {
return func(o *ksCloudOptions) {
o.httpClient = client
}
}
// WithTimeout sets a global timeout on a operations performed by the KS Cloud client.
//
// A value of 0 means no timeout.
//
// The default is 61s.
func WithTimeout(timeout time.Duration) KSCloudOption {
duration := timeout
return func(o *ksCloudOptions) {
o.timeout = &duration
}
}
// WithReportURL specifies the URL to post reports.
func WithReportURL(u string) KSCloudOption {
return func(o *ksCloudOptions) {
o.cloudReportURL = u
}
}
// WithFrontendURL specifies the URL to access the KS Cloud UI.
func WithFrontendURL(u string) KSCloudOption {
return func(o *ksCloudOptions) {
o.cloudUIURL = u
}
}
// WithTrace toggles requests dump for inspection & debugging.
func WithTrace(enabled bool) KSCloudOption {
return func(o *ksCloudOptions) {
o.withTrace = enabled
}
}
var defaultClient = &http.Client{
Timeout: 61 * time.Second,
}
// ksCloudOptionsWithDefaults sets defaults for the KS client and applies overrides.
func ksCloudOptionsWithDefaults(opts []KSCloudOption) *ksCloudOptions {
options := &ksCloudOptions{
httpClient: defaultClient,
}
for _, apply := range opts {
apply(options)
}
if options.timeout != nil {
// non-default timeout (0 means no timeout)
// clone the client and override the timeout
client := *options.httpClient
client.Timeout = *options.timeout
options.httpClient = &client
}
return options
}
// http request options
// withContentJSON sets JSON content type for a request
func withContentJSON(enabled bool) requestOption {
return func(o *requestOptions) {
o.withJSON = enabled
}
}
// withToken sets an Authorization header for a request
func withToken(token string) requestOption {
return func(o *requestOptions) {
o.withToken = token
}
}
// withCookie sets an authentication cookie for a request
func withCookie(cookie *http.Cookie) requestOption {
return func(o *requestOptions) {
o.withCookie = cookie
}
}
// withExtraHeaders adds extra headers to a request
func withExtraHeaders(headers map[string]string) requestOption {
return func(o *requestOptions) {
o.headers = headers
}
}
/* not used yet
// withContext sets the context of a request.
//
// By default, context.Background() is used.
func withContext(ctx context.Context) requestOption {
return func(o *requestOptions) {
o.reqContext = ctx
}
}
*/
// withTrace dumps requests for debugging
func withTrace(enabled bool) requestOption {
return func(o *requestOptions) {
o.withTrace = enabled
}
}
func (o *requestOptions) setHeaders(req *http.Request) {
if o.withJSON {
req.Header.Set("Content-Type", "application/json")
}
if len(o.withToken) > 0 {
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", o.withToken))
}
if o.withCookie != nil {
req.AddCookie(o.withCookie)
}
for k, v := range o.headers {
req.Header.Set(k, v)
}
}
// traceReq dumps the content of an outgoing request for inspecting or debugging the client.
func (o *requestOptions) traceReq(req *http.Request) {
if !o.withTrace {
return
}
dump, _ := httputil.DumpRequestOut(req, true)
log.Printf("%s\n", dump)
}
// traceResp dumps the content of an API response for inspecting or debugging the client.
func (o *requestOptions) traceResp(resp *http.Response) {
if !o.withTrace {
return
}
dump, _ := httputil.DumpResponse(resp, true)
log.Printf("%s\n", dump)
}
func requestOptionsWithDefaults(opts []requestOption) *requestOptions {
o := &requestOptions{
reqContext: context.Background(),
}
for _, apply := range opts {
apply(o)
}
return o
}

View File

@@ -24,13 +24,9 @@ var (
ErrIDRequired = errors.New("missing required input control ID")
ErrFrameworkNotMatching = errors.New("framework from file not matching")
ErrControlNotMatching = errors.New("framework from file not matching")
)
var (
_ IPolicyGetter = &LoadPolicy{}
_ IExceptionsGetter = &LoadPolicy{}
_ IAttackTracksGetter = &LoadPolicy{}
_ IControlsInputsGetter = &LoadPolicy{}
_ IPolicyGetter = &LoadPolicy{}
_ IExceptionsGetter = &LoadPolicy{}
)
func getCacheDir() string {
@@ -164,7 +160,7 @@ func (lp *LoadPolicy) ListFrameworks() ([]string, error) {
continue
}
if framework.Name == "" || contains(frameworkNames, framework.Name) {
if contains(frameworkNames, framework.Name) {
continue
}

View File

@@ -6,7 +6,6 @@ import (
"path/filepath"
"testing"
"github.com/kubescape/kubescape/v2/internal/testutils"
"github.com/stretchr/testify/require"
)
@@ -180,29 +179,6 @@ func TestLoadPolicy(t *testing.T) {
require.Equal(t, extraFramework, fws[1])
})
t.Run("should not return an empty framework", func(t *testing.T) {
t.Parallel()
const (
extraFramework = "NSA"
attackTracks = "attack-tracks"
controlsInputs = "controls-inputs"
)
p := NewLoadPolicy([]string{
testFrameworkFile(testFramework),
testFrameworkFile(extraFramework),
testFrameworkFile(attackTracks), // should be ignored
testFrameworkFile(controlsInputs), // should be ignored
})
fws, err := p.ListFrameworks()
require.NoError(t, err)
require.Len(t, fws, 2)
require.NotContains(t, fws, "")
require.Equal(t, testFramework, fws[0])
require.Equal(t, extraFramework, fws[1])
})
t.Run("should fail on file error", func(t *testing.T) {
t.Parallel()
@@ -387,7 +363,7 @@ func TestLoadPolicy(t *testing.T) {
}
func testFrameworkFile(framework string) string {
return filepath.Join(testutils.CurrentDir(), "testdata", fmt.Sprintf("%s.json", framework))
return filepath.Join(".", "testdata", fmt.Sprintf("%s.json", framework))
}
func writeTempJSONControlInputs(t testing.TB) (string, map[string][]string) {

View File

@@ -6,7 +6,6 @@
},
"creationTime": "",
"description": "Testing MITRE for Kubernetes as suggested by microsoft in https://www.microsoft.com/security/blog/wp-content/uploads/2020/04/k8s-matrix.png",
"typeTags": ["compliance"],
"controls": [
{
"guid": "",

View File

@@ -6,7 +6,6 @@
},
"creationTime": "",
"description": "Implement NSA security advices for K8s ",
"typeTags": ["compliance"],
"controls": [
{
"guid": "",

View File

@@ -1,125 +0,0 @@
{
"publicRegistries": [],
"untrustedRegistries": [],
"listOfDangerousArtifacts": [
"bin/bash",
"sbin/sh",
"bin/ksh",
"bin/tcsh",
"bin/zsh",
"usr/bin/scsh",
"bin/csh",
"bin/busybox",
"usr/bin/busybox"
],
"sensitiveKeyNames": [
"aws_access_key_id",
"aws_secret_access_key",
"azure_batchai_storage_account",
"azure_batchai_storage_key",
"azure_batch_account",
"azure_batch_key",
"secret",
"key",
"password",
"pwd",
"token",
"jwt",
"bearer",
"credential"
],
"servicesNames": [
"nifi-service",
"argo-server",
"minio",
"postgres",
"workflow-controller-metrics",
"weave-scope-app",
"kubernetes-dashboard"
],
"memory_limit_max": [],
"cpu_request_min": [],
"wlKnownNames": [
"coredns",
"kube-proxy",
"event-exporter-gke",
"kube-dns",
"17-default-backend",
"metrics-server",
"ca-audit",
"ca-dashboard-aggregator",
"ca-notification-server",
"ca-ocimage",
"ca-oracle",
"ca-posture",
"ca-rbac",
"ca-vuln-scan",
"ca-webhook",
"ca-websocket",
"clair-clair"
],
"sensitiveInterfaces": [
"nifi",
"argo-server",
"weave-scope-app",
"kubeflow",
"kubernetes-dashboard",
"jenkins",
"prometheus-deployment"
],
"max_high_vulnerabilities": [
"10"
],
"sensitiveValues": [
"BEGIN \\w+ PRIVATE KEY",
"PRIVATE KEY",
"eyJhbGciO",
"JWT",
"Bearer",
"_key_",
"_secret_"
],
"memory_request_max": [],
"memory_request_min": [],
"cpu_request_max": [],
"cpu_limit_max": [],
"cpu_limit_min": [],
"insecureCapabilities": [
"SETPCAP",
"NET_ADMIN",
"NET_RAW",
"SYS_MODULE",
"SYS_RAWIO",
"SYS_PTRACE",
"SYS_ADMIN",
"SYS_BOOT",
"MAC_OVERRIDE",
"MAC_ADMIN",
"PERFMON",
"ALL",
"BPF"
],
"max_critical_vulnerabilities": [
"5"
],
"sensitiveValuesAllowed": [],
"memory_limit_min": [],
"recommendedLabels": [
"app",
"tier",
"phase",
"version",
"owner",
"env"
],
"k8sRecommendedLabels": [
"app.kubernetes.io/name",
"app.kubernetes.io/instance",
"app.kubernetes.io/version",
"app.kubernetes.io/component",
"app.kubernetes.io/part-of",
"app.kubernetes.io/managed-by",
"app.kubernetes.io/created-by"
],
"imageRepositoryAllowList": []
}

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@@ -1,65 +0,0 @@
package getter
import (
"net/url"
"path"
)
// buildAPIURL builds an URL pointing to the API backend.
func (api *KSCloudAPI) buildAPIURL(pth string, pairs ...string) string {
return buildQuery(url.URL{
Scheme: api.scheme,
Host: api.host,
Path: pth,
}, pairs...)
}
// buildUIURL builds an URL pointing to the UI frontend.
func (api *KSCloudAPI) buildUIURL(pth string, pairs ...string) string {
return buildQuery(url.URL{
Scheme: api.uischeme,
Host: api.uihost,
Path: pth,
}, pairs...)
}
// buildAuthURL builds an URL pointing to the authentication endpoint.
func (api *KSCloudAPI) buildAuthURL(pth string, pairs ...string) string {
return buildQuery(url.URL{
Scheme: api.authscheme,
Host: api.authhost,
Path: pth,
}, pairs...)
}
// buildReportURL builds an URL pointing to the reporting endpoint.
func (api *KSCloudAPI) buildReportURL(pth string, pairs ...string) string {
return buildQuery(url.URL{
Scheme: api.reportscheme,
Host: api.reporthost,
Path: pth,
}, pairs...)
}
// buildQuery builds an URL with query params.
//
// Params are provided in pairs (param name, value).
func buildQuery(u url.URL, pairs ...string) string {
if len(pairs)%2 != 0 {
panic("dev error: buildURL accepts query params in (name, value) pairs")
}
q := u.Query()
for i := 0; i < len(pairs)-1; i += 2 {
param := pairs[i]
value := pairs[i+1]
q.Add(param, value)
}
u.RawQuery = q.Encode()
u.Path = path.Clean(u.Path)
return u.String()
}

View File

@@ -1,86 +0,0 @@
package getter
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestBuildURL(t *testing.T) {
t.Parallel()
ks := NewKSCloudAPICustomized(
"api.example.com", "auth.example.com", // required
WithFrontendURL("ui.example.com"), // optional
WithReportURL("report.example.com"), // optional
)
t.Run("should build API URL with query params on https host", func(t *testing.T) {
require.Equal(t,
"https://api.example.com/path?q1=v1&q2=v2",
ks.buildAPIURL("/path", "q1", "v1", "q2", "v2"),
)
})
t.Run("should build API URL with query params on http host", func(t *testing.T) {
ku := NewKSCloudAPICustomized("http://api.example.com", "auth.example.com")
require.Equal(t,
"http://api.example.com/path?q1=v1&q2=v2",
ku.buildAPIURL("/path", "q1", "v1", "q2", "v2"),
)
})
t.Run("should panic when params are not provided in pairs", func(t *testing.T) {
require.Panics(t, func() {
// notice how the linter detects wrong args
_ = ks.buildAPIURL("/path", "q1", "v1", "q2") //nolint:staticcheck
})
})
t.Run("should build UI URL with query params on https host", func(t *testing.T) {
require.Equal(t,
"https://ui.example.com/path?q1=v1&q2=v2",
ks.buildUIURL("/path", "q1", "v1", "q2", "v2"),
)
})
t.Run("should build report URL with query params on https host", func(t *testing.T) {
require.Equal(t,
"https://report.example.com/path?q1=v1&q2=v2",
ks.buildReportURL("/path", "q1", "v1", "q2", "v2"),
)
})
}
func TestViewURL(t *testing.T) {
t.Parallel()
ks := NewKSCloudAPICustomized(
"api.example.com", "auth.example.com", // required
WithFrontendURL("ui.example.com"), // optional
WithReportURL("report.example.com"), // optional
)
ks.SetAccountID("me")
ks.SetInvitationToken("invite")
t.Run("should render UI report URL", func(t *testing.T) {
require.Equal(t, "https://ui.example.com/repository-scanning/xyz", ks.ViewReportURL("xyz"))
})
t.Run("should render UI dashboard URL", func(t *testing.T) {
require.Equal(t, "https://ui.example.com/dashboard", ks.ViewDashboardURL())
})
t.Run("should render UI RBAC URL", func(t *testing.T) {
require.Equal(t, "https://ui.example.com/rbac-visualizer", ks.ViewRBACURL())
})
t.Run("should render UI scan URL", func(t *testing.T) {
require.Equal(t, "https://ui.example.com/compliance/cluster", ks.ViewScanURL("cluster"))
})
t.Run("should render UI sign URL", func(t *testing.T) {
require.Equal(t, "https://ui.example.com/account/sign-up?customerGUID=me&invitationToken=invite&utm_medium=createaccount&utm_source=ARMOgithub", ks.ViewSignURL())
})
}

View File

@@ -1,82 +0,0 @@
package getter
import (
"fmt"
"io"
"net/http"
"strings"
)
// parseHost picks a host from a hostname or an URL and detects the scheme.
//
// The default scheme is https. This may be altered by specifying an explicit http://hostname URL.
func parseHost(host string) (string, string) {
if strings.HasPrefix(host, "http://") {
return "http", strings.Replace(host, "http://", "", 1) // cut... index ...
}
// default scheme
return "https", strings.Replace(host, "https://", "", 1)
}
func isNativeFramework(framework string) bool {
return contains(NativeFrameworks, framework)
}
func contains(s []string, str string) bool {
for _, v := range s {
if strings.EqualFold(v, str) {
return true
}
}
return false
}
func min(a, b int64) int64 {
if a < b {
return a
}
return b
}
// errAPI reports an API error, with a cap on the length of the error message.
func errAPI(resp *http.Response) error {
const maxSize = 1024
reason := new(strings.Builder)
if resp.Body != nil {
size := min(resp.ContentLength, maxSize)
if size > 0 {
reason.Grow(int(size))
}
_, _ = io.CopyN(reason, resp.Body, size)
defer resp.Body.Close()
}
return fmt.Errorf("http-error: '%s', reason: '%s'", resp.Status, reason.String())
}
// errAuth returns an authentication error.
//
// Authentication errors upon login croak a less detailed message.
func errAuth(resp *http.Response) error {
return fmt.Errorf("error authenticating: %d", resp.StatusCode)
}
func readString(rdr io.Reader, sizeHint int64) (string, error) {
// if the response is empty, return an empty string
if sizeHint < 0 {
return "", nil
}
var b strings.Builder
b.Grow(int(sizeHint))
_, err := io.Copy(&b, rdr)
return b.String(), err
}

View File

@@ -1,99 +0,0 @@
package getter
import (
"io"
"testing"
"github.com/stretchr/testify/require"
)
func TestParseHost(t *testing.T) {
t.Parallel()
t.Run("should recognize http scheme", func(t *testing.T) {
t.Parallel()
const input = "http://localhost:7555"
scheme, host := parseHost(input)
require.Equal(t, "http", scheme)
require.Equal(t, "localhost:7555", host)
})
t.Run("should recognize https scheme", func(t *testing.T) {
t.Parallel()
const input = "https://localhost:7555"
scheme, host := parseHost(input)
require.Equal(t, "https", scheme)
require.Equal(t, "localhost:7555", host)
})
t.Run("should adopt https scheme by default", func(t *testing.T) {
t.Parallel()
const input = "portal-dev.armo.cloud"
scheme, host := parseHost(input)
require.Equal(t, "https", scheme)
require.Equal(t, "portal-dev.armo.cloud", host)
})
}
func TestIsNativeFramework(t *testing.T) {
t.Parallel()
require.Truef(t, isNativeFramework("nSa"), "expected nsa to be native (case insensitive)")
require.Falsef(t, isNativeFramework("foo"), "expected framework to be custom")
}
func Test_readString(t *testing.T) {
type args struct {
rdr io.Reader
sizeHint int64
}
tests := []struct {
name string
args args
want string
wantErr bool
}{
{
name: "should return empty string if sizeHint is negative",
args: args{
rdr: nil,
sizeHint: -1,
},
want: "",
wantErr: false,
},
{
name: "should return empty string if sizeHint is zero",
args: args{
rdr: &io.LimitedReader{},
sizeHint: 0,
},
want: "",
wantErr: false,
},
{
name: "should return empty string if sizeHint is positive",
args: args{
rdr: &io.LimitedReader{},
sizeHint: 1,
},
want: "",
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := readString(tt.args.rdr, tt.args.sizeHint)
if (err != nil) != tt.wantErr {
t.Errorf("readString() error = %v, wantErr %v", err, tt.wantErr)
return
}
if got != tt.want {
t.Errorf("readString() = %v, want %v", got, tt.want)
}
})
}
}

View File

@@ -55,8 +55,10 @@ func controlReportV2ToV1(opaSessionObj *OPASessionObj, frameworkName string, con
crv1.Remediation = crv2.Remediation
rulesv1 := map[string]reporthandling.RuleReport{}
l := helpersv1.GetAllListsFromPool()
for resourceID := range crv2.ListResourcesIDs(l).All() {
iter := crv2.ListResourcesIDs().All()
for iter.HasNext() {
resourceID := iter.Next()
if result, ok := opaSessionObj.ResourcesResult[resourceID]; ok {
for _, rulev2 := range result.ListRulesOfControl(crv2.GetID(), "") {
@@ -70,9 +72,9 @@ func controlReportV2ToV1(opaSessionObj *OPASessionObj, frameworkName string, con
}
rulev1 := rulesv1[rulev2.GetName()]
status := rulev2.GetStatus(nil)
status := rulev2.GetStatus(&helpersv1.Filters{FrameworkNames: []string{frameworkName}})
if status.IsFailed() {
if status.IsFailed() || status.IsExcluded() {
// rule response
ruleResponse := reporthandling.RuleResponse{}
@@ -103,7 +105,6 @@ func controlReportV2ToV1(opaSessionObj *OPASessionObj, frameworkName string, con
}
}
}
helpersv1.PutAllListsToPool(l)
if len(rulesv1) > 0 {
for i := range rulesv1 {
crv1.RuleReports = append(crv1.RuleReports, rulesv1[i])

View File

@@ -1,20 +1,20 @@
package cautils
import (
"context"
"encoding/json"
"fmt"
"os"
"path/filepath"
"strings"
"github.com/armosec/armoapi-go/armotypes"
apisv1 "github.com/kubescape/opa-utils/httpserver/apis/v1"
giturl "github.com/kubescape/go-git-url"
"github.com/kubescape/go-logger"
"github.com/kubescape/go-logger/helpers"
"github.com/kubescape/k8s-interface/k8sinterface"
"github.com/kubescape/kubescape/v2/core/cautils/getter"
apisv1 "github.com/kubescape/opa-utils/httpserver/apis/v1"
"github.com/kubescape/opa-utils/objectsenvelopes"
"github.com/kubescape/opa-utils/reporthandling"
reporthandlingv2 "github.com/kubescape/opa-utils/reporthandling/v2"
@@ -87,59 +87,51 @@ func (bpf *BoolPtrFlag) Set(val string) error {
// TODO - UPDATE
type ViewTypes string
type EnvScopeTypes string
type ManageClusterTypes string
const (
ResourceViewType ViewTypes = "resource"
SecurityViewType ViewTypes = "security"
ControlViewType ViewTypes = "control"
)
type PolicyIdentifier struct {
Identifier string // policy Identifier e.g. c-0012 for control, nsa,mitre for frameworks
Kind apisv1.NotificationPolicyKind // policy kind e.g. Framework,Control,Rule
Identifier string // policy Identifier e.g. c-0012 for control, nsa,mitre for frameworks
Kind apisv1.NotificationPolicyKind // policy kind e.g. Framework,Control,Rule
Designators armotypes.PortalDesignator
}
type ScanInfo struct {
Getters // TODO - remove from object
PolicyIdentifier []PolicyIdentifier // TODO - remove from object
UseExceptions string // Load file with exceptions configuration
ControlsInputs string // Load file with inputs for controls
AttackTracks string // Load file with attack tracks
UseFrom []string // Load framework from local file (instead of download). Use when running offline
UseDefault bool // Load framework from cached file (instead of download). Use when running offline
UseArtifactsFrom string // Load artifacts from local path. Use when running offline
VerboseMode bool // Display all of the input resources and not only failed resources
View string // Display all of the input resources and not only failed resources
Format string // Format results (table, json, junit ...)
Output string // Store results in an output file, Output file name
FormatVersion string // Output object can be different between versions, this is for testing and backward compatibility
CustomClusterName string // Set the custom name of the cluster
ExcludedNamespaces string // used for host scanner namespace
IncludeNamespaces string //
InputPatterns []string // Yaml files input patterns
Silent bool // Silent mode - Do not print progress logs
FailThreshold float32 // DEPRECATED - Failure score threshold
ComplianceThreshold float32 // Compliance score threshold
FailThresholdSeverity string // Severity at and above which the command should fail
Submit bool // Submit results to Kubescape Cloud BE
CreateAccount bool // Create account in Kubescape Cloud BE if no account found in local cache
ScanID string // Report id of the current scan
HostSensorEnabled BoolPtrFlag // Deploy Kubescape K8s host scanner to collect data from certain controls
HostSensorYamlPath string // Path to hostsensor file
Local bool // Do not submit results
Credentials Credentials // account ID
KubeContext string // context name
FrameworkScan bool // false if scanning control
ScanAll bool // true if scan all frameworks
OmitRawResources bool // true if omit raw resources from the output
PrintAttackTree bool // true if print attack tree
ScanObject *objectsenvelopes.ScanObject // identifies a single resource (k8s object) to be scanned
ScanType ScanTypes
ScanImages bool
ChartPath string
FilePath string
Getters // TODO - remove from object
PolicyIdentifier []PolicyIdentifier // TODO - remove from object
UseExceptions string // Load file with exceptions configuration
ControlsInputs string // Load file with inputs for controls
AttackTracks string // Load file with attack tracks
UseFrom []string // Load framework from local file (instead of download). Use when running offline
UseDefault bool // Load framework from cached file (instead of download). Use when running offline
UseArtifactsFrom string // Load artifacts from local path. Use when running offline
VerboseMode bool // Display all of the input resources and not only failed resources
View string // Display all of the input resources and not only failed resources
Format string // Format results (table, json, junit ...)
Output string // Store results in an output file, Output file name
FormatVersion string // Output object can be differnet between versions, this is for testing and backward compatibility
CustomClusterName string // Set the custom name of the cluster
ExcludedNamespaces string // used for host scanner namespace
IncludeNamespaces string //
InputPatterns []string // Yaml files input patterns
Silent bool // Silent mode - Do not print progress logs
FailThreshold float32 // Failure score threshold
FailThresholdSeverity string // Severity at and above which the command should fail
Submit bool // Submit results to Kubescape Cloud BE
CreateAccount bool // Create account in Kubescape Cloud BE if no account found in local cache
ScanID string // Report id of the current scan
HostSensorEnabled BoolPtrFlag // Deploy Kubescape K8s host scanner to collect data from certain controls
HostSensorYamlPath string // Path to hostsensor file
Local bool // Do not submit results
Credentials Credentials // account ID
KubeContext string // context name
FrameworkScan bool // false if scanning control
ScanAll bool // true if scan all frameworks
OmitRawResources bool // true if omit raw resources from the output
PrintAttackTree bool // true if print attack tree
}
type Getters struct {
@@ -149,16 +141,16 @@ type Getters struct {
AttackTracksGetter getter.IAttackTracksGetter
}
func (scanInfo *ScanInfo) Init(ctx context.Context) {
func (scanInfo *ScanInfo) Init() {
scanInfo.setUseFrom()
scanInfo.setUseArtifactsFrom(ctx)
scanInfo.setUseArtifactsFrom()
if scanInfo.ScanID == "" {
scanInfo.ScanID = uuid.NewString()
}
}
func (scanInfo *ScanInfo) setUseArtifactsFrom(ctx context.Context) {
func (scanInfo *ScanInfo) setUseArtifactsFrom() {
if scanInfo.UseArtifactsFrom == "" {
return
}
@@ -172,7 +164,7 @@ func (scanInfo *ScanInfo) setUseArtifactsFrom(ctx context.Context) {
// set frameworks files
files, err := os.ReadDir(scanInfo.UseArtifactsFrom)
if err != nil {
logger.L().Ctx(ctx).Fatal("failed to read files from directory", helpers.String("dir", scanInfo.UseArtifactsFrom), helpers.Error(err))
logger.L().Fatal("failed to read files from directory", helpers.String("dir", scanInfo.UseArtifactsFrom), helpers.Error(err))
}
framework := &reporthandling.Framework{}
for _, f := range files {
@@ -211,10 +203,6 @@ func (scanInfo *ScanInfo) Formats() []string {
}
}
func (scanInfo *ScanInfo) SetScanType(scanType ScanTypes) {
scanInfo.ScanType = scanType
}
func (scanInfo *ScanInfo) SetPolicyIdentifiers(policies []string, kind apisv1.NotificationPolicyKind) {
for _, policy := range policies {
if !scanInfo.contains(policy) {
@@ -235,10 +223,10 @@ func (scanInfo *ScanInfo) contains(policyName string) bool {
return false
}
func scanInfoToScanMetadata(ctx context.Context, scanInfo *ScanInfo) *reporthandlingv2.Metadata {
func scanInfoToScanMetadata(scanInfo *ScanInfo) *reporthandlingv2.Metadata {
metadata := &reporthandlingv2.Metadata{}
metadata.ScanMetadata.Formats = []string{scanInfo.Format}
metadata.ScanMetadata.Format = scanInfo.Format
metadata.ScanMetadata.FormatVersion = scanInfo.FormatVersion
metadata.ScanMetadata.Submit = scanInfo.Submit
@@ -262,7 +250,6 @@ func scanInfoToScanMetadata(ctx context.Context, scanInfo *ScanInfo) *reporthand
metadata.ScanMetadata.KubescapeVersion = BuildNumber
metadata.ScanMetadata.VerboseMode = scanInfo.VerboseMode
metadata.ScanMetadata.FailThreshold = scanInfo.FailThreshold
metadata.ScanMetadata.ComplianceThreshold = scanInfo.ComplianceThreshold
metadata.ScanMetadata.HostScanner = scanInfo.HostSensorEnabled.GetBool()
metadata.ScanMetadata.VerboseMode = scanInfo.VerboseMode
metadata.ScanMetadata.ControlsInputs = scanInfo.ControlsInputs
@@ -290,7 +277,7 @@ func scanInfoToScanMetadata(ctx context.Context, scanInfo *ScanInfo) *reporthand
}
setContextMetadata(ctx, &metadata.ContextMetadata, inputFiles)
setContextMetadata(&metadata.ContextMetadata, inputFiles)
return metadata
}
@@ -334,7 +321,7 @@ func GetScanningContext(input string) ScanningContext {
// dir/glob
return ContextDir
}
func setContextMetadata(ctx context.Context, contextMetadata *reporthandlingv2.ContextMetadata, input string) {
func setContextMetadata(contextMetadata *reporthandlingv2.ContextMetadata, input string) {
switch GetScanningContext(input) {
case ContextCluster:
contextMetadata.ClusterContextMetadata = &reporthandlingv2.ClusterMetadata{
@@ -344,7 +331,7 @@ func setContextMetadata(ctx context.Context, contextMetadata *reporthandlingv2.C
// url
context, err := metadataGitURL(input)
if err != nil {
logger.L().Ctx(ctx).Warning("in setContextMetadata", helpers.Interface("case", ContextGitURL), helpers.Error(err))
logger.L().Warning("in setContextMetadata", helpers.Interface("case", ContextGitURL), helpers.Error(err))
}
contextMetadata.RepoContextMetadata = context
case ContextDir:
@@ -352,35 +339,16 @@ func setContextMetadata(ctx context.Context, contextMetadata *reporthandlingv2.C
BasePath: getAbsPath(input),
HostName: getHostname(),
}
// add repo context for submitting
contextMetadata.RepoContextMetadata = &reporthandlingv2.RepoContextMetadata{
Provider: "none",
Repo: fmt.Sprintf("path@%s", getAbsPath(input)),
Owner: getHostname(),
Branch: "none",
DefaultBranch: "none",
LocalRootPath: getAbsPath(input),
}
case ContextFile:
contextMetadata.FileContextMetadata = &reporthandlingv2.FileContextMetadata{
FilePath: getAbsPath(input),
HostName: getHostname(),
}
// add repo context for submitting
contextMetadata.RepoContextMetadata = &reporthandlingv2.RepoContextMetadata{
Provider: "none",
Repo: fmt.Sprintf("file@%s", getAbsPath(input)),
Owner: getHostname(),
Branch: "none",
DefaultBranch: "none",
LocalRootPath: getAbsPath(input),
}
case ContextGitLocal:
// local
context, err := metadataGitLocal(input)
if err != nil {
logger.L().Ctx(ctx).Warning("in setContextMetadata", helpers.Interface("case", ContextGitURL), helpers.Error(err))
logger.L().Warning("in setContextMetadata", helpers.Interface("case", ContextGitURL), helpers.Error(err))
}
contextMetadata.RepoContextMetadata = context
}

View File

@@ -1,7 +1,6 @@
package cautils
import (
"context"
"testing"
reporthandlingv2 "github.com/kubescape/opa-utils/reporthandling/v2"
@@ -11,7 +10,7 @@ import (
func TestSetContextMetadata(t *testing.T) {
{
ctx := reporthandlingv2.ContextMetadata{}
setContextMetadata(context.TODO(), &ctx, "")
setContextMetadata(&ctx, "")
assert.NotNil(t, ctx.ClusterContextMetadata)
assert.Nil(t, ctx.DirectoryContextMetadata)

View File

@@ -2,12 +2,11 @@ package cautils
import (
"fmt"
"os"
"sort"
"strconv"
"strings"
)
const ValueNotFound = -1
func ConvertLabelsToString(labels map[string]string) string {
labelsStr := ""
delimiter := ""
@@ -35,31 +34,11 @@ func ConvertStringToLabels(labelsStr string) map[string]string {
return labels
}
func StringSlicesAreEqual(a, b []string) bool {
if len(a) != len(b) {
return false
}
sort.Strings(a)
sort.Strings(b)
for i := range a {
if a[i] != b[i] {
return false
func StringInSlice(strSlice []string, str string) int {
for i := range strSlice {
if strSlice[i] == str {
return i
}
}
return true
}
func ParseIntEnvVar(varName string, defaultValue int) (int, error) {
varValue, exists := os.LookupEnv(varName)
if !exists {
return defaultValue, nil
}
intValue, err := strconv.Atoi(varValue)
if err != nil {
return defaultValue, fmt.Errorf("failed to parse %s env var as int: %w", varName, err)
}
return intValue, nil
return ValueNotFound
}

View File

@@ -2,11 +2,8 @@ package cautils
import (
"fmt"
"os"
"strings"
"testing"
"github.com/stretchr/testify/assert"
)
func TestConvertLabelsToString(t *testing.T) {
@@ -36,102 +33,3 @@ func TestConvertStringToLabels(t *testing.T) {
t.Errorf("%s != %s", fmt.Sprintf("%v", rstrMap), fmt.Sprintf("%v", strMap))
}
}
func TestParseIntEnvVar(t *testing.T) {
testCases := []struct {
expectedErr string
name string
varName string
varValue string
defaultValue int
expected int
}{
{
name: "Variable does not exist",
varName: "DOES_NOT_EXIST",
varValue: "",
defaultValue: 123,
expected: 123,
expectedErr: "",
},
{
name: "Variable exists and is a valid integer",
varName: "MY_VAR",
varValue: "456",
defaultValue: 123,
expected: 456,
expectedErr: "",
},
{
name: "Variable exists but is not a valid integer",
varName: "MY_VAR",
varValue: "not_an_integer",
defaultValue: 123,
expected: 123,
expectedErr: "failed to parse MY_VAR env var as int",
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if tc.varValue != "" {
os.Setenv(tc.varName, tc.varValue)
} else {
os.Unsetenv(tc.varName)
}
actual, err := ParseIntEnvVar(tc.varName, tc.defaultValue)
if tc.expectedErr != "" {
assert.NotNil(t, err)
assert.ErrorContains(t, err, tc.expectedErr)
} else {
assert.Nil(t, err)
}
assert.Equalf(t, tc.expected, actual, "unexpected result")
})
}
}
func TestStringSlicesAreEqual(t *testing.T) {
tt := []struct {
name string
a []string
b []string
want bool
}{
{
name: "equal unsorted slices",
a: []string{"foo", "bar", "baz"},
b: []string{"baz", "foo", "bar"},
want: true,
},
{
name: "equal sorted slices",
a: []string{"bar", "baz", "foo"},
b: []string{"bar", "baz", "foo"},
want: true,
},
{
name: "unequal slices",
a: []string{"foo", "bar", "baz"},
b: []string{"foo", "bar", "qux"},
want: false,
},
{
name: "different length slices",
a: []string{"foo", "bar", "baz"},
b: []string{"foo", "bar"},
want: false,
},
}
for _, tc := range tt {
t.Run(tc.name, func(t *testing.T) {
got := StringSlicesAreEqual(tc.a, tc.b)
if got != tc.want {
t.Errorf("StringSlicesAreEqual(%v, %v) = %v; want %v", tc.a, tc.b, got, tc.want)
}
})
}
}

View File

@@ -1,7 +1,6 @@
package cautils
import (
"context"
"encoding/json"
"fmt"
"net/http"
@@ -11,7 +10,7 @@ import (
logger "github.com/kubescape/go-logger"
"github.com/kubescape/go-logger/helpers"
"github.com/kubescape/kubescape/v2/core/cautils/getter"
"go.opentelemetry.io/otel"
"golang.org/x/mod/semver"
)
@@ -26,12 +25,12 @@ var LatestReleaseVersion string
const UnknownBuildNumber = "unknown"
type IVersionCheckHandler interface {
CheckLatestVersion(context.Context, *VersionCheckRequest) error
CheckLatestVersion(*VersionCheckRequest) error
}
func NewIVersionCheckHandler(ctx context.Context) IVersionCheckHandler {
func NewIVersionCheckHandler() IVersionCheckHandler {
if BuildNumber == "" {
logger.L().Ctx(ctx).Warning("Unknown build number, this might affect your scan results. Please make sure you are updated to latest version")
logger.L().Warning("unknown build number, this might affect your scan results. Please make sure you are updated to latest version")
}
if v, ok := os.LookupEnv(CLIENT_ENV); ok && v != "" {
@@ -99,17 +98,15 @@ func NewVersionCheckRequest(buildNumber, frameworkName, frameworkVersion, scanni
}
}
func (v *VersionCheckHandlerMock) CheckLatestVersion(_ context.Context, _ *VersionCheckRequest) error {
func (v *VersionCheckHandlerMock) CheckLatestVersion(versionData *VersionCheckRequest) error {
logger.L().Info("Skipping version check")
return nil
}
func (v *VersionCheckHandler) CheckLatestVersion(ctx context.Context, versionData *VersionCheckRequest) error {
ctx, span := otel.Tracer("").Start(ctx, "versionCheckHandler.CheckLatestVersion")
defer span.End()
func (v *VersionCheckHandler) CheckLatestVersion(versionData *VersionCheckRequest) error {
defer func() {
if err := recover(); err != nil {
logger.L().Ctx(ctx).Warning("failed to get latest version", helpers.Interface("error", err))
logger.L().Warning("failed to get latest version", helpers.Interface("error", err))
}
}()
@@ -122,7 +119,7 @@ func (v *VersionCheckHandler) CheckLatestVersion(ctx context.Context, versionDat
if latestVersion.ClientUpdate != "" {
if BuildNumber != "" && semver.Compare(BuildNumber, LatestReleaseVersion) == -1 {
logger.L().Ctx(ctx).Warning(warningMessage(LatestReleaseVersion))
logger.L().Warning(warningMessage(LatestReleaseVersion))
}
}

View File

@@ -4,7 +4,6 @@ import (
"strings"
"github.com/kubescape/k8s-interface/cloudsupport"
cloudapis "github.com/kubescape/k8s-interface/cloudsupport/apis"
"github.com/kubescape/opa-utils/reporthandling/apis"
)
@@ -21,20 +20,16 @@ var (
"KubeProxyInfo",
"ControlPlaneInfo",
"CloudProviderInfo",
"CNIInfo",
}
CloudResources = []string{
cloudapis.CloudProviderDescribeKind,
cloudapis.CloudProviderDescribeRepositoriesKind,
cloudapis.CloudProviderListEntitiesForPoliciesKind,
cloudapis.CloudProviderPolicyVersionKind,
"ClusterDescribe",
string(cloudsupport.TypeApiServerInfo),
}
)
func MapExternalResource(externalResourceMap ExternalResources, resources []string) []string {
func MapKSResource(ksResourceMap *KSResources, resources []string) []string {
var hostResources []string
for k := range externalResourceMap {
for k := range *ksResourceMap {
for _, resource := range resources {
if strings.Contains(k, resource) {
hostResources = append(hostResources, k)
@@ -44,16 +39,16 @@ func MapExternalResource(externalResourceMap ExternalResources, resources []stri
return hostResources
}
func MapHostResources(externalResourceMap ExternalResources) []string {
return MapExternalResource(externalResourceMap, HostSensorResources)
func MapHostResources(ksResourceMap *KSResources) []string {
return MapKSResource(ksResourceMap, HostSensorResources)
}
func MapImageVulnResources(externalResourceMap ExternalResources) []string {
return MapExternalResource(externalResourceMap, ImageVulnResources)
func MapImageVulnResources(ksResourceMap *KSResources) []string {
return MapKSResource(ksResourceMap, ImageVulnResources)
}
func MapCloudResources(externalResourceMap ExternalResources) []string {
return MapExternalResource(externalResourceMap, CloudResources)
func MapCloudResources(ksResourceMap *KSResources) []string {
return MapKSResource(ksResourceMap, CloudResources)
}
func SetInfoMapForResources(info string, resources []string, errorMap map[string]apis.StatusInfo) {

View File

@@ -1,7 +1,6 @@
package core
import (
"context"
"fmt"
metav1 "github.com/kubescape/kubescape/v2/core/meta/datastructures/v1"
@@ -9,7 +8,7 @@ import (
func (ks *Kubescape) SetCachedConfig(setConfig *metav1.SetConfig) error {
tenant := getTenantConfig(nil, "", "", nil)
tenant := getTenantConfig(nil, "", "", getKubernetesApi())
if setConfig.Account != "" {
tenant.GetConfigObj().AccountID = setConfig.Account
@@ -43,8 +42,8 @@ func (ks *Kubescape) ViewCachedConfig(viewConfig *metav1.ViewConfig) error {
return nil
}
func (ks *Kubescape) DeleteCachedConfig(ctx context.Context, deleteConfig *metav1.DeleteConfig) error {
func (ks *Kubescape) DeleteCachedConfig(deleteConfig *metav1.DeleteConfig) error {
tenant := getTenantConfig(nil, "", "", nil) // change k8sinterface
return tenant.DeleteCachedConfig(ctx)
tenant := getTenantConfig(nil, "", "", getKubernetesApi()) // change k8sinterface
return tenant.DeleteCachedConfig()
}

View File

@@ -1,7 +1,6 @@
package core
import (
"context"
"fmt"
"os"
"path/filepath"
@@ -22,7 +21,7 @@ const (
TargetAttackTracks = "attack-tracks"
)
var downloadFunc = map[string]func(context.Context, *metav1.DownloadInfo) error{
var downloadFunc = map[string]func(*metav1.DownloadInfo) error{
TargetControlsInputs: downloadConfigInputs,
TargetExceptions: downloadExceptions,
TargetControl: downloadControl,
@@ -39,20 +38,20 @@ func DownloadSupportCommands() []string {
return commands
}
func (ks *Kubescape) Download(ctx context.Context, downloadInfo *metav1.DownloadInfo) error {
func (ks *Kubescape) Download(downloadInfo *metav1.DownloadInfo) error {
setPathandFilename(downloadInfo)
if err := os.MkdirAll(downloadInfo.Path, os.ModePerm); err != nil {
return err
}
if err := downloadArtifact(ctx, downloadInfo, downloadFunc); err != nil {
if err := downloadArtifact(downloadInfo, downloadFunc); err != nil {
return err
}
return nil
}
func downloadArtifact(ctx context.Context, downloadInfo *metav1.DownloadInfo, downloadArtifactFunc map[string]func(context.Context, *metav1.DownloadInfo) error) error {
func downloadArtifact(downloadInfo *metav1.DownloadInfo, downloadArtifactFunc map[string]func(*metav1.DownloadInfo) error) error {
if f, ok := downloadArtifactFunc[downloadInfo.Target]; ok {
if err := f(ctx, downloadInfo); err != nil {
if err := f(downloadInfo); err != nil {
return err
}
return nil
@@ -74,26 +73,26 @@ func setPathandFilename(downloadInfo *metav1.DownloadInfo) {
}
}
func downloadArtifacts(ctx context.Context, downloadInfo *metav1.DownloadInfo) error {
func downloadArtifacts(downloadInfo *metav1.DownloadInfo) error {
downloadInfo.FileName = ""
var artifacts = map[string]func(context.Context, *metav1.DownloadInfo) error{
var artifacts = map[string]func(*metav1.DownloadInfo) error{
"controls-inputs": downloadConfigInputs,
"exceptions": downloadExceptions,
"framework": downloadFramework,
"attack-tracks": downloadAttackTracks,
}
for artifact := range artifacts {
if err := downloadArtifact(ctx, &metav1.DownloadInfo{Target: artifact, Path: downloadInfo.Path, FileName: fmt.Sprintf("%s.json", artifact)}, artifacts); err != nil {
logger.L().Ctx(ctx).Warning("error downloading", helpers.String("artifact", artifact), helpers.Error(err))
if err := downloadArtifact(&metav1.DownloadInfo{Target: artifact, Path: downloadInfo.Path, FileName: fmt.Sprintf("%s.json", artifact)}, artifacts); err != nil {
logger.L().Error("error downloading", helpers.String("artifact", artifact), helpers.Error(err))
}
}
return nil
}
func downloadConfigInputs(ctx context.Context, downloadInfo *metav1.DownloadInfo) error {
func downloadConfigInputs(downloadInfo *metav1.DownloadInfo) error {
tenant := getTenantConfig(&downloadInfo.Credentials, "", "", getKubernetesApi())
controlsInputsGetter := getConfigInputsGetter(ctx, downloadInfo.Identifier, tenant.GetAccountID(), nil)
controlsInputsGetter := getConfigInputsGetter(downloadInfo.Identifier, tenant.GetAccountID(), nil)
controlInputs, err := controlsInputsGetter.GetControlsInputs(tenant.GetContextName())
if err != nil {
return err
@@ -113,9 +112,9 @@ func downloadConfigInputs(ctx context.Context, downloadInfo *metav1.DownloadInfo
return nil
}
func downloadExceptions(ctx context.Context, downloadInfo *metav1.DownloadInfo) error {
func downloadExceptions(downloadInfo *metav1.DownloadInfo) error {
tenant := getTenantConfig(&downloadInfo.Credentials, "", "", getKubernetesApi())
exceptionsGetter := getExceptionsGetter(ctx, "", tenant.GetAccountID(), nil)
exceptionsGetter := getExceptionsGetter("", tenant.GetAccountID(), nil)
exceptions, err := exceptionsGetter.GetExceptions(tenant.GetContextName())
if err != nil {
@@ -130,15 +129,15 @@ func downloadExceptions(ctx context.Context, downloadInfo *metav1.DownloadInfo)
if err != nil {
return err
}
logger.L().Ctx(ctx).Success("Downloaded", helpers.String("artifact", downloadInfo.Target), helpers.String("path", filepath.Join(downloadInfo.Path, downloadInfo.FileName)))
logger.L().Success("Downloaded", helpers.String("artifact", downloadInfo.Target), helpers.String("path", filepath.Join(downloadInfo.Path, downloadInfo.FileName)))
return nil
}
func downloadAttackTracks(ctx context.Context, downloadInfo *metav1.DownloadInfo) error {
func downloadAttackTracks(downloadInfo *metav1.DownloadInfo) error {
var err error
tenant := getTenantConfig(&downloadInfo.Credentials, "", "", getKubernetesApi())
attackTracksGetter := getAttackTracksGetter(ctx, "", tenant.GetAccountID(), nil)
attackTracksGetter := getAttackTracksGetter("", tenant.GetAccountID(), nil)
attackTracks, err := attackTracksGetter.GetAttackTracks()
if err != nil {
@@ -158,11 +157,11 @@ func downloadAttackTracks(ctx context.Context, downloadInfo *metav1.DownloadInfo
}
func downloadFramework(ctx context.Context, downloadInfo *metav1.DownloadInfo) error {
func downloadFramework(downloadInfo *metav1.DownloadInfo) error {
tenant := getTenantConfig(&downloadInfo.Credentials, "", "", getKubernetesApi())
g := getPolicyGetter(ctx, nil, tenant.GetTenantEmail(), true, nil)
g := getPolicyGetter(nil, tenant.GetTenantEmail(), true, nil)
if downloadInfo.Identifier == "" {
// if framework name not specified - download all frameworks
@@ -200,11 +199,11 @@ func downloadFramework(ctx context.Context, downloadInfo *metav1.DownloadInfo) e
return nil
}
func downloadControl(ctx context.Context, downloadInfo *metav1.DownloadInfo) error {
func downloadControl(downloadInfo *metav1.DownloadInfo) error {
tenant := getTenantConfig(&downloadInfo.Credentials, "", "", getKubernetesApi())
g := getPolicyGetter(ctx, nil, tenant.GetTenantEmail(), false, nil)
g := getPolicyGetter(nil, tenant.GetTenantEmail(), false, nil)
if downloadInfo.Identifier == "" {
// TODO - support

View File

@@ -1,7 +1,6 @@
package core
import (
"context"
"fmt"
"strings"
@@ -11,44 +10,42 @@ import (
"github.com/kubescape/kubescape/v2/core/pkg/fixhandler"
)
const (
noChangesApplied = "No changes were applied."
noResourcesToFix = "No issues to fix."
confirmationQuestion = "Would you like to apply the changes to the files above? [y|n]: "
)
const NoChangesApplied = "No changes were applied."
const NoResourcesToFix = "No issues to fix."
const ConfirmationQuestion = "Would you like to apply the changes to the files above? [y|n]: "
func (ks *Kubescape) Fix(ctx context.Context, fixInfo *metav1.FixInfo) error {
func (ks *Kubescape) Fix(fixInfo *metav1.FixInfo) error {
logger.L().Info("Reading report file...")
handler, err := fixhandler.NewFixHandler(fixInfo)
if err != nil {
return err
}
resourcesToFix := handler.PrepareResourcesToFix(ctx)
resourcesToFix := handler.PrepareResourcesToFix()
if len(resourcesToFix) == 0 {
logger.L().Info(noResourcesToFix)
logger.L().Info(NoResourcesToFix)
return nil
}
handler.PrintExpectedChanges(resourcesToFix)
if fixInfo.DryRun {
logger.L().Info(noChangesApplied)
logger.L().Info(NoChangesApplied)
return nil
}
if !fixInfo.NoConfirm && !userConfirmed() {
logger.L().Info(noChangesApplied)
logger.L().Info(NoChangesApplied)
return nil
}
updatedFilesCount, errors := handler.ApplyChanges(ctx, resourcesToFix)
updatedFilesCount, errors := handler.ApplyChanges(resourcesToFix)
logger.L().Info(fmt.Sprintf("Fixed resources in %d files.", updatedFilesCount))
if len(errors) > 0 {
for _, err := range errors {
logger.L().Ctx(ctx).Warning(err.Error())
logger.L().Error(err.Error())
}
return fmt.Errorf("Failed to fix some resources, check the logs for more details")
}
@@ -60,7 +57,7 @@ func userConfirmed() bool {
var input string
for {
fmt.Println(confirmationQuestion)
fmt.Printf(ConfirmationQuestion)
if _, err := fmt.Scanln(&input); err != nil {
continue
}

View File

@@ -1,7 +1,6 @@
package core
import (
"context"
"fmt"
"os"
@@ -16,7 +15,6 @@ import (
printerv2 "github.com/kubescape/kubescape/v2/core/pkg/resultshandling/printer/v2"
"github.com/kubescape/kubescape/v2/core/pkg/resultshandling/reporter"
reporterv2 "github.com/kubescape/kubescape/v2/core/pkg/resultshandling/reporter/v2"
"go.opentelemetry.io/otel"
"github.com/google/uuid"
@@ -37,7 +35,7 @@ func getTenantConfig(credentials *cautils.Credentials, clusterName string, custo
return cautils.NewClusterConfig(k8s, getter.GetKSCloudAPIConnector(), credentials, clusterName, customClusterName)
}
func getExceptionsGetter(ctx context.Context, useExceptions string, accountID string, downloadReleasedPolicy *getter.DownloadReleasedPolicy) getter.IExceptionsGetter {
func getExceptionsGetter(useExceptions string, accountID string, downloadReleasedPolicy *getter.DownloadReleasedPolicy) getter.IExceptionsGetter {
if useExceptions != "" {
// load exceptions from file
return getter.NewLoadPolicy([]string{useExceptions})
@@ -51,7 +49,7 @@ func getExceptionsGetter(ctx context.Context, useExceptions string, accountID st
downloadReleasedPolicy = getter.NewDownloadReleasedPolicy()
}
if err := downloadReleasedPolicy.SetRegoObjects(); err != nil { // if failed to pull attack tracks, fallback to cache
logger.L().Ctx(ctx).Warning("failed to get exceptions from github release, loading attack tracks from cache", helpers.Error(err))
logger.L().Warning("failed to get exceptions from github release, loading attack tracks from cache", helpers.Error(err))
return getter.NewLoadPolicy([]string{getter.GetDefaultPath(cautils.LocalExceptionsFilename)})
}
return downloadReleasedPolicy
@@ -65,74 +63,66 @@ func getRBACHandler(tenantConfig cautils.ITenantConfig, k8s *k8sinterface.Kubern
return nil
}
func getReporter(ctx context.Context, tenantConfig cautils.ITenantConfig, reportID string, submit, fwScan bool, scanInfo cautils.ScanInfo) reporter.IReport {
_, span := otel.Tracer("").Start(ctx, "getReporter")
defer span.End()
func getReporter(tenantConfig cautils.ITenantConfig, reportID string, submit, fwScan bool, scanningContext cautils.ScanningContext) reporter.IReport {
if submit {
submitData := reporterv2.SubmitContextScan
if scanInfo.GetScanningContext() != cautils.ContextCluster {
if scanningContext != cautils.ContextCluster {
submitData = reporterv2.SubmitContextRepository
}
return reporterv2.NewReportEventReceiver(tenantConfig.GetConfigObj(), reportID, submitData)
}
if tenantConfig.GetAccountID() == "" {
// Add link only when scanning a cluster using a framework
return reporterv2.NewReportMock("", "")
return reporterv2.NewReportMock("https://hub.armosec.io/docs/installing-kubescape", "run kubescape with the '--account' flag")
}
var message string
if !fwScan && scanInfo.ScanType != cautils.ScanTypeWorkload {
if !fwScan {
message = "Kubescape does not submit scan results when scanning controls"
}
return reporterv2.NewReportMock("", message)
}
func getResourceHandler(ctx context.Context, scanInfo *cautils.ScanInfo, tenantConfig cautils.ITenantConfig, k8s *k8sinterface.KubernetesApi, hostSensorHandler hostsensorutils.IHostSensor, registryAdaptors *resourcehandler.RegistryAdaptors) resourcehandler.IResourceHandler {
ctx, span := otel.Tracer("").Start(ctx, "getResourceHandler")
defer span.End()
func getResourceHandler(scanInfo *cautils.ScanInfo, tenantConfig cautils.ITenantConfig, k8s *k8sinterface.KubernetesApi, hostSensorHandler hostsensorutils.IHostSensor, registryAdaptors *resourcehandler.RegistryAdaptors) resourcehandler.IResourceHandler {
if len(scanInfo.InputPatterns) > 0 || k8s == nil {
// scanInfo.HostSensor.SetBool(false)
return resourcehandler.NewFileResourceHandler()
return resourcehandler.NewFileResourceHandler(scanInfo.InputPatterns, registryAdaptors)
}
getter.GetKSCloudAPIConnector()
rbacObjects := getRBACHandler(tenantConfig, k8s, scanInfo.Submit)
return resourcehandler.NewK8sResourceHandler(k8s, hostSensorHandler, rbacObjects, registryAdaptors)
return resourcehandler.NewK8sResourceHandler(k8s, getFieldSelector(scanInfo), hostSensorHandler, rbacObjects, registryAdaptors)
}
// getHostSensorHandler yields a IHostSensor that knows how to collect a host's scanned resources.
//
// A noop sensor is returned whenever host scanning is disabled or an error prevented the scanner to properly deploy.
func getHostSensorHandler(ctx context.Context, scanInfo *cautils.ScanInfo, k8s *k8sinterface.KubernetesApi) hostsensorutils.IHostSensor {
const wantsHostSensorControls = true // defaults to disabling the scanner if not explictly enabled (TODO(fredbi): should be addressed by injecting ScanInfo defaults)
hostSensorVal := scanInfo.HostSensorEnabled.Get()
func getHostSensorHandler(scanInfo *cautils.ScanInfo, k8s *k8sinterface.KubernetesApi) hostsensorutils.IHostSensor {
if !k8sinterface.IsConnectedToCluster() || k8s == nil {
return &hostsensorutils.HostSensorHandlerMock{}
}
switch {
case !k8sinterface.IsConnectedToCluster() || k8s == nil: // TODO(fred): fix race condition on global KSConfig there
return hostsensorutils.NewHostSensorHandlerMock()
case hostSensorVal != nil && *hostSensorVal:
hasHostSensorControls := true
// we need to determined which controls needs host scanner
if scanInfo.HostSensorEnabled.Get() == nil && hasHostSensorControls {
scanInfo.HostSensorEnabled.SetBool(false) // default - do not run host scanner
logger.L().Warning("Kubernetes cluster nodes scanning is disabled. This is required to collect valuable data for certain controls. You can enable it using the --enable-host-scan flag")
}
if hostSensorVal := scanInfo.HostSensorEnabled.Get(); hostSensorVal != nil && *hostSensorVal {
hostSensorHandler, err := hostsensorutils.NewHostSensorHandler(k8s, scanInfo.HostSensorYamlPath)
if err != nil {
logger.L().Ctx(ctx).Warning(fmt.Sprintf("failed to create host scanner: %s", err.Error()))
return hostsensorutils.NewHostSensorHandlerMock()
logger.L().Warning(fmt.Sprintf("failed to create host scanner: %s", err.Error()))
return &hostsensorutils.HostSensorHandlerMock{}
}
return hostSensorHandler
case hostSensorVal == nil && wantsHostSensorControls:
// TODO: we need to determine which controls need the host scanner
scanInfo.HostSensorEnabled.SetBool(false)
fallthrough
default:
return hostsensorutils.NewHostSensorHandlerMock()
}
return &hostsensorutils.HostSensorHandlerMock{}
}
func getFieldSelector(scanInfo *cautils.ScanInfo) resourcehandler.IFieldSelector {
if scanInfo.IncludeNamespaces != "" {
return resourcehandler.NewIncludeSelector(scanInfo.IncludeNamespaces)
}
if scanInfo.ExcludedNamespaces != "" {
return resourcehandler.NewExcludeSelector(scanInfo.ExcludedNamespaces)
}
return &resourcehandler.EmptySelector{}
}
func policyIdentifierIdentities(pi []cautils.PolicyIdentifier) string {
@@ -174,13 +164,13 @@ func setSubmitBehavior(scanInfo *cautils.ScanInfo, tenantConfig cautils.ITenantC
return
}
if scanInfo.Local {
scanningContext := scanInfo.GetScanningContext()
if scanningContext == cautils.ContextFile || scanningContext == cautils.ContextDir {
scanInfo.Submit = false
return
}
// do not submit single resource scan to BE
if scanInfo.ScanObject != nil {
if scanInfo.Local {
scanInfo.Submit = false
return
}
@@ -199,7 +189,7 @@ func setSubmitBehavior(scanInfo *cautils.ScanInfo, tenantConfig cautils.ITenantC
}
// setPolicyGetter set the policy getter - local file/github release/Kubescape Cloud API
func getPolicyGetter(ctx context.Context, loadPoliciesFromFile []string, tenantEmail string, frameworkScope bool, downloadReleasedPolicy *getter.DownloadReleasedPolicy) getter.IPolicyGetter {
func getPolicyGetter(loadPoliciesFromFile []string, tenantEmail string, frameworkScope bool, downloadReleasedPolicy *getter.DownloadReleasedPolicy) getter.IPolicyGetter {
if len(loadPoliciesFromFile) > 0 {
return getter.NewLoadPolicy(loadPoliciesFromFile)
}
@@ -210,12 +200,12 @@ func getPolicyGetter(ctx context.Context, loadPoliciesFromFile []string, tenantE
if downloadReleasedPolicy == nil {
downloadReleasedPolicy = getter.NewDownloadReleasedPolicy()
}
return getDownloadReleasedPolicy(ctx, downloadReleasedPolicy)
return getDownloadReleasedPolicy(downloadReleasedPolicy)
}
// setConfigInputsGetter sets the config input getter - local file/github release/Kubescape Cloud API
func getConfigInputsGetter(ctx context.Context, ControlsInputs string, accountID string, downloadReleasedPolicy *getter.DownloadReleasedPolicy) getter.IControlsInputsGetter {
func getConfigInputsGetter(ControlsInputs string, accountID string, downloadReleasedPolicy *getter.DownloadReleasedPolicy) getter.IControlsInputsGetter {
if len(ControlsInputs) > 0 {
return getter.NewLoadPolicy([]string{ControlsInputs})
}
@@ -227,14 +217,14 @@ func getConfigInputsGetter(ctx context.Context, ControlsInputs string, accountID
downloadReleasedPolicy = getter.NewDownloadReleasedPolicy()
}
if err := downloadReleasedPolicy.SetRegoObjects(); err != nil { // if failed to pull config inputs, fallback to BE
logger.L().Ctx(ctx).Warning("failed to get config inputs from github release, this may affect the scanning results", helpers.Error(err))
logger.L().Warning("failed to get config inputs from github release, this may affect the scanning results", helpers.Error(err))
}
return downloadReleasedPolicy
}
func getDownloadReleasedPolicy(ctx context.Context, downloadReleasedPolicy *getter.DownloadReleasedPolicy) getter.IPolicyGetter {
func getDownloadReleasedPolicy(downloadReleasedPolicy *getter.DownloadReleasedPolicy) getter.IPolicyGetter {
if err := downloadReleasedPolicy.SetRegoObjects(); err != nil { // if failed to pull policy, fallback to cache
logger.L().Ctx(ctx).Warning("failed to get policies from github release, loading policies from cache", helpers.Error(err))
logger.L().Warning("failed to get policies from github release, loading policies from cache", helpers.Error(err))
return getter.NewLoadPolicy(getDefaultFrameworksPaths())
} else {
return downloadReleasedPolicy
@@ -257,7 +247,7 @@ func listFrameworksNames(policyGetter getter.IPolicyGetter) []string {
return getter.NativeFrameworks
}
func getAttackTracksGetter(ctx context.Context, attackTracks, accountID string, downloadReleasedPolicy *getter.DownloadReleasedPolicy) getter.IAttackTracksGetter {
func getAttackTracksGetter(attackTracks, accountID string, downloadReleasedPolicy *getter.DownloadReleasedPolicy) getter.IAttackTracksGetter {
if len(attackTracks) > 0 {
return getter.NewLoadPolicy([]string{attackTracks})
}
@@ -270,23 +260,18 @@ func getAttackTracksGetter(ctx context.Context, attackTracks, accountID string,
}
if err := downloadReleasedPolicy.SetRegoObjects(); err != nil { // if failed to pull attack tracks, fallback to cache
logger.L().Ctx(ctx).Warning("failed to get attack tracks from github release, loading attack tracks from cache", helpers.Error(err))
logger.L().Warning("failed to get attack tracks from github release, loading attack tracks from cache", helpers.Error(err))
return getter.NewLoadPolicy([]string{getter.GetDefaultPath(cautils.LocalAttackTracksFilename)})
}
return downloadReleasedPolicy
}
// getUIPrinter returns a printer that will be used to print to the programs UI (terminal)
func GetUIPrinter(ctx context.Context, scanInfo *cautils.ScanInfo) printer.IPrinter {
var p printer.IPrinter
if helpers.ToLevel(logger.L().GetLevel()) >= helpers.WarningLevel {
p = &printerv2.SilentPrinter{}
} else {
p = printerv2.NewPrettyPrinter(scanInfo.VerboseMode, scanInfo.FormatVersion, scanInfo.PrintAttackTree, cautils.ViewTypes(scanInfo.View), scanInfo.ScanType, scanInfo.InputPatterns)
func getUIPrinter(verboseMode bool, formatVersion string, attackTree bool, viewType cautils.ViewTypes) printer.IPrinter {
p := printerv2.NewPrettyPrinter(verboseMode, formatVersion, attackTree, viewType)
// Since the UI of the program is a CLI (Stdout), it means that it should always print to Stdout
p.SetWriter(ctx, os.Stdout.Name())
}
// Since the UI of the program is a CLI (Stdout), it means that it should always print to Stdout
p.SetWriter(os.Stdout.Name())
return p
}

View File

@@ -1,17 +1,10 @@
package core
import (
"context"
"reflect"
"testing"
"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/pkg/hostsensorutils"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func Test_getUIPrinter(t *testing.T) {
@@ -20,166 +13,27 @@ func Test_getUIPrinter(t *testing.T) {
VerboseMode: true,
View: "control",
}
type args struct {
ctx context.Context
formatVersion string
viewType cautils.ViewTypes
verboseMode bool
printAttack bool
loggerLevel helpers.Level
}
type wantTypes struct {
structType string
formatVersion string
viewType cautils.ViewTypes
verboseMode bool
}
tests := []struct {
name string
args args
want wantTypes
testAllFields bool
}{
{
name: "Test getUIPrinter PrettyPrinter",
args: args{
ctx: context.TODO(),
verboseMode: scanInfo.VerboseMode,
formatVersion: scanInfo.FormatVersion,
printAttack: scanInfo.PrintAttackTree,
viewType: cautils.ViewTypes(scanInfo.View),
loggerLevel: helpers.InfoLevel,
},
want: wantTypes{
structType: "*printer.PrettyPrinter",
formatVersion: scanInfo.FormatVersion,
verboseMode: scanInfo.VerboseMode,
viewType: cautils.ViewTypes(scanInfo.View),
},
testAllFields: true,
},
{
name: "Test getUIPrinter SilentPrinter",
args: args{
ctx: context.TODO(),
verboseMode: scanInfo.VerboseMode,
formatVersion: scanInfo.FormatVersion,
printAttack: scanInfo.PrintAttackTree,
viewType: cautils.ViewTypes(scanInfo.View),
loggerLevel: helpers.WarningLevel,
},
want: wantTypes{
structType: "*printer.SilentPrinter",
formatVersion: "",
verboseMode: false,
viewType: cautils.ViewTypes(""),
},
testAllFields: false,
},
wantFormatVersion := scanInfo.FormatVersion
wantVerboseMode := scanInfo.VerboseMode
wantViewType := cautils.ViewTypes(scanInfo.View)
got := getUIPrinter(scanInfo.VerboseMode, scanInfo.FormatVersion, scanInfo.PrintAttackTree, cautils.ViewTypes(scanInfo.View))
gotValue := reflect.ValueOf(got).Elem()
gotFormatVersion := gotValue.FieldByName("formatVersion").String()
gotVerboseMode := gotValue.FieldByName("verboseMode").Bool()
gotViewType := cautils.ViewTypes(gotValue.FieldByName("viewType").String())
if gotFormatVersion != wantFormatVersion {
t.Errorf("Got: %s, want: %s", gotFormatVersion, wantFormatVersion)
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
logger.L().SetLevel(tt.args.loggerLevel.String())
scanInfo := &cautils.ScanInfo{
FormatVersion: tt.args.formatVersion,
VerboseMode: tt.args.verboseMode,
PrintAttackTree: tt.args.printAttack,
View: string(tt.args.viewType),
}
got := GetUIPrinter(tt.args.ctx, scanInfo)
assert.Equal(t, tt.want.structType, reflect.TypeOf(got).String())
if !tt.testAllFields {
return
}
gotValue := reflect.ValueOf(got).Elem()
gotFormatVersion := gotValue.FieldByName("formatVersion").String()
gotVerboseMode := gotValue.FieldByName("verboseMode").Bool()
gotViewType := cautils.ViewTypes(gotValue.FieldByName("viewType").String())
if gotFormatVersion != tt.want.formatVersion {
t.Errorf("Got: %s, want: %s", gotFormatVersion, tt.want.formatVersion)
}
if gotVerboseMode != tt.want.verboseMode {
t.Errorf("Got: %t, want: %t", gotVerboseMode, tt.want.verboseMode)
}
if gotViewType != tt.want.viewType {
t.Errorf("Got: %v, want: %v", gotViewType, tt.want.viewType)
}
})
if gotVerboseMode != wantVerboseMode {
t.Errorf("Got: %t, want: %t", gotVerboseMode, wantVerboseMode)
}
}
func TestGetSensorHandler(t *testing.T) {
t.Parallel()
ctx := context.Background()
t.Run("should return mock sensor if not k8s interface is provided", func(t *testing.T) {
t.Parallel()
scanInfo := &cautils.ScanInfo{}
var k8s *k8sinterface.KubernetesApi
sensor := getHostSensorHandler(ctx, scanInfo, k8s)
require.NotNil(t, sensor)
_, isMock := sensor.(*hostsensorutils.HostSensorHandlerMock)
require.True(t, isMock)
})
t.Run("should return mock sensor if the sensor is not enabled", func(t *testing.T) {
t.Parallel()
scanInfo := &cautils.ScanInfo{}
k8s := &k8sinterface.KubernetesApi{}
sensor := getHostSensorHandler(ctx, scanInfo, k8s)
require.NotNil(t, sensor)
_, isMock := sensor.(*hostsensorutils.HostSensorHandlerMock)
require.True(t, isMock)
})
t.Run("should return mock sensor if the sensor is disabled", func(t *testing.T) {
t.Parallel()
falseFlag := cautils.NewBoolPtr(nil)
falseFlag.SetBool(false)
scanInfo := &cautils.ScanInfo{
HostSensorEnabled: falseFlag,
}
k8s := &k8sinterface.KubernetesApi{}
sensor := getHostSensorHandler(ctx, scanInfo, k8s)
require.NotNil(t, sensor)
_, isMock := sensor.(*hostsensorutils.HostSensorHandlerMock)
require.True(t, isMock)
})
t.Run("should return mock sensor if the sensor is enabled, but can't deploy (nil)", func(t *testing.T) {
t.Parallel()
falseFlag := cautils.NewBoolPtr(nil)
falseFlag.SetBool(true)
scanInfo := &cautils.ScanInfo{
HostSensorEnabled: falseFlag,
}
var k8s *k8sinterface.KubernetesApi
sensor := getHostSensorHandler(ctx, scanInfo, k8s)
require.NotNil(t, sensor)
_, isMock := sensor.(*hostsensorutils.HostSensorHandlerMock)
require.True(t, isMock)
})
// TODO(fredbi): need to share the k8s client mock to test a happy path / deployment failure path
if gotViewType != wantViewType {
t.Errorf("Got: %v, want: %v", gotViewType, wantViewType)
}
}

View File

@@ -1,7 +1,6 @@
package core
import (
"context"
"encoding/json"
"fmt"
"sort"
@@ -11,17 +10,16 @@ import (
metav1 "github.com/kubescape/kubescape/v2/core/meta/datastructures/v1"
"github.com/kubescape/kubescape/v2/core/pkg/resultshandling/printer"
v2 "github.com/kubescape/kubescape/v2/core/pkg/resultshandling/printer/v2"
"github.com/kubescape/kubescape/v2/core/pkg/resultshandling/printer/v2/prettyprinter/tableprinter/utils"
"github.com/olekukonko/tablewriter"
)
var listFunc = map[string]func(context.Context, *metav1.ListPolicies) ([]string, error){
var listFunc = map[string]func(*metav1.ListPolicies) ([]string, error){
"controls": listControls,
"frameworks": listFrameworks,
"exceptions": listExceptions,
}
var listFormatFunc = map[string]func(context.Context, string, []string){
var listFormatFunc = map[string]func(string, []string){
"pretty-print": prettyPrintListFormat,
"json": jsonListFormat,
}
@@ -33,16 +31,16 @@ func ListSupportActions() []string {
}
return commands
}
func (ks *Kubescape) List(ctx context.Context, listPolicies *metav1.ListPolicies) error {
func (ks *Kubescape) List(listPolicies *metav1.ListPolicies) error {
if policyListerFunc, ok := listFunc[listPolicies.Target]; ok {
policies, err := policyListerFunc(ctx, listPolicies)
policies, err := policyListerFunc(listPolicies)
if err != nil {
return err
}
sort.Strings(policies)
if listFormatFunction, ok := listFormatFunc[listPolicies.Format]; ok {
listFormatFunction(ctx, listPolicies.Target, policies)
listFormatFunction(listPolicies.Target, policies)
} else {
return fmt.Errorf("Invalid format \"%s\", Supported formats: 'pretty-print'/'json' ", listPolicies.Format)
}
@@ -52,26 +50,26 @@ func (ks *Kubescape) List(ctx context.Context, listPolicies *metav1.ListPolicies
return fmt.Errorf("unknown command to download")
}
func listFrameworks(ctx context.Context, listPolicies *metav1.ListPolicies) ([]string, error) {
func listFrameworks(listPolicies *metav1.ListPolicies) ([]string, error) {
tenant := getTenantConfig(&listPolicies.Credentials, "", "", getKubernetesApi()) // change k8sinterface
policyGetter := getPolicyGetter(ctx, nil, tenant.GetTenantEmail(), true, nil)
policyGetter := getPolicyGetter(nil, tenant.GetTenantEmail(), true, nil)
return listFrameworksNames(policyGetter), nil
}
func listControls(ctx context.Context, listPolicies *metav1.ListPolicies) ([]string, error) {
func listControls(listPolicies *metav1.ListPolicies) ([]string, error) {
tenant := getTenantConfig(&listPolicies.Credentials, "", "", getKubernetesApi()) // change k8sinterface
policyGetter := getPolicyGetter(ctx, nil, tenant.GetTenantEmail(), false, nil)
policyGetter := getPolicyGetter(nil, tenant.GetTenantEmail(), false, nil)
return policyGetter.ListControls()
}
func listExceptions(ctx context.Context, listPolicies *metav1.ListPolicies) ([]string, error) {
func listExceptions(listPolicies *metav1.ListPolicies) ([]string, error) {
// load tenant metav1
tenant := getTenantConfig(&listPolicies.Credentials, "", "", getKubernetesApi())
var exceptionsNames []string
ksCloudAPI := getExceptionsGetter(ctx, "", tenant.GetAccountID(), nil)
ksCloudAPI := getExceptionsGetter("", tenant.GetAccountID(), nil)
exceptions, err := ksCloudAPI.GetExceptions("")
if err != nil {
return exceptionsNames, err
@@ -82,68 +80,44 @@ func listExceptions(ctx context.Context, listPolicies *metav1.ListPolicies) ([]s
return exceptionsNames, nil
}
func prettyPrintListFormat(ctx context.Context, targetPolicy string, policies []string) {
func prettyPrintListFormat(targetPolicy string, policies []string) {
if targetPolicy == "controls" {
prettyPrintControls(ctx, policies)
prettyPrintControls(policies)
return
}
policyTable := tablewriter.NewWriter(printer.GetWriter(ctx, ""))
policyTable.SetAutoWrapText(true)
header := fmt.Sprintf("Supported %s", targetPolicy)
policyTable := tablewriter.NewWriter(printer.GetWriter(""))
policyTable.SetAutoWrapText(true)
policyTable.SetHeader([]string{header})
policyTable.SetHeaderLine(true)
policyTable.SetRowLine(true)
policyTable.SetAlignment(tablewriter.ALIGN_CENTER)
policyTable.SetUnicodeHV(tablewriter.Regular, tablewriter.Regular)
data := v2.Matrix{}
controlRows := generatePolicyRows(policies)
var headerColors []tablewriter.Colors
for range controlRows[0] {
headerColors = append(headerColors, tablewriter.Colors{tablewriter.Bold, tablewriter.FgHiYellowColor})
}
policyTable.SetHeaderColor(headerColors...)
data = append(data, controlRows...)
policyTable.SetAlignment(tablewriter.ALIGN_CENTER)
policyTable.AppendBulk(data)
policyTable.Render()
}
func jsonListFormat(_ context.Context, _ string, policies []string) {
func jsonListFormat(targetPolicy string, policies []string) {
j, _ := json.MarshalIndent(policies, "", " ")
fmt.Printf("%s\n", j)
}
func prettyPrintControls(ctx context.Context, policies []string) {
controlsTable := tablewriter.NewWriter(printer.GetWriter(ctx, ""))
controlsTable.SetAutoWrapText(false)
func prettyPrintControls(policies []string) {
controlsTable := tablewriter.NewWriter(printer.GetWriter(""))
controlsTable.SetAutoWrapText(true)
controlsTable.SetHeader([]string{"Control ID", "Control Name", "Docs", "Frameworks"})
controlsTable.SetHeaderLine(true)
controlsTable.SetRowLine(true)
controlsTable.SetUnicodeHV(tablewriter.Regular, tablewriter.Regular)
data := v2.Matrix{}
controlRows := generateControlRows(policies)
short := utils.CheckShortTerminalWidth(controlRows, []string{"Control ID", "Control Name", "Docs", "Frameworks"})
if short {
controlsTable.SetAutoWrapText(false)
controlsTable.SetHeader([]string{"Controls"})
controlRows = shortFormatControlRows(controlRows)
} else {
controlsTable.SetHeader([]string{"Control ID", "Control Name", "Docs", "Frameworks"})
}
var headerColors []tablewriter.Colors
for range controlRows[0] {
headerColors = append(headerColors, tablewriter.Colors{tablewriter.Bold, tablewriter.FgHiYellowColor})
}
controlsTable.SetHeaderColor(headerColors...)
data := v2.Matrix{}
data = append(data, controlRows...)
controlsTable.AppendBulk(data)
@@ -159,7 +133,7 @@ func generateControlRows(policies []string) [][]string {
docs := cautils.GetControlLink(id)
currentRow := []string{id, control, docs, strings.Replace(framework, " ", "\n", -1)}
currentRow := []string{id, control, docs, framework}
rows = append(rows, currentRow)
}
@@ -176,11 +150,3 @@ func generatePolicyRows(policies []string) [][]string {
}
return rows
}
func shortFormatControlRows(controlRows [][]string) [][]string {
rows := [][]string{}
for _, controlRow := range controlRows {
rows = append(rows, []string{fmt.Sprintf("Control ID"+strings.Repeat(" ", 3)+": %+v\nControl Name"+strings.Repeat(" ", 1)+": %+v\nDocs"+strings.Repeat(" ", 9)+": %+v\nFrameworks"+strings.Repeat(" ", 3)+": %+v", controlRow[0], controlRow[1], controlRow[2], strings.Replace(controlRow[3], "\n", " ", -1))})
}
return rows
}

View File

@@ -1,14 +1,14 @@
package core
import (
"context"
"fmt"
apisv1 "github.com/kubescape/opa-utils/httpserver/apis/v1"
"github.com/kubescape/k8s-interface/k8sinterface"
"github.com/kubescape/go-logger"
"github.com/kubescape/go-logger/helpers"
"github.com/kubescape/go-logger/iconlogger"
"github.com/kubescape/k8s-interface/k8sinterface"
"github.com/kubescape/k8s-interface/workloadinterface"
"github.com/kubescape/kubescape/v2/core/cautils"
"github.com/kubescape/kubescape/v2/core/cautils/getter"
"github.com/kubescape/kubescape/v2/core/pkg/hostsensorutils"
@@ -19,10 +19,6 @@ import (
"github.com/kubescape/kubescape/v2/core/pkg/resultshandling"
"github.com/kubescape/kubescape/v2/core/pkg/resultshandling/printer"
"github.com/kubescape/kubescape/v2/core/pkg/resultshandling/reporter"
"github.com/kubescape/kubescape/v2/pkg/imagescan"
apisv1 "github.com/kubescape/opa-utils/httpserver/apis/v1"
"go.opentelemetry.io/otel"
"golang.org/x/exp/slices"
"github.com/kubescape/opa-utils/resources"
)
@@ -31,27 +27,25 @@ type componentInterfaces struct {
tenantConfig cautils.ITenantConfig
resourceHandler resourcehandler.IResourceHandler
report reporter.IReport
outputPrinters []printer.IPrinter
uiPrinter printer.IPrinter
hostSensorHandler hostsensorutils.IHostSensor
outputPrinters []printer.IPrinter
}
func getInterfaces(ctx context.Context, scanInfo *cautils.ScanInfo) componentInterfaces {
ctx, span := otel.Tracer("").Start(ctx, "setup interfaces")
defer span.End()
func getInterfaces(scanInfo *cautils.ScanInfo) componentInterfaces {
// ================== setup k8s interface object ======================================
var k8s *k8sinterface.KubernetesApi
if scanInfo.GetScanningContext() == cautils.ContextCluster {
k8s = getKubernetesApi()
if k8s == nil {
logger.L().Ctx(ctx).Fatal("failed connecting to Kubernetes cluster")
logger.L().Fatal("failed connecting to Kubernetes cluster")
}
}
// ================== setup tenant object ======================================
ctxTenant, spanTenant := otel.Tracer("").Start(ctx, "setup tenant")
tenantConfig := getTenantConfig(&scanInfo.Credentials, k8sinterface.GetContextName(), scanInfo.CustomClusterName, k8s)
tenantConfig := getTenantConfig(&scanInfo.Credentials, scanInfo.KubeContext, scanInfo.CustomClusterName, k8s)
// Set submit behavior AFTER loading tenant config
setSubmitBehavior(scanInfo, tenantConfig)
@@ -59,46 +53,58 @@ func getInterfaces(ctx context.Context, scanInfo *cautils.ScanInfo) componentInt
if scanInfo.Submit {
// submit - Create tenant & Submit report
if err := tenantConfig.SetTenant(); err != nil {
logger.L().Ctx(ctxTenant).Error(err.Error())
logger.L().Error(err.Error())
}
if scanInfo.OmitRawResources {
logger.L().Ctx(ctx).Warning("omit-raw-resources flag will be ignored in submit mode")
logger.L().Warning("omit-raw-resources flag will be ignored in submit mode")
}
}
spanTenant.End()
// ================== version testing ======================================
v := cautils.NewIVersionCheckHandler(ctx)
v.CheckLatestVersion(ctx, cautils.NewVersionCheckRequest(cautils.BuildNumber, policyIdentifierIdentities(scanInfo.PolicyIdentifier), "", cautils.ScanningContextToScanningScope(scanInfo.GetScanningContext())))
v := cautils.NewIVersionCheckHandler()
v.CheckLatestVersion(cautils.NewVersionCheckRequest(cautils.BuildNumber, policyIdentifierIdentities(scanInfo.PolicyIdentifier), "", cautils.ScanningContextToScanningScope(scanInfo.GetScanningContext())))
// ================== setup host scanner object ======================================
ctxHostScanner, spanHostScanner := otel.Tracer("").Start(ctx, "setup host scanner")
hostSensorHandler := getHostSensorHandler(ctx, scanInfo, k8s)
if err := hostSensorHandler.Init(ctxHostScanner); err != nil {
logger.L().Ctx(ctxHostScanner).Error("failed to init host scanner", helpers.Error(err))
hostSensorHandler = hostsensorutils.NewHostSensorHandlerMock()
hostSensorHandler := getHostSensorHandler(scanInfo, k8s)
if err := hostSensorHandler.Init(); err != nil {
logger.L().Error("failed to init host scanner", helpers.Error(err))
hostSensorHandler = &hostsensorutils.HostSensorHandlerMock{}
}
// excluding hostsensor namespace
if len(scanInfo.IncludeNamespaces) == 0 && hostSensorHandler.GetNamespace() != "" {
scanInfo.ExcludedNamespaces = fmt.Sprintf("%s,%s", scanInfo.ExcludedNamespaces, hostSensorHandler.GetNamespace())
}
spanHostScanner.End()
// ================== setup registry adaptors ======================================
registryAdaptors, _ := resourcehandler.NewRegistryAdaptors()
registryAdaptors, err := resourcehandler.NewRegistryAdaptors()
if err != nil {
logger.L().Error("failed to initialize registry adaptors", helpers.Error(err))
}
// ================== setup resource collector object ======================================
resourceHandler := getResourceHandler(ctx, scanInfo, tenantConfig, k8s, hostSensorHandler, registryAdaptors)
resourceHandler := getResourceHandler(scanInfo, tenantConfig, k8s, hostSensorHandler, registryAdaptors)
// ================== setup reporter & printer objects ======================================
// reporting behavior - setup reporter
reportHandler := getReporter(ctx, tenantConfig, scanInfo.ScanID, scanInfo.Submit, scanInfo.FrameworkScan, *scanInfo)
reportHandler := getReporter(tenantConfig, scanInfo.ScanID, scanInfo.Submit, scanInfo.FrameworkScan, scanInfo.GetScanningContext())
// setup printers
outputPrinters := GetOutputPrinters(scanInfo, ctx)
formats := scanInfo.Formats()
uiPrinter := GetUIPrinter(ctx, scanInfo)
outputPrinters := make([]printer.IPrinter, 0)
for _, format := range formats {
printerHandler := resultshandling.NewPrinter(format, scanInfo.FormatVersion, scanInfo.PrintAttackTree, scanInfo.VerboseMode, cautils.ViewTypes(scanInfo.View))
printerHandler.SetWriter(scanInfo.Output)
outputPrinters = append(outputPrinters, printerHandler)
}
uiPrinter := getUIPrinter(scanInfo.VerboseMode, scanInfo.FormatVersion, scanInfo.PrintAttackTree, cautils.ViewTypes(scanInfo.View))
// ================== return interface ======================================
@@ -112,27 +118,13 @@ func getInterfaces(ctx context.Context, scanInfo *cautils.ScanInfo) componentInt
}
}
func GetOutputPrinters(scanInfo *cautils.ScanInfo, ctx context.Context) []printer.IPrinter {
formats := scanInfo.Formats()
outputPrinters := make([]printer.IPrinter, 0)
for _, format := range formats {
printerHandler := resultshandling.NewPrinter(ctx, format, scanInfo.FormatVersion, scanInfo.PrintAttackTree, scanInfo.VerboseMode, cautils.ViewTypes(scanInfo.View))
printerHandler.SetWriter(ctx, scanInfo.Output)
outputPrinters = append(outputPrinters, printerHandler)
}
return outputPrinters
}
func (ks *Kubescape) Scan(ctx context.Context, scanInfo *cautils.ScanInfo) (*resultshandling.ResultsHandler, error) {
ctxInit, spanInit := otel.Tracer("").Start(ctx, "initialization")
logger.InitLogger(iconlogger.LoggerName)
logger.L().Start("Kubescape scanner initializing")
func (ks *Kubescape) Scan(scanInfo *cautils.ScanInfo) (*resultshandling.ResultsHandler, error) {
logger.L().Info("Kubescape scanner starting")
// ===================== Initialization =====================
scanInfo.Init(ctxInit) // initialize scan info
scanInfo.Init() // initialize scan info
interfaces := getInterfaces(ctxInit, scanInfo)
interfaces := getInterfaces(scanInfo)
cautils.ClusterName = interfaces.tenantConfig.GetContextName() // TODO - Deprecated
cautils.CustomerGUID = interfaces.tenantConfig.GetAccountID() // TODO - Deprecated
@@ -142,10 +134,10 @@ func (ks *Kubescape) Scan(ctx context.Context, scanInfo *cautils.ScanInfo) (*res
downloadReleasedPolicy := getter.NewDownloadReleasedPolicy() // download config inputs from github release
// set policy getter only after setting the customerGUID
scanInfo.Getters.PolicyGetter = getPolicyGetter(ctxInit, scanInfo.UseFrom, interfaces.tenantConfig.GetTenantEmail(), scanInfo.FrameworkScan, downloadReleasedPolicy)
scanInfo.Getters.ControlsInputsGetter = getConfigInputsGetter(ctxInit, scanInfo.ControlsInputs, interfaces.tenantConfig.GetAccountID(), downloadReleasedPolicy)
scanInfo.Getters.ExceptionsGetter = getExceptionsGetter(ctxInit, scanInfo.UseExceptions, interfaces.tenantConfig.GetAccountID(), downloadReleasedPolicy)
scanInfo.Getters.AttackTracksGetter = getAttackTracksGetter(ctxInit, scanInfo.AttackTracks, interfaces.tenantConfig.GetAccountID(), downloadReleasedPolicy)
scanInfo.Getters.PolicyGetter = getPolicyGetter(scanInfo.UseFrom, interfaces.tenantConfig.GetTenantEmail(), scanInfo.FrameworkScan, downloadReleasedPolicy)
scanInfo.Getters.ControlsInputsGetter = getConfigInputsGetter(scanInfo.ControlsInputs, interfaces.tenantConfig.GetAccountID(), downloadReleasedPolicy)
scanInfo.Getters.ExceptionsGetter = getExceptionsGetter(scanInfo.UseExceptions, interfaces.tenantConfig.GetAccountID(), downloadReleasedPolicy)
scanInfo.Getters.AttackTracksGetter = getAttackTracksGetter(scanInfo.AttackTracks, interfaces.tenantConfig.GetAccountID(), downloadReleasedPolicy)
// TODO - list supported frameworks/controls
if scanInfo.ScanAll {
@@ -155,62 +147,35 @@ func (ks *Kubescape) Scan(ctx context.Context, scanInfo *cautils.ScanInfo) (*res
// remove host scanner components
defer func() {
if err := interfaces.hostSensorHandler.TearDown(); err != nil {
logger.L().Ctx(ctx).StopError("Failed to tear down host scanner", helpers.Error(err))
logger.L().Error("failed to tear down host scanner", helpers.Error(err))
}
}()
logger.L().StopSuccess("Initialized scanner")
resultsHandling := resultshandling.NewResultsHandler(interfaces.report, interfaces.outputPrinters, interfaces.uiPrinter)
// ===================== policies =====================
ctxPolicies, spanPolicies := otel.Tracer("").Start(ctxInit, "policies")
policyHandler := policyhandler.NewPolicyHandler()
scanData, err := policyHandler.CollectPolicies(ctxPolicies, scanInfo.PolicyIdentifier, scanInfo)
// ===================== policies & resources =====================
policyHandler := policyhandler.NewPolicyHandler(interfaces.resourceHandler)
scanData, err := policyHandler.CollectResources(scanInfo.PolicyIdentifier, scanInfo)
if err != nil {
spanInit.End()
return resultsHandling, err
}
spanPolicies.End()
// ===================== resources =====================
ctxResources, spanResources := otel.Tracer("").Start(ctxInit, "resources")
err = resourcehandler.CollectResources(ctxResources, interfaces.resourceHandler, scanInfo.PolicyIdentifier, scanData, cautils.NewProgressHandler(""), scanInfo)
if err != nil {
spanInit.End()
return resultsHandling, err
}
spanResources.End()
spanInit.End()
// ========================= opa testing =====================
ctxOpa, spanOpa := otel.Tracer("").Start(ctx, "opa testing")
defer spanOpa.End()
deps := resources.NewRegoDependenciesData(k8sinterface.GetK8sConfig(), interfaces.tenantConfig.GetContextName())
reportResults := opaprocessor.NewOPAProcessor(scanData, deps)
if err := reportResults.ProcessRulesListener(ctxOpa, cautils.NewProgressHandler(""), scanInfo); err != nil {
if err := reportResults.ProcessRulesListenner(); err != nil {
// TODO - do something
return resultsHandling, fmt.Errorf("%w", err)
}
// ======================== prioritization ===================
if scanInfo.PrintAttackTree || isPrioritizationScanType(scanInfo.ScanType) {
_, spanPrioritization := otel.Tracer("").Start(ctxOpa, "prioritization")
if priotizationHandler, err := resourcesprioritization.NewResourcesPrioritizationHandler(ctxOpa, scanInfo.Getters.AttackTracksGetter, scanInfo.PrintAttackTree); err != nil {
logger.L().Ctx(ctx).Warning("failed to get attack tracks, this may affect the scanning results", helpers.Error(err))
} else if err := priotizationHandler.PrioritizeResources(scanData); err != nil {
return resultsHandling, fmt.Errorf("%w", err)
}
if err == nil && isPrioritizationScanType(scanInfo.ScanType) {
scanData.SetTopWorkloads()
}
spanPrioritization.End()
if priotizationHandler, err := resourcesprioritization.NewResourcesPrioritizationHandler(scanInfo.Getters.AttackTracksGetter, scanInfo.PrintAttackTree); err != nil {
logger.L().Warning("failed to get attack tracks, this may affect the scanning results", helpers.Error(err))
} else if err := priotizationHandler.PrioritizeResources(scanData); err != nil {
return resultsHandling, fmt.Errorf("%w", err)
}
if scanInfo.ScanImages {
scanImages(scanInfo.ScanType, scanData, ctx, resultsHandling)
}
// ========================= results handling =====================
resultsHandling.SetData(scanData)
@@ -220,62 +185,3 @@ func (ks *Kubescape) Scan(ctx context.Context, scanInfo *cautils.ScanInfo) (*res
return resultsHandling, nil
}
func scanImages(scanType cautils.ScanTypes, scanData *cautils.OPASessionObj, ctx context.Context, resultsHandling *resultshandling.ResultsHandler) {
imagesToScan := []string{}
if scanType == cautils.ScanTypeWorkload {
containers, err := workloadinterface.NewWorkloadObj(scanData.SingleResourceScan.GetObject()).GetContainers()
if err != nil {
logger.L().Error("failed to get containers", helpers.Error(err))
return
}
for _, container := range containers {
if !slices.Contains(imagesToScan, container.Image) {
imagesToScan = append(imagesToScan, container.Image)
}
}
} else {
for _, workload := range scanData.AllResources {
containers, err := workloadinterface.NewWorkloadObj(workload.GetObject()).GetContainers()
if err != nil {
logger.L().Error(fmt.Sprintf("failed to get containers for kind: %s, name: %s, namespace: %s", workload.GetKind(), workload.GetName(), workload.GetNamespace()), helpers.Error(err))
continue
}
for _, container := range containers {
if !slices.Contains(imagesToScan, container.Image) {
imagesToScan = append(imagesToScan, container.Image)
}
}
}
}
dbCfg, _ := imagescan.NewDefaultDBConfig()
svc := imagescan.NewScanService(dbCfg)
for _, img := range imagesToScan {
logger.L().Start("Scanning", helpers.String("image", img))
if err := scanSingleImage(ctx, img, svc, resultsHandling); err != nil {
logger.L().StopError("failed to scan", helpers.String("image", img), helpers.Error(err))
}
logger.L().StopSuccess("Scanned successfully", helpers.String("image", img))
}
}
func scanSingleImage(ctx context.Context, img string, svc imagescan.Service, resultsHandling *resultshandling.ResultsHandler) error {
scanResults, err := svc.Scan(ctx, img, imagescan.RegistryCredentials{})
if err != nil {
return err
}
resultsHandling.ImageScanData = append(resultsHandling.ImageScanData, cautils.ImageScanData{
Image: img,
PresenterConfig: scanResults,
})
return nil
}
func isPrioritizationScanType(scanType cautils.ScanTypes) bool {
return scanType == cautils.ScanTypeCluster || scanType == cautils.ScanTypeRepo
}

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