Compare commits

..

41 Commits

Author SHA1 Message Date
Vlad Klokun
b0f65cce1d Merge pull request #959 from suhasgumma/fix-command
fix: keep user formatting when autofixing
2023-01-11 20:13:16 +02:00
Vlad Klokun
0e8b2f976d tests: extend test cases for autofix inserts
This change re-organizes the test cases for inserts performed by the
autofixing feature.
2023-01-11 19:50:57 +02:00
Vlad Klokun
f1d646ac97 tests: show diffs when comparing autofixes
This change refactors the TestApplyFixKeepsFormatting test to use
assert.Equalf so it will display a convenient diff between the expected
and actual fixing result.
2023-01-11 18:44:06 +02:00
Vlad Klokun
5f668037a7 tests: test fixing close to newline-separated keys in hybrid scenarios 2023-01-11 18:43:14 +02:00
Vlad Klokun
c448c97463 tests: test autofixing files with comments between fields 2023-01-10 19:43:25 +02:00
Vlad Klokun
da3bc8e8ea tests: test autofixing indented lists in hybrid scenarios 2023-01-10 19:43:09 +02:00
Vlad Klokun
2fce139a9a tests: re-organize autofixing unit tests
This change:
- Changes test data naming convention to be lexicographically sortable
  and have input and expected data side-by-side.
- Executes each test case in a separate run.
2023-01-10 19:43:04 +02:00
suhasgumma
85d2f5c250 Minor Changes to Improve Code Quality 2022-12-27 13:02:03 +05:30
suhasgumma
c3771eec7e Remove redundant functions and clean code after refactoring 2022-12-16 15:57:23 +05:30
suhasgumma
76d2154152 All the minor Changes 2022-12-16 12:56:40 +05:30
suhasgumma
fa5e7fef23 Restructure Code to Improve Code Quality 2022-12-16 03:25:13 +05:30
suhasgumma
38d2696058 Break updateFileFixInfo function into getResourceFileFix and addResourceFileFix functions 2022-12-15 19:14:52 +05:30
suhasgumma
e9a8ffbda9 Add abstraction while adding contentToAdd and linesToBeRemoved to fileFixInfo 2022-12-15 17:03:54 +05:30
suhasgumma
0d76fffa48 Don't export structs that are not needed outside fixhandler package 2022-12-15 11:54:00 +05:30
suhasgumma
218c77f3ae Delete .DS_Store files that are added by mistake 2022-12-15 11:27:14 +05:30
suhasgumma
89fd7eb439 Move Test files to tesdata directory 2022-12-15 11:19:04 +05:30
suhasgumma
8079f9ae7d Improve Code Readability 2022-12-13 15:19:26 +05:30
suhasgumma
f9a26b7a95 Added Test cases for multiple resources in the same YAML file 2022-12-12 23:30:37 +05:30
suhasgumma
663401d908 Dealing with Insertion and Removal at last line of a Resource 2022-12-12 23:09:13 +05:30
suhasgumma
926790f49d Dealing with Multiple Resources in a single YAML file 2022-12-12 18:42:59 +05:30
suhasgumma
566b7c29c1 Simplify dealing with comments and empty lines at the top 2022-12-11 18:47:52 +05:30
suhasgumma
af5cdefc5f Improve Readability of code 2022-12-11 16:35:40 +05:30
suhasgumma
36b7b8e2ac Replace Single Line Sequence Node 2022-12-11 15:30:51 +05:30
suhasgumma
17c52bd0ae Handle Single Line Sequence Node in Replacement 2022-12-10 18:24:06 +05:30
suhasgumma
e02086e90c Handle Single Line Sequence Node in Removal 2022-12-10 18:11:17 +05:30
suhasgumma
baf62887b9 Handle Single Line Sequence Node 2022-12-10 17:29:04 +05:30
suhasgumma
99fa81e411 Fix Bugs Remove Scenario 2022-12-10 12:00:15 +05:30
suhasgumma
f64200f42f Small Changes 2022-12-09 23:44:04 +05:30
suhasgumma
f72cb215d7 Adjust Content Line 2022-12-09 22:45:55 +05:30
suhasgumma
fa03a9dae3 Fixed: Comments and empty lines at the head are excluded 2022-12-09 18:55:31 +05:30
suhasgumma
48516b891f unit tests 2022-12-09 15:31:09 +05:30
suhasgumma
252a564552 Remove, Replace and Hybrid Scenarios 2022-12-09 12:23:51 +05:30
suhasgumma
30e5b9b57d Insert Scenarios 2022-12-09 02:29:14 +05:30
suhasgumma
7fcfa27d9a Initial Implementation 2022-12-08 23:06:05 +05:30
suhasgumma
4b898b0075 Initial Implementation 2022-12-08 22:56:53 +05:30
Suhas Gumma
f3665866af import "yqlib" properly
Co-authored-by: Vlad Klokun <vladklokun@users.noreply.github.com>
2022-11-30 01:31:49 +05:30
Suhas Gumma
a7989bbe76 Update core/pkg/fixhandler/datastructures.go
Co-authored-by: Vlad Klokun <vladklokun@users.noreply.github.com>
2022-11-30 01:30:02 +05:30
suhasgumma
5ce69a750d Fix git directory relative path 2022-11-29 18:19:49 +05:30
suhasgumma
2b61989073 Fix local directory relative path 2022-11-29 18:15:30 +05:30
suhasgumma
be33054973 Possible Solution to Indentation Issue 2022-11-23 22:54:59 +05:30
Amir Malka
4b9bd5f3ae Initial implementation of fix command (#898)
* Fix command initial implementation
2022-11-07 14:57:17 +02:00
237 changed files with 3163 additions and 20804 deletions

BIN
.DS_Store vendored

Binary file not shown.

2
.gitattributes vendored
View File

@@ -1,2 +0,0 @@
# Keep CRLF newlines in appropriate test files to have reproducible tests
core/pkg/fixhandler/testdata/inserts/*-crlf-newlines.yaml text eol=crlf

View File

@@ -2,32 +2,33 @@
name: Bug report
about: Create a report to help us improve
title: ''
labels: 'bug'
labels: ''
assignees: ''
---
# Description
<!-- A clear and concise description of what the bug is. -->
# Describe the bug
A clear and concise description of what the bug is.
# Environment
OS: ` ` <!-- the OS + version youre running Kubescape on, e.g Ubuntu 22.04 LTS -->
Version: ` ` <!-- the version that Kubescape reports when you run `kubescape version` -->
OS: the OS + version youre running Kubescape on, e.g Ubuntu 22.04 LTS
Version: the version that Kubescape reports when you run `kubescape version`
```
Your current version is:
```
# Steps To Reproduce
<!--
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
-->
# Expected behavior
<!-- A clear and concise description of what you expected to happen. -->
A clear and concise description of what you expected to happen.
# Actual Behavior
<!-- A clear and concise description of what happened. If applicable, add screenshots to help explain your problem. -->
A clear and concise description of what happened. If applicable, add screenshots to help explain your problem.
# Additional context
<!-- Add any other context about the problem here. -->
Add any other context about the problem here.

View File

@@ -2,23 +2,18 @@
name: Feature request
about: Suggest an idea for this project
title: ''
labels: 'feature'
labels: ''
assignees: ''
---
**Is your feature request related to a problem? Please describe.**</br>
> A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
## Overview
<!-- A brief overview of the related current state -->
**Describe the solution you'd like.**</br>
> A clear and concise description of what you want to happen.
## Problem
<!-- A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] -->
**Describe alternatives you've considered.**</br>
> A clear and concise description of any alternative solutions or features you've considered.
## Solution
<!-- A clear and concise description of what you want to happen. -->
## Alternatives
<!-- A clear and concise description of any alternative solutions or features you've considered. -->
## Additional context
<!-- Add any other context or screenshots about the feature request here. -->
**Additional context.**</br>
> Add any other context or screenshots about the feature request here.

View File

@@ -1,39 +1,13 @@
## Overview
<!-- Please provide a brief overview of the changes made in this pull request. e.g. current behavior/future behavior -->
## Describe your changes
<!--
## Additional Information
## Screenshots - If Any (Optional)
> Any additional information that may be useful for reviewers to know
-->
## This PR fixes:
<!--
## How to Test
* Resolved #
> Please provide instructions on how to test the changes made in this pull request
-->
<!--
## Examples/Screenshots
> Here you add related screenshots
-->
<!--
## Related issues/PRs:
Here you add related issues and PRs.
If this resolved an issue, write "Resolved #<issue number>
e.g. If this PR resolves issues 1 and 2, it should look as follows:
* Resolved #1
* Resolved #2
-->
<!--
## Checklist before requesting a review
put an [x] in the box to get it checked
<!-- put an [x] in the box to get it checked -->
- [ ] My code follows the style guidelines of this project
- [ ] I have commented on my code, particularly in hard-to-understand areas
@@ -41,4 +15,4 @@ 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,37 +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: |
SUB='-rc'
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,33 +0,0 @@
name: 00-pr_scanner
on:
pull_request:
types: [opened, reopened, synchronize, ready_for_review]
branches:
- 'master'
- 'main'
- 'dev'
paths-ignore:
- '**.yaml'
- '**.md'
- '**.sh'
- 'website/*'
- 'examples/*'
- 'docs/*'
- 'build/*'
- '.github/*'
concurrency:
group: ${{ github.head_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

View File

@@ -1,57 +0,0 @@
name: 01-code_review_approved
on:
pull_request_review:
types: [submitted]
branches:
- 'master'
- 'main'
paths-ignore:
- '**.yaml'
- '**.md'
- '**.sh'
- 'website/*'
- 'examples/*'
- 'docs/*'
- 'build/*'
- '.github/*'
concurrency:
group: code-review-approved
cancel-in-progress: true
jobs:
binary-build:
if: ${{ github.event.review.state == 'approved' &&
contains( github.event.pull_request.labels.*.name, 'trigger-integration-test') &&
github.event.pull_request.base.ref == 'master' }} ## run only if labeled as "trigger-integration-test" and base branch is master
uses: ./.github/workflows/b-binary-build-and-e2e-tests.yaml
with:
COMPONENT_NAME: kubescape
CGO_ENABLED: 1
GO111MODULE: ""
GO_VERSION: "1.19"
RELEASE: ""
CLIENT: test
secrets: inherit
merge-to-master:
needs: binary-build
env:
GH_PERSONAL_ACCESS_TOKEN: ${{ secrets.GH_PERSONAL_ACCESS_TOKEN }}
if: ${{ (github.event.review.state == 'approved' && github.event.pull_request.base.ref == 'master') &&
(always() && (contains(needs.*.result, 'success') || contains(needs.*.result, 'skipped')) && !(contains(needs.*.result, 'failure')) && !(contains(needs.*.result, 'cancelled'))) }}
runs-on: ubuntu-latest
steps:
- name: merge-to-master
if: ${{ env.GH_PERSONAL_ACCESS_TOKEN }}
uses: pascalgn/automerge-action@v0.15.5
env:
GITHUB_TOKEN: "${{ secrets.GH_PERSONAL_ACCESS_TOKEN }}"
MERGE_COMMIT_MESSAGE: "Merge to master - PR number: {pullRequest.number}"
MERGE_ERROR_FAIL: "true"
MERGE_METHOD: "merge"
MERGE_LABELS: ""
UPDATE_LABELS: ""

View File

@@ -1,69 +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@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.19"
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-krew-plugin:
# name: Publish Krew plugin
# runs-on: ubuntu-latest
# if: "${{ github.repository_owner }} == kubescape"
# needs: create-release
# steps:
# - uses: actions/checkout@v3
# with:
# submodules: recursive
# - name: Update new version in krew-index
# uses: rajatjindal/krew-release-bot@v0.0.43
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,52 +0,0 @@
# Kubescape workflows
Tag terminology: `v<major>.<minor>.<patch>`
## Developing process
Kubescape's main branch is `main`, any PR will be opened against the main branch.
### Opening a PR
When a user opens a PR, this will trigger some basic tests (units, license, etc.)
### Reviewing a PR
The reviewer/maintainer of a PR will decide whether the PR introduces changes that require running the E2E system tests. If so, the reviewer will add the `trigger-integration-test` label.
### Approving a PR
Once a maintainer approves the PR, if the `trigger-integration-test` label was added to the PR, the GitHub actions will trigger the system test. The PR will be merged only after the system tests passed successfully. If the label was not added, the PR can be merged.
### Merging a PR
The code is merged, no other actions are needed
## Release process
Every two weeks, we will create a new tag by bumping the minor version, this will create the release and publish the artifacts.
If we are introducing breaking changes, we will update the `major` version instead.
When we wish to push a hot-fix/feature within the two weeks, we will bump the `patch`.
### Creating a new tag
Every two weeks or upon the decision of the maintainers, a maintainer can create a tag.
The tag should look as follows: `v<A>.<B>.<C>-rc.D` (release candidate).
When creating a tag, GitHub will trigger the following actions:
1. Basic tests - unit tests, license, etc.
2. System tests (integration tests). If the tests fail, the actions will stop here.
3. Create a new tag: `v<A>.<B>.<C>` (same tag just without the `rc` suffix)
4. Create a release
5. Publish artifacts
6. Build and publish the docker image (this is meanwhile until we separate the microservice code from the LCI codebase)
## Additional Information
The "callers" have the alphabetic prefix and the "executes" have the numeric prefix
## Screenshot
<img width="1469" alt="image" src="https://user-images.githubusercontent.com/64066841/212532727-e82ec9e7-263d-408b-b4b0-a8c943f0109a.png">

View File

@@ -1,177 +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
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@v3
with:
fetch-depth: 0
submodules: recursive
- uses: actions/setup-go@v3 # Install go because go-licenses use it
name: Installing go
with:
go-version: '1.19'
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@master
with:
args: -v --all-policies
env:
GITHUB_PUSH_BEFORE_SHA: ${{ github.event.before }}
GITHUB_PUSH_BASE_SHA: ${{ github.event.base }}
GITHUB_PULL_BASE_SHA: ${{ github.event.pull_request.base.sha }}
GITHUB_DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
GITGUARDIAN_API_KEY: ${{ secrets.GITGUARDIAN_API_KEY }}
- name: Scanning - Vulnerabilities (Snyk)
if: ${{ env.SNYK_TOKEN }}
id: vulnerabilities-scan
continue-on-error: true
uses: snyk/actions/golang@master
with:
command: test --all-projects
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
- name: Comment results to PR
continue-on-error: true # Warning: This might break opening PRs from forks
uses: 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'
basic-tests:
needs: scanners
name: Create cross-platform build
runs-on: ${{ matrix.os }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
RELEASE: ${{ inputs.RELEASE }}
CLIENT: ${{ inputs.CLIENT }}
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-latest'
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: ${{ inputs.CLIENT }}
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/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/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:
version: latest
args: --timeout 10m --build-tags=static
only-new-issues: true

View File

@@ -1,275 +0,0 @@
name: b-binary-build-and-e2e-tests
on:
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.19"
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"
]'
jobs:
check-secret:
name: secret-validator
runs-on: ubuntu-latest
outputs:
is-secret-set: ${{ steps.check-secret-set.outputs.is-secret-set }}
steps:
- name: check if the necessary secrets are set in github secrets
id: check-secret-set
env:
CUSTOMER: ${{ secrets.CUSTOMER }}
USERNAME: ${{ secrets.USERNAME }}
PASSWORD: ${{ secrets.PASSWORD }}
CLIENT_ID: ${{ secrets.CLIENT_ID_PROD }}
SECRET_KEY: ${{ secrets.SECRET_KEY_PROD }}
REGISTRY_USERNAME: ${{ secrets.REGISTRY_USERNAME }}
REGISTRY_PASSWORD: ${{ secrets.REGISTRY_PASSWORD }}
run: |
echo "is-secret-set=${{ env.CUSTOMER != '' &&
env.USERNAME != '' &&
env.PASSWORD != '' &&
env.CLIENT_ID != '' &&
env.SECRET_KEY != '' &&
env.REGISTRY_USERNAME != '' &&
env.REGISTRY_PASSWORD != ''
}}" >> $GITHUB_OUTPUT
binary-build:
name: Create cross-platform build
outputs:
TEST_NAMES: ${{ steps.export_tests_to_env.outputs.TEST_NAMES }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-20.04, macos-latest, windows-latest]
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
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-
- uses: actions/setup-go@v3
name: Installing go
with:
go-version: ${{ inputs.GO_VERSION }}
cache: true
- 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: ${{ inputs.CLIENT }}
CGO_ENABLED: ${{ inputs.CGO_ENABLED }}
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/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/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:
version: latest
args: --timeout 10m --build-tags=static
only-new-issues: true
- id: export_tests_to_env
name: set test name
run: |
echo "TEST_NAMES=$input" >> $GITHUB_OUTPUT
env:
input: ${{ inputs.BINARY_TESTS }}
- uses: actions/upload-artifact@v3.1.1
name: Upload artifact (Linux)
if: matrix.os == 'ubuntu-20.04'
with:
name: kubescape-ubuntu-latest
path: build/
if-no-files-found: error
- uses: actions/upload-artifact@v3.1.1
name: Upload artifact (MacOS, Win)
if: matrix.os != 'ubuntu-20.04'
with:
name: kubescape-${{ matrix.os }}
path: build/
if-no-files-found: error
run-tests:
strategy:
fail-fast: false
matrix:
TEST: ${{ fromJson(needs.binary-build.outputs.TEST_NAMES) }}
needs: [check-secret, binary-build]
if: needs.check-secret.outputs.is-secret-set == 'true'
runs-on: ubuntu-latest # This cannot change
steps:
- uses: 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@v3
with:
repository: armosec/system-tests
path: .
- uses: actions/setup-python@v4
with:
python-version: '3.8.13'
cache: 'pip'
- name: create env
run: ./create_env.sh
- name: Generate uuid
id: uuid
run: |
echo "RANDOM_UUID=$(uuidgen)" >> $GITHUB_OUTPUT
- name: Create k8s Kind Cluster
id: kind-cluster-install
uses: helm/kind-action@v1.3.0
with:
cluster_name: ${{ steps.uuid.outputs.RANDOM_UUID }}
- name: run-tests
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@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,4 +1,4 @@
name: d-publish-image
name: build
on:
workflow_call:
@@ -26,26 +26,20 @@ on:
type: boolean
description: 'support amd64/arm64'
jobs:
check-secret:
name: check if QUAYIO_REGISTRY_USERNAME & QUAYIO_REGISTRY_PASSWORD is set in github secrets
runs-on: ubuntu-latest
outputs:
is-secret-set: ${{ steps.check-secret-set.outputs.is-secret-set }}
steps:
- name: check if QUAYIO_REGISTRY_USERNAME & QUAYIO_REGISTRY_PASSWORD is set in github secrets
id: check-secret-set
env:
QUAYIO_REGISTRY_USERNAME: ${{ secrets.QUAYIO_REGISTRY_USERNAME }}
QUAYIO_REGISTRY_PASSWORD: ${{ secrets.QUAYIO_REGISTRY_PASSWORD }}
run: |
echo "is-secret-set=${{ env.QUAYIO_REGISTRY_USERNAME != '' && env.QUAYIO_REGISTRY_PASSWORD != '' }}" >> $GITHUB_OUTPUT
secrets:
QUAYIO_REGISTRY_USERNAME:
required: true
QUAYIO_REGISTRY_PASSWORD:
required: true
jobs:
build-image:
needs: [check-secret]
if: needs.check-secret.outputs.is-secret-set == 'true'
name: Build image and upload to registry
runs-on: ubuntu-latest
permissions:
id-token: write
packages: write
contents: read
steps:
- uses: actions/checkout@v3
@@ -67,10 +61,10 @@ jobs:
- 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@main
@@ -81,5 +75,6 @@ jobs:
env:
COSIGN_EXPERIMENTAL: "true"
run: |
cosign sign --force ${{ inputs.image_name }}
cosign sign --force ${{ inputs.image_name }}:latest
cosign sign --force ${{ inputs.image_name }}:${{ inputs.image_tag }}

91
.github/workflows/build.yaml vendored Normal file
View File

@@ -0,0 +1,91 @@
name: build
on:
push:
branches: [ master ]
paths-ignore:
# Do not run the pipeline if only Markdown files changed
- '**.md'
jobs:
test:
uses: ./.github/workflows/test.yaml
with:
release: "v2.0.${{ github.run_number }}"
client: test
create-release:
uses: ./.github/workflows/release.yaml
needs: test
with:
release_name: "Release v2.0.${{ github.run_number }}"
tag_name: "v2.0.${{ github.run_number }}"
secrets: inherit
publish-artifacts:
name: Build and publish artifacts
needs: create-release
runs-on: ${{ matrix.os }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
strategy:
matrix:
os: [ubuntu-latest, 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.18
- 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: v2.0.${{ github.run_number }}
CLIENT: release
CGO_ENABLED: 1
run: python3 --version && python3 build.py
- name: Upload release binaries
id: upload-release-asset
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ needs.create-release.outputs.upload_url }}
asset_path: build/${{ matrix.os }}/kubescape
asset_name: kubescape-${{ matrix.os }}
asset_content_type: application/octet-stream
- name: Upload release hash
id: upload-release-hash
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ needs.create-release.outputs.upload_url }}
asset_path: build/${{ matrix.os }}/kubescape.sha256
asset_name: kubescape-${{ matrix.os }}-sha256
asset_content_type: application/octet-stream
publish-image:
if: ${{ github.repository == 'kubescape/kubescape' }} # TODO
uses: ./.github/workflows/build-image.yaml
needs: create-release
with:
client: "image-release"
image_name: "quay.io/${{ github.repository_owner }}/kubescape"
image_tag: "v2.0.${{ github.run_number }}"
support_platforms: false
cosign: true
secrets: inherit

26
.github/workflows/build_dev.yaml vendored Normal file
View File

@@ -0,0 +1,26 @@
name: build-dev
on:
push:
branches: [ dev ]
paths-ignore:
# Do not run the pipeline if only Markdown files changed
- '**.md'
jobs:
test:
uses: ./.github/workflows/test.yaml
with:
release: "v2.0.${{ github.run_number }}"
client: test
publish-dev-image:
if: ${{ github.repository == 'kubescape/kubescape' }} # TODO
uses: ./.github/workflows/build-image.yaml
needs: test
with:
client: "image-dev"
image_name: "quay.io/${{ github.repository_owner }}/kubescape"
image_tag: "dev-v2.0.${{ github.run_number }}"
support_platforms: false
cosign: true
secrets: inherit

View File

@@ -1,57 +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
# permissions:
# contents: write
steps:
- uses: actions/download-artifact@v3.0.2
id: download-artifact
with:
path: .
- name: Release
uses: softprops/action-gh-release@v1
env:
MAC_OS: macos-latest
UBUNTU_OS: ubuntu-latest
WINDOWS_OS: windows-latest
with:
token: ${{ secrets.GITHUB_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
files: |
./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-${{ env.WINDOWS_OS }}
./kubescape-${{ env.WINDOWS_OS }}/kubescape-${{ env.WINDOWS_OS }}.sha256
./kubescape-${{ env.WINDOWS_OS }}/kubescape-${{ env.WINDOWS_OS }}.tar.gz

22
.github/workflows/community.yml vendored Normal file
View File

@@ -0,0 +1,22 @@
on:
fork:
issues:
types: [opened]
issue_comment:
types: [created]
pull_request_target:
types: [opened]
pull_request_review_comment:
types: [created]
jobs:
welcome:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- uses: EddieHubCommunity/gh-action-community/src/welcome@main
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
issue-message: '<h3>Hi! Welcome to Kubescape. Thank you for taking the time and reporting an issue</h3>'
pr-message: '<h3>Hi! Welcome to Kubescape. Thank you for taking the time and contributing to the open source community</h3>'
footer: '<h4>We will try to review as soon as possible!</h4>'

View File

@@ -1,14 +1,12 @@
name: 03-create_release_digests
name: create release digests
on:
release:
types: [ published ]
branches:
- 'master'
- 'main'
types: [ published]
branches: [ master ]
jobs:
create_release_digests:
once:
name: Creating digests
runs-on: ubuntu-latest
steps:

16
.github/workflows/pr_checks.yaml vendored Normal file
View File

@@ -0,0 +1,16 @@
name: pr-checks
on:
pull_request:
branches: [ master, dev ]
types: [ edited, opened, synchronize, reopened ]
paths-ignore:
# Do not run the pipeline if only Markdown files changed
- '**.yaml'
- '**.md'
jobs:
test:
uses: ./.github/workflows/test.yaml
with:
release: "v2.0.${{ github.run_number }}"
client: test

41
.github/workflows/release.yaml vendored Normal file
View File

@@ -0,0 +1,41 @@
name: build
on:
workflow_call:
inputs:
release_name:
description: 'release'
required: true
type: string
tag_name:
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:
tag_name: ${{ inputs.tag_name }}
release_name: ${{ inputs.release_name }}
draft: ${{ inputs.draft }}
prerelease: false

93
.github/workflows/test.yaml vendored Normal file
View File

@@ -0,0 +1,93 @@
name: test
on:
workflow_call:
inputs:
release:
description: 'release'
required: true
type: string
client:
description: 'Client name'
required: true
type: string
jobs:
build:
name: Create cross-platform build
runs-on: ${{ matrix.os }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
steps:
- uses: actions/checkout@v3
with:
submodules: recursive
- name: Cache Go modules (Linux)
if: matrix.os == 'ubuntu-latest'
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.18
- 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 -v ./...
- name: Test httphandler pkg
run: cd httphandler && go test -tags=static -v ./...
- name: Build
env:
RELEASE: ${{ inputs.release }}
CLIENT: test
CGO_ENABLED: 1
run: python3 --version && python3 build.py
- name: Smoke Testing
env:
RELEASE: ${{ inputs.release }}
KUBESCAPE_SKIP_UPDATE_CHECK: "true"
run: python3 smoke_testing/init.py ${PWD}/build/${{ matrix.os }}/kubescape

3
.gitignore vendored
View File

@@ -5,5 +5,4 @@
*.pyc*
.idea
.history
ca.srl
*.out
ca.srl

View File

@@ -1,57 +0,0 @@
linters-settings:
govet:
check-shadowing: true
dupl:
threshold: 200
goconst:
min-len: 3
min-occurrences: 2
gocognit:
min-complexity: 65
linters:
enable:
- gosec
- staticcheck
- nolintlint
- gofmt
- unused
- govet
- bodyclose
- typecheck
- goimports
- ineffassign
- gosimple
disable:
# temporarily disabled
- varcheck
- errcheck
- dupl
- gocritic
- gocognit
- nakedret
- revive
- stylecheck
- unconvert
- unparam
#- forbidigo # <- see later
# should remain disabled
- deadcode # deprecated linter
- maligned
- lll
- gochecknoinits
- gochecknoglobals
issues:
exclude-rules:
- linters:
- revive
text: "var-naming"
- linters:
- revive
text: "type name will be used as (.+?) by other packages, and that stutters"
- linters:
- stylecheck
text: "ST1003"
run:
skip-dirs:
- git2go

View File

@@ -1,36 +0,0 @@
apiVersion: krew.googlecontainertools.github.com/v1alpha2
kind: Plugin
metadata:
name: kubescape
spec:
homepage: https://kubescape.io/
shortDescription: An open-source Kubernetes security platform for your IDE, CI/CD pipelines, and clusters
version: {{ .TagName }}
description: |
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: linux
arch: amd64
{{ addURIAndSha "https://github.com/kubescape/kubescape/releases/download/{{ .TagName }}/kubescape-ubuntu-latest.tar.gz" .TagName }}
bin: kubescape
- selector:
matchLabels:
os: windows
arch: amd64
{{ addURIAndSha "https://github.com/kubescape/kubescape/releases/download/{{ .TagName }}/kubescape-windows-latest.tar.gz" .TagName }}
bin: kubescape

View File

@@ -1,3 +1,127 @@
## Code of Conduct
# Contributor Covenant Code of Conduct
The Kubescape project follows the [CNCF Code of Conduct](https://github.com/cncf/foundation/blob/master/code-of-conduct.md).
## Our Pledge
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, religion, or sexual identity
and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our
community include:
* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
* Focusing on what is best not just for us as individuals, but for the
overall community
Examples of unacceptable behavior include:
* The use of sexualized language or imagery, and sexual attention or
advances of any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email
address, without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.
Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement [here](mailto:ben@armosec.io).
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the
reporter of any incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series
of actions.
**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or
permanent ban.
### 3. Temporary Ban
**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within
the community.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.0, available at
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
Community Impact Guidelines were inspired by [Mozilla's code of conduct
enforcement ladder](https://github.com/mozilla/diversity).
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see the FAQ at
https://www.contributor-covenant.org/faq. Translations are available at
https://www.contributor-covenant.org/translations.

View File

@@ -4,16 +4,14 @@ First, it is awesome that you are considering contributing to Kubescape! Contrib
When contributing, we categorize contributions into two:
* Small code changes or fixes, whose scope is limited to a single or two files
* Complex features and improvements, with potentially unlimited scope
* Complex features and improvements, that are not limited
If you have a small change, feel free to fire up a Pull Request.
When planning a bigger change, please first discuss the change you wish to make via an issue,
so the maintainers are able to help guide you and let you know if you are going in the right direction.
When planning a bigger change, please first discuss the change you wish to make via issue,
email, or any other method with the owners of this repository before making a change. Most likely your changes or features are great, but sometimes we might be already going in this direction (or the exact opposite ;-) ) and we don't want to waste your time.
## Code of Conduct
Please follow our [code of conduct](CODE_OF_CONDUCT.md) in all of your interactions within the project.
Please note we have a code of conduct, please follow it in all your interactions with the project.
## Pull Request Process
@@ -21,44 +19,82 @@ Please follow our [code of conduct](CODE_OF_CONDUCT.md) in all of your interacti
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
## Code of Conduct
All commits to the project must be "signed off", which states that you agree to the terms of the [Developer Certificate of Origin](https://developercertificate.org/). This is done by adding a "Signed-off-by:" line in the commit message, with your name and email address.
### Our Pledge
Commits made through the GitHub web application are automatically signed off.
In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to make participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, gender identity and expression, level of experience,
nationality, personal appearance, race, religion, or sexual identity and
orientation.
### Configuring Git to sign off commits
### Our Standards
First, configure your name and email address in Git global settings:
Examples of behavior that contributes to creating a positive environment
include:
```
$ git config --global user.name "John Doe"
$ git config --global user.email johndoe@example.com
```
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
You can now sign off per-commit, or configure Git to always sign off commits per repository.
Examples of unacceptable behavior by participants include:
### Sign off per-commit
* The use of sexualized language or imagery and unwelcome sexual attention or
advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic
address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
Add [`-s`](https://git-scm.com/docs/git-commit#Documentation/git-commit.txt--s) to your Git command line. For example:
We will distance those who constantly adhere to unacceptable behavior.
```git commit -s -m "Fix issue 64738"```
### Our Responsibilities
This is tedious, and if you forget, you'll have to [amend your commit](#f)
Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective actions in
response to any instances of unacceptable behavior.
### Configure a repository to always include sign off
Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.
There are many ways to achieve this with Git hooks, but the simplest is to do the following:
### Scope
```
cd your-repo
curl -Ls https://gist.githubusercontent.com/dixudx/7d7edea35b4d91e1a2a8fbf41d0954fa/raw/prepare-commit-msg -o .git/hooks/prepare-commit-msg
chmod +x .git/hooks/prepare-commit-msg
```
This Code of Conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community. Examples of
representing a project or community include using an official project e-mail
address, posting via an official social media account, or acting as an appointed
representative at an online or offline event. Representation of a project may be
further defined and clarified by project maintainers.
## Fixing a commit where the DCO failed
### Enforcement
Check out [this guide](https://github.com/src-d/guide/blob/master/developer-community/fix-DCO.md).
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at [INSERT EMAIL ADDRESS]. All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership.
### Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at [http://contributor-covenant.org/version/1/4][version]
[homepage]: http://contributor-covenant.org
[version]: http://contributor-covenant.org/version/1/4/

View File

@@ -1,11 +1,10 @@
# Maintainers
The following table lists the Kubescape project maintainers:
The following table lists Kubescape project maintainers
| Name | GitHub | Organization | Added/Renewed On |
| --- | --- | --- | --- |
| [Ben Hirschberg](https://www.linkedin.com/in/benyamin-ben-hirschberg-66141890) | [@slashben](https://github.com/slashben) | [ARMO](https://www.armosec.io/) | 2021-09-01 |
| [Rotem Refael](https://www.linkedin.com/in/rotem-refael) | [@rotemamsa](https://github.com/rotemamsa) | [ARMO](https://www.armosec.io/) | 2021-10-11 |
| [David Wertenteil](https://www.linkedin.com/in/david-wertenteil-0ba277b9) | [@dwertent](https://github.com/dwertent) | [ARMO](https://www.armosec.io/) | 2021-09-01 |
| [Bezalel Brandwine](https://www.linkedin.com/in/bezalel-brandwine) | [@Bezbran](https://github.com/Bezbran) | [ARMO](https://www.armosec.io/) | 2021-09-01 |
| [Craig Box](https://www.linkedin.com/in/crbnz/) | [@craigbox](https://github.com/craigbox) | [ARMO](https://www.armosec.io/) | 2022-10-31 |
| Name | GitHub | Email | Organization | Role | Added/Renewed On |
| --- | --- | --- | --- | --- | --- |
| [Ben Hirschberg](https://www.linkedin.com/in/benyamin-ben-hirschberg-66141890) | [@slashben](https://github.com/slashben) | ben@armosec.io | [ARMO](https://www.armosec.io/) | VP R&D | 2021-09-01 |
| [Rotem Refael](https://www.linkedin.com/in/rotem-refael) | [@rotemamsa](https://github.com/rotemamsa) | rrefael@armosec.io | [ARMO](https://www.armosec.io/) | Team Leader | 2021-10-11 |
| [David Wertenteil](https://www.linkedin.com/in/david-wertenteil-0ba277b9) | [@dwertent](https://github.com/dwertent) | dwertent@armosec.io | [ARMO](https://www.armosec.io/) | Kubescape CLI Developer | 2021-09-01 |
| [Bezalel Brandwine](https://www.linkedin.com/in/bezalel-brandwine) | [@Bezbran](https://github.com/Bezbran) | bbrandwine@armosec.io | [ARMO](https://www.armosec.io/) | Kubescape SaaS Developer | 2021-09-01 |

View File

@@ -11,7 +11,7 @@ libgit2:
cd git2go; make install-static
# go build tags
TAGS = "gitenabled,static"
TAGS = "static"
build:
go build -v -tags=$(TAGS) .

491
README.md
View File

@@ -1,94 +1,471 @@
[![Version](https://img.shields.io/github/v/release/kubescape/kubescape)](releases)
<div align="center">
<img src="docs/kubescape.png" width="300" alt="logo">
</div>
---
[![build](https://github.com/kubescape/kubescape/actions/workflows/build.yaml/badge.svg)](https://github.com/kubescape/kubescape/actions/workflows/build.yaml)
[![Go Report Card](https://goreportcard.com/badge/github.com/kubescape/kubescape)](https://goreportcard.com/report/github.com/kubescape/kubescape)
[![Gitpod Ready-to-Code](https://img.shields.io/badge/Gitpod-Ready--to--Code-blue?logo=gitpod)](https://gitpod.io/#https://github.com/kubescape/kubescape)
[![GitHub](https://img.shields.io/github/license/kubescape/kubescape)](https://github.com/kubescape/kubescape/blob/master/LICENSE)
[![CNCF](https://shields.io/badge/CNCF-Sandbox%20project-blue?logo=linux-foundation&style=flat)](https://landscape.cncf.io/card-mode?project=sandbox&selected=kubescape)
[![Twitter Follow](https://img.shields.io/twitter/follow/kubescape?style=social)](https://twitter.com/kubescape)
# Kubescape
:sunglasses: [Want to contribute?](#being-a-part-of-the-team) :innocent:
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/cncf/artwork/master/projects/kubescape/stacked/white/kubescape-stacked-white.svg" width="150">
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/cncf/artwork/master/projects/kubescape/stacked/color/kubescape-stacked-color.svg" width="150">
<img alt="Kubescape logo" align="right" src="https://raw.githubusercontent.com/cncf/artwork/master/projects/kubescape/stacked/color/kubescape-stacked-color.svg" width="150">
</picture>
_An open-source Kubernetes security platform for your IDE, CI/CD pipelines, and clusters_
Kubescape is a K8s open-source tool providing a Kubernetes single pane of glass, including risk analysis, security compliance, RBAC visualizer, and image vulnerability scanning.
Kubescape scans K8s clusters, YAML files, and HELM charts, detecting misconfigurations according to multiple frameworks (such as the [NSA-CISA](https://www.armosec.io/blog/kubernetes-hardening-guidance-summary-by-armo/?utm_source=github&utm_medium=repository), [MITRE ATT&CK®](https://www.microsoft.com/security/blog/2021/03/23/secure-containerized-environments-with-updated-threat-matrix-for-kubernetes/)), software vulnerabilities, and RBAC (role-based-access-control) violations at early stages of the CI/CD pipeline, calculates risk score instantly and shows risk trends over time.
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.
It has become one of the fastest-growing Kubernetes tools among developers due to its easy-to-use CLI interface, flexible output formats, and automated scanning capabilities, saving Kubernetes users and admins precious time, effort, and resources.
Kubescape integrates natively with other DevOps tools, including Jenkins, CircleCI, Github workflows, Prometheus, and Slack, and supports multi-cloud K8s deployments like EKS, GKE, and AKS.
Kubescape scans clusters, YAML files, and Helm charts. It detects misconfigurations according to multiple frameworks (including [NSA-CISA](https://www.armosec.io/blog/kubernetes-hardening-guidance-summary-by-armo/?utm_source=github&utm_medium=repository), [MITRE ATT&CK®](https://www.microsoft.com/security/blog/2021/03/23/secure-containerized-environments-with-updated-threat-matrix-for-kubernetes/) and the [CIS Benchmark](https://www.armosec.io/blog/cis-kubernetes-benchmark-framework-scanning-tools-comparison/?utm_source=github&utm_medium=repository)).
</br>
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/).
# Kubescape CLI:
<img src="docs/demo.gif">
## Demo
<img src="docs/img/demo.gif">
_Please [star ⭐](https://github.com/kubescape/kubescape/stargazers) the repo if you want us to continue developing and improving Kubescape! 😀_
## Getting started
Experimenting with Kubescape is as easy as:
</br>
# TL;DR
## Install:
```sh
curl -s https://raw.githubusercontent.com/kubescape/kubescape/master/install.sh | /bin/bash
```
Learn more about:
*OR:*
* [Installing Kubescape](docs/getting-started.md#install-kubescape)
* [Running your first scan](docs/getting-started.md#run-your-first-scan)
* [Usage](docs/getting-started.md#examples)
* [Architecture](docs/architecture.md)
* [Building Kubescape from source](docs/building.md)
[Install on windows](#install-on-windows)
_Did you know you can use Kubescape in all these places?_
[Install on macOS](#install-on-macos)
[Install on NixOS or Linux/macOS via nix](#install-on-nixos-or-with-nix-community)
## Run:
```sh
kubescape scan --enable-host-scan --verbose
```
<img src="docs/summary.png">
</br>
> Kubescape is an open source project. We welcome your feedback and ideas for improvement. Were also aiming to collaborate with the Kubernetes community to help make the tests more robust and complete as Kubernetes develops.
</br>
## Architecture in short
### [CLI](#kubescape-cli)
<div align="center">
<img src="docs/img/ksfromcodetodeploy.png" alt="Places you can use Kubescape: in your IDE, CI, CD, or against a running cluster.">
<img src="docs/ks-cli-arch.png" width="300" alt="cli-diagram">
</div>
## Under the hood
### [Operator](https://github.com/kubescape/helm-charts#readme)
<div align="center">
<img src="docs/ks-operator-arch.png" width="300" alt="operator-diagram">
</div>
Kubescape uses [Open Policy Agent](https://github.com/open-policy-agent/opa) to verify Kubernetes objects against [a library of posture controls](https://github.com/kubescape/regolibrary).
### Please [star ⭐](https://github.com/kubescape/kubescape/stargazers) the repo if you want us to continue developing and improving Kubescape 😀
By default, the results are printed in a console-friendly manner, but they can be:
</br>
* exported to JSON or junit XML
* rendered to HTML or PDF
* submitted to a [cloud service](docs/providers.md)
It retrieves Kubernetes objects from the API server and runs a set of [Rego snippets](https://www.openpolicyagent.org/docs/latest/policy-language/) developed by [ARMO](https://www.armosec.io?utm_source=github&utm_medium=repository).
# Being a part of the team
## Community
We invite you to our community! We are excited about this project and want to return the love we get.
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://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).
We hold community meetings in [Zoom](https://us02web.zoom.us/j/84020231442) on the first Tuesday of every month at 14:00 GMT! :sunglasses:
## Contributions
[Want to contribute?](https://github.com/kubescape/kubescape/blob/master/CONTRIBUTING.md) Want to discuss something? Have an issue? Please make sure that you follow our [Code Of Conduct](https://github.com/kubescape/kubescape/blob/master/CODE_OF_CONDUCT.md) .
Thanks to all our contributors! Check out our [CONTRIBUTING](CONTRIBUTING.md) file to learn how to join them.
* Feel free to pick a task from the [issues](https://github.com/kubescape/kubescape/issues?q=is%3Aissue+is%3Aopen+label%3A%22open+for+contribution%22), [roadmap](docs/roadmap.md) or suggest a feature of your own. [Contact us](MAINTAINERS.md) directly for more information :)
* [Open an issue](https://github.com/kubescape/kubescape/issues/new/choose) , we are trying to respond within 48 hours
* [Join us](https://discord.com/invite/WKZRaCtBxN) in the discussion on our discord server!
* Feel free to pick a task from the [issues](https://github.com/kubescape/kubescape/issues?q=is%3Aissue+is%3Aopen+label%3A%22open+for+contribution%22), [roadmap](docs/roadmap.md) or suggest a feature of your own.
* [Open an issue](https://github.com/kubescape/kubescape/issues/new/choose): we aim to respond to all issues within 48 hours.
* [Join the CNCF Slack](https://slack.cncf.io/) and then our [users](https://cloud-native.slack.com/archives/C04EY3ZF9GE) or [developers](https://cloud-native.slack.com/archives/C04GY6H082K) channel.
[<img src="docs/discord-banner.png" width="100" alt="logo" align="center">](https://discord.com/invite/WKZRaCtBxN)
![discord](https://img.shields.io/discord/893048809884643379)
<br>
# Options and examples
[Kubescape docs](https://hub.armosec.io/docs?utm_source=github&utm_medium=repository)
## Playground
* [Kubescape playground](https://killercoda.com/saiyampathak/scenario/kubescape)
## Tutorials
* [Overview](https://youtu.be/wdBkt_0Qhbg)
* [How To Secure Kubernetes Clusters With Kubescape And Armo](https://youtu.be/ZATGiDIDBQk)
* [Scan Kubernetes YAML files](https://youtu.be/Ox6DaR7_4ZI)
* [Scan container image registry](https://youtu.be/iQ_k8EnK-3s)
* [Scan Kubescape on an air-gapped environment (offline support)](https://youtu.be/IGXL9s37smM)
* [Managing exceptions in the Kubescape SaaS version](https://youtu.be/OzpvxGmCR80)
* [Configure and run customized frameworks](https://youtu.be/12Sanq_rEhs)
* Customize control configurations:
- [Kubescape CLI](https://youtu.be/955psg6TVu4)
- [Kubescape SaaS](https://youtu.be/lIMVSVhH33o)
## Install on Windows
<details><summary>Windows</summary>
**Requires powershell v5.0+**
``` powershell
iwr -useb https://raw.githubusercontent.com/kubescape/kubescape/master/install.ps1 | iex
```
Note: if you get an error you might need to change the execution policy (i.e. enable Powershell) with
``` powershell
Set-ExecutionPolicy RemoteSigned -scope CurrentUser
```
</details>
## Install on macOS
<details><summary>MacOS</summary>
1. ```sh
brew tap kubescape/tap
```
2. ```sh
brew install kubescape-cli
```
</details>
## Install on NixOS or with nix (Community)
<details><summary>Nix/NixOS</summary>
Direct issues installing `kubescape` via `nix` through the channels mentioned [here](https://nixos.wiki/wiki/Support)
You can use `nix` on Linux or macOS and on other platforms unofficially.
Try it out in an ephemeral shell: `nix-shell -p kubescape`
Install declarative as usual
NixOS:
```nix
# your other config ...
environment.systemPackages = with pkgs; [
# your other packages ...
kubescape
];
```
home-manager:
```nix
# your other config ...
home.packages = with pkgs; [
# your other packages ...
kubescape
];
```
Or to your profile (not preferred): `nix-env --install -A nixpkgs.kubescape`
</details>
## Usage & Examples
### Examples
#### Scan a running Kubernetes cluster
```
kubescape scan --enable-host-scan --verbose
```
> Read [here](https://hub.armosec.io/docs/host-sensor?utm_source=github&utm_medium=repository) more about the `enable-host-scan` flag
#### Scan a running Kubernetes cluster with [`nsa`](https://www.nsa.gov/Press-Room/News-Highlights/Article/Article/2716980/nsa-cisa-release-kubernetes-hardening-guidance/) framework
```
kubescape scan framework nsa
```
#### Scan a running Kubernetes cluster with [`MITRE ATT&CK®`](https://www.microsoft.com/security/blog/2021/03/23/secure-containerized-environments-with-updated-threat-matrix-for-kubernetes/) framework
```
kubescape scan framework mitre
```
#### Scan a running Kubernetes cluster with a specific control using the control name or control ID. [List of controls](https://hub.armosec.io/docs/controls?utm_source=github&utm_medium=repository)
```
kubescape scan control "Privileged container"
```
#### Scan using an alternative kubeconfig file
```
kubescape scan --kubeconfig cluster.conf
```
#### Scan specific namespaces
```
kubescape scan --include-namespaces development,staging,production
```
#### Scan cluster and exclude some namespaces
```
kubescape scan --exclude-namespaces kube-system,kube-public
```
#### Scan local `yaml`/`json` files before deploying. [Take a look at the demonstration](https://youtu.be/Ox6DaR7_4ZI).
```
kubescape scan *.yaml
```
#### Scan Kubernetes manifest files from a git repository
kubescape scan https://github.com/kubescape/kubescape
```
#### Display all scanned resources (including the resources which passed)
```
kubescape scan --verbose
```
#### Output in `json` format
> Add the `--format-version v2` flag
```
kubescape scan --format json --format-version v2 --output results.json
```
#### Output in `junit xml` format
```
kubescape scan --format junit --output results.xml
```
#### Output in `pdf` format - Contributed by [@alegrey91](https://github.com/alegrey91)
```
kubescape scan --format pdf --output results.pdf
```
#### Output in `prometheus` metrics format - Contributed by [@Joibel](https://github.com/Joibel)
```
kubescape scan --format prometheus
```
#### Output in `html` format
```
kubescape scan --format html --output results.html
```
#### Scan with exceptions, objects with exceptions will be presented as `exclude` and not `fail`
[Full documentation](examples/exceptions/README.md)
```
kubescape scan --exceptions examples/exceptions/exclude-kube-namespaces.json
```
#### Scan Helm charts
```
kubescape scan </path/to/directory>
```
> Kubescape will load the default value file
#### Scan Kustomize Directory
```
kubescape scan </path/to/directory>
```
> Kubescape will generate Kubernetes Yaml Objects using 'Kustomize' file and scans them for security.
### Offline/Air-gaped Environment Support
[Video tutorial](https://youtu.be/IGXL9s37smM)
It is possible to run Kubescape offline!
#### Download all artifacts
1. Download and save in local directory, if path not specified, will save all in `~/.kubescape`
```
kubescape download artifacts --output path/to/local/dir
```
2. Copy the downloaded artifacts to the air-gaped/offline environment
3. Scan using the downloaded artifacts
```
kubescape scan --use-artifacts-from path/to/local/dir
```
#### Download a single artifact
You can also download a single artifact and scan with the `--use-from` flag
1. Download and save in a file, if the file name is not specified, will save in `~/.kubescape/<framework name>.json`
```
kubescape download framework nsa --output /path/nsa.json
```
2. Copy the downloaded artifacts to the air-gaped/offline environment
3. Scan using the downloaded framework
```
kubescape scan framework nsa --use-from /path/nsa.json
```
## Scan Periodically using Helm
[Please follow the instructions here](https://hub.armosec.io/docs/installation-of-armo-in-cluster?utm_source=github&utm_medium=repository)
[helm chart repo](https://github.com/armosec/armo-helm)
# Integrations
## VS Code Extension
![Visual Studio Marketplace Downloads](https://img.shields.io/visual-studio-marketplace/d/kubescape.kubescape?label=VScode) ![Open VSX](https://img.shields.io/open-vsx/dt/kubescape/kubescape?label=openVSX&color=yellowgreen)
Scan the YAML files while writing them using the [vs code extension](https://github.com/armosec/vscode-kubescape/blob/master/README.md)
## Lens Extension
View Kubescape scan results directly in [Lens IDE](https://k8slens.dev/) using kubescape [Lens extension](https://github.com/armosec/lens-kubescape/blob/master/README.md)
# Building Kubescape
## Build on Windows
<details><summary>Windows</summary>
1. Install MSYS2 & build libgit _(needed only for the first time)_
```
build.bat all
```
> You can install MSYS2 separately by running `build.bat install` and build libgit2 separately by running `build.bat build`
2. Build kubescape
```
make build
```
OR
```
go build -tags=static .
```
</details>
## Build on Linux/MacOS
<details><summary>Linux / MacOS</summary>
1. Install libgit2 dependency _(needed only for the first time)_
```
make libgit2
```
> `cmake` is required to build libgit2. You can install it by running `sudo apt-get install cmake` (Linux) or `brew install cmake` (macOS)
2. Build kubescape
```
make build
```
OR
```
go build -tags=static .
```
3. Test
```
make test
```
</details>
## Build on pre-configured killercoda's ubuntu playground
* [Pre-configured Killercoda's Ubuntu Playground](https://killercoda.com/suhas-gumma/scenario/kubescape-build-for-development)
<details><summary> Pre-programmed actions executed by the playground </summary>
* Clone the official GitHub repository of `Kubescape`.
* [Automate the build process on Linux](https://github.com/kubescape/kubescape#build-on-linuxmacos)
* The entire process involves executing multiple commands in order and it takes around 5-6 minutes to execute them all.
</details>
<details>
<summary>Instructions to use the playground</summary>
* Apply changes you wish to make to the kubescape directory using text editors like `Vim`.
* [Build on Linux](https://github.com/kubescape/kubescape#build-on-linuxmacos)
* Now, you can use Kubescape just like a normal user. Instead of using `kubescape`, use `./kubescape`. (Make sure you are inside kubescape directory because the command will execute the binary named `kubescape` in `kubescape directory`)
</details>
## VS code configuration samples
You can use the sample files below to setup your VS code environment for building and debugging purposes.
<details><summary>.vscode/settings.json</summary>
```json5
// .vscode/settings.json
{
"go.testTags": "static",
"go.buildTags": "static",
"go.toolsEnvVars": {
"CGO_ENABLED": "1"
}
}
```
</details>
<details><summary>.vscode/launch.json</summary>
```json5
// .vscode/launch.json
{
"version": "0.2.0",
"configurations": [
{
"name": "Launch Package",
"type": "go",
"request": "launch",
"mode": "auto",
"program": "${workspaceFolder}/main.go",
"args": [
"scan",
"--logger",
"debug"
],
"buildFlags": "-tags=static"
}
]
}
```
</details>
# Under the hood
## Technology
Kubescape is based on the [OPA engine](https://github.com/open-policy-agent/opa) and ARMO's posture controls.
The tools retrieve Kubernetes objects from the API server and run a set of [rego's snippets](https://www.openpolicyagent.org/docs/latest/policy-language/) developed by [ARMO](https://www.armosec.io?utm_source=github&utm_medium=repository).
The results by default are printed in a pretty "console friendly" manner, but they can be retrieved in JSON format for further processing.
Kubescape is an open source project, we welcome your feedback and ideas for improvement. Were also aiming to collaborate with the Kubernetes community to help make the tests more robust and complete as Kubernetes develops.
## Thanks to all the contributors ❤️
<a href = "https://github.com/kubescape/kubescape/graphs/contributors">
<img src = "https://contrib.rocks/image?repo=kubescape/kubescape"/>
</a>
## License
Copyright 2021-2023, the Kubescape Authors. All rights reserved. Kubescape is released under the Apache 2.0 license. See the [LICENSE](LICENSE) file for details.
Kubescape is a [Cloud Native Computing Foundation (CNCF) sandbox project](https://www.cncf.io/sandbox-projects/) and was contributed by [ARMO](https://www.armosec.io/?utm_source=github&utm_medium=repository).
<div align="center">
<img src="https://raw.githubusercontent.com/cncf/artwork/master/other/cncf-sandbox/horizontal/color/cncf-sandbox-horizontal-color.svg" width="300" alt="CNCF Sandbox Project">
</div>

View File

@@ -3,16 +3,9 @@ import sys
import hashlib
import platform
import subprocess
import tarfile
BASE_GETTER_CONST = "github.com/kubescape/kubescape/v2/core/cautils/getter"
platformSuffixes = {
"Windows": "windows-latest",
"Linux": "ubuntu-latest",
"Darwin": "macos-latest",
}
def check_status(status, msg):
if status != 0:
sys.stderr.write(msg)
@@ -20,15 +13,21 @@ def check_status(status, msg):
def get_build_dir():
return "build"
current_platform = platform.system()
build_dir = ""
if current_platform == "Windows": build_dir = "windows-latest"
elif current_platform == "Linux": build_dir = "ubuntu-latest"
elif current_platform == "Darwin": build_dir = "macos-latest"
else: raise OSError("Platform %s is not supported!" % (current_platform))
return os.path.join("build", build_dir)
def get_package_name():
current_platform = platform.system()
package_name = "kubescape"
if current_platform not in platformSuffixes: raise OSError("Platform %s is not supported!" % (current_platform))
return "kubescape-" + platformSuffixes[current_platform]
return package_name
def main():
@@ -41,13 +40,12 @@ def main():
client_var = "github.com/kubescape/kubescape/v2/core/cautils.Client"
client_name = os.getenv("CLIENT")
# Create build directory
build_dir = get_build_dir()
ks_file = os.path.join(build_dir, package_name)
hash_file = ks_file + ".sha256"
tar_file = ks_file + ".tar.gz"
if not os.path.isdir(build_dir):
os.makedirs(build_dir)
@@ -58,15 +56,15 @@ def main():
ldflags += " -X {}={}".format(build_url, release_version)
if client_name:
ldflags += " -X {}={}".format(client_var, client_name)
build_command = ["go", "build", "-buildmode=pie", "-tags=static,gitenabled", "-o", ks_file, "-ldflags" ,ldflags]
build_command = ["go", "build", "-tags=static", "-o", ks_file, "-ldflags" ,ldflags]
print("Building kubescape and saving here: {}".format(ks_file))
print("Build command: {}".format(" ".join(build_command)))
status = subprocess.call(build_command)
check_status(status, "Failed to build kubescape")
sha256 = hashlib.sha256()
with open(ks_file, "rb") as kube:
sha256.update(kube.read())
@@ -75,12 +73,8 @@ def main():
print("kubescape hash: {}, file: {}".format(hash, hash_file))
kube_sha.write(sha256.hexdigest())
with tarfile.open(tar_file, 'w:gz') as archive:
archive.add(ks_file, "kubescape")
archive.add(ks_file, "LICENSE")
print("Build Done")
if __name__ == "__main__":
main()

View File

@@ -1,4 +1,4 @@
FROM golang:1.19-alpine as builder
FROM golang:1.18-alpine as builder
ARG image_version
ARG client
@@ -12,7 +12,7 @@ ENV CGO_ENABLED=1
# Install required python/pip
ENV PYTHONUNBUFFERED=1
RUN apk add --update --no-cache python3 gcc make git libc-dev binutils-gold cmake pkgconfig && ln -sf python3 /usr/bin/python
RUN apk add --update --no-cache python3 git openssl-dev musl-dev gcc make cmake pkgconfig && ln -sf python3 /usr/bin/python
RUN python3 -m ensurepip
RUN pip3 install --no-cache --upgrade pip setuptools
@@ -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 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 /usr/bin/ksserver
COPY --from=builder /work/build/ubuntu-latest/kubescape /usr/bin/kubescape
ENTRYPOINT ["ksserver"]

View File

@@ -1,23 +1,23 @@
package completion
import (
"fmt"
"os"
"strings"
"github.com/kubescape/kubescape/v2/core/cautils"
"github.com/spf13/cobra"
)
var completionCmdExamples = fmt.Sprintf(`
# Enable BASH shell autocompletion
$ source <(%[1]s completion bash)
$ echo 'source <(%[1]s completion bash)' >> ~/.bashrc
var completionCmdExamples = `
# Enable ZSH shell autocompletion
# Enable BASH shell autocompletion
$ source <(kubescape completion bash)
$ echo 'source <(kubescape completion bash)' >> ~/.bashrc
# Enable ZSH shell autocompletion
$ source <(kubectl completion zsh)
$ echo 'source <(kubectl completion zsh)' >> "${fpath[1]}/_kubectl"
`, cautils.ExecName())
`
func GetCompletionCmd() *cobra.Command {
completionCmd := &cobra.Command{
@@ -27,7 +27,7 @@ func GetCompletionCmd() *cobra.Command {
Example: completionCmdExamples,
DisableFlagsInUseLine: true,
ValidArgs: []string{"bash", "zsh", "fish", "powershell"},
Args: cobra.MatchAll(cobra.ExactArgs(1), cobra.OnlyValidArgs),
Args: cobra.ExactValidArgs(1),
Run: func(cmd *cobra.Command, args []string) {
switch strings.ToLower(args[0]) {
case "bash":

View File

@@ -1,37 +1,34 @@
package config
import (
"fmt"
"github.com/kubescape/kubescape/v2/core/cautils"
"github.com/kubescape/kubescape/v2/core/meta"
"github.com/spf13/cobra"
)
var (
configExample = fmt.Sprintf(`
configExample = `
# View cached configurations
%[1]s config view
kubescape config view
# Delete cached configurations
%[1]s config delete
kubescape config delete
# Set cached configurations
%[1]s config set --help
`, cautils.ExecName())
setConfigExample = fmt.Sprintf(`
kubescape config set --help
`
setConfigExample = `
# Set account id
%[1]s config set accountID <account id>
kubescape config set accountID <account id>
# Set client id
%[1]s config set clientID <client id>
kubescape config set clientID <client id>
# Set access key
%[1]s config set secretKey <access key>
kubescape config set secretKey <access key>
# Set cloudAPIURL
%[1]s config set cloudAPIURL <cloud API URL>
`, cautils.ExecName())
kubescape config set cloudAPIURL <cloud API URL>
`
)
func GetConfigCmd(ks meta.IKubescape) *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,21 +1,18 @@
package delete
import (
"fmt"
"github.com/kubescape/kubescape/v2/core/cautils"
"github.com/kubescape/kubescape/v2/core/meta"
v1 "github.com/kubescape/kubescape/v2/core/meta/datastructures/v1"
"github.com/spf13/cobra"
)
var deleteExceptionsExamples = fmt.Sprintf(`
var deleteExceptionsExamples = `
# Delete single exception
%[1]s delete exceptions "exception name"
kubescape delete exceptions "exception name"
# Delete multiple exceptions
%[1]s delete exceptions "first exception;second exception;third exception"
`, cautils.ExecName())
kubescape delete exceptions "first exception;second exception;third exception"
`
func GetDeleteCmd(ks meta.IKubescape) *cobra.Command {
var deleteInfo v1.Delete

View File

@@ -5,7 +5,6 @@ import (
"strings"
logger "github.com/kubescape/go-logger"
"github.com/kubescape/kubescape/v2/core/cautils"
"github.com/kubescape/kubescape/v2/core/meta"
v1 "github.com/kubescape/kubescape/v2/core/meta/datastructures/v1"
"github.com/spf13/cobra"
@@ -14,7 +13,7 @@ import (
func getExceptionsCmd(ks meta.IKubescape, deleteInfo *v1.Delete) *cobra.Command {
return &cobra.Command{
Use: "exceptions <exception name>",
Short: fmt.Sprintf("Delete exceptions from Kubescape SaaS version. Run '%[1]s list exceptions' for all exceptions names", cautils.ExecName()),
Short: "Delete exceptions from Kubescape SaaS version. Run 'kubescape list exceptions' for all exceptions names",
Example: deleteExceptionsExamples,
Args: func(cmd *cobra.Command, args []string) error {
if len(args) != 1 {

View File

@@ -1,7 +1,6 @@
package download
import (
"context"
"fmt"
"path/filepath"
"strings"
@@ -15,34 +14,32 @@ import (
)
var (
downloadExample = fmt.Sprintf(`
downloadExample = `
# Download all artifacts and save them in the default path (~/.kubescape)
%[1]s download artifacts
kubescape download artifacts
# Download all artifacts and save them in /tmp path
%[1]s download artifacts --output /tmp
kubescape download artifacts --output /tmp
# Download the NSA framework. Run '%[1]s list frameworks' for all frameworks names
%[1]s download framework nsa
# Download the NSA framework. Run 'kubescape list frameworks' for all frameworks names
kubescape download framework nsa
# Download the "C-0001" control. Run '%[1]s list controls --id' for all controls ids
%[1]s download control "C-0001"
# Download the "Allowed hostPath" control. Run 'kubescape list controls' for all controls names
kubescape download control "Allowed hostPath"
# Download the "C-0001" control. Run '%[1]s list controls --id' for all controls ids
%[1]s download control C-0001
# Download the "C-0001" control. Run 'kubescape list controls --id' for all controls ids
kubescape download control C-0001
# Download the configured exceptions
%[1]s download exceptions
kubescape download exceptions
# Download the configured controls-inputs
%[1]s download controls-inputs
kubescape download controls-inputs
# Download the attack tracks
%[1]s download attack-tracks
`, cautils.ExecName())
`
)
func GetDownloadCmd(ks meta.IKubescape) *cobra.Command {
func GeDownloadCmd(ks meta.IKubescape) *cobra.Command {
var downloadInfo = v1.DownloadInfo{}
downloadCmd := &cobra.Command{
@@ -71,11 +68,9 @@ func GetDownloadCmd(ks meta.IKubescape) *cobra.Command {
}
downloadInfo.Target = args[0]
if len(args) >= 2 {
downloadInfo.Identifier = args[1]
downloadInfo.Name = 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 --format-version v2 --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"
@@ -14,19 +13,22 @@ import (
)
var (
listExample = fmt.Sprintf(`
listExample = `
# List default supported frameworks names
%[1]s list frameworks
kubescape list frameworks
# List all supported frameworks names
%[1]s list frameworks --account <account id>
kubescape list frameworks --account <account id>
# List all supported controls names with ids
%[1]s list controls
# List all supported controls names
kubescape list controls
# List all supported controls ids
kubescape list controls --id
Control documentation:
https://hub.armosec.io/docs/controls
`, cautils.ExecName())
`
)
func GetListCmd(ks meta.IKubescape) *cobra.Command {
@@ -56,7 +58,7 @@ func GetListCmd(ks meta.IKubescape) *cobra.Command {
listPolicies.Target = args[0]
if err := ks.List(context.TODO(), &listPolicies); err != nil {
if err := ks.List(&listPolicies); err != nil {
logger.L().Fatal(err.Error())
}
return nil
@@ -65,8 +67,8 @@ func GetListCmd(ks meta.IKubescape) *cobra.Command {
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")
listCmd.PersistentFlags().StringVar(&listPolicies.Format, "format", "pretty-print", "output format. supported: 'pretty-printer'/'json'")
listCmd.PersistentFlags().BoolVarP(&listPolicies.ListIDs, "id", "", false, "List control ID's instead of controls names")
return listCmd
}

View File

@@ -26,19 +26,19 @@ import (
var rootInfo cautils.RootInfo
var ksExamples = fmt.Sprintf(`
var ksExamples = `
# Scan command
%[1]s scan
kubescape scan --submit
# List supported frameworks
%[1]s list frameworks
kubescape list frameworks
# Download artifacts (air-gapped environment support)
%[1]s download artifacts
kubescape download artifacts
# View cached configurations
%[1]s config view
`, cautils.ExecName())
kubescape config view
`
func NewDefaultKubescapeCommand() *cobra.Command {
ks := core.NewKubescape()
@@ -53,16 +53,6 @@ func getRootCmd(ks meta.IKubescape) *cobra.Command {
Example: ksExamples,
}
if cautils.IsKrewPlugin() {
// Invoked as a kubectl plugin.
// Cobra doesn't have a way to specify a two word command (i.e. "kubectl kubescape"), so set a custom usage template
// with kubectl in it. Cobra will use this template for the root and all child commands.
oldUsageTemplate := rootCmd.UsageTemplate()
newUsageTemplate := strings.NewReplacer("{{.UseLine}}", "kubectl {{.UseLine}}", "{{.CommandPath}}", "kubectl {{.CommandPath}}").Replace(oldUsageTemplate)
rootCmd.SetUsageTemplate(newUsageTemplate)
}
rootCmd.PersistentFlags().StringVar(&rootInfo.KSCloudBEURLsDep, "environment", "", envFlagUsage)
rootCmd.PersistentFlags().StringVar(&rootInfo.KSCloudBEURLs, "env", "", envFlagUsage)
rootCmd.PersistentFlags().MarkDeprecated("environment", "use 'env' instead")
@@ -81,7 +71,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

@@ -1,7 +1,6 @@
package scan
import (
"context"
"fmt"
"io"
"os"
@@ -19,28 +18,28 @@ import (
)
var (
controlExample = fmt.Sprintf(`
controlExample = `
# Scan the 'privileged container' control
%[1]s scan control "privileged container"
kubescape scan control "privileged container"
# Scan list of controls separated with a comma
%[1]s scan control "privileged container","HostPath mount"
kubescape scan control "privileged container","allowed hostpath"
# Scan list of controls using the control ID separated with a comma
%[1]s scan control C-0058,C-0057
kubescape scan control C-0058,C-0057
Run '%[1]s list controls' for the list of supported controls
Run 'kubescape list controls' for the list of supported controls
Control documentation:
https://hub.armosec.io/docs/controls
`, cautils.ExecName())
`
)
// controlCmd represents the control command
func getControlCmd(ks meta.IKubescape, scanInfo *cautils.ScanInfo) *cobra.Command {
return &cobra.Command{
Use: "control <control names list>/<control ids list>",
Short: fmt.Sprintf("The controls you wish to use. Run '%[1]s list controls' for the list of supported controls", cautils.ExecName()),
Short: "The controls you wish to use. Run 'kubescape list controls' for the list of supported controls",
Example: controlExample,
Args: func(cmd *cobra.Command, args []string) error {
if len(args) > 0 {
@@ -62,13 +61,13 @@ func getControlCmd(ks meta.IKubescape, scanInfo *cautils.ScanInfo) *cobra.Comman
if err := validateFrameworkScanInfo(scanInfo); err != nil {
return err
}
// flagValidationControl(scanInfo)
scanInfo.PolicyIdentifier = []cautils.PolicyIdentifier{}
if len(args) == 0 {
scanInfo.ScanAll = true
} else { // expected control or list of control separated by ","
} else { // expected control or list of control sepparated by ","
// Read controls from input args
scanInfo.SetPolicyIdentifiers(strings.Split(args[0], ","), apisv1.KindControl)
@@ -97,12 +96,11 @@ func getControlCmd(ks meta.IKubescape, scanInfo *cautils.ScanInfo) *cobra.Comman
return err
}
ctx := context.TODO()
results, err := ks.Scan(ctx, scanInfo)
results, err := ks.Scan(scanInfo)
if err != nil {
logger.L().Fatal(err.Error())
}
if err := results.HandleResults(ctx); err != nil {
if err := results.HandleResults(); err != nil {
logger.L().Fatal(err.Error())
}
if !scanInfo.VerboseMode {
@@ -111,7 +109,7 @@ func getControlCmd(ks meta.IKubescape, scanInfo *cautils.ScanInfo) *cobra.Comman
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)))
}
enforceSeverityThresholds(results.GetResults().SummaryDetails.GetResourcesSeverityCounters(), scanInfo, terminateOnExceedingSeverity)
enforceSeverityThresholds(&results.GetResults().SummaryDetails.SeverityCounters, scanInfo, terminateOnExceedingSeverity)
return nil
},
@@ -122,10 +120,6 @@ func getControlCmd(ks meta.IKubescape, scanInfo *cautils.ScanInfo) *cobra.Comman
func validateControlScanInfo(scanInfo *cautils.ScanInfo) error {
severity := scanInfo.FailThresholdSeverity
if scanInfo.Submit && scanInfo.OmitRawResources {
return fmt.Errorf("you can use `omit-raw-resources` or `submit`, but not both")
}
if err := validateSeverity(severity); severity != "" && err != nil {
return err
}

View File

@@ -1,7 +1,6 @@
package scan
import (
"context"
"errors"
"fmt"
"io"
@@ -15,31 +14,31 @@ import (
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/enescakir/emoji"
"github.com/spf13/cobra"
)
var (
frameworkExample = fmt.Sprintf(`
# Scan all frameworks
%[1]s scan framework all
frameworkExample = `
# Scan all frameworks and submit the results
kubescape scan framework all --submit
# Scan the NSA framework
%[1]s scan framework nsa
kubescape scan framework nsa
# Scan the NSA and MITRE framework
%[1]s scan framework nsa,mitre
kubescape scan framework nsa,mitre
# Scan all frameworks
%[1]s scan framework all
kubescape scan framework all
# Scan kubernetes YAML manifest files (single file or glob)
%[1]s scan framework nsa .
kubescape scan framework nsa *.yaml
Run '%[1]s list frameworks' for the list of supported frameworks
`, cautils.ExecName())
Run 'kubescape list frameworks' for the list of supported frameworks
`
ErrUnknownSeverity = errors.New("unknown severity")
)
@@ -48,7 +47,7 @@ func getFrameworkCmd(ks meta.IKubescape, scanInfo *cautils.ScanInfo) *cobra.Comm
return &cobra.Command{
Use: "framework <framework names list> [`<glob pattern>`/`-`] [flags]",
Short: fmt.Sprintf("The framework you wish to use. Run '%[1]s list frameworks' for the list of supported frameworks", cautils.ExecName()),
Short: "The framework you wish to use. Run 'kubescape list frameworks' for the list of supported frameworks",
Example: frameworkExample,
Long: "Execute a scan on a running Kubernetes cluster or `yaml`/`json` files (use glob) or `-` for stdin",
Args: func(cmd *cobra.Command, args []string) error {
@@ -73,9 +72,6 @@ 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 { // scan all frameworks
@@ -85,12 +81,11 @@ func getFrameworkCmd(ks meta.IKubescape, scanInfo *cautils.ScanInfo) *cobra.Comm
frameworks = strings.Split(args[0], ",")
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 {
@@ -109,23 +104,22 @@ func getFrameworkCmd(ks meta.IKubescape, scanInfo *cautils.ScanInfo) *cobra.Comm
scanInfo.SetPolicyIdentifiers(frameworks, apisv1.KindFramework)
ctx := context.TODO()
results, err := ks.Scan(ctx, scanInfo)
results, err := ks.Scan(scanInfo)
if err != nil {
logger.L().Fatal(err.Error())
}
if err = results.HandleResults(ctx); err != nil {
if err = results.HandleResults(); err != nil {
logger.L().Fatal(err.Error())
}
if !scanInfo.VerboseMode {
cautils.SimpleDisplay(os.Stderr, "Run with '--verbose'/'-v' flag for detailed resources view\n\n")
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)))
}
enforceSeverityThresholds(results.GetData().Report.SummaryDetails.GetResourcesSeverityCounters(), scanInfo, terminateOnExceedingSeverity)
enforceSeverityThresholds(&results.GetData().Report.SummaryDetails.SeverityCounters, scanInfo, terminateOnExceedingSeverity)
return nil
},
}
@@ -142,10 +136,10 @@ func countersExceedSeverityThreshold(severityCounters reportsummary.ISeverityCou
SeverityName string
GetFailedResources func() int
}{
{reporthandlingapis.SeverityLowString, severityCounters.NumberOfLowSeverity},
{reporthandlingapis.SeverityMediumString, severityCounters.NumberOfMediumSeverity},
{reporthandlingapis.SeverityHighString, severityCounters.NumberOfHighSeverity},
{reporthandlingapis.SeverityCriticalString, severityCounters.NumberOfCriticalSeverity},
{reporthandlingapis.SeverityLowString, severityCounters.NumberOfResourcesWithLowSeverity},
{reporthandlingapis.SeverityMediumString, severityCounters.NumberOfResourcesWithMediumSeverity},
{reporthandlingapis.SeverityHighString, severityCounters.NumberOfResourcesWithHighSeverity},
{reporthandlingapis.SeverityCriticalString, severityCounters.NumberOfResourcesWithCriticalSeverity},
}
targetSeverityIdx := 0
@@ -168,14 +162,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
@@ -207,9 +201,7 @@ func validateFrameworkScanInfo(scanInfo *cautils.ScanInfo) error {
if 100 < scanInfo.FailThreshold || 0 > scanInfo.FailThreshold {
return fmt.Errorf("bad argument: out of range threshold")
}
if scanInfo.Submit && scanInfo.OmitRawResources {
return fmt.Errorf("you can use `omit-raw-resources` or `submit`, but not both")
}
severity := scanInfo.FailThresholdSeverity
if err := validateSeverity(severity); severity != "" && err != nil {
return err

View File

@@ -3,33 +3,32 @@ package scan
import (
"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"
"github.com/spf13/cobra"
)
var scanCmdExamples = fmt.Sprintf(`
var scanCmdExamples = `
Scan command is for scanning an existing cluster or kubernetes manifest files based on pre-defined frameworks
# Scan current cluster with all frameworks
%[1]s scan --enable-host-scan --verbose
kubescape scan --enable-host-scan --verbose
# Scan kubernetes YAML manifest files
%[1]s scan .
kubescape scan *.yaml
# Scan and save the results in the JSON format
%[1]s scan --format json --output results.json --format-version=v2
kubescape scan --format json --output results.json
# Display all resources
%[1]s scan --verbose
kubescape scan --verbose
# Scan different clusters from the kubectl context
%[1]s scan --kube-context <kubernetes context>
`, cautils.ExecName())
kubescape scan --kube-context <kubernetes context>
`
func GetScanCommand(ks meta.IKubescape) *cobra.Command {
var scanInfo cautils.ScanInfo
@@ -43,6 +42,7 @@ func GetScanCommand(ks meta.IKubescape) *cobra.Command {
Args: func(cmd *cobra.Command, args []string) error {
if len(args) > 0 {
if args[0] != "framework" && args[0] != "control" {
scanInfo.ScanAll = true
return getFrameworkCmd(ks, &scanInfo).RunE(cmd, append([]string{"all"}, args...))
}
}
@@ -51,13 +51,13 @@ func GetScanCommand(ks meta.IKubescape) *cobra.Command {
RunE: func(cmd *cobra.Command, args []string) error {
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
},
PreRun: func(cmd *cobra.Command, args []string) {
k8sinterface.SetClusterContextName(scanInfo.KubeContext)
},
PostRun: func(cmd *cobra.Command, args []string) {
// TODO - revert context
@@ -65,7 +65,6 @@ 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")
@@ -77,7 +76,7 @@ func GetScanCommand(ks meta.IKubescape) *cobra.Command {
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().StringVar(&scanInfo.FailThresholdSeverity, "severity-threshold", "", "Severity threshold is the severity of failed controls at which the command fails and returns exit code 1")
scanCmd.PersistentFlags().StringVarP(&scanInfo.Format, "format", "f", "", `Output file format. Supported formats: "pretty-printer", "json", "junit", "prometheus", "pdf", "html", "sarif"`)
scanCmd.PersistentFlags().StringVarP(&scanInfo.Format, "format", "f", "pretty-printer", `Output format. Supported formats: "pretty-printer", "json", "junit", "prometheus", "pdf", "html", "sarif"`)
scanCmd.PersistentFlags().StringVar(&scanInfo.IncludeNamespaces, "include-namespaces", "", "scan specific namespaces. e.g: --include-namespaces ns-a,ns-b")
scanCmd.PersistentFlags().BoolVarP(&scanInfo.Local, "keep-local", "", false, "If you do not want your Kubescape results reported to configured backend.")
scanCmd.PersistentFlags().StringVarP(&scanInfo.Output, "output", "o", "", "Output file. Print output to file and not stdout")
@@ -86,18 +85,14 @@ func GetScanCommand(ks meta.IKubescape) *cobra.Command {
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().MarkDeprecated("silent", "use '--logger' flag instead. Flag will be removed at 1.May.2022")
// hidden flags
scanCmd.PersistentFlags().MarkHidden("host-scan-yaml") // this flag should be used very cautiously. We prefer users will not use it at all unless the DaemonSet can not run pods on the nodes
scanCmd.PersistentFlags().MarkHidden("omit-raw-resources")
scanCmd.PersistentFlags().MarkHidden("print-attack-tree")
// Retrieve --kubeconfig flag from https://github.com/kubernetes/kubectl/blob/master/pkg/cmd/cmd.go
scanCmd.PersistentFlags().AddGoFlag(flag.Lookup("kubeconfig"))

View File

@@ -1,8 +1,7 @@
package scan
import (
"context"
logger "github.com/kubescape/go-logger"
"github.com/kubescape/go-logger/helpers"
"github.com/kubescape/kubescape/v2/core/cautils"
@@ -25,91 +24,91 @@ func TestExceedsSeverity(t *testing.T) {
{
Description: "Critical failed resource should exceed Critical threshold",
ScanInfo: &cautils.ScanInfo{FailThresholdSeverity: "critical"},
SeverityCounters: &reportsummary.SeverityCounters{CriticalSeverityCounter: 1},
SeverityCounters: &reportsummary.SeverityCounters{ResourcesWithCriticalSeverityCounter: 1},
Want: true,
},
{
Description: "Critical failed resource should exceed Critical threshold set as constant",
ScanInfo: &cautils.ScanInfo{FailThresholdSeverity: apis.SeverityCriticalString},
SeverityCounters: &reportsummary.SeverityCounters{CriticalSeverityCounter: 1},
SeverityCounters: &reportsummary.SeverityCounters{ResourcesWithCriticalSeverityCounter: 1},
Want: true,
},
{
Description: "High failed resource should not exceed Critical threshold",
ScanInfo: &cautils.ScanInfo{FailThresholdSeverity: "critical"},
SeverityCounters: &reportsummary.SeverityCounters{HighSeverityCounter: 1},
SeverityCounters: &reportsummary.SeverityCounters{ResourcesWithHighSeverityCounter: 1},
Want: false,
},
{
Description: "Critical failed resource exceeds High threshold",
ScanInfo: &cautils.ScanInfo{FailThresholdSeverity: "high"},
SeverityCounters: &reportsummary.SeverityCounters{CriticalSeverityCounter: 1},
SeverityCounters: &reportsummary.SeverityCounters{ResourcesWithCriticalSeverityCounter: 1},
Want: true,
},
{
Description: "High failed resource exceeds High threshold",
ScanInfo: &cautils.ScanInfo{FailThresholdSeverity: "high"},
SeverityCounters: &reportsummary.SeverityCounters{HighSeverityCounter: 1},
SeverityCounters: &reportsummary.SeverityCounters{ResourcesWithHighSeverityCounter: 1},
Want: true,
},
{
Description: "Medium failed resource does not exceed High threshold",
ScanInfo: &cautils.ScanInfo{FailThresholdSeverity: "high"},
SeverityCounters: &reportsummary.SeverityCounters{MediumSeverityCounter: 1},
SeverityCounters: &reportsummary.SeverityCounters{ResourcesWithMediumSeverityCounter: 1},
Want: false,
},
{
Description: "Critical failed resource exceeds Medium threshold",
ScanInfo: &cautils.ScanInfo{FailThresholdSeverity: "medium"},
SeverityCounters: &reportsummary.SeverityCounters{CriticalSeverityCounter: 1},
SeverityCounters: &reportsummary.SeverityCounters{ResourcesWithCriticalSeverityCounter: 1},
Want: true,
},
{
Description: "High failed resource exceeds Medium threshold",
ScanInfo: &cautils.ScanInfo{FailThresholdSeverity: "medium"},
SeverityCounters: &reportsummary.SeverityCounters{HighSeverityCounter: 1},
SeverityCounters: &reportsummary.SeverityCounters{ResourcesWithHighSeverityCounter: 1},
Want: true,
},
{
Description: "Medium failed resource exceeds Medium threshold",
ScanInfo: &cautils.ScanInfo{FailThresholdSeverity: "medium"},
SeverityCounters: &reportsummary.SeverityCounters{MediumSeverityCounter: 1},
SeverityCounters: &reportsummary.SeverityCounters{ResourcesWithMediumSeverityCounter: 1},
Want: true,
},
{
Description: "Low failed resource does not exceed Medium threshold",
ScanInfo: &cautils.ScanInfo{FailThresholdSeverity: "medium"},
SeverityCounters: &reportsummary.SeverityCounters{LowSeverityCounter: 1},
SeverityCounters: &reportsummary.SeverityCounters{ResourcesWithLowSeverityCounter: 1},
Want: false,
},
{
Description: "Critical failed resource exceeds Low threshold",
ScanInfo: &cautils.ScanInfo{FailThresholdSeverity: "low"},
SeverityCounters: &reportsummary.SeverityCounters{CriticalSeverityCounter: 1},
SeverityCounters: &reportsummary.SeverityCounters{ResourcesWithCriticalSeverityCounter: 1},
Want: true,
},
{
Description: "High failed resource exceeds Low threshold",
ScanInfo: &cautils.ScanInfo{FailThresholdSeverity: "low"},
SeverityCounters: &reportsummary.SeverityCounters{HighSeverityCounter: 1},
SeverityCounters: &reportsummary.SeverityCounters{ResourcesWithHighSeverityCounter: 1},
Want: true,
},
{
Description: "Medium failed resource exceeds Low threshold",
ScanInfo: &cautils.ScanInfo{FailThresholdSeverity: "low"},
SeverityCounters: &reportsummary.SeverityCounters{MediumSeverityCounter: 1},
SeverityCounters: &reportsummary.SeverityCounters{ResourcesWithMediumSeverityCounter: 1},
Want: true,
},
{
Description: "Low failed resource exceeds Low threshold",
ScanInfo: &cautils.ScanInfo{FailThresholdSeverity: "low"},
SeverityCounters: &reportsummary.SeverityCounters{LowSeverityCounter: 1},
SeverityCounters: &reportsummary.SeverityCounters{ResourcesWithLowSeverityCounter: 1},
Want: true,
},
{
Description: "Unknown severity returns an error",
ScanInfo: &cautils.ScanInfo{FailThresholdSeverity: "unknown"},
SeverityCounters: &reportsummary.SeverityCounters{LowSeverityCounter: 1},
SeverityCounters: &reportsummary.SeverityCounters{ResourcesWithLowSeverityCounter: 1},
Want: false,
Error: ErrUnknownSeverity,
},
@@ -140,7 +139,7 @@ func Test_enforceSeverityThresholds(t *testing.T) {
}{
{
"Exceeding Critical severity counter should call the terminating function",
&reportsummary.SeverityCounters{CriticalSeverityCounter: 1},
&reportsummary.SeverityCounters{ResourcesWithCriticalSeverityCounter: 1},
&cautils.ScanInfo{FailThresholdSeverity: apis.SeverityCriticalString},
true,
},
@@ -161,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
}
@@ -194,7 +193,6 @@ 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) Fatal(msg string, details ...helpers.IDetails) {
firstDetail := details[0]

View File

@@ -1,9 +1,8 @@
package scan
import (
"testing"
"github.com/kubescape/kubescape/v2/core/cautils"
"testing"
)
// Test_validateControlScanInfo tests how scan info is validated for the `scan control` command

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"
@@ -20,24 +19,23 @@ import (
)
var (
rbacExamples = fmt.Sprintf(`
rbacExamples = `
# Submit cluster's Role-Based Access Control(RBAC)
%[1]s submit rbac
kubescape submit rbac
# Submit cluster's Role-Based Access Control(RBAC) with account ID
%[1]s submit rbac --account <account-id>
`, cautils.ExecName())
kubescape submit rbac --account <account-id>
`
)
// getRBACCmd represents the RBAC command
func getRBACCmd(ks meta.IKubescape, submitInfo *v1.Submit) *cobra.Command {
return &cobra.Command{
Use: "rbac",
Deprecated: "This command is deprecated and will not be supported after 1/Jan/2023. Please use the 'scan' command instead.",
Example: rbacExamples,
Short: "Submit cluster's Role-Based Access Control(RBAC)",
Long: ``,
RunE: func(_ *cobra.Command, args []string) error {
Use: "rbac",
Example: rbacExamples,
Short: "Submit cluster's Role-Based Access Control(RBAC)",
Long: ``,
RunE: func(cmd *cobra.Command, args []string) error {
if err := flagValidationSubmit(submitInfo); err != nil {
return err
@@ -52,7 +50,7 @@ func getRBACCmd(ks meta.IKubescape, submitInfo *v1.Submit) *cobra.Command {
}
if clusterConfig.GetAccountID() == "" {
return fmt.Errorf("account ID is not set, run '%[1]s submit rbac --account <account-id>'", cautils.ExecName())
return fmt.Errorf("account ID is not set, run 'kubescape submit rbac --account <account-id>'")
}
// list RBAC
@@ -67,7 +65,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,13 +1,11 @@
package submit
import (
"context"
"encoding/json"
"fmt"
"os"
"github.com/google/uuid"
"github.com/kubescape/kubescape/v2/core/cautils"
reporthandlingv2 "github.com/kubescape/opa-utils/reporthandling/v2"
logger "github.com/kubescape/go-logger"
@@ -52,7 +50,7 @@ func (resultsObject *ResultsObject) ListAllResources() (map[string]workloadinter
func getResultsCmd(ks meta.IKubescape, submitInfo *v1.Submit) *cobra.Command {
var resultsCmd = &cobra.Command{
Use: fmt.Sprintf("results <json file>\nExample:\n$ %[1]s submit results path/to/results.json --format-version v2", cautils.ExecName()),
Use: "results <json file>\nExample:\n$ kubescape submit results path/to/results.json --format-version v2",
Short: "Submit a pre scanned results file. The file must be in json format",
Long: ``,
RunE: func(cmd *cobra.Command, args []string) error {
@@ -83,13 +81,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 differnet between versions, this is for maintaining backward and forward compatibility. Supported:'v1'/'v2'")
return resultsCmd
}

View File

@@ -1,30 +1,22 @@
package submit
import (
"fmt"
"github.com/kubescape/kubescape/v2/core/cautils"
"github.com/kubescape/kubescape/v2/core/meta"
metav1 "github.com/kubescape/kubescape/v2/core/meta/datastructures/v1"
"github.com/spf13/cobra"
)
var submitCmdExamples = fmt.Sprintf(`
# Submit Kubescape scan results file
%[1]s submit results
var submitCmdExamples = `
# Submit exceptions file to Kubescape SaaS
%[1]s submit exceptions
`, cautils.ExecName())
`
func GetSubmitCmd(ks meta.IKubescape) *cobra.Command {
var submitInfo metav1.Submit
submitCmd := &cobra.Command{
Use: "submit <command>",
Short: "Submit an object to the Kubescape SaaS version",
Long: ``,
Example: submitCmdExamples,
Use: "submit <command>",
Short: "Submit an object to the Kubescape SaaS version",
Long: ``,
Run: func(cmd *cobra.Command, args []string) {
},
}

View File

@@ -5,7 +5,6 @@ package update
// kubescape update
import (
"fmt"
"os/exec"
"runtime"
@@ -14,17 +13,11 @@ import (
"github.com/spf13/cobra"
)
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 {

View File

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

View File

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

View File

@@ -1,7 +1,6 @@
package version
import (
"context"
"fmt"
"os"
@@ -15,14 +14,9 @@ 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"))
fmt.Fprintf(os.Stdout,
"Your current version is: %s [git enabled in build: %t]\n",
cautils.BuildNumber,
isGitEnabled(),
)
v := cautils.NewIVersionCheckHandler()
v.CheckLatestVersion(cautils.NewVersionCheckRequest(cautils.BuildNumber, "", "", "version"))
fmt.Fprintln(os.Stdout, "Your current version is: "+cautils.BuildNumber)
return nil
},
}

View File

@@ -1,12 +0,0 @@
package cautils
import (
"fmt"
"strings"
)
func GetControlLink(controlID string) string {
// For CIS Controls, cis-1.1.3 will be transformed to cis-1-1-3 in documentation link.
docLinkID := strings.ReplaceAll(controlID, ".", "-")
return fmt.Sprintf("https://hub.armosec.io/docs/%s", strings.ToLower(docLinkID))
}

View File

@@ -70,7 +70,7 @@ type ITenantConfig interface {
// set
SetTenant() error
UpdateCachedConfig() error
DeleteCachedConfig(ctx context.Context) error
DeleteCachedConfig() error
// getters
GetContextName() string
@@ -175,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
}
@@ -330,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
}
@@ -470,7 +470,10 @@ func (c *ClusterConfig) updateConfigMap() error {
}
func updateConfigFile(configObj *ConfigObj) error {
return os.WriteFile(ConfigFileFullPath(), configObj.Config(), 0664) //nolint:gosec
if err := os.WriteFile(ConfigFileFullPath(), configObj.Config(), 0664); err != nil {
return err
}
return nil
}
func (c *ClusterConfig) updateConfigData(configMap *corev1.ConfigMap) {

View File

@@ -1,13 +1,10 @@
package cautils
import (
"context"
"github.com/armosec/armoapi-go/armotypes"
"github.com/kubescape/k8s-interface/workloadinterface"
"github.com/kubescape/opa-utils/reporthandling"
apis "github.com/kubescape/opa-utils/reporthandling/apis"
"github.com/kubescape/opa-utils/reporthandling/attacktrack/v1alpha1"
"github.com/kubescape/opa-utils/reporthandling/results/v1/prioritization"
"github.com/kubescape/opa-utils/reporthandling/results/v1/resourcesresults"
reporthandlingv2 "github.com/kubescape/opa-utils/reporthandling/v2"
@@ -21,24 +18,21 @@ type OPASessionObj struct {
K8SResources *K8SResources // input k8s objects
ArmoResource *KSResources // input ARMO objects
AllPolicies *Policies // list of all frameworks
Policies []reporthandling.Framework // list of frameworks to scan
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>
ResourcesPrioritized map[string]prioritization.PrioritizedResource // resources prioritization information, map[<resource ID>]<prioritized resource>
ResourceAttackTracks map[string]v1alpha1.IAttackTrack // resources attack tracks, map[<resource ID>]<attack track>
AttackTracks map[string]v1alpha1.IAttackTrack
Report *reporthandlingv2.PostureReport // scan results v2 - Remove
RegoInputData RegoInputData // input passed to rego for scanning. map[<control name>][<input arguments>]
Report *reporthandlingv2.PostureReport // scan results v2 - Remove
Exceptions []armotypes.PostureExceptionPolicy // list of exceptions to apply on scan results
RegoInputData RegoInputData // input passed to rego for scanning. map[<control name>][<input arguments>]
Metadata *reporthandlingv2.Metadata
InfoMap map[string]apis.StatusInfo // Map errors of resources to StatusInfo
ResourceToControlsMap map[string][]string // map[<apigroup/apiversion/resource>] = [<control_IDs>]
SessionID string // SessionID
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
InfoMap map[string]apis.StatusInfo // Map errors of resources to StatusInfo
ResourceToControlsMap map[string][]string // map[<apigroup/apiversion/resource>] = [<control_IDs>]
SessionID string // SessionID
}
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,
@@ -50,8 +44,7 @@ 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),
OmitRawResources: scanInfo.OmitRawResources,
Metadata: scanInfoToScanMetadata(scanInfo),
}
}
@@ -101,7 +94,6 @@ type Exception struct {
type RegoInputData struct {
PostureControlInputs map[string][]string `json:"postureControlInputs"`
DataControlInputs map[string]string `json:"dataControlInputs"`
// ClusterName string `json:"clusterName"`
// K8sConfig RegoK8sConfig `json:"k8sconfig"`
}

View File

@@ -7,7 +7,6 @@ import (
spinnerpkg "github.com/briandowns/spinner"
"github.com/fatih/color"
"github.com/mattn/go-isatty"
"github.com/schollz/progressbar/v3"
)
var FailureDisplay = color.New(color.Bold, color.FgHiRed).FprintfFunc()
@@ -40,28 +39,3 @@ func StopSpinner() {
}
spinner.Stop()
}
type ProgressHandler struct {
title string
pb *progressbar.ProgressBar
}
func NewProgressHandler(title string) *ProgressHandler {
return &ProgressHandler{title: title}
}
func (p *ProgressHandler) Start(allSteps int) {
if isatty.IsTerminal(os.Stderr.Fd()) {
p.pb = progressbar.Default(int64(allSteps), p.title)
} else {
p.pb = progressbar.DefaultSilent(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

@@ -2,7 +2,6 @@ package cautils
import (
"bytes"
"context"
"encoding/json"
"fmt"
"os"
@@ -32,7 +31,7 @@ const (
)
// 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]string) {
func LoadResourcesFromHelmCharts(basePath string) (map[string][]workloadinterface.IMetadata, map[string]string) {
directories, _ := listDirs(basePath)
helmDirectories := make([]string, 0)
for _, dir := range directories {
@@ -48,7 +47,7 @@ func LoadResourcesFromHelmCharts(ctx context.Context, basePath string) (map[stri
if err == nil {
wls, errs := chart.GetWorkloadsWithDefaultValues()
if len(errs) > 0 {
logger.L().Ctx(ctx).Error(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
}
@@ -64,7 +63,7 @@ func LoadResourcesFromHelmCharts(ctx context.Context, basePath string) (map[stri
// If the contents at given path is a Kustomize Directory, LoadResourcesFromKustomizeDirectory will
// generate yaml files using "Kustomize" & renders a map of workloads from those yaml files
func LoadResourcesFromKustomizeDirectory(ctx context.Context, basePath string) (map[string][]workloadinterface.IMetadata, string) {
func LoadResourcesFromKustomizeDirectory(basePath string) (map[string][]workloadinterface.IMetadata, string) {
isKustomizeDirectory := IsKustomizeDirectory(basePath)
isKustomizeFile := IsKustomizeFile(basePath)
if ok := isKustomizeDirectory || isKustomizeFile; !ok {
@@ -88,7 +87,7 @@ func LoadResourcesFromKustomizeDirectory(ctx context.Context, basePath string) (
kustomizeDirectoryName := GetKustomizeDirectoryName(newBasePath)
if len(errs) > 0 {
logger.L().Ctx(ctx).Error(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 {
@@ -97,10 +96,10 @@ 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).Error(fmt.Sprintf("%v", errs))
logger.L().Error(fmt.Sprintf("%v", errs))
}
if len(files) == 0 {
return nil
@@ -108,7 +107,7 @@ func LoadResourcesFromFiles(ctx context.Context, input, rootPath string) map[str
workloads, errs := loadFiles(rootPath, files)
if len(errs) > 0 {
logger.L().Ctx(ctx).Error(fmt.Sprintf("%v", errs))
logger.L().Error(fmt.Sprintf("%v", errs))
}
return workloads

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 {

View File

@@ -1,14 +1,12 @@
package getter
import (
"fmt"
"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"
)
// =======================================================================================================================
@@ -26,11 +24,11 @@ func NewDownloadReleasedPolicy() *DownloadReleasedPolicy {
}
}
func (drp *DownloadReleasedPolicy) GetControl(ID string) (*reporthandling.Control, error) {
func (drp *DownloadReleasedPolicy) GetControl(policyName string) (*reporthandling.Control, error) {
var control *reporthandling.Control
var err error
control, err = drp.gs.GetOPAControlByID(ID)
control, err = drp.gs.GetOPAControl(policyName)
if err != nil {
return nil, err
}
@@ -57,29 +55,13 @@ func (drp *DownloadReleasedPolicy) ListFrameworks() ([]string, error) {
return drp.gs.GetOPAFrameworksNamesList()
}
func (drp *DownloadReleasedPolicy) ListControls() ([]string, error) {
controlsIDsList, err := drp.gs.GetOPAControlsIDsList()
if err != nil {
return []string{}, err
func (drp *DownloadReleasedPolicy) ListControls(listType ListType) ([]string, error) {
switch listType {
case ListID:
return drp.gs.GetOPAControlsIDsList()
default:
return drp.gs.GetOPAControlsNamesList()
}
controlsNamesList, err := drp.gs.GetOPAControlsNamesList()
if err != nil {
return []string{}, err
}
controls, err := drp.gs.GetOPAControls()
if err != nil {
return []string{}, err
}
var controlsFrameworksList [][]string
for _, control := range controls {
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], ","))
}
return controlsNamesWithIDsandFrameworksList, nil
}
func (drp *DownloadReleasedPolicy) GetControlsInputs(clusterName string) (map[string][]string, error) {

View File

@@ -6,13 +6,19 @@ import (
"github.com/kubescape/opa-utils/reporthandling/attacktrack/v1alpha1"
)
// supported listing
type ListType string
const ListID ListType = "id"
const ListName ListType = "name"
type IPolicyGetter interface {
GetFramework(name string) (*reporthandling.Framework, error)
GetFrameworks() ([]reporthandling.Framework, error)
GetControl(ID string) (*reporthandling.Control, error)
GetControl(name string) (*reporthandling.Control, error)
ListFrameworks() ([]string, error)
ListControls() ([]string, error)
ListControls(ListType) ([]string, error)
}
type IExceptionsGetter interface {

View File

@@ -2,6 +2,7 @@ package getter
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
@@ -20,19 +21,18 @@ func SaveInFile(policy interface{}, pathStr string) error {
if err != nil {
return err
}
err = os.WriteFile(pathStr, encodedData, 0644) //nolint:gosec
err = os.WriteFile(pathStr, []byte(fmt.Sprintf("%v", string(encodedData))), 0644)
if err != nil {
if os.IsNotExist(err) {
pathDir := path.Dir(pathStr)
// pathDir could contain subdirectories
if erm := os.MkdirAll(pathDir, 0755); erm != nil {
return erm
if err := os.Mkdir(pathDir, 0744); err != nil {
return err
}
} else {
return err
}
err = os.WriteFile(pathStr, encodedData, 0644) //nolint:gosec
err = os.WriteFile(pathStr, []byte(fmt.Sprintf("%v", string(encodedData))), 0644)
if err != nil {
return err
}
@@ -40,6 +40,13 @@ func SaveInFile(policy interface{}, pathStr string) error {
return nil
}
// JSONDecoder returns JSON decoder for given string
func JSONDecoder(origin string) *json.Decoder {
dec := json.NewDecoder(strings.NewReader(origin))
dec.UseNumber()
return dec
}
func HttpDelete(httpClient *http.Client, fullURL string, headers map[string]string) (string, error) {
req, err := http.NewRequest("DELETE", fullURL, nil)
@@ -58,7 +65,6 @@ func HttpDelete(httpClient *http.Client, fullURL string, headers map[string]stri
}
return respStr, nil
}
func HttpGetter(httpClient *http.Client, fullURL string, headers map[string]string) (string, error) {
req, err := http.NewRequest("GET", fullURL, nil)

View File

@@ -1,26 +0,0 @@
package getter
import (
"strings"
stdjson "encoding/json"
jsoniter "github.com/json-iterator/go"
)
var (
json jsoniter.API
)
func init() {
// NOTE(fredbi): attention, this configuration rounds floats down to 6 digits
// For finer-grained config, see: https://pkg.go.dev/github.com/json-iterator/go#section-readme
json = jsoniter.ConfigFastest
}
// JSONDecoder returns JSON decoder for given string
func JSONDecoder(origin string) *stdjson.Decoder {
dec := stdjson.NewDecoder(strings.NewReader(origin))
dec.UseNumber()
return dec
}

View File

@@ -1,32 +0,0 @@
package getter
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestJSONDecoder(t *testing.T) {
t.Run("should decode json string", func(t *testing.T) {
const input = `"xyz"`
d := JSONDecoder(input)
var receiver string
require.NoError(t, d.Decode(&receiver))
require.Equal(t, "xyz", receiver)
})
t.Run("should decode json number", func(t *testing.T) {
const input = `123.01`
d := JSONDecoder(input)
var receiver float64
require.NoError(t, d.Decode(&receiver))
require.Equal(t, 123.01, receiver)
})
t.Run("requires json quotes", func(t *testing.T) {
const input = `xyz`
d := JSONDecoder(input)
var receiver string
require.Error(t, d.Decode(&receiver))
})
}

View File

@@ -2,8 +2,9 @@ package getter
import (
"bytes"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"strings"
"time"
@@ -191,7 +192,7 @@ func (api *KSCloudAPI) GetFrameworks() ([]reporthandling.Framework, error) {
return frameworks, err
}
func (api *KSCloudAPI) GetControl(ID string) (*reporthandling.Control, error) {
func (api *KSCloudAPI) GetControl(policyName string) (*reporthandling.Control, error) {
return nil, fmt.Errorf("control api is not public")
}
@@ -305,7 +306,7 @@ func (api *KSCloudAPI) ListFrameworks() ([]string, error) {
return frameworkList, nil
}
func (api *KSCloudAPI) ListControls() ([]string, error) {
func (api *KSCloudAPI) ListControls(l ListType) ([]string, error) {
return nil, fmt.Errorf("control api is not public")
}
@@ -357,7 +358,7 @@ func (api *KSCloudAPI) Login() error {
return fmt.Errorf("error authenticating: %d", resp.StatusCode)
}
responseBody, err := io.ReadAll(resp.Body)
responseBody, err := ioutil.ReadAll(resp.Body)
if err != nil {
return err
}

View File

@@ -2,13 +2,14 @@ package getter
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"net/url"
"strings"
)
var NativeFrameworks = []string{"allcontrols", "nsa", "mitre"}
var NativeFrameworks = []string{"nsa", "mitre", "armobest", "devopsbest"}
func (api *KSCloudAPI) getFrameworkURL(frameworkName string) string {
u := url.URL{}

View File

@@ -1,7 +1,7 @@
package getter
import (
"errors"
"encoding/json"
"fmt"
"os"
"path/filepath"
@@ -9,25 +9,12 @@ import (
"github.com/armosec/armoapi-go/armotypes"
"github.com/kubescape/opa-utils/reporthandling"
"github.com/kubescape/opa-utils/reporthandling/attacktrack/v1alpha1"
)
// =======================================================================================================================
// ============================================== LoadPolicy =============================================================
// =======================================================================================================================
var (
DefaultLocalStore = getCacheDir()
ErrNotImplemented = errors.New("feature is currently not supported")
ErrNotFound = errors.New("name not found")
ErrNameRequired = errors.New("missing required input framework name")
ErrIDRequired = errors.New("missing required input control ID")
ErrFrameworkNotMatching = errors.New("framework from file not matching")
ErrControlNotMatching = errors.New("framework from file not matching")
_ IPolicyGetter = &LoadPolicy{}
_ IExceptionsGetter = &LoadPolicy{}
)
var DefaultLocalStore = getCacheDir()
func getCacheDir() string {
defaultDirPath := ".kubescape"
@@ -37,225 +24,125 @@ func getCacheDir() string {
return defaultDirPath
}
// LoadPolicy loads policies from a local repository.
// Load policies from a local repository
type LoadPolicy struct {
filePaths []string
}
// NewLoadPolicy builds a LoadPolicy.
func NewLoadPolicy(filePaths []string) *LoadPolicy {
return &LoadPolicy{
filePaths: filePaths,
}
}
// GetControl returns a control from the policy file.
func (lp *LoadPolicy) GetControl(controlID string) (*reporthandling.Control, error) {
if controlID == "" {
return nil, ErrIDRequired
}
// Return control from file
func (lp *LoadPolicy) GetControl(controlName string) (*reporthandling.Control, error) {
// NOTE: this assumes that only the first path contains either a valid control descriptor or a framework descriptor
control := &reporthandling.Control{}
filePath := lp.filePath()
buf, err := os.ReadFile(filePath)
f, err := os.ReadFile(filePath)
if err != nil {
return nil, err
}
// check if the file is a control descriptor: a ControlID field is populated.
var control reporthandling.Control
if err = json.Unmarshal(buf, &control); err == nil && control.ControlID != "" {
if strings.EqualFold(controlID, control.ControlID) {
return &control, nil
}
return nil, fmt.Errorf("controlID: %s: %w", controlID, ErrControlNotMatching)
if err = json.Unmarshal(f, control); err != nil {
return control, err
}
// check if the file is a framework descriptor
var framework reporthandling.Framework
if err = json.Unmarshal(buf, &framework); err != nil {
return nil, err
}
for _, toPin := range framework.Controls {
ctrl := toPin
if strings.EqualFold(ctrl.ControlID, controlID) {
return &ctrl, nil
if controlName != "" && !strings.EqualFold(controlName, control.Name) && !strings.EqualFold(controlName, control.ControlID) {
framework, err := lp.GetFramework(control.Name)
if err != nil {
return nil, fmt.Errorf("control from file not matching")
} else {
for _, ctrl := range framework.Controls {
if strings.EqualFold(ctrl.Name, controlName) || strings.EqualFold(ctrl.ControlID, controlName) {
control = &ctrl
break
}
}
}
}
return nil, fmt.Errorf("controlID: %s: %w", controlID, ErrControlNotMatching)
return control, err
}
// GetFramework retrieves a framework configuration from the policy paths.
func (lp *LoadPolicy) GetFramework(frameworkName string) (*reporthandling.Framework, error) {
if frameworkName == "" {
return nil, ErrNameRequired
}
for _, filePath := range lp.filePaths {
buf, err := os.ReadFile(filePath)
if err != nil {
return nil, err
}
var framework reporthandling.Framework
if err = json.Unmarshal(buf, &framework); err != nil {
return nil, err
}
if strings.EqualFold(frameworkName, framework.Name) {
return &framework, nil
}
}
return nil, fmt.Errorf("framework: %s: %w", frameworkName, ErrFrameworkNotMatching)
}
// GetFrameworks returns all configured framework descriptors.
func (lp *LoadPolicy) GetFrameworks() ([]reporthandling.Framework, error) {
frameworks := make([]reporthandling.Framework, 0, 10)
seenFws := make(map[string]struct{})
for _, f := range lp.filePaths {
buf, err := os.ReadFile(f)
if err != nil {
return nil, err
}
var framework reporthandling.Framework
if err = json.Unmarshal(buf, &framework); err != nil {
// ignore invalid framework files
continue
}
// dedupe
_, alreadyLoaded := seenFws[framework.Name]
if alreadyLoaded {
continue
}
seenFws[framework.Name] = struct{}{}
frameworks = append(frameworks, framework)
}
return frameworks, nil
}
// ListFrameworks lists the names of all configured frameworks in this policy.
func (lp *LoadPolicy) ListFrameworks() ([]string, error) {
frameworkNames := make([]string, 0, 10)
for _, f := range lp.filePaths {
buf, err := os.ReadFile(f)
if err != nil {
return nil, err
}
var framework reporthandling.Framework
if err := json.Unmarshal(buf, &framework); err != nil {
continue
}
if framework.Name == "" || contains(frameworkNames, framework.Name) {
continue
}
frameworkNames = append(frameworkNames, framework.Name)
}
return frameworkNames, nil
}
// ListControls returns the list of controls for this framework.
//
// At this moment, controls are listed for one single configured framework.
func (lp *LoadPolicy) ListControls() ([]string, error) {
controlIDs := make([]string, 0, 100)
filePath := lp.filePath()
buf, err := os.ReadFile(filePath)
if err != nil {
return nil, err
}
var framework reporthandling.Framework
if err = json.Unmarshal(buf, &framework); err != nil {
return nil, err
var err error
for _, filePath := range lp.filePaths {
framework = reporthandling.Framework{}
f, err := os.ReadFile(filePath)
if err != nil {
return nil, err
}
if err = json.Unmarshal(f, &framework); err != nil {
return nil, err
}
if strings.EqualFold(frameworkName, framework.Name) {
break
}
}
if frameworkName != "" && !strings.EqualFold(frameworkName, framework.Name) {
for _, ctrl := range framework.Controls {
controlIDs = append(controlIDs, ctrl.ControlID)
return nil, fmt.Errorf("framework from file not matching")
}
return controlIDs, nil
return &framework, err
}
// GetExceptions retrieves configured exceptions.
//
// NOTE: the cluster parameter is not used at this moment.
func (lp *LoadPolicy) GetExceptions(_ /* clusterName */ string) ([]armotypes.PostureExceptionPolicy, error) {
// NOTE: this assumes that the first path contains a valid exceptions descriptor
filePath := lp.filePath()
func (lp *LoadPolicy) GetFrameworks() ([]reporthandling.Framework, error) {
frameworks := []reporthandling.Framework{}
var err error
return frameworks, err
}
buf, err := os.ReadFile(filePath)
func (lp *LoadPolicy) ListFrameworks() ([]string, error) {
fwNames := []string{}
framework := &reporthandling.Framework{}
for _, f := range lp.filePaths {
file, err := os.ReadFile(f)
if err == nil {
if err := json.Unmarshal(file, framework); err == nil {
if !contains(fwNames, framework.Name) {
fwNames = append(fwNames, framework.Name)
}
}
}
}
return fwNames, nil
}
func (lp *LoadPolicy) ListControls(listType ListType) ([]string, error) {
// TODO - Support
return []string{}, fmt.Errorf("loading controls list from file is not supported")
}
func (lp *LoadPolicy) GetExceptions(clusterName string) ([]armotypes.PostureExceptionPolicy, error) {
filePath := lp.filePath()
exception := []armotypes.PostureExceptionPolicy{}
f, err := os.ReadFile(filePath)
if err != nil {
return nil, err
}
exception := make([]armotypes.PostureExceptionPolicy, 0, 300)
err = json.Unmarshal(buf, &exception)
err = json.Unmarshal(f, &exception)
return exception, err
}
// GetControlsInputs retrieves the map of control configs.
//
// NOTE: the cluster parameter is not used at this moment.
func (lp *LoadPolicy) GetControlsInputs(_ /* clusterName */ string) (map[string][]string, error) {
// NOTE: this assumes that only the first path contains a valid control inputs descriptor
func (lp *LoadPolicy) GetControlsInputs(clusterName string) (map[string][]string, error) {
filePath := lp.filePath()
accountConfig := &armotypes.CustomerConfig{}
f, err := os.ReadFile(filePath)
fileName := filepath.Base(filePath)
buf, err := os.ReadFile(filePath)
if err != nil {
formattedError := fmt.Errorf(
`Error opening %s file, "controls-config" will be downloaded from ARMO management portal`,
fileName,
)
formattedError := fmt.Errorf("Error opening %s file, \"controls-config\" will be downloaded from ARMO management portal", fileName)
return nil, formattedError
}
controlInputs := make(map[string][]string, 100) // from armotypes.Settings.PostureControlInputs
if err = json.Unmarshal(buf, &controlInputs); err != nil {
formattedError := fmt.Errorf(
`Error reading %s file, %v, "controls-config" will be downloaded from ARMO management portal`,
fileName, err,
)
return nil, formattedError
if err = json.Unmarshal(f, &accountConfig.Settings.PostureControlInputs); err == nil {
return accountConfig.Settings.PostureControlInputs, nil
}
return controlInputs, nil
}
formattedError := fmt.Errorf("Error reading %s file, %s, \"controls-config\" will be downloaded from ARMO management portal", fileName, err.Error())
// GetAttackTracks yields the attack tracks from a config file.
func (lp *LoadPolicy) GetAttackTracks() ([]v1alpha1.AttackTrack, error) {
attackTracks := make([]v1alpha1.AttackTrack, 0, 20)
buf, err := os.ReadFile(lp.filePath())
if err != nil {
return nil, err
}
if err = json.Unmarshal(buf, &attackTracks); err != nil {
return nil, err
}
return attackTracks, nil
return nil, formattedError
}
// temporary support for a list of files

View File

@@ -1,409 +1,13 @@
package getter
import (
"fmt"
"os"
"path/filepath"
"testing"
"github.com/stretchr/testify/require"
)
var mockFrameworkBasePath = filepath.Join("examples", "mocks", "frameworks")
func MockNewLoadPolicy() *LoadPolicy {
return &LoadPolicy{
filePaths: []string{""},
}
}
func TestLoadPolicy(t *testing.T) {
t.Parallel()
const (
testFramework = "MITRE"
testControl = "C-0053"
)
t.Run("with GetFramework", func(t *testing.T) {
t.Run("should retrieve named framework", func(t *testing.T) {
t.Parallel()
p := NewLoadPolicy([]string{testFrameworkFile(testFramework)})
fw, err := p.GetFramework(testFramework)
require.NoError(t, err)
require.NotNil(t, fw)
require.Equal(t, testFramework, fw.Name)
})
t.Run("should fail to retrieve framework", func(t *testing.T) {
t.Parallel()
p := NewLoadPolicy([]string{testFrameworkFile(testFramework)})
fw, err := p.GetFramework("wrong")
require.Error(t, err)
require.Nil(t, fw)
})
t.Run("edge case: should error on empty framework", func(t *testing.T) {
t.Parallel()
p := NewLoadPolicy([]string{testFrameworkFile(testFramework)})
fw, err := p.GetFramework("")
require.ErrorIs(t, err, ErrNameRequired)
require.Nil(t, fw)
})
t.Run("edge case: corrupted json", func(t *testing.T) {
t.Parallel()
const invalidFramework = "invalid-fw"
p := NewLoadPolicy([]string{testFrameworkFile(invalidFramework)})
fw, err := p.GetFramework(invalidFramework)
require.Error(t, err)
require.Nil(t, fw)
})
t.Run("edge case: missing json", func(t *testing.T) {
t.Parallel()
const invalidFramework = "nowheretobefound"
p := NewLoadPolicy([]string{testFrameworkFile(invalidFramework)})
_, err := p.GetFramework(invalidFramework)
require.Error(t, err)
})
})
t.Run("with GetControl", func(t *testing.T) {
t.Run("should retrieve named control from framework", func(t *testing.T) {
t.Parallel()
const (
expectedControlName = "Access container service account"
)
p := NewLoadPolicy([]string{testFrameworkFile(testFramework)})
ctrl, err := p.GetControl(testControl)
require.NoError(t, err)
require.NotNil(t, ctrl)
require.Equal(t, testControl, ctrl.ControlID)
require.Equal(t, expectedControlName, ctrl.Name)
})
t.Run("with single control descriptor", func(t *testing.T) {
const (
singleControl = "C-0001"
expectedControlName = "Forbidden Container Registries"
)
t.Run("should retrieve named control from control descriptor", func(t *testing.T) {
t.Parallel()
p := NewLoadPolicy([]string{testFrameworkFile(singleControl)})
ctrl, err := p.GetControl(singleControl)
require.NoError(t, err)
require.NotNil(t, ctrl)
require.Equal(t, singleControl, ctrl.ControlID)
require.Equal(t, expectedControlName, ctrl.Name)
})
t.Run("should fail to retrieve named control from control descriptor", func(t *testing.T) {
t.Parallel()
p := NewLoadPolicy([]string{testFrameworkFile(singleControl)})
ctrl, err := p.GetControl("wrong")
require.Error(t, err)
require.Nil(t, ctrl)
})
})
t.Run("with framework descriptor", func(t *testing.T) {
t.Run("should fail to retrieve named control", func(t *testing.T) {
t.Parallel()
const testControl = "wrong"
p := NewLoadPolicy([]string{testFrameworkFile(testFramework)})
ctrl, err := p.GetControl(testControl)
require.ErrorIs(t, err, ErrControlNotMatching)
require.Nil(t, ctrl)
})
})
t.Run("edge case: corrupted json", func(t *testing.T) {
t.Parallel()
const invalidControl = "invalid-fw"
p := NewLoadPolicy([]string{testFrameworkFile(invalidControl)})
_, err := p.GetControl(invalidControl)
require.Error(t, err)
})
t.Run("edge case: missing json", func(t *testing.T) {
t.Parallel()
const invalidControl = "nowheretobefound"
p := NewLoadPolicy([]string{testFrameworkFile(invalidControl)})
_, err := p.GetControl(invalidControl)
require.Error(t, err)
})
t.Run("edge case: should error on empty control", func(t *testing.T) {
t.Parallel()
p := NewLoadPolicy([]string{testFrameworkFile(testFramework)})
ctrl, err := p.GetControl("")
require.ErrorIs(t, err, ErrIDRequired)
require.Nil(t, ctrl)
})
})
t.Run("with ListFrameworks", func(t *testing.T) {
t.Run("should return all frameworks in the policy path", func(t *testing.T) {
t.Parallel()
const (
extraFramework = "NSA"
attackTracks = "attack-tracks"
)
p := NewLoadPolicy([]string{
testFrameworkFile(testFramework),
testFrameworkFile(extraFramework),
testFrameworkFile(extraFramework), // should be deduped
testFrameworkFile(attackTracks), // should be ignored
})
fws, err := p.ListFrameworks()
require.NoError(t, err)
require.Len(t, fws, 2)
require.Equal(t, testFramework, fws[0])
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()
const (
extraFramework = "NSA"
nowhere = "nowheretobeseen"
)
p := NewLoadPolicy([]string{
testFrameworkFile(testFramework),
testFrameworkFile(extraFramework),
testFrameworkFile(nowhere), // should raise an error
})
fws, err := p.ListFrameworks()
require.Error(t, err)
require.Nil(t, fws)
})
})
t.Run("edge case: policy without path", func(t *testing.T) {
t.Parallel()
p := NewLoadPolicy([]string{})
require.Empty(t, p.filePath())
})
t.Run("with GetFrameworks", func(t *testing.T) {
const extraFramework = "NSA"
t.Run("should return all configured frameworks", func(t *testing.T) {
t.Parallel()
p := NewLoadPolicy([]string{
testFrameworkFile(testFramework),
testFrameworkFile(extraFramework),
})
fws, err := p.GetFrameworks()
require.NoError(t, err)
require.Len(t, fws, 2)
require.Equal(t, testFramework, fws[0].Name)
require.Equal(t, extraFramework, fws[1].Name)
})
t.Run("should return dedupe configured frameworks", func(t *testing.T) {
t.Parallel()
const attackTracks = "attack-tracks"
p := NewLoadPolicy([]string{
testFrameworkFile(testFramework),
testFrameworkFile(extraFramework),
testFrameworkFile(extraFramework),
testFrameworkFile(attackTracks), // should be ignored
})
fws, err := p.GetFrameworks()
require.NoError(t, err)
require.Len(t, fws, 2)
require.Equal(t, testFramework, fws[0].Name)
require.Equal(t, extraFramework, fws[1].Name)
})
})
t.Run("with ListControls", func(t *testing.T) {
t.Run("should return controls", func(t *testing.T) {
t.Parallel()
p := NewLoadPolicy([]string{testFrameworkFile(testFramework)})
controlIDs, err := p.ListControls()
require.NoError(t, err)
require.Greater(t, len(controlIDs), 0)
require.Equal(t, testControl, controlIDs[0])
})
})
t.Run("with GetAttackTracks", func(t *testing.T) {
t.Run("should return attack tracks", func(t *testing.T) {
t.Parallel()
const attackTracks = "attack-tracks"
p := NewLoadPolicy([]string{testFrameworkFile(attackTracks)})
tracks, err := p.GetAttackTracks()
require.NoError(t, err)
require.Greater(t, len(tracks), 0)
for _, track := range tracks {
require.Equal(t, "AttackTrack", track.Kind)
}
})
t.Run("edge case: corrupted json", func(t *testing.T) {
t.Parallel()
const invalidTracks = "invalid-fw"
p := NewLoadPolicy([]string{testFrameworkFile(invalidTracks)})
_, err := p.GetAttackTracks()
require.Error(t, err)
})
t.Run("edge case: missing json", func(t *testing.T) {
t.Parallel()
const invalidTracks = "nowheretobefound"
p := NewLoadPolicy([]string{testFrameworkFile(invalidTracks)})
_, err := p.GetAttackTracks()
require.Error(t, err)
})
})
t.Run("with GetControlsInputs", func(t *testing.T) {
const cluster = "dummy" // unused parameter at the moment
t.Run("should return control inputs for a cluster", func(t *testing.T) {
t.Parallel()
fixture, expected := writeTempJSONControlInputs(t)
t.Cleanup(func() {
_ = os.Remove(fixture)
})
p := NewLoadPolicy([]string{fixture})
inputs, err := p.GetControlsInputs(cluster)
require.NoError(t, err)
require.EqualValues(t, expected, inputs)
})
t.Run("edge case: corrupted json", func(t *testing.T) {
t.Parallel()
const invalidInputs = "invalid-fw"
p := NewLoadPolicy([]string{testFrameworkFile(invalidInputs)})
_, err := p.GetControlsInputs(cluster)
require.Error(t, err)
})
t.Run("edge case: missing json", func(t *testing.T) {
t.Parallel()
const invalidInputs = "nowheretobefound"
p := NewLoadPolicy([]string{testFrameworkFile(invalidInputs)})
_, err := p.GetControlsInputs(cluster)
require.Error(t, err)
})
})
t.Run("with GetExceptions", func(t *testing.T) {
const cluster = "dummy" // unused parameter at the moment
t.Run("should return exceptions", func(t *testing.T) {
t.Parallel()
const exceptions = "exceptions"
p := NewLoadPolicy([]string{testFrameworkFile(exceptions)})
exceptionPolicies, err := p.GetExceptions(cluster)
require.NoError(t, err)
require.Greater(t, len(exceptionPolicies), 0)
t.Logf("len=%d", len(exceptionPolicies))
for _, policy := range exceptionPolicies {
require.NotEmpty(t, policy.Name)
}
})
t.Run("edge case: corrupted json", func(t *testing.T) {
t.Parallel()
const invalidInputs = "invalid-fw"
p := NewLoadPolicy([]string{testFrameworkFile(invalidInputs)})
_, err := p.GetExceptions(cluster)
require.Error(t, err)
})
t.Run("edge case: missing json", func(t *testing.T) {
t.Parallel()
const invalidInputs = "nowheretobefound"
p := NewLoadPolicy([]string{testFrameworkFile(invalidInputs)})
_, err := p.GetExceptions(cluster)
require.Error(t, err)
})
})
}
func testFrameworkFile(framework string) string {
return filepath.Join(".", "testdata", fmt.Sprintf("%s.json", framework))
}
func writeTempJSONControlInputs(t testing.TB) (string, map[string][]string) {
fileName := testFrameworkFile("control-inputs")
mock := map[string][]string{
"key1": {
"val1", "val2",
},
"key2": {
"val3", "val4",
},
}
buf, err := json.Marshal(mock)
require.NoError(t, err)
require.NoError(t, os.WriteFile(fileName, buf, 0600))
return fileName, mock
}

View File

@@ -1,85 +0,0 @@
{
"guid": "",
"name": "Forbidden Container Registries",
"attributes": {
"armoBuiltin": true,
"attackTracks": [
{
"attackTrack": "container",
"categories": [
"Initial access"
]
}
],
"controlTypeTags": [
"security",
"compliance"
],
"microsoftMitreColumns": [
"Initial Access"
]
},
"id": "C-0001",
"controlID": "C-0001",
"creationTime": "",
"description": "In cases where the Kubernetes cluster is provided by a CSP (e.g., AKS in Azure, GKE in GCP, or EKS in AWS), compromised cloud credential can lead to the cluster takeover. Attackers may abuse cloud account credentials or IAM mechanism to the clusters management layer.",
"remediation": "Limit the registries from which you pull container images from",
"rules": [
{
"guid": "",
"name": "rule-identify-blocklisted-image-registries",
"attributes": {
"armoBuiltin": true,
"m$K8sThreatMatrix": "Initial Access::Compromised images in registry"
},
"creationTime": "",
"rule": "package armo_builtins\nimport data\n# Check for images from blocklisted repos\n\nuntrustedImageRepo[msga] {\n\tpod := input[_]\n\tk := pod.kind\n\tk == \"Pod\"\n\tcontainer := pod.spec.containers[i]\n\tpath := sprintf(\"spec.containers[%v].image\", [format_int(i, 10)])\n\timage := container.image\n untrusted_or_public_registries(image)\n\n\tmsga := {\n\t\t\"alertMessage\": sprintf(\"image '%v' in container '%s' comes from untrusted registry\", [image, container.name]),\n\t\t\"packagename\": \"armo_builtins\",\n\t\t\"alertScore\": 2,\n\t\t\"fixPaths\": [],\n\t\t\"failedPaths\": [path],\n \"alertObject\": {\n\t\t\t\"k8sApiObjects\": [pod]\n\t\t}\n }\n}\n\nuntrustedImageRepo[msga] {\n\twl := input[_]\n\tspec_template_spec_patterns := {\"Deployment\",\"ReplicaSet\",\"DaemonSet\",\"StatefulSet\",\"Job\"}\n\tspec_template_spec_patterns[wl.kind]\n\tcontainer := wl.spec.template.spec.containers[i]\n\tpath := sprintf(\"spec.template.spec.containers[%v].image\", [format_int(i, 10)])\n\timage := container.image\n untrusted_or_public_registries(image)\n\n\tmsga := {\n\t\t\"alertMessage\": sprintf(\"image '%v' in container '%s' comes from untrusted registry\", [image, container.name]),\n\t\t\"packagename\": \"armo_builtins\",\n\t\t\"alertScore\": 2,\n\t\t\"fixPaths\": [],\n\t\t\"failedPaths\": [path],\n \"alertObject\": {\n\t\t\t\"k8sApiObjects\": [wl]\n\t\t}\n }\n}\n\nuntrustedImageRepo[msga] {\n\twl := input[_]\n\twl.kind == \"CronJob\"\n\tcontainer := wl.spec.jobTemplate.spec.template.spec.containers[i]\n\tpath := sprintf(\"spec.jobTemplate.spec.template.spec.containers[%v].image\", [format_int(i, 10)])\n\timage := container.image\n untrusted_or_public_registries(image)\n\n\tmsga := {\n\t\t\"alertMessage\": sprintf(\"image '%v' in container '%s' comes from untrusted registry\", [image, container.name]),\n\t\t\"packagename\": \"armo_builtins\",\n\t\t\"alertScore\": 2,\n\t\t\"fixPaths\": [],\n\t\t\"failedPaths\": [path],\n \"alertObject\": {\n\t\t\t\"k8sApiObjects\": [wl]\n\t\t}\n }\n}\n\nuntrusted_or_public_registries(image){\n\t# see default-config-inputs.json for list values\n\tuntrusted_registries := data.postureControlInputs.untrustedRegistries\n\trepo_prefix := untrusted_registries[_]\n\tstartswith(image, repo_prefix)\n}\n\nuntrusted_or_public_registries(image){\n\t# see default-config-inputs.json for list values\n\tpublic_registries := data.postureControlInputs.publicRegistries\n\trepo_prefix := public_registries[_]\n\tstartswith(image, repo_prefix)\n}",
"resourceEnumerator": "",
"ruleLanguage": "Rego",
"match": [
{
"apiGroups": [
"*"
],
"apiVersions": [
"*"
],
"resources": [
"Pod",
"Deployment",
"ReplicaSet",
"DaemonSet",
"StatefulSet",
"Job",
"CronJob"
]
}
],
"ruleDependencies": [],
"configInputs": [
"settings.postureControlInputs.publicRegistries",
"settings.postureControlInputs.untrustedRegistries"
],
"controlConfigInputs": [
{
"path": "settings.postureControlInputs.publicRegistries",
"name": "Public registries",
"description": "Kubescape checks none of these public registries are in use."
},
{
"path": "settings.postureControlInputs.untrustedRegistries",
"name": "Registries block list",
"description": "Kubescape checks none of the following registries are in use."
}
],
"description": "Identifying if pod container images are from unallowed registries",
"remediation": "Use images from safe registry",
"ruleQuery": "",
"relevantCloudProviders": null
}
],
"rulesIDs": [
""
],
"baseScore": 7
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,136 +0,0 @@
[
{
"apiVersion": "regolibrary.kubescape/v1alpha1",
"kind": "AttackTrack",
"metadata": {
"name": "node"
},
"spec": {
"data": {
"name": "Initial access",
"subSteps": [
{
"name": "Execution",
"subSteps": [
{
"name": "Persistence"
},
{
"name": "Credential access"
},
{
"name": "Defense evasion"
},
{
"name": "Discovery"
},
{
"name": "Lateral movement"
},
{
"name": "Impact - data theft"
},
{
"name": "Impact - data destruction"
},
{
"name": "Impact - service injection"
}
]
}
]
}
}
},
{
"apiVersion": "regolibrary.kubescape/v1alpha1",
"kind": "AttackTrack",
"metadata": {
"name": "kubeapi"
},
"spec": {
"data": {
"name": "Initial access",
"subSteps": [
{
"name": "Persistence"
},
{
"name": "Privilege escalation"
},
{
"name": "Credential access"
},
{
"name": "Discovery"
},
{
"name": "Lateral movement"
},
{
"name": "Defense evasion"
},
{
"name": "Impact - data destruction"
},
{
"name": "Impact - service injection"
}
]
}
}
},
{
"apiVersion": "regolibrary.kubescape/v1alpha1",
"kind": "AttackTrack",
"metadata": {
"name": "container"
},
"spec": {
"data": {
"name": "Initial access",
"subSteps": [
{
"name": "Execution",
"subSteps": [
{
"name": "Privilege escalation"
},
{
"name": "Credential access",
"subSteps": [
{
"name": "Impact - service access"
},
{
"name": "Impact - K8s API access",
"subSteps": [
{
"name": "Defense evasion - KubeAPI"
}
]
}
]
},
{
"name": "Discovery"
},
{
"name": "Lateral movement"
},
{
"name": "Impact - Data access in container"
},
{
"name": "Persistence"
}
]
},
{
"name": "Impact - service destruction"
}
]
}
}
}
]

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

View File

@@ -1,3 +0,0 @@
{
"guid": "",
}

View File

@@ -1,22 +0,0 @@
//go:build !gitenabled
package cautils
import (
"errors"
"github.com/kubescape/go-git-url/apis"
)
var ErrFatalNotSupportedByBuild = errors.New(`git scan not supported by this build. Build with tag "gitenabled" to enable the git scan feature`)
type gitRepository struct {
}
func newGitRepository(root string) (*gitRepository, error) {
return &gitRepository{}, ErrWarnNotSupportedByBuild
}
func (g *gitRepository) GetFileLastCommit(filePath string) (*apis.Commit, error) {
return nil, ErrFatalNotSupportedByBuild
}

View File

@@ -1,11 +0,0 @@
//go:build !gitenabled
package cautils
func (s *LocalGitRepositoryTestSuite) TestGetLastCommit() {
s.T().Log("warn: skipped testing native git functionality [GetLastCommit]")
}
func (s *LocalGitRepositoryTestSuite) TestGetFileLastCommit() {
s.T().Log("warn: skipped testing native git functionality [GetFileLastCommit]")
}

View File

@@ -1,141 +0,0 @@
//go:build gitenabled
package cautils
import (
"fmt"
"time"
"github.com/kubescape/go-git-url/apis"
git2go "github.com/libgit2/git2go/v33"
)
type gitRepository struct {
git2GoRepo *git2go.Repository
fileToLastCommit map[string]*git2go.Commit
}
func newGitRepository(root string) (*gitRepository, error) {
git2GoRepo, err := git2go.OpenRepository(root)
if err != nil {
return nil, err
}
return &gitRepository{
git2GoRepo: git2GoRepo,
}, nil
}
func (g *gitRepository) GetFileLastCommit(filePath string) (*apis.Commit, error) {
if len(g.fileToLastCommit) == 0 {
filePathToCommitTime := map[string]time.Time{}
filePathToCommit := map[string]*git2go.Commit{}
allCommits, _ := g.getAllCommits()
// builds a map of all files to their last commit
for _, commit := range allCommits {
// Ignore merge commits (2+ parents)
if commit.ParentCount() <= 1 {
tree, err := commit.Tree()
if err != nil {
continue
}
// ParentCount can be either 1 or 0 (initial commit)
// In case it's the initial commit, prevTree is nil
var prevTree *git2go.Tree
if commit.ParentCount() == 1 {
prevCommit := commit.Parent(0)
prevTree, err = prevCommit.Tree()
if err != nil {
continue
}
}
diff, err := g.git2GoRepo.DiffTreeToTree(prevTree, tree, nil)
if err != nil {
continue
}
numDeltas, err := diff.NumDeltas()
if err != nil {
continue
}
for i := 0; i < numDeltas; i++ {
delta, err := diff.Delta(i)
if err != nil {
continue
}
deltaFilePath := delta.NewFile.Path
commitTime := commit.Author().When
// In case we have the commit information for the file which is not the latest - we override it
if currentCommitTime, exists := filePathToCommitTime[deltaFilePath]; exists {
if currentCommitTime.Before(commitTime) {
filePathToCommitTime[deltaFilePath] = commitTime
filePathToCommit[deltaFilePath] = commit
}
} else {
filePathToCommitTime[deltaFilePath] = commitTime
filePathToCommit[deltaFilePath] = commit
}
}
}
}
g.fileToLastCommit = filePathToCommit
}
if relevantCommit, exists := g.fileToLastCommit[filePath]; exists {
return g.getCommit(relevantCommit), nil
}
return nil, fmt.Errorf("failed to get commit information for file: %s", filePath)
}
func (g *gitRepository) getAllCommits() ([]*git2go.Commit, error) {
logItr, itrErr := g.git2GoRepo.Walk()
if itrErr != nil {
return nil, itrErr
}
pushErr := logItr.PushHead()
if pushErr != nil {
return nil, pushErr
}
var allCommits []*git2go.Commit
err := logItr.Iterate(func(commit *git2go.Commit) bool {
if commit != nil {
allCommits = append(allCommits, commit)
return true
}
return false
})
if err != nil {
return nil, err
}
if err != nil {
return nil, err
}
return allCommits, nil
}
func (g *gitRepository) getCommit(commit *git2go.Commit) *apis.Commit {
return &apis.Commit{
SHA: commit.Id().String(),
Author: apis.Committer{
Name: commit.Author().Name,
Email: commit.Author().Email,
Date: commit.Author().When,
},
Message: commit.Message(),
Committer: apis.Committer{},
Files: []apis.Files{},
}
}

View File

@@ -1,44 +0,0 @@
//go:build gitenabled
package cautils
func (s *LocalGitRepositoryTestSuite) TestGetLastCommit() {
if localRepo, err := NewLocalGitRepository(s.gitRepositoryPaths["localrepo"]); s.NoError(err) {
if commit, err := localRepo.GetLastCommit(); s.NoError(err) {
s.Equal("7e09312b8017695fadcd606882e3779f10a5c832", commit.SHA)
s.Equal("Amir Malka", commit.Author.Name)
s.Equal("amirm@armosec.io", commit.Author.Email)
s.Equal("2022-05-22 19:11:57 +0300 +0300", commit.Author.Date.String())
s.Equal("added file B\n", commit.Message)
}
}
}
func (s *LocalGitRepositoryTestSuite) TestGetFileLastCommit() {
s.Run("fileA", func() {
if localRepo, err := NewLocalGitRepository(s.gitRepositoryPaths["localrepo"]); s.NoError(err) {
if commit, err := localRepo.GetFileLastCommit("fileA"); s.NoError(err) {
s.Equal("9fae4be19624297947d2b605cefbff516628612d", commit.SHA)
s.Equal("Amir Malka", commit.Author.Name)
s.Equal("amirm@armosec.io", commit.Author.Email)
s.Equal("2022-05-22 18:55:48 +0300 +0300", commit.Author.Date.String())
s.Equal("added file A\n", commit.Message)
}
}
})
s.Run("fileB", func() {
if localRepo, err := NewLocalGitRepository(s.gitRepositoryPaths["localrepo"]); s.NoError(err) {
if commit, err := localRepo.GetFileLastCommit("dirA/fileB"); s.NoError(err) {
s.Equal("7e09312b8017695fadcd606882e3779f10a5c832", commit.SHA)
s.Equal("Amir Malka", commit.Author.Name)
s.Equal("amirm@armosec.io", commit.Author.Email)
s.Equal("2022-05-22 19:11:57 +0300 +0300", commit.Author.Date.String())
s.Equal("added file B\n", commit.Message)
}
}
})
}

View File

@@ -1,16 +0,0 @@
package cautils
import (
"testing"
giturl "github.com/kubescape/go-git-url"
"github.com/stretchr/testify/require"
)
func TestEnsureRemoteParsed(t *testing.T) {
const remote = "git@gitlab.com:foobar/gitlab-tests/sample-project.git"
require.NotPanics(t, func() {
_, _ = giturl.NewGitURL(remote)
})
}

View File

@@ -3,6 +3,7 @@ package cautils
import (
_ "embed"
"encoding/json"
"io/ioutil"
"os"
"path/filepath"
"strings"
@@ -38,7 +39,7 @@ func (s *HelmChartTestSuite) SetupSuite() {
}
var obj interface{}
file, _ := os.ReadFile(filepath.Join("testdata", "helm_expected_default_values.json"))
file, _ := ioutil.ReadFile(filepath.Join("testdata", "helm_expected_default_values.json"))
_ = json.Unmarshal([]byte(file), &obj)
s.expectedDefaultValues = obj.(map[string]interface{})
}

View File

@@ -1,20 +0,0 @@
package cautils
import (
"os"
"path/filepath"
"strings"
)
// ExecName returns the correct name to use in examples depending on how kubescape is invoked
func ExecName() string {
n := "kubescape"
if IsKrewPlugin() {
return "kubectl " + n
}
return n
}
func IsKrewPlugin() bool {
return strings.HasPrefix(filepath.Base(os.Args[0]), "kubectl-")
}

View File

@@ -1,26 +1,26 @@
package cautils
import (
"errors"
"fmt"
"path"
"strings"
"time"
"github.com/armosec/go-git-url/apis"
gitv5 "github.com/go-git/go-git/v5"
configv5 "github.com/go-git/go-git/v5/config"
plumbingv5 "github.com/go-git/go-git/v5/plumbing"
"github.com/kubescape/go-git-url/apis"
git2go "github.com/libgit2/git2go/v33"
)
type LocalGitRepository struct {
*gitRepository
goGitRepo *gitv5.Repository
head *plumbingv5.Reference
config *configv5.Config
goGitRepo *gitv5.Repository
git2GoRepo *git2go.Repository
head *plumbingv5.Reference
config *configv5.Config
fileToLastCommit map[string]*git2go.Commit
}
var ErrWarnNotSupportedByBuild = errors.New(`git commits retrieval not supported by this build. Build with tag "gitenabled" to enable the full git scan feature`)
func NewLocalGitRepository(path string) (*LocalGitRepository, error) {
goGitRepo, err := gitv5.PlainOpenWithOptions(path, &gitv5.PlainOpenOptions{DetectDotGit: true})
if err != nil {
@@ -52,12 +52,11 @@ func NewLocalGitRepository(path string) (*LocalGitRepository, error) {
}
if repoRoot, err := l.GetRootDir(); err == nil {
gitRepository, err := newGitRepository(repoRoot)
if err != nil && !errors.Is(err, ErrWarnNotSupportedByBuild) {
git2GoRepo, err := git2go.OpenRepository(repoRoot)
if err != nil {
return l, err
}
l.gitRepository = gitRepository
l.git2GoRepo = git2GoRepo
}
return l, nil
@@ -73,10 +72,6 @@ func (g *LocalGitRepository) GetRemoteUrl() (string, error) {
branchName := g.GetBranchName()
if branchRef, branchFound := g.config.Branches[branchName]; branchFound {
remoteName := branchRef.Remote
// branchRef.Remote can be a reference to a config.Remotes entry or directly a gitUrl
if _, found := g.config.Remotes[remoteName]; !found {
return remoteName, nil
}
if len(g.config.Remotes[remoteName].URLs) == 0 {
return "", fmt.Errorf("expected to find URLs for remote '%s', branch '%s'", remoteName, branchName)
}
@@ -84,13 +79,10 @@ func (g *LocalGitRepository) GetRemoteUrl() (string, error) {
}
const defaultRemoteName string = "origin"
defaultRemote, ok := g.config.Remotes[defaultRemoteName]
if !ok {
return "", fmt.Errorf("did not find a default remote with name '%s'", defaultRemoteName)
} else if len(defaultRemote.URLs) == 0 {
if len(g.config.Remotes[defaultRemoteName].URLs) == 0 {
return "", fmt.Errorf("expected to find URLs for remote '%s'", defaultRemoteName)
}
return defaultRemote.URLs[0], nil
return g.config.Remotes[defaultRemoteName].URLs[0], nil
}
// GetName get origin name without the .git suffix
@@ -130,6 +122,120 @@ func (g *LocalGitRepository) GetLastCommit() (*apis.Commit, error) {
}, nil
}
func (g *LocalGitRepository) getAllCommits() ([]*git2go.Commit, error) {
logItr, itrErr := g.git2GoRepo.Walk()
if itrErr != nil {
return nil, itrErr
}
pushErr := logItr.PushHead()
if pushErr != nil {
return nil, pushErr
}
var allCommits []*git2go.Commit
err := logItr.Iterate(func(commit *git2go.Commit) bool {
if commit != nil {
allCommits = append(allCommits, commit)
return true
}
return false
})
if err != nil {
return nil, err
}
if err != nil {
return nil, err
}
return allCommits, nil
}
func (g *LocalGitRepository) GetFileLastCommit(filePath string) (*apis.Commit, error) {
if len(g.fileToLastCommit) == 0 {
filePathToCommitTime := map[string]time.Time{}
filePathToCommit := map[string]*git2go.Commit{}
allCommits, _ := g.getAllCommits()
// builds a map of all files to their last commit
for _, commit := range allCommits {
// Ignore merge commits (2+ parents)
if commit.ParentCount() <= 1 {
tree, err := commit.Tree()
if err != nil {
continue
}
// ParentCount can be either 1 or 0 (initial commit)
// In case it's the initial commit, prevTree is nil
var prevTree *git2go.Tree
if commit.ParentCount() == 1 {
prevCommit := commit.Parent(0)
prevTree, err = prevCommit.Tree()
if err != nil {
continue
}
}
diff, err := g.git2GoRepo.DiffTreeToTree(prevTree, tree, nil)
if err != nil {
continue
}
numDeltas, err := diff.NumDeltas()
if err != nil {
continue
}
for i := 0; i < numDeltas; i++ {
delta, err := diff.Delta(i)
if err != nil {
continue
}
deltaFilePath := delta.NewFile.Path
commitTime := commit.Author().When
// In case we have the commit information for the file which is not the latest - we override it
if currentCommitTime, exists := filePathToCommitTime[deltaFilePath]; exists {
if currentCommitTime.Before(commitTime) {
filePathToCommitTime[deltaFilePath] = commitTime
filePathToCommit[deltaFilePath] = commit
}
} else {
filePathToCommitTime[deltaFilePath] = commitTime
filePathToCommit[deltaFilePath] = commit
}
}
}
}
g.fileToLastCommit = filePathToCommit
}
if relevantCommit, exists := g.fileToLastCommit[filePath]; exists {
return g.getCommit(relevantCommit), nil
}
return nil, fmt.Errorf("failed to get commit information for file: %s", filePath)
}
func (g *LocalGitRepository) getCommit(commit *git2go.Commit) *apis.Commit {
return &apis.Commit{
SHA: commit.Id().String(),
Author: apis.Committer{
Name: commit.Author().Name,
Email: commit.Author().Email,
Date: commit.Author().When,
},
Message: commit.Message(),
Committer: apis.Committer{},
Files: []apis.Files{},
}
}
func (g *LocalGitRepository) GetRootDir() (string, error) {
wt, err := g.goGitRepo.Worktree()
if err != nil {

View File

@@ -9,8 +9,6 @@ import (
"strings"
"testing"
configv5 "github.com/go-git/go-git/v5/config"
plumbingv5 "github.com/go-git/go-git/v5/plumbing"
"github.com/stretchr/testify/suite"
)
@@ -28,58 +26,40 @@ func unzipFile(zipPath, destinationFolder string) (*zip.ReadCloser, error) {
if err != nil {
return nil, err
}
for _, f := range archive.File {
filePath := filepath.Join(destinationFolder, f.Name) //nolint:gosec
filePath := filepath.Join(destinationFolder, f.Name)
if !strings.HasPrefix(filePath, filepath.Clean(destinationFolder)+string(os.PathSeparator)) {
return nil, fmt.Errorf("invalid file path")
}
if f.FileInfo().IsDir() {
os.MkdirAll(filePath, os.ModePerm)
continue
}
if erc := copyFileInFolder(filePath, f); erc != nil {
return nil, erc
if err := os.MkdirAll(filepath.Dir(filePath), os.ModePerm); err != nil {
return nil, err
}
dstFile, err := os.OpenFile(filePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
if err != nil {
return nil, err
}
fileInArchive, err := f.Open()
if err != nil {
return nil, err
}
if _, err := io.Copy(dstFile, fileInArchive); err != nil {
return nil, err
}
dstFile.Close()
fileInArchive.Close()
}
return archive, err
}
func copyFileInFolder(filePath string, f *zip.File) (err error) {
if err = os.MkdirAll(filepath.Dir(filePath), os.ModePerm); err != nil {
return err
}
dstFile, err := os.OpenFile(filePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
if err != nil {
return err
}
defer func() {
_ = dstFile.Close()
}()
fileInArchive, err := f.Open()
if err != nil {
return err
}
defer func() {
_ = fileInArchive.Close()
}()
_, err = io.Copy(dstFile, fileInArchive) //nolint:gosec
if err = dstFile.Close(); err != nil {
return err
}
if err = fileInArchive.Close(); err != nil {
return err
}
return err
}
func (s *LocalGitRepositoryTestSuite) SetupSuite() {
@@ -152,49 +132,44 @@ func (s *LocalGitRepositoryTestSuite) TestGetOriginUrl() {
}
}
func TestGetRemoteUrl(t *testing.T) {
testCases := []struct {
Name string
LocalRepo LocalGitRepository
Want string
WantErr error
}{
{
Name: "Branch with missing upstream and missing 'origin' fallback should return an error",
LocalRepo: LocalGitRepository{
config: &configv5.Config{
Branches: make(map[string]*configv5.Branch),
Remotes: make(map[string]*configv5.RemoteConfig),
},
head: plumbingv5.NewReferenceFromStrings("HEAD", "ref: refs/heads/v4"),
},
Want: "",
WantErr: fmt.Errorf("did not find a default remote with name 'origin'"),
},
}
for _, tc := range testCases {
t.Run(tc.Name, func(t *testing.T) {
localRepo := LocalGitRepository{
config: &configv5.Config{
Branches: make(map[string]*configv5.Branch),
Remotes: make(map[string]*configv5.RemoteConfig),
},
head: plumbingv5.NewReferenceFromStrings("HEAD", "ref: refs/heads/v4"),
}
want := tc.Want
wantErr := tc.WantErr
got, gotErr := localRepo.GetRemoteUrl()
if got != want {
t.Errorf("Remote URLs dont match: got '%s', want '%s'", got, want)
}
if gotErr.Error() != wantErr.Error() {
t.Errorf("Errors dont match: got '%v', want '%v'", gotErr, wantErr)
}
},
)
func (s *LocalGitRepositoryTestSuite) TestGetLastCommit() {
if localRepo, err := NewLocalGitRepository(s.gitRepositoryPaths["localrepo"]); s.NoError(err) {
if commit, err := localRepo.GetLastCommit(); s.NoError(err) {
s.Equal("7e09312b8017695fadcd606882e3779f10a5c832", commit.SHA)
s.Equal("Amir Malka", commit.Author.Name)
s.Equal("amirm@armosec.io", commit.Author.Email)
s.Equal("2022-05-22 19:11:57 +0300 +0300", commit.Author.Date.String())
s.Equal("added file B\n", commit.Message)
}
}
}
func (s *LocalGitRepositoryTestSuite) TestGetFileLastCommit() {
s.Run("fileA", func() {
if localRepo, err := NewLocalGitRepository(s.gitRepositoryPaths["localrepo"]); s.NoError(err) {
if commit, err := localRepo.GetFileLastCommit("fileA"); s.NoError(err) {
s.Equal("9fae4be19624297947d2b605cefbff516628612d", commit.SHA)
s.Equal("Amir Malka", commit.Author.Name)
s.Equal("amirm@armosec.io", commit.Author.Email)
s.Equal("2022-05-22 18:55:48 +0300 +0300", commit.Author.Date.String())
s.Equal("added file A\n", commit.Message)
}
}
})
s.Run("fileB", func() {
if localRepo, err := NewLocalGitRepository(s.gitRepositoryPaths["localrepo"]); s.NoError(err) {
if commit, err := localRepo.GetFileLastCommit("dirA/fileB"); s.NoError(err) {
s.Equal("7e09312b8017695fadcd606882e3779f10a5c832", commit.SHA)
s.Equal("Amir Malka", commit.Author.Name)
s.Equal("amirm@armosec.io", commit.Author.Email)
s.Equal("2022-05-22 19:11:57 +0300 +0300", commit.Author.Date.String())
s.Equal("added file B\n", commit.Message)
}
}
})
}

View File

@@ -1,20 +1,21 @@
package cautils
import (
"context"
"encoding/json"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"
"github.com/armosec/armoapi-go/armotypes"
giturl "github.com/kubescape/go-git-url"
"github.com/kubescape/go-logger"
apisv1 "github.com/kubescape/opa-utils/httpserver/apis/v1"
giturl "github.com/armosec/go-git-url"
logger "github.com/kubescape/go-logger"
"github.com/kubescape/go-logger/helpers"
"github.com/kubescape/k8s-interface/k8sinterface"
"github.com/kubescape/kubescape/v2/core/cautils/getter"
apisv1 "github.com/kubescape/opa-utils/httpserver/apis/v1"
"github.com/kubescape/opa-utils/reporthandling"
reporthandlingv2 "github.com/kubescape/opa-utils/reporthandling/v2"
@@ -39,8 +40,7 @@ const (
// ScanCluster string = "cluster"
// ScanLocalFiles string = "yaml"
localControlInputsFilename string = "controls-inputs.json"
LocalExceptionsFilename string = "exceptions.json"
LocalAttackTracksFilename string = "attack-tracks.json"
localExceptionsFilename string = "exceptions.json"
)
type BoolPtrFlag struct {
@@ -94,7 +94,7 @@ const (
)
type PolicyIdentifier struct {
Identifier string // policy Identifier e.g. c-0012 for control, nsa,mitre for frameworks
Name string // policy name e.g. nsa,mitre,c-0012
Kind apisv1.NotificationPolicyKind // policy kind e.g. Framework,Control,Rule
Designators armotypes.PortalDesignator
}
@@ -104,7 +104,6 @@ type ScanInfo struct {
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
@@ -112,7 +111,7 @@ type ScanInfo struct {
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
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 //
@@ -121,7 +120,6 @@ type ScanInfo struct {
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
@@ -130,8 +128,6 @@ type ScanInfo struct {
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 {
@@ -141,16 +137,17 @@ type Getters struct {
AttackTracksGetter getter.IAttackTracksGetter
}
func (scanInfo *ScanInfo) Init(ctx context.Context) {
func (scanInfo *ScanInfo) Init() {
scanInfo.setUseFrom()
scanInfo.setUseArtifactsFrom(ctx)
scanInfo.setOutputFile()
scanInfo.setUseArtifactsFrom()
if scanInfo.ScanID == "" {
scanInfo.ScanID = uuid.NewString()
}
}
func (scanInfo *ScanInfo) setUseArtifactsFrom(ctx context.Context) {
func (scanInfo *ScanInfo) setUseArtifactsFrom() {
if scanInfo.UseArtifactsFrom == "" {
return
}
@@ -162,9 +159,9 @@ func (scanInfo *ScanInfo) setUseArtifactsFrom(ctx context.Context) {
scanInfo.UseArtifactsFrom = dir
}
// set frameworks files
files, err := os.ReadDir(scanInfo.UseArtifactsFrom)
files, err := ioutil.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 {
@@ -179,27 +176,35 @@ func (scanInfo *ScanInfo) setUseArtifactsFrom(ctx context.Context) {
// set config-inputs file
scanInfo.ControlsInputs = filepath.Join(scanInfo.UseArtifactsFrom, localControlInputsFilename)
// set exceptions
scanInfo.UseExceptions = filepath.Join(scanInfo.UseArtifactsFrom, LocalExceptionsFilename)
// set attack tracks
scanInfo.AttackTracks = filepath.Join(scanInfo.UseArtifactsFrom, LocalAttackTracksFilename)
scanInfo.UseExceptions = filepath.Join(scanInfo.UseArtifactsFrom, localExceptionsFilename)
}
func (scanInfo *ScanInfo) setUseFrom() {
if scanInfo.UseDefault {
for _, policy := range scanInfo.PolicyIdentifier {
scanInfo.UseFrom = append(scanInfo.UseFrom, getter.GetDefaultPath(policy.Identifier+".json"))
scanInfo.UseFrom = append(scanInfo.UseFrom, getter.GetDefaultPath(policy.Name+".json"))
}
}
}
// Formats returns a slice of output formats that have been requested for a given scan
func (scanInfo *ScanInfo) Formats() []string {
formatString := scanInfo.Format
if formatString != "" {
return strings.Split(scanInfo.Format, ",")
} else {
return []string{}
func (scanInfo *ScanInfo) setOutputFile() {
if scanInfo.Output == "" {
return
}
if scanInfo.Format == "json" {
if filepath.Ext(scanInfo.Output) != ".json" {
scanInfo.Output += ".json"
}
}
if scanInfo.Format == "junit" {
if filepath.Ext(scanInfo.Output) != ".xml" {
scanInfo.Output += ".xml"
}
}
if scanInfo.Format == "pdf" {
if filepath.Ext(scanInfo.Output) != ".pdf" {
scanInfo.Output += ".pdf"
}
}
}
@@ -208,7 +213,7 @@ func (scanInfo *ScanInfo) SetPolicyIdentifiers(policies []string, kind apisv1.No
if !scanInfo.contains(policy) {
newPolicy := PolicyIdentifier{}
newPolicy.Kind = kind
newPolicy.Identifier = policy
newPolicy.Name = policy
scanInfo.PolicyIdentifier = append(scanInfo.PolicyIdentifier, newPolicy)
}
}
@@ -216,14 +221,14 @@ func (scanInfo *ScanInfo) SetPolicyIdentifiers(policies []string, kind apisv1.No
func (scanInfo *ScanInfo) contains(policyName string) bool {
for _, policy := range scanInfo.PolicyIdentifier {
if policy.Identifier == policyName {
if policy.Name == policyName {
return true
}
}
return false
}
func scanInfoToScanMetadata(ctx context.Context, scanInfo *ScanInfo) *reporthandlingv2.Metadata {
func scanInfoToScanMetadata(scanInfo *ScanInfo) *reporthandlingv2.Metadata {
metadata := &reporthandlingv2.Metadata{}
metadata.ScanMetadata.Format = scanInfo.Format
@@ -244,7 +249,7 @@ func scanInfoToScanMetadata(ctx context.Context, scanInfo *ScanInfo) *reporthand
}
// append frameworks
for _, policy := range scanInfo.PolicyIdentifier {
metadata.ScanMetadata.TargetNames = append(metadata.ScanMetadata.TargetNames, policy.Identifier)
metadata.ScanMetadata.TargetNames = append(metadata.ScanMetadata.TargetNames, policy.Name)
}
metadata.ScanMetadata.KubescapeVersion = BuildNumber
@@ -277,7 +282,7 @@ func scanInfoToScanMetadata(ctx context.Context, scanInfo *ScanInfo) *reporthand
}
setContextMetadata(ctx, &metadata.ContextMetadata, inputFiles)
setContextMetadata(&metadata.ContextMetadata, inputFiles)
return metadata
}
@@ -321,7 +326,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{
@@ -331,7 +336,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:
@@ -348,7 +353,7 @@ func setContextMetadata(ctx context.Context, contextMetadata *reporthandlingv2.C
// 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)
@@ -44,30 +43,3 @@ func TestGetScanningContext(t *testing.T) {
assert.Equal(t, ContextCluster, GetScanningContext(""))
assert.Equal(t, ContextGitURL, GetScanningContext("https://github.com/kubescape/kubescape"))
}
func TestScanInfoFormats(t *testing.T) {
testCases := []struct {
Input string
Want []string
}{
{"", []string{}},
{"json", []string{"json"}},
{"pdf", []string{"pdf"}},
{"html", []string{"html"}},
{"sarif", []string{"sarif"}},
{"html,pdf,sarif", []string{"html", "pdf", "sarif"}},
{"pretty-printer,pdf,sarif", []string{"pretty-printer", "pdf", "sarif"}},
}
for _, tc := range testCases {
t.Run(tc.Input, func(t *testing.T) {
input := tc.Input
want := tc.Want
scanInfo := &ScanInfo{Format: input}
got := scanInfo.Formats()
assert.Equal(t, want, got)
})
}
}

View File

@@ -1,7 +1,6 @@
package cautils
import (
"context"
"encoding/json"
"fmt"
"net/http"
@@ -11,13 +10,12 @@ 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"
)
const SKIP_VERSION_CHECK_DEPRECATED_ENV = "KUBESCAPE_SKIP_UPDATE_CHECK"
const SKIP_VERSION_CHECK_ENV = "KS_SKIP_UPDATE_CHECK"
const CLIENT_ENV = "KS_CLIENT"
const SKIP_VERSION_CHECK_DEPRECATED = "KUBESCAPE_SKIP_UPDATE_CHECK"
const SKIP_VERSION_CHECK = "KS_SKIP_UPDATE_CHECK"
var BuildNumber string
var Client string
@@ -26,21 +24,16 @@ 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 != "" {
Client = v
}
if v, ok := os.LookupEnv(SKIP_VERSION_CHECK_ENV); ok && boolutils.StringToBool(v) {
if v, ok := os.LookupEnv(SKIP_VERSION_CHECK); ok && boolutils.StringToBool(v) {
return NewVersionCheckHandlerMock()
} else if v, ok := os.LookupEnv(SKIP_VERSION_CHECK_DEPRECATED_ENV); ok && boolutils.StringToBool(v) {
} else if v, ok := os.LookupEnv(SKIP_VERSION_CHECK_DEPRECATED); ok && boolutils.StringToBool(v) {
return NewVersionCheckHandlerMock()
}
return NewVersionCheckHandler()
@@ -99,17 +92,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 +113,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"
)
@@ -20,13 +19,9 @@ var (
"KubeletInfo",
"KubeProxyInfo",
"ControlPlaneInfo",
"CloudProviderInfo",
"CNIInfo",
}
CloudResources = []string{
cloudapis.CloudProviderDescribeKind,
cloudapis.CloudProviderDescribeRepositoriesKind,
cloudapis.CloudProviderListEntitiesForPoliciesKind,
"ClusterDescribe",
string(cloudsupport.TypeApiServerInfo),
}
)

View File

@@ -1,7 +1,6 @@
package core
import (
"context"
"fmt"
metav1 "github.com/kubescape/kubescape/v2/core/meta/datastructures/v1"
@@ -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, "", "", getKubernetesApi()) // change k8sinterface
return tenant.DeleteCachedConfig(ctx)
return tenant.DeleteCachedConfig()
}

View File

@@ -1,34 +1,24 @@
package core
import (
"context"
"fmt"
"os"
"path/filepath"
"strings"
"github.com/armosec/armoapi-go/armotypes"
logger "github.com/kubescape/go-logger"
"github.com/kubescape/go-logger/helpers"
"github.com/kubescape/kubescape/v2/core/cautils/getter"
metav1 "github.com/kubescape/kubescape/v2/core/meta/datastructures/v1"
)
const (
TargetControlsInputs = "controls-inputs"
TargetExceptions = "exceptions"
TargetControl = "control"
TargetFramework = "framework"
TargetArtifacts = "artifacts"
TargetAttackTracks = "attack-tracks"
)
var downloadFunc = map[string]func(context.Context, *metav1.DownloadInfo) error{
TargetControlsInputs: downloadConfigInputs,
TargetExceptions: downloadExceptions,
TargetControl: downloadControl,
TargetFramework: downloadFramework,
TargetArtifacts: downloadArtifacts,
TargetAttackTracks: downloadAttackTracks,
var downloadFunc = map[string]func(*metav1.DownloadInfo) error{
"controls-inputs": downloadConfigInputs,
"exceptions": downloadExceptions,
"control": downloadControl,
"framework": downloadFramework,
"artifacts": downloadArtifacts,
}
func DownloadSupportCommands() []string {
@@ -39,20 +29,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 +64,25 @@ 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).Error("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.Name, tenant.GetAccountID(), nil)
controlInputs, err := controlsInputsGetter.GetControlsInputs(tenant.GetContextName())
if err != nil {
return err
@@ -113,15 +102,18 @@ 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 {
var err error
tenant := getTenantConfig(&downloadInfo.Credentials, "", "", getKubernetesApi())
exceptionsGetter := getExceptionsGetter(ctx, "", tenant.GetAccountID(), nil)
exceptions, err := exceptionsGetter.GetExceptions(tenant.GetContextName())
if err != nil {
return err
exceptionsGetter := getExceptionsGetter("", tenant.GetAccountID(), nil)
exceptions := []armotypes.PostureExceptionPolicy{}
if tenant.GetAccountID() != "" {
exceptions, err = exceptionsGetter.GetExceptions(tenant.GetContextName())
if err != nil {
return err
}
}
if downloadInfo.FileName == "" {
downloadInfo.FileName = fmt.Sprintf("%s.json", downloadInfo.Target)
}
@@ -130,41 +122,17 @@ 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 {
var err error
tenant := getTenantConfig(&downloadInfo.Credentials, "", "", getKubernetesApi())
attackTracksGetter := getAttackTracksGetter(ctx, "", tenant.GetAccountID(), nil)
attackTracks, err := attackTracksGetter.GetAttackTracks()
if err != nil {
return err
}
if downloadInfo.FileName == "" {
downloadInfo.FileName = fmt.Sprintf("%s.json", downloadInfo.Target)
}
// save in file
err = getter.SaveInFile(attackTracks, filepath.Join(downloadInfo.Path, downloadInfo.FileName))
if err != nil {
return err
}
logger.L().Success("Downloaded", helpers.String("attack tracks", downloadInfo.Target), helpers.String("path", filepath.Join(downloadInfo.Path, downloadInfo.FileName)))
return nil
}
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 downloadInfo.Name == "" {
// if framework name not specified - download all frameworks
frameworks, err := g.GetFrameworks()
if err != nil {
@@ -181,9 +149,9 @@ func downloadFramework(ctx context.Context, downloadInfo *metav1.DownloadInfo) e
// return fmt.Errorf("missing framework name")
} else {
if downloadInfo.FileName == "" {
downloadInfo.FileName = fmt.Sprintf("%s.json", downloadInfo.Identifier)
downloadInfo.FileName = fmt.Sprintf("%s.json", downloadInfo.Name)
}
framework, err := g.GetFramework(downloadInfo.Identifier)
framework, err := g.GetFramework(downloadInfo.Name)
if err != nil {
return err
}
@@ -200,31 +168,31 @@ 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 == "" {
if downloadInfo.Name == "" {
// TODO - support
return fmt.Errorf("missing control ID")
return fmt.Errorf("missing control name")
}
if downloadInfo.FileName == "" {
downloadInfo.FileName = fmt.Sprintf("%s.json", downloadInfo.Identifier)
downloadInfo.FileName = fmt.Sprintf("%s.json", downloadInfo.Name)
}
controls, err := g.GetControl(downloadInfo.Identifier)
controls, err := g.GetControl(downloadInfo.Name)
if err != nil {
return fmt.Errorf("failed to download control id '%s', %s", downloadInfo.Identifier, err.Error())
return err
}
if controls == nil {
return fmt.Errorf("failed to download control id '%s' - received an empty objects", downloadInfo.Identifier)
return fmt.Errorf("failed to download control - received an empty objects")
}
downloadTo := filepath.Join(downloadInfo.Path, downloadInfo.FileName)
err = getter.SaveInFile(controls, downloadTo)
if err != nil {
return err
}
logger.L().Success("Downloaded", helpers.String("artifact", downloadInfo.Target), helpers.String("ID", downloadInfo.Identifier), helpers.String("path", downloadTo))
logger.L().Success("Downloaded", helpers.String("artifact", downloadInfo.Target), helpers.String("name", downloadInfo.Name), helpers.String("path", downloadTo))
return nil
}

View File

@@ -1,7 +1,6 @@
package core
import (
"context"
"fmt"
"strings"
@@ -15,14 +14,14 @@ 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)
@@ -41,12 +40,12 @@ func (ks *Kubescape) Fix(ctx context.Context, fixInfo *metav1.FixInfo) error {
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).Error(err.Error())
logger.L().Error(err.Error())
}
return fmt.Errorf("Failed to fix some resources, check the logs for more details")
}

View File

@@ -1,22 +1,17 @@
package core
import (
"context"
"fmt"
"os"
"github.com/kubescape/go-logger"
logger "github.com/kubescape/go-logger"
"github.com/kubescape/go-logger/helpers"
"github.com/kubescape/k8s-interface/k8sinterface"
"github.com/kubescape/kubescape/v2/core/cautils"
"github.com/kubescape/kubescape/v2/core/cautils/getter"
"github.com/kubescape/kubescape/v2/core/pkg/hostsensorutils"
"github.com/kubescape/kubescape/v2/core/pkg/resourcehandler"
"github.com/kubescape/kubescape/v2/core/pkg/resultshandling/printer"
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 +32,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})
@@ -50,9 +45,8 @@ func getExceptionsGetter(ctx context.Context, useExceptions string, accountID st
if downloadReleasedPolicy == nil {
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))
return getter.NewLoadPolicy([]string{getter.GetDefaultPath(cautils.LocalExceptionsFilename)})
if err := downloadReleasedPolicy.SetRegoObjects(); err != nil {
logger.L().Warning("failed to get exceptions from github release, this may affect the scanning results", helpers.Error(err))
}
return downloadReleasedPolicy
@@ -65,9 +59,7 @@ func getRBACHandler(tenantConfig cautils.ITenantConfig, k8s *k8sinterface.Kubern
return nil
}
func getReporter(ctx context.Context, tenantConfig cautils.ITenantConfig, reportID string, submit, fwScan bool, scanningContext cautils.ScanningContext) reporter.IReport {
ctx, 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 scanningContext != cautils.ContextCluster {
@@ -87,19 +79,17 @@ func getReporter(ctx context.Context, tenantConfig cautils.ITenantConfig, report
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(ctx, scanInfo.InputPatterns, registryAdaptors)
return resourcehandler.NewFileResourceHandler(scanInfo.InputPatterns, registryAdaptors)
}
getter.GetKSCloudAPIConnector()
rbacObjects := getRBACHandler(tenantConfig, k8s, scanInfo.Submit)
return resourcehandler.NewK8sResourceHandler(k8s, getFieldSelector(scanInfo), hostSensorHandler, rbacObjects, registryAdaptors)
}
func getHostSensorHandler(ctx context.Context, scanInfo *cautils.ScanInfo, k8s *k8sinterface.KubernetesApi) hostsensorutils.IHostSensor {
func getHostSensorHandler(scanInfo *cautils.ScanInfo, k8s *k8sinterface.KubernetesApi) hostsensorutils.IHostSensor {
if !k8sinterface.IsConnectedToCluster() || k8s == nil {
return &hostsensorutils.HostSensorHandlerMock{}
}
@@ -108,11 +98,12 @@ func getHostSensorHandler(ctx context.Context, scanInfo *cautils.ScanInfo, k8s *
// 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()))
logger.L().Warning(fmt.Sprintf("failed to create host scanner: %s", err.Error()))
return &hostsensorutils.HostSensorHandlerMock{}
}
return hostSensorHandler
@@ -130,18 +121,18 @@ func getFieldSelector(scanInfo *cautils.ScanInfo) resourcehandler.IFieldSelector
return &resourcehandler.EmptySelector{}
}
func policyIdentifierIdentities(pi []cautils.PolicyIdentifier) string {
policiesIdentities := ""
func policyIdentifierNames(pi []cautils.PolicyIdentifier) string {
policiesNames := ""
for i := range pi {
policiesIdentities += pi[i].Identifier
policiesNames += pi[i].Name
if i+1 < len(pi) {
policiesIdentities += ","
policiesNames += ","
}
}
if policiesIdentities == "" {
policiesIdentities = "all"
if policiesNames == "" {
policiesNames = "all"
}
return policiesIdentities
return policiesNames
}
// setSubmitBehavior - Setup the desired cluster behavior regarding submitting to the Kubescape Cloud BE
@@ -187,14 +178,10 @@ func setSubmitBehavior(scanInfo *cautils.ScanInfo, tenantConfig cautils.ITenantC
scanInfo.Submit = true
}
if scanInfo.CreateAccount {
scanInfo.Submit = true
}
}
// 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)
}
@@ -205,12 +192,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})
}
@@ -222,14 +209,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
@@ -252,10 +239,7 @@ func listFrameworksNames(policyGetter getter.IPolicyGetter) []string {
return getter.NativeFrameworks
}
func getAttackTracksGetter(ctx context.Context, attackTracks, accountID string, downloadReleasedPolicy *getter.DownloadReleasedPolicy) getter.IAttackTracksGetter {
if len(attackTracks) > 0 {
return getter.NewLoadPolicy([]string{attackTracks})
}
func getAttackTracksGetter(accountID string, downloadReleasedPolicy *getter.DownloadReleasedPolicy) getter.IAttackTracksGetter {
if accountID != "" {
g := getter.GetKSCloudAPIConnector() // download attack tracks from Kubescape Cloud backend
return g
@@ -263,20 +247,8 @@ func getAttackTracksGetter(ctx context.Context, attackTracks, accountID string,
if downloadReleasedPolicy == nil {
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 attack tracks from github release, loading attack tracks from cache", helpers.Error(err))
return getter.NewLoadPolicy([]string{getter.GetDefaultPath(cautils.LocalAttackTracksFilename)})
if err := downloadReleasedPolicy.SetRegoObjects(); err != nil {
logger.L().Warning("failed to get attack tracks from github release, this may affect the scanning results", helpers.Error(err))
}
return downloadReleasedPolicy
}
// getUIPrinter returns a printer that will be used to print to the programs UI (terminal)
func getUIPrinter(ctx context.Context, 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())
return p
}

View File

@@ -1,40 +0,0 @@
package core
import (
"context"
"reflect"
"testing"
"github.com/kubescape/kubescape/v2/core/cautils"
)
func Test_getUIPrinter(t *testing.T) {
scanInfo := &cautils.ScanInfo{
FormatVersion: "v2",
VerboseMode: true,
View: "control",
}
wantFormatVersion := scanInfo.FormatVersion
wantVerboseMode := scanInfo.VerboseMode
wantViewType := cautils.ViewTypes(scanInfo.View)
got := getUIPrinter(context.TODO(), 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)
}
if gotVerboseMode != wantVerboseMode {
t.Errorf("Got: %t, want: %t", gotVerboseMode, wantVerboseMode)
}
if gotViewType != wantViewType {
t.Errorf("Got: %v, want: %v", gotViewType, wantViewType)
}
}

View File

@@ -1,26 +1,22 @@
package core
import (
"context"
"encoding/json"
"fmt"
"sort"
"strings"
"github.com/kubescape/kubescape/v2/core/cautils"
"github.com/kubescape/kubescape/v2/core/cautils/getter"
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/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(*metav1.ListPolicies, []string){
"pretty-print": prettyPrintListFormat,
"json": jsonListFormat,
}
@@ -32,45 +28,45 @@ func ListSupportActions() []string {
}
return commands
}
func (ks *Kubescape) List(ctx context.Context, listPolicies *metav1.ListPolicies) error {
if policyListerFunc, ok := listFunc[listPolicies.Target]; ok {
policies, err := policyListerFunc(ctx, listPolicies)
func (ks *Kubescape) List(listPolicies *metav1.ListPolicies) error {
if f, ok := listFunc[listPolicies.Target]; ok {
policies, err := f(listPolicies)
if err != nil {
return err
}
sort.Strings(policies)
if listFormatFunction, ok := listFormatFunc[listPolicies.Format]; ok {
listFormatFunction(ctx, listPolicies.Target, policies)
} else {
return fmt.Errorf("Invalid format \"%s\", Supported formats: 'pretty-print'/'json' ", listPolicies.Format)
}
listFormatFunc[listPolicies.Format](listPolicies, policies)
return nil
}
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)
g := getPolicyGetter(nil, tenant.GetTenantEmail(), true, nil)
return listFrameworksNames(policyGetter), nil
return listFrameworksNames(g), 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)
return policyGetter.ListControls()
g := getPolicyGetter(nil, tenant.GetTenantEmail(), false, nil)
l := getter.ListName
if listPolicies.ListIDs {
l = getter.ListID
}
return g.ListControls(l)
}
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
@@ -81,73 +77,12 @@ func listExceptions(ctx context.Context, listPolicies *metav1.ListPolicies) ([]s
return exceptionsNames, nil
}
func prettyPrintListFormat(ctx context.Context, targetPolicy string, policies []string) {
if targetPolicy == "controls" {
prettyPrintControls(ctx, policies)
return
}
header := fmt.Sprintf("Supported %s", targetPolicy)
policyTable := tablewriter.NewWriter(printer.GetWriter(ctx, ""))
policyTable.SetAutoWrapText(true)
policyTable.SetHeader([]string{header})
policyTable.SetHeaderLine(true)
policyTable.SetRowLine(true)
data := v2.Matrix{}
controlRows := generatePolicyRows(policies)
data = append(data, controlRows...)
policyTable.SetAlignment(tablewriter.ALIGN_CENTER)
policyTable.AppendBulk(data)
policyTable.Render()
func prettyPrintListFormat(listPolicies *metav1.ListPolicies, policies []string) {
sep := "\n * "
fmt.Printf("Supported %s:%s%s\n", listPolicies.Target, sep, strings.Join(policies, sep))
}
func jsonListFormat(_ context.Context, _ string, policies []string) {
func jsonListFormat(listPolicies *metav1.ListPolicies, 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(true)
controlsTable.SetHeader([]string{"Control ID", "Control Name", "Docs", "Frameworks"})
controlsTable.SetHeaderLine(true)
controlsTable.SetRowLine(true)
data := v2.Matrix{}
controlRows := generateControlRows(policies)
data = append(data, controlRows...)
controlsTable.AppendBulk(data)
controlsTable.Render()
}
func generateControlRows(policies []string) [][]string {
rows := [][]string{}
for _, control := range policies {
idAndControlAndFrameworks := strings.Split(control, "|")
id, control, framework := idAndControlAndFrameworks[0], idAndControlAndFrameworks[1], idAndControlAndFrameworks[2]
docs := cautils.GetControlLink(id)
currentRow := []string{id, control, docs, framework}
rows = append(rows, currentRow)
}
return rows
}
func generatePolicyRows(policies []string) [][]string {
rows := [][]string{}
for _, policy := range policies {
currentRow := []string{policy}
rows = append(rows, currentRow)
}
return rows
}

View File

@@ -1,12 +1,14 @@
package core
import (
"context"
"fmt"
"github.com/kubescape/go-logger"
"github.com/kubescape/go-logger/helpers"
apisv1 "github.com/kubescape/opa-utils/httpserver/apis/v1"
"github.com/kubescape/k8s-interface/k8sinterface"
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/pkg/hostsensorutils"
@@ -17,8 +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"
apisv1 "github.com/kubescape/opa-utils/httpserver/apis/v1"
"go.opentelemetry.io/otel"
"github.com/kubescape/opa-utils/resources"
)
@@ -27,27 +27,24 @@ type componentInterfaces struct {
tenantConfig cautils.ITenantConfig
resourceHandler resourcehandler.IResourceHandler
report reporter.IReport
outputPrinters []printer.IPrinter
uiPrinter printer.IPrinter
printerHandler printer.IPrinter
hostSensorHandler hostsensorutils.IHostSensor
}
func getInterfaces(ctx context.Context, scanInfo *cautils.ScanInfo) componentInterfaces {
ctx, span := otel.Tracer("").Start(ctx, "getInterfaces")
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)
@@ -55,56 +52,46 @@ 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())
}
if scanInfo.OmitRawResources {
logger.L().Ctx(ctx).Warning("omit-raw-resources flag will be ignored in submit mode")
logger.L().Error(err.Error())
}
}
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, policyIdentifierNames(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 := getHostSensorHandler(scanInfo, k8s)
if err := hostSensorHandler.Init(); err != nil {
logger.L().Error("failed to init host scanner", helpers.Error(err))
hostSensorHandler = &hostsensorutils.HostSensorHandlerMock{}
}
spanHostScanner.End()
// excluding hostsensor namespace
if len(scanInfo.IncludeNamespaces) == 0 && hostSensorHandler.GetNamespace() != "" {
scanInfo.ExcludedNamespaces = fmt.Sprintf("%s,%s", scanInfo.ExcludedNamespaces, hostSensorHandler.GetNamespace())
}
// ================== setup registry adaptors ======================================
registryAdaptors, err := resourcehandler.NewRegistryAdaptors()
if err != nil {
logger.L().Ctx(ctx).Error("failed to initialize registry adaptors", helpers.Error(err))
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.GetScanningContext())
reportHandler := getReporter(tenantConfig, scanInfo.ScanID, scanInfo.Submit, scanInfo.FrameworkScan, scanInfo.GetScanningContext())
// setup printers
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)
}
uiPrinter := getUIPrinter(ctx, scanInfo.VerboseMode, scanInfo.FormatVersion, scanInfo.PrintAttackTree, cautils.ViewTypes(scanInfo.View))
// setup printer
printerHandler := resultshandling.NewPrinter(scanInfo.Format, scanInfo.FormatVersion, scanInfo.VerboseMode, cautils.ViewTypes(scanInfo.View))
printerHandler.SetWriter(scanInfo.Output)
// ================== return interface ======================================
@@ -112,22 +99,18 @@ func getInterfaces(ctx context.Context, scanInfo *cautils.ScanInfo) componentInt
tenantConfig: tenantConfig,
resourceHandler: resourceHandler,
report: reportHandler,
outputPrinters: outputPrinters,
uiPrinter: uiPrinter,
printerHandler: printerHandler,
hostSensorHandler: hostSensorHandler,
}
}
func (ks *Kubescape) Scan(ctx context.Context, scanInfo *cautils.ScanInfo) (*resultshandling.ResultsHandler, error) {
ctx, spanScan := otel.Tracer("").Start(ctx, "kubescape.Scan")
defer spanScan.End()
func (ks *Kubescape) Scan(scanInfo *cautils.ScanInfo) (*resultshandling.ResultsHandler, error) {
logger.L().Info("Kubescape scanner starting")
// ===================== Initialization =====================
ctxInit, spanInit := otel.Tracer("").Start(ctx, "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
@@ -137,10 +120,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(ctx, scanInfo.UseFrom, interfaces.tenantConfig.GetTenantEmail(), scanInfo.FrameworkScan, downloadReleasedPolicy)
scanInfo.Getters.ControlsInputsGetter = getConfigInputsGetter(ctx, scanInfo.ControlsInputs, interfaces.tenantConfig.GetAccountID(), downloadReleasedPolicy)
scanInfo.Getters.ExceptionsGetter = getExceptionsGetter(ctx, scanInfo.UseExceptions, interfaces.tenantConfig.GetAccountID(), downloadReleasedPolicy)
scanInfo.Getters.AttackTracksGetter = getAttackTracksGetter(ctx, 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(interfaces.tenantConfig.GetAccountID(), downloadReleasedPolicy)
// TODO - list supported frameworks/controls
if scanInfo.ScanAll {
@@ -150,40 +133,34 @@ 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(ctxInit).Error("failed to tear down host scanner", helpers.Error(err))
logger.L().Error("failed to tear down host scanner", helpers.Error(err))
}
}()
resultsHandling := resultshandling.NewResultsHandler(interfaces.report, interfaces.outputPrinters, interfaces.uiPrinter)
spanInit.End()
resultsHandling := resultshandling.NewResultsHandler(interfaces.report, interfaces.printerHandler)
// ===================== policies & resources =====================
ctxPolicies, spanPolicies := otel.Tracer("").Start(ctx, "policies & resources")
policyHandler := policyhandler.NewPolicyHandler(interfaces.resourceHandler)
scanData, err := policyHandler.CollectResources(ctxPolicies, scanInfo.PolicyIdentifier, scanInfo)
scanData, err := policyHandler.CollectResources(scanInfo.PolicyIdentifier, scanInfo)
if err != nil {
return resultsHandling, err
}
spanPolicies.End()
// ========================= opa testing =====================
ctxOpa, spanOpa := otel.Tracer("").Start(ctx, "opa testing")
deps := resources.NewRegoDependenciesData(k8sinterface.GetK8sConfig(), interfaces.tenantConfig.GetContextName())
reportResults := opaprocessor.NewOPAProcessor(scanData, deps)
if err := reportResults.ProcessRulesListenner(ctxOpa, cautils.NewProgressHandler("")); err != nil {
if err := reportResults.ProcessRulesListenner(); err != nil {
// TODO - do something
return resultsHandling, fmt.Errorf("%w", err)
}
spanOpa.End()
// ======================== prioritization ===================
_, spanPrioritization := otel.Tracer("").Start(ctx, "prioritization")
if priotizationHandler, err := resourcesprioritization.NewResourcesPrioritizationHandler(ctx, 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))
if priotizationHandler, err := resourcesprioritization.NewResourcesPrioritizationHandler(scanInfo.Getters.AttackTracksGetter); 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)
}
spanPrioritization.End()
// ========================= results handling =====================
resultsHandling.SetData(scanData)

View File

@@ -1,8 +1,6 @@
package core
import (
"context"
"github.com/kubescape/kubescape/v2/core/cautils"
"github.com/kubescape/kubescape/v2/core/cautils/getter"
"github.com/kubescape/kubescape/v2/core/meta/cliinterfaces"
@@ -11,7 +9,7 @@ import (
"github.com/kubescape/go-logger/helpers"
)
func (ks *Kubescape) Submit(ctx context.Context, submitInterfaces cliinterfaces.SubmitInterfaces) error {
func (ks *Kubescape) Submit(submitInterfaces cliinterfaces.SubmitInterfaces) error {
// list resources
report, err := submitInterfaces.SubmitObjects.SetResourcesReport()
@@ -28,7 +26,7 @@ func (ks *Kubescape) Submit(ctx context.Context, submitInterfaces cliinterfaces.
AllResources: allresources,
Metadata: &report.Metadata,
}
if err := submitInterfaces.Reporter.Submit(ctx, o); err != nil {
if err := submitInterfaces.Reporter.Submit(o); err != nil {
return err
}
logger.L().Success("Data has been submitted successfully")
@@ -37,13 +35,13 @@ func (ks *Kubescape) Submit(ctx context.Context, submitInterfaces cliinterfaces.
return nil
}
func (ks *Kubescape) SubmitExceptions(ctx context.Context, credentials *cautils.Credentials, excPath string) error {
func (ks *Kubescape) SubmitExceptions(credentials *cautils.Credentials, excPath string) error {
logger.L().Info("submitting exceptions", helpers.String("path", excPath))
// load cached config
tenantConfig := getTenantConfig(credentials, "", "", getKubernetesApi())
if err := tenantConfig.SetTenant(); err != nil {
logger.L().Ctx(ctx).Error("failed setting account ID", helpers.Error(err))
logger.L().Error("failed setting account ID", helpers.Error(err))
}
// load exceptions from file

View File

@@ -6,6 +6,6 @@ type DownloadInfo struct {
Path string // directory to save artifact. Default is "~/.kubescape/"
FileName string // can be empty
Target string // type of artifact to download
Identifier string // identifier of artifact to download
Name string // name of artifact to download
Credentials cautils.Credentials
}

View File

@@ -4,6 +4,7 @@ import "github.com/kubescape/kubescape/v2/core/cautils"
type ListPolicies struct {
Target string
ListIDs bool
Format string
Credentials cautils.Credentials
}

View File

@@ -1,8 +1,6 @@
package meta
import (
"context"
"github.com/kubescape/kubescape/v2/core/cautils"
"github.com/kubescape/kubescape/v2/core/meta/cliinterfaces"
metav1 "github.com/kubescape/kubescape/v2/core/meta/datastructures/v1"
@@ -10,24 +8,24 @@ import (
)
type IKubescape interface {
Scan(ctx context.Context, scanInfo *cautils.ScanInfo) (*resultshandling.ResultsHandler, error) // TODO - use scanInfo from v1
Scan(scanInfo *cautils.ScanInfo) (*resultshandling.ResultsHandler, error) // TODO - use scanInfo from v1
// policies
List(ctx context.Context, listPolicies *metav1.ListPolicies) error // TODO - return list response
Download(ctx context.Context, downloadInfo *metav1.DownloadInfo) error // TODO - return downloaded policies
List(listPolicies *metav1.ListPolicies) error // TODO - return list response
Download(downloadInfo *metav1.DownloadInfo) error // TODO - return downloaded policies
// submit
Submit(ctx context.Context, submitInterfaces cliinterfaces.SubmitInterfaces) error // TODO - func should receive object
SubmitExceptions(ctx context.Context, credentials *cautils.Credentials, excPath string) error // TODO - remove
Submit(submitInterfaces cliinterfaces.SubmitInterfaces) error // TODO - func should receive object
SubmitExceptions(credentials *cautils.Credentials, excPath string) error // TODO - remove
// config
SetCachedConfig(setConfig *metav1.SetConfig) error
ViewCachedConfig(viewConfig *metav1.ViewConfig) error
DeleteCachedConfig(ctx context.Context, deleteConfig *metav1.DeleteConfig) error
DeleteCachedConfig(deleteConfig *metav1.DeleteConfig) error
// delete
DeleteExceptions(deleteexceptions *metav1.DeleteExceptions) error
// fix
Fix(ctx context.Context, fixInfo *metav1.FixInfo) error
Fix(fixInfo *metav1.FixInfo) error
}

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