Compare commits

..

1 Commits

Author SHA1 Message Date
David Wertenteil
e09bb2e310 revert dockerfile
Signed-off-by: David Wertenteil <dwertent@armosec.io>
2023-08-17 14:36:58 +03:00
199 changed files with 7343 additions and 5133 deletions

2
.dockerignore Normal file
View File

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

View File

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

View File

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

View File

@@ -1,5 +1,4 @@
name: 02-create_release
permissions: read-all
on:
push:
tags:
@@ -16,20 +15,6 @@ jobs:
with:
SUB_STRING: "-rc"
binary-build:
permissions:
actions: read
checks: read
contents: read
deployments: read
discussions: read
id-token: write
issues: read
packages: write
pages: read
pull-requests: read
repository-projects: read
security-events: read
statuses: read
needs: [retag]
uses: ./.github/workflows/b-binary-build-and-e2e-tests.yaml
with:
@@ -42,19 +27,7 @@ jobs:
secrets: inherit
create-release:
permissions:
actions: read
checks: read
contents: write
deployments: read
discussions: read
id-token: write
issues: read
packages: read
pages: read
pull-requests: read
repository-projects: read
statuses: read
security-events: read
needs: [retag, binary-build]
uses: ./.github/workflows/c-create-release.yaml
with:
@@ -64,24 +37,14 @@ jobs:
secrets: inherit
publish-image:
permissions:
actions: read
checks: read
contents: read
deployments: read
discussions: read
id-token: write
issues: read
packages: write
pages: read
pull-requests: read
repository-projects: read
security-events: read
statuses: read
contents: read
uses: ./.github/workflows/d-publish-image.yaml
needs: [create-release, retag]
with:
client: "image-release"
image_name: "quay.io/${{ github.repository_owner }}/kubescape-cli"
image_name: "quay.io/${{ github.repository_owner }}/kubescape"
image_tag: ${{ needs.retag.outputs.NEW_TAG }}
support_platforms: true
cosign: true

View File

@@ -1,5 +1,4 @@
name: 03-post_release
permissions: read-all
on:
release:
types: [published]

View File

@@ -1,5 +1,4 @@
name: 04-publish_krew_plugin
permissions: read-all
on:
push:
tags:

View File

@@ -1,5 +1,4 @@
name: a-pr-scanner
permissions: read-all
on:
workflow_call:
inputs:
@@ -69,7 +68,7 @@ jobs:
- name: Convert coverage count to lcov format
uses: jandelgado/gcov2lcov-action@v1
- name: Submit coverage tests to Coveralls
continue-on-error: true
uses: coverallsapp/github-action@v1

View File

@@ -1,5 +1,5 @@
name: b-binary-build-and-e2e-tests
permissions: read-all
on:
workflow_dispatch:
inputs:
@@ -38,7 +38,7 @@ on:
BINARY_TESTS:
type: string
required: false
default: '[ "scan_nsa", "scan_mitre", "scan_with_exceptions", "scan_repository", "scan_local_file", "scan_local_glob_files", "scan_local_list_of_files", "scan_nsa_and_submit_to_backend", "scan_mitre_and_submit_to_backend", "scan_local_repository_and_submit_to_backend", "scan_repository_from_url_and_submit_to_backend", "scan_with_exception_to_backend", "scan_with_custom_framework", "scan_customer_configuration", "host_scanner", "scan_compliance_score", "control_cluster_from_CLI_config_scan_exclude_namespaces", "control_cluster_from_CLI_config_scan_include_namespaces", "control_cluster_from_CLI_config_scan_host_scanner_enabled", "control_cluster_from_CLI_config_scan_MITRE_framework", "control_cluster_from_CLI_vulnerabilities_scan_default", "control_cluster_from_CLI_vulnerabilities_scan_include_namespaces" ]'
default: '[ "scan_nsa", "scan_mitre", "scan_with_exceptions", "scan_repository", "scan_local_file", "scan_local_glob_files", "scan_local_list_of_files", "scan_nsa_and_submit_to_backend", "scan_mitre_and_submit_to_backend", "scan_local_repository_and_submit_to_backend", "scan_repository_from_url_and_submit_to_backend", "scan_with_exception_to_backend", "scan_with_custom_framework", "scan_customer_configuration", "host_scanner", "scan_compliance_score" ]'
workflow_call:
inputs:
@@ -71,7 +71,7 @@ on:
type: string
required: false
default: '[ "", "arm64"]'
jobs:
wf-preparation:
name: secret-validator
@@ -115,20 +115,7 @@ jobs:
echo "ARCH_MATRIX=$input" >> $GITHUB_OUTPUT
env:
input: ${{ inputs.ARCH_MATRIX }}
check-secret:
name: check if QUAYIO_REGISTRY_USERNAME & QUAYIO_REGISTRY_PASSWORD is set in github secrets
runs-on: ubuntu-latest
outputs:
is-secret-set: ${{ steps.check-secret-set.outputs.is-secret-set }}
steps:
- name: check if QUAYIO_REGISTRY_USERNAME & QUAYIO_REGISTRY_PASSWORD is set in github secrets
id: check-secret-set
env:
QUAYIO_REGISTRY_USERNAME: ${{ secrets.QUAYIO_REGISTRY_USERNAME }}
QUAYIO_REGISTRY_PASSWORD: ${{ secrets.QUAYIO_REGISTRY_PASSWORD }}
run: |
echo "is-secret-set=${{ env.QUAYIO_REGISTRY_USERNAME != '' && env.QUAYIO_REGISTRY_PASSWORD != '' }}" >> $GITHUB_OUTPUT
binary-build:
name: Create cross-platform build
@@ -207,6 +194,11 @@ jobs:
echo "DOCKER_CMD=${DOCKER_CMD}" >> $GITHUB_ENV;
if: matrix.os == 'ubuntu-20.04' && matrix.arch != ''
- name: Install MSYS2 & libgit2 (Windows)
shell: pwsh
run: .\build.ps1 all
if: matrix.os == 'windows-latest'
- name: Install pkg-config (macOS)
run: brew install pkg-config
if: matrix.os == 'macos-latest'
@@ -216,11 +208,11 @@ jobs:
if: matrix.os != 'windows-latest'
- name: Test core pkg
run: ${{ env.DOCKER_CMD }} go test -v ./...
run: ${{ env.DOCKER_CMD }} go test "-tags=static,gitenabled" -v ./...
if: "!startsWith(github.ref, 'refs/tags') && matrix.os == 'ubuntu-20.04' && matrix.arch == '' || startsWith(github.ref, 'refs/tags') && (matrix.os != 'macos-latest' || matrix.arch != 'arm64')"
- name: Test httphandler pkg
run: ${{ env.DOCKER_CMD }} sh -c 'cd httphandler && go test -v ./...'
run: ${{ env.DOCKER_CMD }} sh -c 'cd httphandler && go test "-tags=static,gitenabled" -v ./...'
if: "!startsWith(github.ref, 'refs/tags') && matrix.os == 'ubuntu-20.04' && matrix.arch == '' || startsWith(github.ref, 'refs/tags') && (matrix.os != 'macos-latest' || matrix.arch != 'arm64')"
- name: Build
@@ -276,39 +268,6 @@ jobs:
path: build/
if-no-files-found: error
build-http-image:
permissions:
contents: read
id-token: write
packages: write
pull-requests: read
needs: [check-secret]
uses: kubescape/workflows/.github/workflows/incluster-comp-pr-merged.yaml@main
with:
IMAGE_NAME: quay.io/${{ github.repository_owner }}/kubescape
IMAGE_TAG: ${{ inputs.RELEASE }}
COMPONENT_NAME: kubescape
CGO_ENABLED: 0
GO111MODULE: "on"
BUILD_PLATFORM: linux/amd64,linux/arm64
GO_VERSION: "1.20"
REQUIRED_TESTS: '[
"ks_microservice_create_2_cronjob_mitre_and_nsa_proxy",
"ks_microservice_triggering_with_cron_job",
"ks_microservice_update_cronjob_schedule",
"ks_microservice_delete_cronjob",
"ks_microservice_create_2_cronjob_mitre_and_nsa",
"ks_microservice_ns_creation",
"ks_microservice_on_demand",
"ks_microservice_mitre_framework_on_demand",
"ks_microservice_nsa_and_mitre_framework_demand",
"scan_compliance_score"
]'
COSIGN: true
HELM_E2E_TEST: true
FORCE: true
secrets: inherit
run-tests:
strategy:
fail-fast: false

View File

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

View File

@@ -1,5 +1,4 @@
name: c-create_release
permissions: read-all
on:
workflow_call:
inputs:

View File

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

View File

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

View File

@@ -1,5 +1,4 @@
name: d-publish-image
permissions: read-all
on:
workflow_call:
inputs:
@@ -39,8 +38,7 @@ jobs:
QUAYIO_REGISTRY_PASSWORD: ${{ secrets.QUAYIO_REGISTRY_PASSWORD }}
run: |
echo "is-secret-set=${{ env.QUAYIO_REGISTRY_USERNAME != '' && env.QUAYIO_REGISTRY_PASSWORD != '' }}" >> $GITHUB_OUTPUT
build-cli-image:
build-image:
needs: [check-secret]
if: needs.check-secret.outputs.is-secret-set == 'true'
name: Build image and upload to registry
@@ -58,16 +56,12 @@ jobs:
QUAY_PASSWORD: ${{ secrets.QUAYIO_REGISTRY_PASSWORD }}
QUAY_USERNAME: ${{ secrets.QUAYIO_REGISTRY_USERNAME }}
run: docker login -u="${QUAY_USERNAME}" -p="${QUAY_PASSWORD}" quay.io
- uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # ratchet:actions/download-artifact@v3.0.2
id: download-artifact
with:
path: .
- name: chmod +x
run: chmod +x -v kubescape-*/kubescape-*
- name: Build and push image for linux/amd64
run: docker buildx build . --file build/kubescape-cli.Dockerfile --tag ${{ inputs.image_name }}:${{ inputs.image_tag }} --tag ${{ inputs.image_name }}:latest --build-arg image_version=${{ inputs.image_tag }} --build-arg client=${{ inputs.client }} --build-arg ks_binary=kubescape-ubuntu-latest/kubescape-ubuntu-latest --push --platform linux/amd64
- name: Build and push image for linux/arm64
run: docker buildx build . --file build/kubescape-cli.Dockerfile --tag ${{ inputs.image_name }}:${{ inputs.image_tag }} --tag ${{ inputs.image_name }}:latest --build-arg image_version=${{ inputs.image_tag }} --build-arg client=${{ inputs.client }} --build-arg ks_binary=kubescape-arm64-ubuntu-latest/kubescape-arm64-ubuntu-latest --push --platform linux/arm64
- name: Build and push image
if: ${{ inputs.support_platforms }}
run: docker buildx build . --file build/Dockerfile --tag ${{ inputs.image_name }}:${{ inputs.image_tag }} --tag ${{ inputs.image_name }}:latest --build-arg image_version=${{ inputs.image_tag }} --build-arg client=${{ inputs.client }} --push --platform linux/amd64,linux/arm64
- name: Build and push image without amd64/arm64 support
if: ${{ !inputs.support_platforms }}
run: docker buildx build . --file build/Dockerfile --tag ${{ inputs.image_name }}:${{ inputs.image_tag }} --tag ${{ inputs.image_name }}:latest --build-arg image_version=${{ inputs.image_tag }} --build-arg client=${{ inputs.client }} --push
- name: Install cosign
uses: sigstore/cosign-installer@4079ad3567a89f68395480299c77e40170430341 # ratchet:sigstore/cosign-installer@main
with:

View File

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

View File

@@ -1,4 +1,3 @@
permissions: read-all
on:
issues:
types: [opened, labeled]

3
.gitignore vendored
View File

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

View File

@@ -1,23 +0,0 @@
# Adopters
# Well-known companies
Well-known companies who are using and/or contributing to Kubescape are (in alphabetical order):
* Accenture
* Amazon.com
* IBM
* Intel
* Meetup
* RedHat
* Scaleway
# Users
If you want to be listed here and share with others your experience, open a PR and add the bellow table:
| Name | Company | Use case | Contact for questions (optional) |
| ---- | ------- | -------- | -------------------------------- |
| Yonathan Amzallag | ARMO | Vulnerability monitoring | yonatana@armosec.io |

View File

@@ -1,65 +0,0 @@
# Governance of Kubescape
## Overview
The Kubescape project is an open-source initiative dedicated to improve security and best practices in Kubernetes environments. This document outlines the governance structure of the Kubescape project and provides guidance for its community contributors.
## Decision Making
### Maintainers
- Maintainers are responsible for the smooth operation of the project.
- They review and merge pull requests, manage releases, and ensure the quality and stability of the codebase.
- Maintainers are chosen based on their ongoing contributions and their demonstrated commitment to the project.
- Everyone who had at least 5 code contribution in the last 12 month can submit her/himself for joining the maintainer team
- Maintainers who are not taken part in the project work (code, reviews, discussions) for 12 month are automaticaly removed from the maintainer team
### Committers
- Committers are contributors who have made significant and consistent contributions to the project.
- They have the ability to merge minor pull requests if assigned by maintainers.
- A contributor can be proposed as a committer by any existing maintainer. The proposal will be reviewed and voted on by the existing maintainers.
### Community Members
- Anyone can become a community member by contributing to the project. This can be in the form of code contributions, documentation, or any other form of project support.
## Processes
### Proposing Changes
1. Open an issue on the project repository to discuss the proposed change.
2. Once there is consensus around the proposed change, create a pull request.
3. Pull requests will be reviewed by committers and/or maintainers.
4. Once the pull request has received approval, it can be merged into the main codebase.
### Conflict Resolution
1. In case of any conflicts, it is primarily the responsibility of the parties involved to resolve it.
2. If the conflict cannot be resolved, it will be escalated to the maintainers for resolution.
3. Maintainers' decision will be final in case of unresolved conflicts.
## Roles and Responsibilities
### Maintainers
- Ensure the quality and stability of the project.
- Resolve conflicts.
- Provide direction and set priorities for the project.
### Committers
- Review and merge minor pull requests.
- Assist maintainers in project tasks.
- Promote best practices within the community.
### Community Members
- Contribute to the project in any form.
- Participate in discussions and provide feedback.
- Respect the code of conduct and governance of the project.
## Changes to the Governance Document
Proposed changes to this governance document should follow the same process as any other code change to the Kubescape project (see "Proposing Changes").

View File

@@ -1,12 +1,11 @@
# Maintainers
The following table lists the Kubescape project core maintainers:
The following table lists the Kubescape project maintainers:
| Name | GitHub | Organization | Added/Renewed On |
| --- | --- | --- | --- |
| [Matthias Bertschy](https://www.linkedin.com/in/matthias-bertschy-b427b815/) | [@matthyx](https://github.com/matthyx) | [ARMO](https://www.armosec.io/) | 2023-01-01 |
| [Craig Box](https://www.linkedin.com/in/crbnz/) | [@craigbox](https://github.com/craigbox) | [ARMO](https://www.armosec.io/) | 2022-10-31 |
| [Ben Hirschberg](https://www.linkedin.com/in/benyamin-ben-hirschberg-66141890) | [@slashben](https://github.com/slashben) | [ARMO](https://www.armosec.io/) | 2021-09-01 |
| [Rotem Refael](https://www.linkedin.com/in/rotem-refael) | [@rotemamsa](https://github.com/rotemamsa) | [ARMO](https://www.armosec.io/) | 2021-10-11 |
| [David Wertenteil](https://www.linkedin.com/in/david-wertenteil-0ba277b9) | [@dwertent](https://github.com/dwertent) | [ARMO](https://www.armosec.io/) | 2021-09-01 |
| [Bezalel Brandwine](https://www.linkedin.com/in/bezalel-brandwine) | [@Bezbran](https://github.com/Bezbran) | [ARMO](https://www.armosec.io/) | 2021-09-01 |
| [Craig Box](https://www.linkedin.com/in/crbnz/) | [@craigbox](https://github.com/craigbox) | [ARMO](https://www.armosec.io/) | 2022-10-31 |

View File

@@ -4,10 +4,6 @@
[![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)
[![Artifact HUB](https://img.shields.io/endpoint?url=https://artifacthub.io/badge/repository/kubescape)](https://artifacthub.io/packages/search?repo=kubescape)
[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fkubescape%2Fkubescape.svg?type=shield&issueType=license)](https://app.fossa.com/projects/git%2Bgithub.com%2Fkubescape%2Fkubescape?ref=badge_shield&issueType=license)
[![OpenSSF Best Practices](https://www.bestpractices.dev/projects/6944/badge)](https://www.bestpractices.dev/projects/6944)
[![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/kubescape/kubescape/badge)](https://securityscorecards.dev/viewer/?uri=github.com/kubescape/kubescape)
[![Twitter Follow](https://img.shields.io/twitter/follow/kubescape?style=social)](https://twitter.com/kubescape)
# Kubescape
@@ -73,11 +69,7 @@ We hold [community meetings](https://zoom.us/j/95174063585) on Zoom, on the firs
The Kubescape project follows the [CNCF Code of Conduct](https://github.com/cncf/foundation/blob/master/code-of-conduct.md).
### Adopters
See [here](ADOPTERS.md) a list of adopters.
## Contributions
## Contributions
Thanks to all our contributors! Check out our [CONTRIBUTING](CONTRIBUTING.md) file to learn how to join them.
@@ -91,10 +83,6 @@ Thanks to all our contributors! Check out our [CONTRIBUTING](CONTRIBUTING.md) f
<img src = "https://contrib.rocks/image?repo=kubescape/kubescape"/>
</a>
## Changelog
Kubescape changes are tracked on the [release](https://github.com/kubescape/kubescape/releases) page
## License
Copyright 2021-2023, the Kubescape Authors. All rights reserved. Kubescape is released under the Apache 2.0 license. See the [LICENSE](LICENSE) file for details.

View File

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

View File

@@ -1,7 +0,0 @@
# Reporting Security Issues
To report a security issue or vulnerability, submit a [private vulnerability report via GitHub](https://github.com/kubescape/kubescape/security/advisories/new) to the repository maintainers with a description of the issue, the steps you took to create the issue, affected versions, and, if known, mitigations for the issue.
The maintainers will respond within 7 working days of your report. If the issue is confirmed as a vulnerability, we will open a Security Advisory and acknowledge your contributions as part of it. This project follows a 90 day disclosure timeline.
Other contacts: cncf-kubescape-maintainers@lists.cncf.io

View File

@@ -65,9 +65,6 @@ def main():
ldflags += " -X {}={}".format(client_var, client_name)
build_command = ["go", "build", "-buildmode=pie", "-tags=static,gitenabled", "-o", ks_file, "-ldflags" ,ldflags]
if CURRENT_PLATFORM == "Windows":
os.putenv("CGO_ENABLED", "0")
build_command = ["go", "build", "-o", ks_file, "-ldflags", ldflags]
print("Building kubescape and saving here: {}".format(ks_file))
print("Build command: {}".format(" ".join(build_command)))

View File

@@ -1,22 +1,51 @@
FROM --platform=$BUILDPLATFORM golang:1.20-bullseye as builder
FROM golang:1.20-alpine as builder
ARG image_version
ARG client
ENV RELEASE=$image_version
ENV CLIENT=$client
ENV GO111MODULE=
ENV CGO_ENABLED=1
# Install required python/pip
ENV PYTHONUNBUFFERED=1
RUN apk add --update --no-cache python3 gcc make git libc-dev binutils-gold cmake pkgconfig && ln -sf python3 /usr/bin/python
RUN python3 -m ensurepip
RUN pip3 install --no-cache --upgrade pip setuptools
ENV GO111MODULE=on CGO_ENABLED=0
WORKDIR /work
ARG TARGETOS TARGETARCH
ADD . .
RUN --mount=target=. \
--mount=type=cache,target=/root/.cache/go-build \
--mount=type=cache,target=/go/pkg \
cd httphandler && GOOS=$TARGETOS GOARCH=$TARGETARCH go build -o /out/ksserver .
# install libgit2
RUN rm -rf git2go && make libgit2
FROM gcr.io/distroless/static-debian11:nonroot
# build kubescape server
WORKDIR /work/httphandler
RUN python build.py
RUN ls -ltr build/
USER nonroot
WORKDIR /home/nonroot/
# build kubescape cmd
WORKDIR /work
RUN python build.py
COPY --from=builder /out/ksserver /usr/bin/ksserver
RUN /work/build/kubescape-ubuntu-latest download artifacts -o /work/artifacts
ARG image_version client
ENV RELEASE=$image_version CLIENT=$client
FROM alpine:3.16.2
RUN addgroup -S ks && adduser -S ks -G ks
COPY --from=builder /work/artifacts/ /home/ks/.kubescape
RUN chown -R ks:ks /home/ks/.kubescape
USER ks
WORKDIR /home/ks
COPY --from=builder /work/httphandler/build/kubescape-ubuntu-latest /usr/bin/ksserver
COPY --from=builder /work/build/kubescape-ubuntu-latest /usr/bin/kubescape
ENTRYPOINT ["ksserver"]

View File

@@ -1,3 +0,0 @@
.git
git2go
kubescape*

View File

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

View File

@@ -1,12 +0,0 @@
FROM gcr.io/distroless/base-debian11:debug-nonroot
USER nonroot
WORKDIR /home/nonroot/
ARG image_version client ks_binary
ENV RELEASE=$image_version CLIENT=$client
COPY $ks_binary /usr/bin/kubescape
RUN ["kubescape", "download", "artifacts"]
ENTRYPOINT ["kubescape"]

View File

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

View File

@@ -23,8 +23,14 @@ var (
# Set account id
%[1]s config set accountID <account id>
# Set cloud report URL
%[1]s config set cloudReportURL <cloud Report URL>
# Set client id
%[1]s config set clientID <client id>
# Set access key
%[1]s config set secretKey <access key>
# Set cloudAPIURL
%[1]s config set cloudAPIURL <cloud API URL>
`, cautils.ExecName())
)

View File

@@ -34,8 +34,12 @@ func getSetCmd(ks meta.IKubescape) *cobra.Command {
var supportConfigSet = map[string]func(*metav1.SetConfig, string){
"accountID": func(s *metav1.SetConfig, account string) { s.Account = account },
"clientID": func(s *metav1.SetConfig, clientID string) { s.ClientID = clientID },
"secretKey": func(s *metav1.SetConfig, secretKey string) { s.SecretKey = secretKey },
"cloudAPIURL": func(s *metav1.SetConfig, cloudAPIURL string) { s.CloudAPIURL = cloudAPIURL },
"cloudAuthURL": func(s *metav1.SetConfig, cloudAuthURL string) { s.CloudAuthURL = cloudAuthURL },
"cloudReportURL": func(s *metav1.SetConfig, cloudReportURL string) { s.CloudReportURL = cloudReportURL },
"cloudUIURL": func(s *metav1.SetConfig, cloudUIURL string) { s.CloudUIURL = cloudUIURL },
}
func stringKeysToSlice(m map[string]func(*metav1.SetConfig, string)) []string {

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

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

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

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

View File

@@ -83,9 +83,9 @@ func GetDownloadCmd(ks meta.IKubescape) *cobra.Command {
},
}
downloadCmd.PersistentFlags().StringVarP(&downloadInfo.AccountID, "account", "", "", "Kubescape SaaS account ID. Default will load account ID from cache")
downloadCmd.PersistentFlags().MarkDeprecated("client-id", "Client ID is no longer supported. Feel free to contact the Kubescape maintainers for more information.")
downloadCmd.PersistentFlags().MarkDeprecated("secret-key", "Secret Key is no longer supported. Feel free to contact the Kubescape maintainers for more information.")
downloadCmd.PersistentFlags().StringVarP(&downloadInfo.Credentials.Account, "account", "", "", "Kubescape SaaS account ID. Default will load account ID from cache")
downloadCmd.PersistentFlags().StringVarP(&downloadInfo.Credentials.ClientID, "client-id", "", "", "Kubescape SaaS client ID. Default will load client ID from cache, read more - https://hub.armosec.io/docs/authentication")
downloadCmd.PersistentFlags().StringVarP(&downloadInfo.Credentials.SecretKey, "secret-key", "", "", "Kubescape SaaS secret key. Default will load secret key from cache, read more - https://hub.armosec.io/docs/authentication")
downloadCmd.Flags().StringVarP(&downloadInfo.Path, "output", "o", "", "Output file. If not specified, will save in `~/.kubescape/<policy name>.json`")
return downloadCmd
@@ -95,5 +95,5 @@ func GetDownloadCmd(ks meta.IKubescape) *cobra.Command {
func flagValidationDownload(downloadInfo *v1.DownloadInfo) error {
// Validate the user's credentials
return cautils.ValidateAccountID(downloadInfo.AccountID)
return downloadInfo.Credentials.Validate()
}

View File

@@ -27,7 +27,7 @@ func GetFixCmd(ks meta.IKubescape) *cobra.Command {
fixCmd := &cobra.Command{
Use: "fix <report output file>",
Short: "Propose a fix for the misconfiguration found when scanning Kubernetes manifest files",
Short: "Fix misconfiguration in files",
Long: ``,
Example: fixCmdExamples,
RunE: func(cmd *cobra.Command, args []string) error {

View File

@@ -63,7 +63,7 @@ func GetListCmd(ks meta.IKubescape) *cobra.Command {
return nil
},
}
listCmd.PersistentFlags().StringVarP(&listPolicies.AccountID, "account", "", "", "Kubescape SaaS account ID. Default will load account ID from cache")
listCmd.PersistentFlags().StringVarP(&listPolicies.Credentials.Account, "account", "", "", "Kubescape SaaS account ID. Default will load account ID from cache")
listCmd.PersistentFlags().StringVar(&listPolicies.Format, "format", "pretty-print", "output format. supported: 'pretty-print'/'json'")
listCmd.PersistentFlags().MarkDeprecated("id", "Control ID's are included in list outputs")
@@ -74,5 +74,5 @@ func GetListCmd(ks meta.IKubescape) *cobra.Command {
func flagValidationList(listPolicies *v1.ListPolicies) error {
// Validate the user's credentials
return cautils.ValidateAccountID(listPolicies.AccountID)
return listPolicies.Credentials.Validate()
}

View File

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

View File

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

View File

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

View File

@@ -1,56 +0,0 @@
package operator
import (
"fmt"
"github.com/kubescape/go-logger"
"github.com/kubescape/go-logger/helpers"
"github.com/kubescape/k8s-interface/k8sinterface"
"github.com/kubescape/kubescape/v2/core/cautils"
"github.com/kubescape/kubescape/v2/core/core"
"github.com/kubescape/kubescape/v2/core/meta"
"github.com/spf13/cobra"
)
var operatorScanVulnerabilitiesExamples = fmt.Sprintf(`
# Trigger a vulnerabilities scan
%[1]s operator scan vulnerabilities
`, cautils.ExecName())
func getOperatorScanVulnerabilitiesCmd(ks meta.IKubescape, operatorInfo cautils.OperatorInfo) *cobra.Command {
configCmd := &cobra.Command{
Use: "vulnerabilities",
Short: "Vulnerabilities use for scan your cluster vulnerabilities using Kubescape operator in the in cluster components",
Long: ``,
Example: operatorScanVulnerabilitiesExamples,
Args: func(cmd *cobra.Command, args []string) error {
operatorInfo.Subcommands = append(operatorInfo.Subcommands, "vulnerabilities")
return nil
},
RunE: func(cmd *cobra.Command, args []string) error {
operatorAdapter, err := core.NewOperatorAdapter(operatorInfo.OperatorScanInfo)
if err != nil {
return err
}
logger.L().Start("Triggering the Kubescape-Operator for vulnerability scanning")
_, err = operatorAdapter.OperatorScan()
if err != nil {
logger.L().StopError("Failed to trigger the Kubescape-Operator for vulnerability scanning", helpers.Error(err))
return err
}
logger.L().StopSuccess("Triggered Kubescape-Operator for vulnerability scanning. View the scanning results once they are ready using the following command: \"kubectl get vulnerabilitysummaries\"")
return err
},
}
vulnerabilitiesScanInfo := &cautils.VulnerabilitiesScanInfo{
ClusterName: k8sinterface.GetContextName(),
}
operatorInfo.OperatorScanInfo = vulnerabilitiesScanInfo
configCmd.PersistentFlags().StringSliceVar(&vulnerabilitiesScanInfo.IncludeNamespaces, "include-namespaces", nil, "scan specific namespaces. e.g: --include-namespaces ns-a,ns-b")
return configCmd
}

View File

@@ -6,14 +6,14 @@ import (
logger "github.com/kubescape/go-logger"
"github.com/kubescape/go-logger/helpers"
"github.com/kubescape/k8s-interface/k8sinterface"
"github.com/kubescape/kubescape/v2/cmd/completion"
"github.com/kubescape/kubescape/v2/cmd/config"
"github.com/kubescape/kubescape/v2/cmd/delete"
"github.com/kubescape/kubescape/v2/cmd/download"
"github.com/kubescape/kubescape/v2/cmd/fix"
"github.com/kubescape/kubescape/v2/cmd/list"
"github.com/kubescape/kubescape/v2/cmd/operator"
"github.com/kubescape/kubescape/v2/cmd/scan"
"github.com/kubescape/kubescape/v2/cmd/submit"
"github.com/kubescape/kubescape/v2/cmd/update"
"github.com/kubescape/kubescape/v2/cmd/version"
"github.com/kubescape/kubescape/v2/core/cautils"
@@ -27,11 +27,11 @@ import (
var rootInfo cautils.RootInfo
var ksExamples = fmt.Sprintf(`
# Scan a Kubernetes cluster or YAML files for image vulnerabilities and misconfigurations
# Scan command
%[1]s scan
# List supported controls
%[1]s list controls
# List supported frameworks
%[1]s list frameworks
# Download artifacts (air-gapped environment support)
%[1]s download artifacts
@@ -51,13 +51,6 @@ func getRootCmd(ks meta.IKubescape) *cobra.Command {
Use: "kubescape",
Short: "Kubescape is a tool for testing Kubernetes security posture. Docs: https://hub.armosec.io/docs",
Example: ksExamples,
PersistentPreRun: func(cmd *cobra.Command, args []string) {
k8sinterface.SetClusterContextName(rootInfo.KubeContext)
initLogger()
initLoggerLevel()
initEnvironment()
initCacheDir()
},
}
if cautils.IsKrewPlugin() {
@@ -70,10 +63,9 @@ func getRootCmd(ks meta.IKubescape) *cobra.Command {
rootCmd.SetUsageTemplate(newUsageTemplate)
}
rootCmd.PersistentFlags().StringVar(&rootInfo.DiscoveryServerURL, "server", "", "Backend discovery server URL")
rootCmd.PersistentFlags().MarkDeprecated("environment", "'environment' is no longer supported, Use 'server' instead. Feel free to contact the Kubescape maintainers for more information.")
rootCmd.PersistentFlags().MarkDeprecated("env", "'env' is no longer supported, Use 'server' instead. Feel free to contact the Kubescape maintainers for more information.")
rootCmd.PersistentFlags().StringVar(&rootInfo.KSCloudBEURLsDep, "environment", "", envFlagUsage)
rootCmd.PersistentFlags().StringVar(&rootInfo.KSCloudBEURLs, "env", "", envFlagUsage)
rootCmd.PersistentFlags().MarkDeprecated("environment", "use 'env' instead")
rootCmd.PersistentFlags().MarkHidden("environment")
rootCmd.PersistentFlags().MarkHidden("env")
@@ -82,30 +74,22 @@ func getRootCmd(ks meta.IKubescape) *cobra.Command {
rootCmd.PersistentFlags().StringVarP(&rootInfo.Logger, "logger", "l", helpers.InfoLevel.String(), fmt.Sprintf("Logger level. Supported: %s [$KS_LOGGER]", strings.Join(helpers.SupportedLevels(), "/")))
rootCmd.PersistentFlags().StringVar(&rootInfo.CacheDir, "cache-dir", getter.DefaultLocalStore, "Cache directory [$KS_CACHE_DIR]")
rootCmd.PersistentFlags().BoolVarP(&rootInfo.DisableColor, "disable-color", "", false, "Disable color output for logging")
rootCmd.PersistentFlags().BoolVarP(&rootInfo.EnableColor, "enable-color", "", false, "Force enable color output for logging")
rootCmd.PersistentFlags().BoolVarP(&rootInfo.DisableColor, "disable-color", "", false, "Disable Color output for logging")
rootCmd.PersistentFlags().BoolVarP(&rootInfo.EnableColor, "enable-color", "", false, "Force enable Color output for logging")
cobra.OnInitialize(initLogger, initLoggerLevel, initEnvironment, initCacheDir)
rootCmd.PersistentFlags().StringVarP(&rootInfo.KubeContext, "kube-context", "", "", "Kube context. Default will use the current-context")
// Supported commands
rootCmd.AddCommand(scan.GetScanCommand(ks))
rootCmd.AddCommand(download.GetDownloadCmd(ks))
rootCmd.AddCommand(delete.GetDeleteCmd(ks))
rootCmd.AddCommand(list.GetListCmd(ks))
rootCmd.AddCommand(submit.GetSubmitCmd(ks))
rootCmd.AddCommand(completion.GetCompletionCmd())
rootCmd.AddCommand(version.GetVersionCmd())
rootCmd.AddCommand(config.GetConfigCmd(ks))
rootCmd.AddCommand(update.GetUpdateCmd())
rootCmd.AddCommand(fix.GetFixCmd(ks))
rootCmd.AddCommand(operator.GetOperatorCmd(ks))
// deprecated commands
rootCmd.AddCommand(&cobra.Command{
Use: "submit",
Deprecated: "This command is deprecated. Contact Kubescape maintainers for more information.",
})
rootCmd.AddCommand(&cobra.Command{
Use: "delete",
Deprecated: "This command is deprecated. Contact Kubescape maintainers for more information.",
})
return rootCmd
}

View File

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

View File

@@ -91,7 +91,6 @@ func getControlCmd(ks meta.IKubescape, scanInfo *cautils.ScanInfo) *cobra.Comman
}
scanInfo.FrameworkScan = false
scanInfo.SetScanType(cautils.ScanTypeControl)
if err := validateControlScanInfo(scanInfo); err != nil {
return err

View File

@@ -42,11 +42,7 @@ var (
Run '%[1]s list frameworks' for the list of supported frameworks
`, cautils.ExecName())
ErrUnknownSeverity = errors.New("unknown severity")
ErrSecurityViewNotSupported = errors.New("security view is not supported for framework scan")
ErrBadThreshold = errors.New("bad argument: out of range threshold")
ErrKeepLocalOrSubmit = errors.New("you can use `keep-local` or `submit`, but not both")
ErrOmitRawResourcesOrSubmit = errors.New("you can use `omit-raw-resources` or `submit`, but not both")
ErrUnknownSeverity = errors.New("unknown severity")
)
func getFrameworkCmd(ks meta.IKubescape, scanInfo *cautils.ScanInfo) *cobra.Command {
@@ -212,21 +208,17 @@ func validateSeverity(severity string) error {
// validateFrameworkScanInfo validates the scan info struct for the `scan framework` command
func validateFrameworkScanInfo(scanInfo *cautils.ScanInfo) error {
if scanInfo.View == string(cautils.SecurityViewType) {
return ErrSecurityViewNotSupported
}
if scanInfo.Submit && scanInfo.Local {
return ErrKeepLocalOrSubmit
return fmt.Errorf("you can use `keep-local` or `submit`, but not both")
}
if 100 < scanInfo.ComplianceThreshold || 0 > scanInfo.ComplianceThreshold {
return ErrBadThreshold
return fmt.Errorf("bad argument: out of range threshold")
}
if 100 < scanInfo.FailThreshold || 0 > scanInfo.FailThreshold {
return ErrBadThreshold
return fmt.Errorf("bad argument: out of range threshold")
}
if scanInfo.Submit && scanInfo.OmitRawResources {
return ErrOmitRawResourcesOrSubmit
return fmt.Errorf("you can use `omit-raw-resources` or `submit`, but not both")
}
severity := scanInfo.FailThresholdSeverity
if err := validateSeverity(severity); severity != "" && err != nil {
@@ -234,5 +226,5 @@ func validateFrameworkScanInfo(scanInfo *cautils.ScanInfo) error {
}
// Validate the user's credentials
return cautils.ValidateAccountID(scanInfo.AccountID)
return scanInfo.Credentials.Validate()
}

View File

@@ -5,6 +5,7 @@ import (
"fmt"
logger "github.com/kubescape/go-logger"
"github.com/kubescape/go-logger/iconlogger"
"github.com/kubescape/kubescape/v2/core/cautils"
"github.com/kubescape/kubescape/v2/core/core"
"github.com/kubescape/kubescape/v2/core/meta"
@@ -22,7 +23,7 @@ type imageScanInfo struct {
// TODO(vladklokun): document image scanning on the Kubescape Docs Hub?
var (
imageExample = fmt.Sprintf(`
This command is still in BETA. Feel free to contact the Kubescape maintainers for more information.
This command is still in BETA. Feel free to contact the kubescape maintainers for more information.
Scan an image for vulnerabilities.
@@ -54,6 +55,8 @@ func getImageCmd(ks meta.IKubescape, scanInfo *cautils.ScanInfo, imgScanInfo *im
ctx := context.Background()
logger.InitLogger(iconlogger.LoggerName)
dbCfg, _ := imagescan.NewDefaultDBConfig()
svc := imagescan.NewScanService(dbCfg)
@@ -74,9 +77,9 @@ func getImageCmd(ks meta.IKubescape, scanInfo *cautils.ScanInfo, imgScanInfo *im
scanInfo.SetScanType(cautils.ScanTypeImage)
outputPrinters := core.GetOutputPrinters(scanInfo, ctx, "")
outputPrinters := core.GetOutputPrinters(scanInfo, ctx)
uiPrinter := core.GetUIPrinter(ctx, scanInfo, "")
uiPrinter := core.GetUIPrinter(ctx, scanInfo)
resultsHandler := resultshandling.NewResultsHandler(nil, outputPrinters, uiPrinter)

View File

@@ -6,6 +6,7 @@ import (
"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"
@@ -16,14 +17,14 @@ import (
var scanCmdExamples = fmt.Sprintf(`
Scan command is for scanning an existing cluster or kubernetes manifest files based on pre-defined frameworks
# Scan current cluster
# Scan current cluster with all frameworks
%[1]s scan
# Scan kubernetes manifest files
# Scan kubernetes YAML manifest files
%[1]s scan .
# Scan and save the results in the JSON format
%[1]s scan --format json --output results.json
%[1]s scan --format json --output results.json --format-version=v2
# Display all resources
%[1]s scan --verbose
@@ -38,9 +39,18 @@ func GetScanCommand(ks meta.IKubescape) *cobra.Command {
// scanCmd represents the scan command
scanCmd := &cobra.Command{
Use: "scan",
Short: "Scan a Kubernetes cluster or YAML files for image vulnerabilities and misconfigurations",
Short: "Scan the current running cluster or yaml files",
Long: `The action you want to perform`,
Example: scanCmdExamples,
Args: func(cmd *cobra.Command, args []string) error {
// setting input patterns for framework scan is only relevancy for non-security view
if len(args) > 0 && scanInfo.View != string(cautils.SecurityViewType) {
if args[0] != "framework" && args[0] != "control" {
return getFrameworkCmd(ks, &scanInfo).RunE(cmd, append([]string{strings.Join(getter.NativeFrameworks, ",")}, args...))
}
}
return nil
},
RunE: func(cmd *cobra.Command, args []string) error {
if scanInfo.View == string(cautils.SecurityViewType) {
setSecurityViewScanInfo(args, &scanInfo)
@@ -48,17 +58,23 @@ func GetScanCommand(ks meta.IKubescape) *cobra.Command {
return securityScan(scanInfo, ks)
}
if len(args) == 0 || (args[0] != "framework" && args[0] != "control") {
return getFrameworkCmd(ks, &scanInfo).RunE(cmd, append([]string{strings.Join(getter.NativeFrameworks, ",")}, args...))
if len(args) == 0 {
return getFrameworkCmd(ks, &scanInfo).RunE(cmd, []string{strings.Join(getter.NativeFrameworks, ",")})
}
return nil
},
PreRun: func(cmd *cobra.Command, args []string) {
k8sinterface.SetClusterContextName(scanInfo.KubeContext)
},
PostRun: func(cmd *cobra.Command, args []string) {
// TODO - revert context
},
}
scanCmd.PersistentFlags().StringVarP(&scanInfo.AccountID, "account", "", "", "Kubescape SaaS account ID. Default will load account ID from cache")
scanCmd.PersistentFlags().StringVarP(&scanInfo.Credentials.Account, "account", "", "", "Kubescape SaaS account ID. Default will load account ID from cache")
scanCmd.PersistentFlags().BoolVar(&scanInfo.CreateAccount, "create-account", false, "Create a Kubescape SaaS account ID account ID is not found in cache. After creating the account, the account ID will be saved in cache. In addition, the scanning results will be uploaded to the Kubescape SaaS")
scanCmd.PersistentFlags().StringVarP(&scanInfo.KubeContext, "kube-context", "", "", "Kube context. Default will use the current-context")
scanCmd.PersistentFlags().StringVar(&scanInfo.ControlsInputs, "controls-config", "", "Path to an controls-config obj. If not set will download controls-config from ARMO management portal")
scanCmd.PersistentFlags().StringVar(&scanInfo.UseExceptions, "exceptions", "", "Path to an exceptions obj. If not set will download exceptions from ARMO management portal")
scanCmd.PersistentFlags().StringVar(&scanInfo.UseArtifactsFrom, "use-artifacts-from", "", "Load artifacts from local directory. If not used will download them")
@@ -87,9 +103,10 @@ func GetScanCommand(ks meta.IKubescape) *cobra.Command {
scanCmd.PersistentFlags().MarkDeprecated("silent", "use '--logger' flag instead. Flag will be removed at 1.May.2022")
scanCmd.PersistentFlags().MarkDeprecated("fail-threshold", "use '--compliance-threshold' flag instead. Flag will be removed at 1.Dec.2023")
scanCmd.PersistentFlags().MarkDeprecated("client-id", "Client ID is no longer supported. Feel free to contact the Kubescape maintainers for more information.")
scanCmd.PersistentFlags().MarkDeprecated("create-account", "Create account is no longer supported. In case of a missing Account ID and a configured backend server, a new account id will be generated automatically by Kubescape. Feel free to contact the Kubescape maintainers for more information.")
scanCmd.PersistentFlags().MarkDeprecated("secret-key", "Secret Key is no longer supported. Feel free to contact the Kubescape maintainers for more information.")
scanCmd.PersistentFlags().StringVarP(&scanInfo.Credentials.ClientID, "client-id", "", "", "Kubescape SaaS client ID. Default will load client ID from cache, read more - https://hub.armosec.io/docs/authentication")
scanCmd.PersistentFlags().StringVarP(&scanInfo.Credentials.SecretKey, "secret-key", "", "", "Kubescape SaaS secret key. Default will load secret key from cache, read more - https://hub.armosec.io/docs/authentication")
scanCmd.PersistentFlags().MarkDeprecated("client-id", "login to Kubescape SaaS will be unsupported, please contact the Kubescape maintainers for more information")
scanCmd.PersistentFlags().MarkDeprecated("secret-key", "login to Kubescape SaaS will be unsupported, please contact the Kubescape maintainers for more information")
// hidden flags
scanCmd.PersistentFlags().MarkHidden("omit-raw-resources")
@@ -102,10 +119,10 @@ func GetScanCommand(ks meta.IKubescape) *cobra.Command {
hostF.NoOptDefVal = "true"
hostF.DefValue = "false, for no TTY in stdin"
scanCmd.PersistentFlags().MarkHidden("enable-host-scan")
scanCmd.PersistentFlags().MarkDeprecated("enable-host-scan", "To activate the host scanner capability, proceed with the installation of the kubescape operator chart found here: https://github.com/kubescape/helm-charts/tree/main/charts/kubescape-operator. The flag will be removed at 1.Dec.2023")
scanCmd.PersistentFlags().MarkDeprecated("enable-host-scan", "To activate the host scanner capability, proceed with the installation of the kubescape operator chart found here: https://github.com/kubescape/helm-charts/tree/main/charts/kubescape-cloud-operator. The flag will be removed at 1.Dec.2023")
scanCmd.PersistentFlags().MarkHidden("host-scan-yaml") // this flag should be used very cautiously. We prefer users will not use it at all unless the DaemonSet can not run pods on the nodes
scanCmd.PersistentFlags().MarkDeprecated("host-scan-yaml", "To activate the host scanner capability, proceed with the installation of the kubescape operator chart found here: https://github.com/kubescape/helm-charts/tree/main/charts/kubescape-operator. The flag will be removed at 1.Dec.2023")
scanCmd.PersistentFlags().MarkDeprecated("host-scan-yaml", "To activate the host scanner capability, proceed with the installation of the kubescape operator chart found here: https://github.com/kubescape/helm-charts/tree/main/charts/kubescape-cloud-operator. The flag will be removed at 1.Dec.2023")
scanCmd.AddCommand(getControlCmd(ks, &scanInfo))
scanCmd.AddCommand(getFrameworkCmd(ks, &scanInfo))

View File

@@ -68,16 +68,6 @@ func Test_validateFrameworkScanInfo(t *testing.T) {
&cautils.ScanInfo{FailThresholdSeverity: "Unknown"},
ErrUnknownSeverity,
},
{
"Security view should be invalid for scan info",
&cautils.ScanInfo{View: string(cautils.SecurityViewType)},
ErrSecurityViewNotSupported,
},
{
"Empty view should be valid for scan info",
&cautils.ScanInfo{},
nil,
},
}
for _, tc := range testCases {

View File

@@ -17,7 +17,7 @@ import (
var (
workloadExample = fmt.Sprintf(`
This command is still in BETA. Feel free to contact the Kubescape maintainers for more information.
This command is still in BETA. Feel free to contact the kubescape maintainers for more information.
Scan a workload for misconfigurations and image vulnerabilities.

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

@@ -0,0 +1,35 @@
package submit
import (
"context"
"fmt"
logger "github.com/kubescape/go-logger"
"github.com/kubescape/kubescape/v2/core/meta"
metav1 "github.com/kubescape/kubescape/v2/core/meta/datastructures/v1"
"github.com/spf13/cobra"
)
func getExceptionsCmd(ks meta.IKubescape, submitInfo *metav1.Submit) *cobra.Command {
return &cobra.Command{
Use: "exceptions <full path to exceptions file>",
Short: "Submit exceptions to the Kubescape SaaS version",
Args: func(cmd *cobra.Command, args []string) error {
if len(args) != 1 {
return fmt.Errorf("missing full path to exceptions file")
}
return nil
},
Run: func(cmd *cobra.Command, args []string) {
if err := flagValidationSubmit(submitInfo); err != nil {
logger.L().Fatal(err.Error())
}
if err := ks.SubmitExceptions(context.TODO(), &submitInfo.Credentials, args[0]); err != nil {
logger.L().Fatal(err.Error())
}
},
}
}

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

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

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

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

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

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

View File

@@ -8,7 +8,6 @@ import (
"fmt"
logger "github.com/kubescape/go-logger"
"github.com/kubescape/go-logger/helpers"
"github.com/kubescape/kubescape/v2/core/cautils"
"github.com/spf13/cobra"
)
@@ -25,16 +24,16 @@ var updateCmdExamples = fmt.Sprintf(`
func GetUpdateCmd() *cobra.Command {
updateCmd := &cobra.Command{
Use: "update",
Short: "Update to latest release version",
Short: "Update your version",
Long: ``,
Example: updateCmdExamples,
RunE: func(_ *cobra.Command, args []string) error {
//Checking the user's version of kubescape to the latest release
if cautils.BuildNumber == cautils.LatestReleaseVersion {
//your version == latest version
logger.L().Info(("Nothing to update, you are running the latest version"), helpers.String("Version", cautils.BuildNumber))
logger.L().Info(("You are in the latest version"))
} else {
fmt.Printf("Please refer to our installation docs in the following link: %s\n", installationLink)
fmt.Printf("please refer to our installation docs in the following link: %s", installationLink)
}
return nil
},

View File

@@ -5,7 +5,6 @@ import (
"fmt"
"os"
"github.com/kubescape/go-logger"
"github.com/kubescape/kubescape/v2/core/cautils"
"github.com/spf13/cobra"
)
@@ -20,10 +19,10 @@ func GetVersionCmd() *cobra.Command {
v := cautils.NewIVersionCheckHandler(ctx)
v.CheckLatestVersion(ctx, cautils.NewVersionCheckRequest(cautils.BuildNumber, "", "", "version"))
fmt.Fprintf(os.Stdout,
"Your current version is: %s\n",
"Your current version is: %s [git enabled in build: %t]\n",
cautils.BuildNumber,
isGitEnabled(),
)
logger.L().Debug(fmt.Sprintf("git enabled in build: %t", isGitEnabled()))
return nil
},
}

View File

@@ -3,16 +3,13 @@ package cautils
import (
"context"
"encoding/json"
"fmt"
"os"
"path/filepath"
"regexp"
"strings"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/google/uuid"
v1 "github.com/kubescape/backend/pkg/client/v1"
"github.com/kubescape/backend/pkg/servicediscovery"
servicediscoveryv1 "github.com/kubescape/backend/pkg/servicediscovery/v1"
logger "github.com/kubescape/go-logger"
"github.com/kubescape/go-logger/helpers"
"github.com/kubescape/k8s-interface/k8sinterface"
@@ -21,19 +18,9 @@ import (
)
const (
configFileName string = "config"
kubescapeNamespace string = "kubescape"
kubescapeConfigMapName string = "kubescape-config"
kubescapeCloudConfigMapName string = "ks-cloud-config"
// env vars
defaultConfigMapNameEnvVar string = "KS_DEFAULT_CONFIGMAP_NAME"
defaultCloudConfigMapNameEnvVar string = "KS_DEFAULT_CLOUD_CONFIGMAP_NAME"
defaultConfigMapNamespaceEnvVar string = "KS_DEFAULT_CONFIGMAP_NAMESPACE"
accountIdEnvVar string = "KS_ACCOUNT_ID"
cloudApiUrlEnvVar string = "KS_CLOUD_API_URL"
cloudReportUrlEnvVar string = "KS_CLOUD_REPORT_URL"
storageEnabledEnvVar string = "KS_STORAGE_ENABLED"
configFileName string = "config"
kubescapeNamespace string = "kubescape"
kubescapeConfigMapName string = "kubescape-config"
)
func ConfigFileFullPath() string { return getter.GetDefaultPath(configFileName + ".json") }
@@ -43,11 +30,16 @@ func ConfigFileFullPath() string { return getter.GetDefaultPath(configFileName +
// ======================================================================================
type ConfigObj struct {
AccountID string `json:"accountID,omitempty"`
ClusterName string `json:"clusterName,omitempty"`
CloudReportURL string `json:"cloudReportURL,omitempty"`
CloudAPIURL string `json:"cloudAPIURL,omitempty"`
StorageEnabled bool `json:"storageEnabled,omitempty"`
AccountID string `json:"accountID,omitempty"`
ClientID string `json:"clientID,omitempty"`
SecretKey string `json:"secretKey,omitempty"`
Token string `json:"invitationParam,omitempty"`
CustomerAdminEMail string `json:"adminMail,omitempty"`
ClusterName string `json:"clusterName,omitempty"`
CloudReportURL string `json:"cloudReportURL,omitempty"`
CloudAPIURL string `json:"cloudAPIURL,omitempty"`
CloudUIURL string `json:"cloudUIURL,omitempty"`
CloudAuthURL string `json:"cloudAuthURL,omitempty"`
}
// Config - convert ConfigObj to config file
@@ -55,11 +47,17 @@ func (co *ConfigObj) Config() []byte {
// remove cluster name before saving to file
clusterName := co.ClusterName
customerAdminEMail := co.CustomerAdminEMail
token := co.Token
co.ClusterName = ""
co.Token = ""
co.CustomerAdminEMail = ""
b, err := json.MarshalIndent(co, "", " ")
co.ClusterName = clusterName
co.CustomerAdminEMail = customerAdminEMail
co.Token = token
if err == nil {
return b
@@ -75,12 +73,24 @@ func (co *ConfigObj) updateEmptyFields(inCO *ConfigObj) error {
if inCO.CloudAPIURL != "" {
co.CloudAPIURL = inCO.CloudAPIURL
}
if inCO.CloudAuthURL != "" {
co.CloudAuthURL = inCO.CloudAuthURL
}
if inCO.CloudReportURL != "" {
co.CloudReportURL = inCO.CloudReportURL
}
if inCO.CloudUIURL != "" {
co.CloudUIURL = inCO.CloudUIURL
}
if inCO.ClusterName != "" {
co.ClusterName = inCO.ClusterName
}
if inCO.CustomerAdminEMail != "" {
co.CustomerAdminEMail = inCO.CustomerAdminEMail
}
if inCO.Token != "" {
co.Token = inCO.Token
}
return nil
}
@@ -89,18 +99,27 @@ func (co *ConfigObj) updateEmptyFields(inCO *ConfigObj) error {
// =============================== interface ============================================
// ======================================================================================
type ITenantConfig interface {
// set
SetTenant() error
UpdateCachedConfig() error
DeleteCachedConfig(ctx context.Context) error
GenerateAccountID() (string, error)
DeleteAccountID() error
// getters
GetContextName() string
GetAccountID() string
GetTenantEmail() string
GetToken() string
GetClientID() string
GetSecretKey() string
GetConfigObj() *ConfigObj
GetCloudReportURL() string
GetCloudAPIURL() string
IsStorageEnabled() bool
GetCloudUIURL() string
GetCloudAuthURL() string
// GetBackendAPI() getter.IBackend
// GenerateURL()
IsConfigFound() bool
}
// ======================================================================================
@@ -111,21 +130,24 @@ type ITenantConfig interface {
var _ ITenantConfig = &LocalConfig{}
type LocalConfig struct {
configObj *ConfigObj
backendAPI getter.IBackend
configObj *ConfigObj
}
func NewLocalConfig(accountID, clusterName string, customClusterName string) *LocalConfig {
func NewLocalConfig(
backendAPI getter.IBackend, credentials *Credentials, clusterName string, customClusterName string) *LocalConfig {
lc := &LocalConfig{
configObj: &ConfigObj{},
backendAPI: backendAPI,
configObj: &ConfigObj{},
}
// get from configMap
if existsConfigFile() { // get from file
loadConfigFromFile(lc.configObj)
}
updateAccountID(lc.configObj, accountID)
updateCredentials(lc.configObj, credentials)
updateCloudURLs(lc.configObj)
updateStorageEnabled(lc.configObj)
// If a custom cluster name is provided then set that name, else use the cluster's original name
if customClusterName != "" {
@@ -134,32 +156,59 @@ func NewLocalConfig(accountID, clusterName string, customClusterName string) *Lo
lc.configObj.ClusterName = AdoptClusterName(clusterName) // override config clusterName
}
updatedKsCloud := initializeCloudAPI(lc)
logger.L().Debug("Kubescape Cloud URLs", helpers.String("api", updatedKsCloud.GetCloudAPIURL()), helpers.String("report", updatedKsCloud.GetCloudReportURL()))
lc.backendAPI.SetAccountID(lc.configObj.AccountID)
lc.backendAPI.SetClientID(lc.configObj.ClientID)
lc.backendAPI.SetSecretKey(lc.configObj.SecretKey)
if lc.configObj.CloudAPIURL != "" {
lc.backendAPI.SetCloudAPIURL(lc.configObj.CloudAPIURL)
} else {
lc.configObj.CloudAPIURL = lc.backendAPI.GetCloudAPIURL()
}
if lc.configObj.CloudAuthURL != "" {
lc.backendAPI.SetCloudAuthURL(lc.configObj.CloudAuthURL)
} else {
lc.configObj.CloudAuthURL = lc.backendAPI.GetCloudAuthURL()
}
if lc.configObj.CloudReportURL != "" {
lc.backendAPI.SetCloudReportURL(lc.configObj.CloudReportURL)
} else {
lc.configObj.CloudReportURL = lc.backendAPI.GetCloudReportURL()
}
if lc.configObj.CloudUIURL != "" {
lc.backendAPI.SetCloudUIURL(lc.configObj.CloudUIURL)
} else {
lc.configObj.CloudUIURL = lc.backendAPI.GetCloudUIURL()
}
logger.L().Debug("Kubescape Cloud URLs", helpers.String("api", lc.backendAPI.GetCloudAPIURL()), helpers.String("auth", lc.backendAPI.GetCloudAuthURL()), helpers.String("report", lc.backendAPI.GetCloudReportURL()), helpers.String("UI", lc.backendAPI.GetCloudUIURL()))
initializeCloudAPI(lc)
return lc
}
func (lc *LocalConfig) GetConfigObj() *ConfigObj { return lc.configObj }
func (lc *LocalConfig) GetTenantEmail() string { return lc.configObj.CustomerAdminEMail }
func (lc *LocalConfig) GetAccountID() string { return lc.configObj.AccountID }
func (lc *LocalConfig) GetClientID() string { return lc.configObj.ClientID }
func (lc *LocalConfig) GetSecretKey() string { return lc.configObj.SecretKey }
func (lc *LocalConfig) GetContextName() string { return lc.configObj.ClusterName }
func (lc *LocalConfig) GetToken() string { return lc.configObj.Token }
func (lc *LocalConfig) GetCloudReportURL() string { return lc.configObj.CloudReportURL }
func (lc *LocalConfig) GetCloudAPIURL() string { return lc.configObj.CloudAPIURL }
func (lc *LocalConfig) IsStorageEnabled() bool { return lc.configObj.StorageEnabled }
func (lc *LocalConfig) GetCloudUIURL() string { return lc.configObj.CloudUIURL }
func (lc *LocalConfig) GetCloudAuthURL() string { return lc.configObj.CloudAuthURL }
func (lc *LocalConfig) IsConfigFound() bool { return existsConfigFile() }
func (lc *LocalConfig) SetTenant() error {
// Kubescape Cloud tenant GUID
if err := getTenantConfigFromBE(lc.backendAPI, lc.configObj); err != nil {
return err
}
lc.UpdateCachedConfig()
return nil
func (lc *LocalConfig) GenerateAccountID() (string, error) {
lc.configObj.AccountID = uuid.NewString()
err := lc.UpdateCachedConfig()
return lc.configObj.AccountID, err
}
func (lc *LocalConfig) DeleteAccountID() error {
lc.configObj.AccountID = ""
return lc.UpdateCachedConfig()
}
func (lc *LocalConfig) UpdateCachedConfig() error {
logger.L().Debug("updating cached config", helpers.Interface("configObj", lc.configObj))
return updateConfigFile(lc.configObj)
}
@@ -170,6 +219,26 @@ func (lc *LocalConfig) DeleteCachedConfig(ctx context.Context) error {
return nil
}
func getTenantConfigFromBE(backendAPI getter.IBackend, configObj *ConfigObj) error {
// get from Kubescape Cloud API
tenantResponse, err := backendAPI.GetTenant()
if err == nil && tenantResponse != nil {
if tenantResponse.AdminMail != "" { // registered tenant
configObj.CustomerAdminEMail = tenantResponse.AdminMail
} else { // new tenant
configObj.Token = tenantResponse.Token
configObj.AccountID = tenantResponse.TenantID
}
} else {
if err != nil && !strings.Contains(err.Error(), "already exists") {
return err
}
}
return nil
}
// ======================================================================================
// ========================== Cluster Config ============================================
// ======================================================================================
@@ -182,6 +251,8 @@ KS_DEFAULT_CONFIGMAP_NAME // name of configmap, if not set default is 'kubescap
KS_DEFAULT_CONFIGMAP_NAMESPACE // configmap namespace, if not set default is 'default'
KS_ACCOUNT_ID
KS_CLIENT_ID
KS_SECRET_KEY
TODO - support:
KS_CACHE // path to cached files
@@ -189,20 +260,21 @@ KS_CACHE // path to cached files
var _ ITenantConfig = &ClusterConfig{}
type ClusterConfig struct {
k8s *k8sinterface.KubernetesApi
configObj *ConfigObj
configMapNamespace string
ksConfigMapName string
ksCloudConfigMapName string
backendAPI getter.IBackend
k8s *k8sinterface.KubernetesApi
configObj *ConfigObj
configMapName string
configMapNamespace string
}
func NewClusterConfig(k8s *k8sinterface.KubernetesApi, accountID, clusterName string, customClusterName string) *ClusterConfig {
func NewClusterConfig(k8s *k8sinterface.KubernetesApi, backendAPI getter.IBackend, credentials *Credentials, clusterName string, customClusterName string) *ClusterConfig {
// var configObj *ConfigObj
c := &ClusterConfig{
k8s: k8s,
configObj: &ConfigObj{},
ksConfigMapName: getKubescapeConfigMapName(),
ksCloudConfigMapName: getKubescapeCloudConfigMapName(),
configMapNamespace: GetConfigMapNamespace(),
k8s: k8s,
backendAPI: backendAPI,
configObj: &ConfigObj{},
configMapName: getConfigMapName(),
configMapNamespace: GetConfigMapNamespace(),
}
// first, load from file
@@ -211,18 +283,12 @@ func NewClusterConfig(k8s *k8sinterface.KubernetesApi, accountID, clusterName st
}
// second, load from configMap
if c.existsConfigMap(c.ksConfigMapName) {
c.updateConfigEmptyFieldsFromKubescapeConfigMap()
if c.existsConfigMap() {
c.updateConfigEmptyFieldsFromConfigMap()
}
// third, load urls from cloudConfigMap
if c.existsConfigMap(c.ksCloudConfigMapName) {
c.updateConfigEmptyFieldsFromKubescapeCloudConfigMap()
}
updateAccountID(c.configObj, accountID)
updateCredentials(c.configObj, credentials)
updateCloudURLs(c.configObj)
updateStorageEnabled(c.configObj)
// If a custom cluster name is provided then set that name, else use the cluster's original name
if customClusterName != "" {
@@ -236,24 +302,80 @@ func NewClusterConfig(k8s *k8sinterface.KubernetesApi, accountID, clusterName st
} else { // override the cluster name if it has unwanted characters
c.configObj.ClusterName = AdoptClusterName(c.configObj.ClusterName)
}
updatedKsCloud := initializeCloudAPI(c)
logger.L().Debug("Kubescape Cloud URLs", helpers.String("api", updatedKsCloud.GetCloudAPIURL()), helpers.String("report", updatedKsCloud.GetCloudReportURL()))
c.backendAPI.SetAccountID(c.configObj.AccountID)
c.backendAPI.SetClientID(c.configObj.ClientID)
c.backendAPI.SetSecretKey(c.configObj.SecretKey)
if c.configObj.CloudAPIURL != "" {
c.backendAPI.SetCloudAPIURL(c.configObj.CloudAPIURL)
} else {
c.configObj.CloudAPIURL = c.backendAPI.GetCloudAPIURL()
}
if c.configObj.CloudAuthURL != "" {
c.backendAPI.SetCloudAuthURL(c.configObj.CloudAuthURL)
} else {
c.configObj.CloudAuthURL = c.backendAPI.GetCloudAuthURL()
}
if c.configObj.CloudReportURL != "" {
c.backendAPI.SetCloudReportURL(c.configObj.CloudReportURL)
} else {
c.configObj.CloudReportURL = c.backendAPI.GetCloudReportURL()
}
if c.configObj.CloudUIURL != "" {
c.backendAPI.SetCloudUIURL(c.configObj.CloudUIURL)
} else {
c.configObj.CloudUIURL = c.backendAPI.GetCloudUIURL()
}
logger.L().Debug("Kubescape Cloud URLs", helpers.String("api", c.backendAPI.GetCloudAPIURL()), helpers.String("auth", c.backendAPI.GetCloudAuthURL()), helpers.String("report", c.backendAPI.GetCloudReportURL()), helpers.String("UI", c.backendAPI.GetCloudUIURL()))
initializeCloudAPI(c)
return c
}
func (c *ClusterConfig) GetConfigObj() *ConfigObj { return c.configObj }
func (c *ClusterConfig) GetDefaultNS() string { return c.configMapNamespace }
func (c *ClusterConfig) GetAccountID() string { return c.configObj.AccountID }
func (c *ClusterConfig) GetClientID() string { return c.configObj.ClientID }
func (c *ClusterConfig) GetSecretKey() string { return c.configObj.SecretKey }
func (c *ClusterConfig) GetTenantEmail() string { return c.configObj.CustomerAdminEMail }
func (c *ClusterConfig) GetToken() string { return c.configObj.Token }
func (c *ClusterConfig) GetCloudReportURL() string { return c.configObj.CloudReportURL }
func (c *ClusterConfig) GetCloudAPIURL() string { return c.configObj.CloudAPIURL }
func (c *ClusterConfig) IsStorageEnabled() bool { return c.configObj.StorageEnabled }
func (c *ClusterConfig) GetCloudUIURL() string { return c.configObj.CloudUIURL }
func (c *ClusterConfig) GetCloudAuthURL() string { return c.configObj.CloudAuthURL }
func (c *ClusterConfig) IsConfigFound() bool { return existsConfigFile() || c.existsConfigMap() }
func (c *ClusterConfig) SetTenant() error {
// ARMO tenant GUID
if err := getTenantConfigFromBE(c.backendAPI, c.configObj); err != nil {
return err
}
c.UpdateCachedConfig()
return nil
}
func (c *ClusterConfig) UpdateCachedConfig() error {
logger.L().Debug("updating cached config", helpers.Interface("configObj", c.configObj))
// update/create config
if c.existsConfigMap() {
if err := c.updateConfigMap(); err != nil {
return err
}
} else {
if err := c.createConfigMap(); err != nil {
return err
}
}
return updateConfigFile(c.configObj)
}
func (c *ClusterConfig) DeleteCachedConfig(ctx context.Context) error {
if err := c.deleteConfigMap(); err != nil {
logger.L().Ctx(ctx).Warning(err.Error())
}
if err := DeleteConfigFile(); err != nil {
logger.L().Ctx(ctx).Warning(err.Error())
}
@@ -271,44 +393,28 @@ func (c *ClusterConfig) ToMapString() map[string]interface{} {
return m
}
func (c *ClusterConfig) updateConfigEmptyFieldsFromKubescapeConfigMap() error {
configMap, err := c.k8s.KubernetesClient.CoreV1().ConfigMaps(c.configMapNamespace).Get(context.Background(), c.ksConfigMapName, metav1.GetOptions{})
func (c *ClusterConfig) updateConfigEmptyFieldsFromConfigMap() error {
configMap, err := c.k8s.KubernetesClient.CoreV1().ConfigMaps(c.configMapNamespace).Get(context.Background(), c.configMapName, metav1.GetOptions{})
if err != nil {
return err
}
tempCO := ConfigObj{}
if jsonConf, ok := configMap.Data["config.json"]; ok {
if err = json.Unmarshal([]byte(jsonConf), &tempCO); err != nil {
return err
}
json.Unmarshal([]byte(jsonConf), &tempCO)
return c.configObj.updateEmptyFields(&tempCO)
}
return err
}
func (c *ClusterConfig) updateConfigEmptyFieldsFromKubescapeCloudConfigMap() error {
configMap, err := c.k8s.KubernetesClient.CoreV1().ConfigMaps(c.configMapNamespace).Get(context.Background(), c.ksCloudConfigMapName, metav1.GetOptions{})
func (c *ClusterConfig) loadConfigFromConfigMap() error {
configMap, err := c.k8s.KubernetesClient.CoreV1().ConfigMaps(c.configMapNamespace).Get(context.Background(), c.configMapName, metav1.GetOptions{})
if err != nil {
return err
}
if jsonConf, ok := configMap.Data["services"]; ok {
services, err := servicediscovery.GetServices(
servicediscoveryv1.NewServiceDiscoveryStreamV1([]byte(jsonConf)),
)
if err != nil {
return err
}
if services.GetApiServerUrl() != "" {
c.configObj.CloudAPIURL = services.GetApiServerUrl()
}
if services.GetReportReceiverHttpUrl() != "" {
c.configObj.CloudReportURL = services.GetReportReceiverHttpUrl()
}
}
return nil
return loadConfigFromData(c.configObj, configMap.Data)
}
func loadConfigFromData(co *ConfigObj, data map[string]string) error {
@@ -322,36 +428,107 @@ func loadConfigFromData(co *ConfigObj, data map[string]string) error {
return e
}
func (c *ClusterConfig) existsConfigMap(name string) bool {
_, err := c.k8s.KubernetesClient.CoreV1().ConfigMaps(c.configMapNamespace).Get(context.Background(), name, metav1.GetOptions{})
func (c *ClusterConfig) existsConfigMap() bool {
_, err := c.k8s.KubernetesClient.CoreV1().ConfigMaps(c.configMapNamespace).Get(context.Background(), c.configMapName, metav1.GetOptions{})
// TODO - check if has customerGUID
return err == nil
}
func (c *ClusterConfig) GetValueByKeyFromConfigMap(key string) (string, error) {
configMap, err := c.k8s.KubernetesClient.CoreV1().ConfigMaps(c.configMapNamespace).Get(context.Background(), c.configMapName, metav1.GetOptions{})
if err != nil {
return "", err
}
if val, ok := configMap.Data[key]; ok {
return val, nil
} else {
return "", fmt.Errorf("value does not exist")
}
}
func GetValueFromConfigJson(key string) (string, error) {
data, err := os.ReadFile(ConfigFileFullPath())
if err != nil {
return "", err
}
var obj map[string]interface{}
if err := json.Unmarshal(data, &obj); err != nil {
return "", err
}
if val, ok := obj[key]; ok {
return fmt.Sprint(val), nil
} else {
return "", fmt.Errorf("value does not exist")
}
}
func (c *ClusterConfig) SetKeyValueInConfigmap(key string, value string) error {
configMap, err := c.k8s.KubernetesClient.CoreV1().ConfigMaps(c.configMapNamespace).Get(context.Background(), c.configMapName, metav1.GetOptions{})
if err != nil {
configMap = &corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: c.configMapName,
},
}
}
if len(configMap.Data) == 0 {
configMap.Data = make(map[string]string)
}
configMap.Data[key] = value
if err != nil {
_, err = c.k8s.KubernetesClient.CoreV1().ConfigMaps(c.configMapNamespace).Create(context.Background(), configMap, metav1.CreateOptions{})
} else {
_, err = c.k8s.KubernetesClient.CoreV1().ConfigMaps(c.configMapNamespace).Update(context.Background(), configMap, metav1.UpdateOptions{})
}
return err
}
func existsConfigFile() bool {
_, err := os.ReadFile(ConfigFileFullPath())
return err == nil
}
func updateConfigFile(configObj *ConfigObj) error {
fullPath := ConfigFileFullPath()
dir := filepath.Dir(fullPath)
if err := os.MkdirAll(dir, 0755); err != nil {
func (c *ClusterConfig) createConfigMap() error {
if c.k8s == nil {
return nil
}
configMap := &corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: c.configMapName,
},
}
c.updateConfigData(configMap)
_, err := c.k8s.KubernetesClient.CoreV1().ConfigMaps(c.configMapNamespace).Create(context.Background(), configMap, metav1.CreateOptions{})
return err
}
func (c *ClusterConfig) updateConfigMap() error {
if c.k8s == nil {
return nil
}
configMap, err := c.k8s.KubernetesClient.CoreV1().ConfigMaps(c.configMapNamespace).Get(context.Background(), c.configMapName, metav1.GetOptions{})
if err != nil {
return err
}
return os.WriteFile(fullPath, configObj.Config(), 0664) //nolint:gosec
c.updateConfigData(configMap)
_, err = c.k8s.KubernetesClient.CoreV1().ConfigMaps(c.configMapNamespace).Update(context.Background(), configMap, metav1.UpdateOptions{})
return err
}
func (c *ClusterConfig) GenerateAccountID() (string, error) {
c.configObj.AccountID = uuid.NewString()
err := c.UpdateCachedConfig()
return c.configObj.AccountID, err
}
func (c *ClusterConfig) DeleteAccountID() error {
c.configObj.AccountID = ""
return c.UpdateCachedConfig()
func updateConfigFile(configObj *ConfigObj) error {
return os.WriteFile(ConfigFileFullPath(), configObj.Config(), 0664) //nolint:gosec
}
func (c *ClusterConfig) updateConfigData(configMap *corev1.ConfigMap) {
@@ -384,6 +561,28 @@ func readConfig(dat []byte, configObj *ConfigObj) error {
return nil
}
// Check if the customer is submitted
func (clusterConfig *ClusterConfig) IsSubmitted() bool {
return clusterConfig.existsConfigMap() || existsConfigFile()
}
// Check if the customer is registered
func (clusterConfig *ClusterConfig) IsRegistered() bool {
// get from armoBE
tenantResponse, err := clusterConfig.backendAPI.GetTenant()
if err == nil && tenantResponse != nil {
if tenantResponse.AdminMail != "" { // this customer already belongs to some user
return true
}
}
return false
}
func (clusterConfig *ClusterConfig) deleteConfigMap() error {
return clusterConfig.k8s.KubernetesClient.CoreV1().ConfigMaps(clusterConfig.configMapNamespace).Delete(context.Background(), clusterConfig.configMapName, metav1.DeleteOptions{})
}
func DeleteConfigFile() error {
return os.Remove(ConfigFileFullPath())
}
@@ -396,53 +595,67 @@ func AdoptClusterName(clusterName string) string {
return re.ReplaceAllString(clusterName, "-")
}
func getKubescapeConfigMapName() string {
if n := os.Getenv(defaultConfigMapNameEnvVar); n != "" {
func getConfigMapName() string {
if n := os.Getenv("KS_DEFAULT_CONFIGMAP_NAME"); n != "" {
return n
}
return kubescapeConfigMapName
}
func getKubescapeCloudConfigMapName() string {
if n := os.Getenv(defaultCloudConfigMapNameEnvVar); n != "" {
return n
}
return kubescapeCloudConfigMapName
}
// GetConfigMapNamespace returns the namespace of the cluster config, which is the same for all in-cluster components
func GetConfigMapNamespace() string {
if n := os.Getenv(defaultConfigMapNamespaceEnvVar); n != "" {
if n := os.Getenv("KS_DEFAULT_CONFIGMAP_NAMESPACE"); n != "" {
return n
}
return kubescapeNamespace
}
func updateAccountID(configObj *ConfigObj, accountID string) {
if accountID != "" {
configObj.AccountID = accountID
func getAccountFromEnv(credentials *Credentials) {
// load from env
if accountID := os.Getenv("KS_ACCOUNT_ID"); credentials.Account == "" && accountID != "" {
credentials.Account = accountID
}
if envAccountID := os.Getenv(accountIdEnvVar); envAccountID != "" {
configObj.AccountID = envAccountID
if clientID := os.Getenv("KS_CLIENT_ID"); credentials.ClientID == "" && clientID != "" {
credentials.ClientID = clientID
}
if secretKey := os.Getenv("KS_SECRET_KEY"); credentials.SecretKey == "" && secretKey != "" {
credentials.SecretKey = secretKey
}
}
func updateStorageEnabled(configObj *ConfigObj) {
configObj.StorageEnabled, _ = ParseBoolEnvVar(storageEnabledEnvVar, configObj.StorageEnabled)
func updateCredentials(configObj *ConfigObj, credentials *Credentials) {
if credentials == nil {
credentials = &Credentials{}
}
getAccountFromEnv(credentials)
if credentials.Account != "" {
configObj.AccountID = credentials.Account // override config Account
}
if credentials.ClientID != "" {
configObj.ClientID = credentials.ClientID // override config ClientID
}
if credentials.SecretKey != "" {
configObj.SecretKey = credentials.SecretKey // override config SecretKey
}
}
func getCloudURLsFromEnv(cloudURLs *CloudURLs) {
// load from env
if cloudAPIURL := os.Getenv(cloudApiUrlEnvVar); cloudAPIURL != "" {
logger.L().Debug("cloud API URL updated from env var", helpers.Interface(cloudApiUrlEnvVar, cloudAPIURL))
if cloudAPIURL := os.Getenv("KS_CLOUD_API_URL"); cloudAPIURL != "" {
cloudURLs.CloudAPIURL = cloudAPIURL
}
if cloudReportURL := os.Getenv(cloudReportUrlEnvVar); cloudReportURL != "" {
logger.L().Debug("cloud Report URL updated from env var", helpers.Interface(cloudReportUrlEnvVar, cloudReportURL))
if cloudAuthURL := os.Getenv("KS_CLOUD_AUTH_URL"); cloudAuthURL != "" {
cloudURLs.CloudAuthURL = cloudAuthURL
}
if cloudReportURL := os.Getenv("KS_CLOUD_REPORT_URL"); cloudReportURL != "" {
cloudURLs.CloudReportURL = cloudReportURL
}
if cloudUIURL := os.Getenv("KS_CLOUD_UI_URL"); cloudUIURL != "" {
cloudURLs.CloudUIURL = cloudUIURL
}
}
func updateCloudURLs(configObj *ConfigObj) {
@@ -453,25 +666,26 @@ func updateCloudURLs(configObj *ConfigObj) {
if cloudURLs.CloudAPIURL != "" {
configObj.CloudAPIURL = cloudURLs.CloudAPIURL // override config CloudAPIURL
}
if cloudURLs.CloudAuthURL != "" {
configObj.CloudAuthURL = cloudURLs.CloudAuthURL // override config CloudAuthURL
}
if cloudURLs.CloudReportURL != "" {
configObj.CloudReportURL = cloudURLs.CloudReportURL // override config CloudReportURL
}
if cloudURLs.CloudUIURL != "" {
configObj.CloudUIURL = cloudURLs.CloudUIURL // override config CloudUIURL
}
}
func initializeCloudAPI(c ITenantConfig) *v1.KSCloudAPI {
logger.L().Debug("initializing KS Cloud API from config", helpers.String("accountID", c.GetAccountID()), helpers.String("cloudAPIURL", c.GetCloudAPIURL()), helpers.String("cloudReportURL", c.GetCloudReportURL()))
cloud, err := v1.NewKSCloudAPI(c.GetCloudAPIURL(), c.GetCloudReportURL(), c.GetAccountID())
if err != nil {
logger.L().Fatal("failed to create KS Cloud client", helpers.Error(err))
}
func initializeCloudAPI(c ITenantConfig) {
cloud := getter.GetKSCloudAPIConnector()
cloud.SetAccountID(c.GetAccountID())
cloud.SetClientID(c.GetClientID())
cloud.SetSecretKey(c.GetSecretKey())
cloud.SetCloudAuthURL(c.GetCloudAuthURL())
cloud.SetCloudReportURL(c.GetCloudReportURL())
cloud.SetCloudUIURL(c.GetCloudUIURL())
cloud.SetCloudAPIURL(c.GetCloudAPIURL())
getter.SetKSCloudAPIConnector(cloud)
return getter.GetKSCloudAPIConnector()
}
func GetTenantConfig(accountID, clusterName, customClusterName string, k8s *k8sinterface.KubernetesApi) ITenantConfig {
if !k8sinterface.IsConnectedToCluster() || k8s == nil {
return NewLocalConfig(accountID, clusterName, customClusterName)
}
return NewClusterConfig(k8s, accountID, clusterName, customClusterName)
}

View File

@@ -12,21 +12,29 @@ import (
func mockConfigObj() *ConfigObj {
return &ConfigObj{
AccountID: "aaa",
ClusterName: "ddd",
CloudReportURL: "report.domain.com",
CloudAPIURL: "api.domain.com",
AccountID: "aaa",
ClientID: "bbb",
SecretKey: "ccc",
ClusterName: "ddd",
CustomerAdminEMail: "ab@cd",
Token: "eee",
CloudReportURL: "report.armo.cloud",
CloudAPIURL: "api.armosec.io",
CloudUIURL: "cloud.armosec.io",
CloudAuthURL: "auth.armosec.io",
}
}
func mockLocalConfig() *LocalConfig {
return &LocalConfig{
configObj: mockConfigObj(),
backendAPI: nil,
configObj: mockConfigObj(),
}
}
func mockClusterConfig() *ClusterConfig {
return &ClusterConfig{
configObj: mockConfigObj(),
backendAPI: nil,
configObj: mockConfigObj(),
}
}
func TestConfig(t *testing.T) {
@@ -35,9 +43,15 @@ func TestConfig(t *testing.T) {
assert.NoError(t, json.Unmarshal(co.Config(), &cop))
assert.Equal(t, co.AccountID, cop.AccountID)
assert.Equal(t, co.ClientID, cop.ClientID)
assert.Equal(t, co.SecretKey, cop.SecretKey)
assert.Equal(t, co.CloudReportURL, cop.CloudReportURL)
assert.Equal(t, co.CloudAPIURL, cop.CloudAPIURL)
assert.Equal(t, "", cop.ClusterName) // Not copied to bytes
assert.Equal(t, co.CloudUIURL, cop.CloudUIURL)
assert.Equal(t, co.CloudAuthURL, cop.CloudAuthURL)
assert.Equal(t, "", cop.ClusterName) // Not copied to bytes
assert.Equal(t, "", cop.CustomerAdminEMail) // Not copied to bytes
assert.Equal(t, "", cop.Token) // Not copied to bytes
}
@@ -51,15 +65,27 @@ func TestITenantConfig(t *testing.T) {
// test LocalConfig methods
assert.Equal(t, co.AccountID, lc.GetAccountID())
assert.Equal(t, co.ClientID, lc.GetClientID())
assert.Equal(t, co.SecretKey, lc.GetSecretKey())
assert.Equal(t, co.ClusterName, lc.GetContextName())
assert.Equal(t, co.CustomerAdminEMail, lc.GetTenantEmail())
assert.Equal(t, co.Token, lc.GetToken())
assert.Equal(t, co.CloudReportURL, lc.GetCloudReportURL())
assert.Equal(t, co.CloudAPIURL, lc.GetCloudAPIURL())
assert.Equal(t, co.CloudUIURL, lc.GetCloudUIURL())
assert.Equal(t, co.CloudAuthURL, lc.GetCloudAuthURL())
// test ClusterConfig methods
assert.Equal(t, co.AccountID, c.GetAccountID())
assert.Equal(t, co.ClientID, c.GetClientID())
assert.Equal(t, co.SecretKey, c.GetSecretKey())
assert.Equal(t, co.ClusterName, c.GetContextName())
assert.Equal(t, co.CustomerAdminEMail, c.GetTenantEmail())
assert.Equal(t, co.Token, c.GetToken())
assert.Equal(t, co.CloudReportURL, c.GetCloudReportURL())
assert.Equal(t, co.CloudAPIURL, c.GetCloudAPIURL())
assert.Equal(t, co.CloudUIURL, c.GetCloudUIURL())
assert.Equal(t, co.CloudAuthURL, c.GetCloudAuthURL())
}
func TestUpdateConfigData(t *testing.T) {
@@ -70,8 +96,12 @@ func TestUpdateConfigData(t *testing.T) {
c.updateConfigData(configMap)
assert.Equal(t, c.GetAccountID(), configMap.Data["accountID"])
assert.Equal(t, c.GetClientID(), configMap.Data["clientID"])
assert.Equal(t, c.GetSecretKey(), configMap.Data["secretKey"])
assert.Equal(t, c.GetCloudReportURL(), configMap.Data["cloudReportURL"])
assert.Equal(t, c.GetCloudAPIURL(), configMap.Data["cloudAPIURL"])
assert.Equal(t, c.GetCloudUIURL(), configMap.Data["cloudUIURL"])
assert.Equal(t, c.GetCloudAuthURL(), configMap.Data["cloudAuthURL"])
}
func TestReadConfig(t *testing.T) {
@@ -84,9 +114,15 @@ func TestReadConfig(t *testing.T) {
readConfig(b, co)
assert.Equal(t, com.AccountID, co.AccountID)
assert.Equal(t, com.ClientID, co.ClientID)
assert.Equal(t, com.SecretKey, co.SecretKey)
assert.Equal(t, com.ClusterName, co.ClusterName)
assert.Equal(t, com.CustomerAdminEMail, co.CustomerAdminEMail)
assert.Equal(t, com.Token, co.Token)
assert.Equal(t, com.CloudReportURL, co.CloudReportURL)
assert.Equal(t, com.CloudAPIURL, co.CloudAPIURL)
assert.Equal(t, com.CloudUIURL, co.CloudUIURL)
assert.Equal(t, com.CloudAuthURL, co.CloudAuthURL)
}
func TestLoadConfigFromData(t *testing.T) {
@@ -105,9 +141,15 @@ func TestLoadConfigFromData(t *testing.T) {
loadConfigFromData(c.configObj, configMap.Data)
assert.Equal(t, c.GetAccountID(), co.AccountID)
assert.Equal(t, c.GetClientID(), co.ClientID)
assert.Equal(t, c.GetSecretKey(), co.SecretKey)
assert.Equal(t, c.GetContextName(), co.ClusterName)
assert.Equal(t, c.GetTenantEmail(), co.CustomerAdminEMail)
assert.Equal(t, c.GetToken(), co.Token)
assert.Equal(t, c.GetCloudReportURL(), co.CloudReportURL)
assert.Equal(t, c.GetCloudAPIURL(), co.CloudAPIURL)
assert.Equal(t, c.GetCloudUIURL(), co.CloudUIURL)
assert.Equal(t, c.GetCloudAuthURL(), co.CloudAuthURL)
}
// use case: all data is in config.json
@@ -125,8 +167,12 @@ func TestLoadConfigFromData(t *testing.T) {
loadConfigFromData(c.configObj, configMap.Data)
assert.Equal(t, c.GetAccountID(), co.AccountID)
assert.Equal(t, c.GetClientID(), co.ClientID)
assert.Equal(t, c.GetSecretKey(), co.SecretKey)
assert.Equal(t, c.GetCloudReportURL(), co.CloudReportURL)
assert.Equal(t, c.GetCloudAPIURL(), co.CloudAPIURL)
assert.Equal(t, c.GetCloudUIURL(), co.CloudUIURL)
assert.Equal(t, c.GetCloudAuthURL(), co.CloudAuthURL)
}
// use case: some data is in config.json
@@ -137,15 +183,21 @@ func TestLoadConfigFromData(t *testing.T) {
}
// add to map
configMap.Data["clientID"] = c.configObj.ClientID
configMap.Data["secretKey"] = c.configObj.SecretKey
configMap.Data["cloudReportURL"] = c.configObj.CloudReportURL
// delete the content
c.configObj.ClientID = ""
c.configObj.SecretKey = ""
c.configObj.CloudReportURL = ""
configMap.Data["config.json"] = string(c.GetConfigObj().Config())
loadConfigFromData(c.configObj, configMap.Data)
assert.NotEmpty(t, c.GetAccountID())
assert.NotEmpty(t, c.GetClientID())
assert.NotEmpty(t, c.GetSecretKey())
assert.NotEmpty(t, c.GetCloudReportURL())
}
@@ -160,11 +212,19 @@ func TestLoadConfigFromData(t *testing.T) {
// add to map
configMap.Data["accountID"] = mockConfigObj().AccountID
configMap.Data["clientID"] = c.configObj.ClientID
configMap.Data["secretKey"] = c.configObj.SecretKey
// delete the content
c.configObj.ClientID = ""
c.configObj.SecretKey = ""
configMap.Data["config.json"] = string(c.GetConfigObj().Config())
loadConfigFromData(c.configObj, configMap.Data)
assert.Equal(t, mockConfigObj().AccountID, c.GetAccountID())
assert.NotEmpty(t, c.GetClientID())
assert.NotEmpty(t, c.GetSecretKey())
}
}
@@ -229,9 +289,13 @@ func Test_initializeCloudAPI(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
initializeCloudAPI(tt.args.c)
cloud := getter.GetKSCloudAPIConnector()
assert.Equal(t, "https://api.domain.com", cloud.GetCloudAPIURL())
assert.Equal(t, "https://report.domain.com", cloud.GetCloudReportURL())
assert.Equal(t, tt.args.c.GetCloudAPIURL(), cloud.GetCloudAPIURL())
assert.Equal(t, tt.args.c.GetCloudAuthURL(), cloud.GetCloudAuthURL())
assert.Equal(t, tt.args.c.GetCloudUIURL(), cloud.GetCloudUIURL())
assert.Equal(t, tt.args.c.GetCloudReportURL(), cloud.GetCloudReportURL())
assert.Equal(t, tt.args.c.GetAccountID(), cloud.GetAccountID())
assert.Equal(t, tt.args.c.GetClientID(), cloud.GetClientID())
assert.Equal(t, tt.args.c.GetSecretKey(), cloud.GetSecretKey())
})
}
}
@@ -290,58 +354,90 @@ func TestUpdateEmptyFields(t *testing.T) {
}{
{
outCo: &ConfigObj{
AccountID: "",
ClusterName: "",
CloudReportURL: "",
CloudAPIURL: "",
AccountID: "",
Token: "",
CustomerAdminEMail: "",
ClusterName: "",
CloudReportURL: "",
CloudAPIURL: "",
CloudUIURL: "",
CloudAuthURL: "",
},
inCo: &ConfigObj{
AccountID: shouldUpdate,
ClusterName: shouldUpdate,
CloudReportURL: shouldUpdate,
CloudAPIURL: shouldUpdate,
AccountID: shouldUpdate,
Token: shouldUpdate,
CustomerAdminEMail: shouldUpdate,
ClusterName: shouldUpdate,
CloudReportURL: shouldUpdate,
CloudAPIURL: shouldUpdate,
CloudUIURL: shouldUpdate,
CloudAuthURL: shouldUpdate,
},
},
{
outCo: &ConfigObj{
AccountID: anyString,
ClusterName: "",
CloudReportURL: "",
CloudAPIURL: "",
AccountID: anyString,
Token: anyString,
CustomerAdminEMail: "",
ClusterName: "",
CloudReportURL: "",
CloudAPIURL: "",
CloudUIURL: "",
CloudAuthURL: "",
},
inCo: &ConfigObj{
AccountID: shouldNotUpdate,
ClusterName: shouldUpdate,
CloudReportURL: shouldUpdate,
CloudAPIURL: shouldUpdate,
AccountID: shouldNotUpdate,
Token: shouldNotUpdate,
CustomerAdminEMail: shouldUpdate,
ClusterName: shouldUpdate,
CloudReportURL: shouldUpdate,
CloudAPIURL: shouldUpdate,
CloudUIURL: shouldUpdate,
CloudAuthURL: shouldUpdate,
},
},
{
outCo: &ConfigObj{
AccountID: "",
ClusterName: anyString,
CloudReportURL: anyString,
CloudAPIURL: anyString,
AccountID: "",
Token: "",
CustomerAdminEMail: anyString,
ClusterName: anyString,
CloudReportURL: anyString,
CloudAPIURL: anyString,
CloudUIURL: anyString,
CloudAuthURL: anyString,
},
inCo: &ConfigObj{
AccountID: shouldUpdate,
ClusterName: shouldNotUpdate,
CloudReportURL: shouldNotUpdate,
CloudAPIURL: shouldNotUpdate,
AccountID: shouldUpdate,
Token: shouldUpdate,
CustomerAdminEMail: shouldNotUpdate,
ClusterName: shouldNotUpdate,
CloudReportURL: shouldNotUpdate,
CloudAPIURL: shouldNotUpdate,
CloudUIURL: shouldNotUpdate,
CloudAuthURL: shouldNotUpdate,
},
},
{
outCo: &ConfigObj{
AccountID: anyString,
ClusterName: anyString,
CloudReportURL: "",
CloudAPIURL: anyString,
AccountID: anyString,
Token: anyString,
CustomerAdminEMail: "",
ClusterName: anyString,
CloudReportURL: "",
CloudAPIURL: anyString,
CloudUIURL: "",
CloudAuthURL: anyString,
},
inCo: &ConfigObj{
AccountID: shouldNotUpdate,
ClusterName: shouldNotUpdate,
CloudReportURL: shouldUpdate,
CloudAPIURL: shouldNotUpdate,
AccountID: shouldNotUpdate,
Token: shouldNotUpdate,
CustomerAdminEMail: shouldUpdate,
ClusterName: shouldNotUpdate,
CloudReportURL: shouldUpdate,
CloudAPIURL: shouldNotUpdate,
CloudUIURL: shouldUpdate,
CloudAuthURL: shouldNotUpdate,
},
},
}
@@ -351,7 +447,11 @@ func TestUpdateEmptyFields(t *testing.T) {
tests[i].outCo.updateEmptyFields(tests[i].inCo)
checkIsUpdateCorrectly(t, beforeChangesOutCO.AccountID, tests[i].outCo.AccountID)
checkIsUpdateCorrectly(t, beforeChangesOutCO.CloudAPIURL, tests[i].outCo.CloudAPIURL)
checkIsUpdateCorrectly(t, beforeChangesOutCO.CloudAuthURL, tests[i].outCo.CloudAuthURL)
checkIsUpdateCorrectly(t, beforeChangesOutCO.CloudReportURL, tests[i].outCo.CloudReportURL)
checkIsUpdateCorrectly(t, beforeChangesOutCO.CloudUIURL, tests[i].outCo.CloudUIURL)
checkIsUpdateCorrectly(t, beforeChangesOutCO.ClusterName, tests[i].outCo.ClusterName)
checkIsUpdateCorrectly(t, beforeChangesOutCO.CustomerAdminEMail, tests[i].outCo.CustomerAdminEMail)
checkIsUpdateCorrectly(t, beforeChangesOutCO.Token, tests[i].outCo.Token)
}
}

View File

@@ -33,7 +33,6 @@ const (
ScanTypeImage ScanTypes = "image"
ScanTypeWorkload ScanTypes = "workload"
ScanTypeFramework ScanTypes = "framework"
ScanTypeControl ScanTypes = "control"
)
type OPASessionObj struct {
@@ -57,7 +56,6 @@ type OPASessionObj struct {
Exceptions []armotypes.PostureExceptionPolicy // list of exceptions to apply on scan results
OmitRawResources bool // omit raw resources from output
SingleResourceScan workloadinterface.IWorkload // single resource scan
TopWorkloadsByScore []reporthandling.IResource
}
func NewOPASessionObj(ctx context.Context, frameworks []reporthandling.Framework, k8sResources K8SResources, scanInfo *ScanInfo) *OPASessionObj {
@@ -111,7 +109,7 @@ func (sessionObj *OPASessionObj) SetTopWorkloads() {
Source: &source,
}
sessionObj.TopWorkloadsByScore = append(sessionObj.TopWorkloadsByScore, wlObj)
sessionObj.Report.SummaryDetails.TopWorkloadsByScore = append(sessionObj.Report.SummaryDetails.TopWorkloadsByScore, wlObj)
count++
}
}

View File

@@ -5,6 +5,7 @@ import (
"github.com/armosec/utils-go/boolutils"
cloudsupport "github.com/kubescape/k8s-interface/cloudsupport/v1"
"github.com/kubescape/k8s-interface/k8sinterface"
"github.com/kubescape/opa-utils/reporthandling"
"github.com/kubescape/opa-utils/reporthandling/apis"
)
@@ -88,50 +89,58 @@ func isRuleKubescapeVersionCompatible(attributes map[string]interface{}, version
return true
}
func getCloudProvider(scanInfo *ScanInfo) reporthandling.ScanningScopeType {
func getCloudType(scanInfo *ScanInfo) (bool, reporthandling.ScanningScopeType) {
if cloudsupport.IsAKS() {
return reporthandling.ScopeCloudAKS
return true, reporthandling.ScopeCloudAKS
}
if cloudsupport.IsEKS() {
return reporthandling.ScopeCloudEKS
if cloudsupport.IsEKS(k8sinterface.GetConfig()) {
return true, reporthandling.ScopeCloudEKS
}
if cloudsupport.IsGKE() {
return reporthandling.ScopeCloudGKE
if cloudsupport.IsGKE(k8sinterface.GetConfig()) {
return true, reporthandling.ScopeCloudGKE
}
return ""
return false, ""
}
func GetScanningScope(scanInfo *ScanInfo) reporthandling.ScanningScopeType {
var result reporthandling.ScanningScopeType
switch scanInfo.GetScanningContext() {
case ContextCluster:
if cloudProvider := getCloudProvider(scanInfo); cloudProvider != "" {
return cloudProvider
isCloud, cloudType := getCloudType(scanInfo)
if isCloud {
result = cloudType
} else {
result = reporthandling.ScopeCluster
}
return reporthandling.ScopeCluster
default:
return reporthandling.ScopeFile
result = reporthandling.ScopeFile
}
return result
}
func isScanningScopeMatchToControlScope(scanScope reporthandling.ScanningScopeType, controlScope reporthandling.ScanningScopeType) bool {
result := false
switch controlScope {
case reporthandling.ScopeFile:
return reporthandling.ScopeFile == scanScope
result = (reporthandling.ScopeFile == scanScope)
case reporthandling.ScopeCluster:
return reporthandling.ScopeCluster == scanScope || reporthandling.ScopeCloud == scanScope || reporthandling.ScopeCloudAKS == scanScope || reporthandling.ScopeCloudEKS == scanScope || reporthandling.ScopeCloudGKE == scanScope
result = (reporthandling.ScopeCluster == scanScope) || (reporthandling.ScopeCloud == scanScope) || (reporthandling.ScopeCloudAKS == scanScope) || (reporthandling.ScopeCloudEKS == scanScope) || (reporthandling.ScopeCloudGKE == scanScope)
case reporthandling.ScopeCloud:
return reporthandling.ScopeCloud == scanScope || reporthandling.ScopeCloudAKS == scanScope || reporthandling.ScopeCloudEKS == scanScope || reporthandling.ScopeCloudGKE == scanScope
result = (reporthandling.ScopeCloud == scanScope) || (reporthandling.ScopeCloudAKS == scanScope) || (reporthandling.ScopeCloudEKS == scanScope) || (reporthandling.ScopeCloudGKE == scanScope)
case reporthandling.ScopeCloudAKS:
return reporthandling.ScopeCloudAKS == scanScope
result = (reporthandling.ScopeCloudAKS == scanScope)
case reporthandling.ScopeCloudEKS:
return reporthandling.ScopeCloudEKS == scanScope
result = (reporthandling.ScopeCloudEKS == scanScope)
case reporthandling.ScopeCloudGKE:
return reporthandling.ScopeCloudGKE == scanScope
result = (reporthandling.ScopeCloudGKE == scanScope)
default:
return true
result = true
}
return result
}
func isControlFitToScanScope(control reporthandling.Control, scanScopeMatches reporthandling.ScanningScopeType) bool {

View File

@@ -4,8 +4,8 @@ import (
"fmt"
"testing"
"github.com/armosec/armoapi-go/armotypes"
"github.com/kubescape/opa-utils/reporthandling"
"github.com/stretchr/testify/assert"
)
@@ -79,6 +79,7 @@ func TestIsControlFitToScanScope(t *testing.T) {
scanInfo: &ScanInfo{},
Control: reporthandling.Control{
ScanningScope: &reporthandling.ScanningScope{
Matches: []reporthandling.ScanningScopeType{
reporthandling.ScopeCloudEKS,
},
@@ -98,237 +99,6 @@ func TestIsControlFitToScanScope(t *testing.T) {
expected_res: false,
}}
for i := range tests {
assert.Equal(t, tests[i].expected_res, isControlFitToScanScope(tests[i].Control, GetScanningScope(tests[i].scanInfo)), fmt.Sprintf("tests_true index %d", i))
}
}
func TestIsScanningScopeMatchToControlScope(t *testing.T) {
tests := []struct {
scanScope reporthandling.ScanningScopeType
controlScope reporthandling.ScanningScopeType
expected bool
}{
{
scanScope: reporthandling.ScopeFile,
controlScope: reporthandling.ScopeFile,
expected: true,
},
{
scanScope: ScopeCluster,
controlScope: ScopeCluster,
expected: true,
},
{
scanScope: reporthandling.ScopeCloud,
controlScope: reporthandling.ScopeCloud,
expected: true,
},
{
scanScope: reporthandling.ScopeCloudAKS,
controlScope: reporthandling.ScopeCloudAKS,
expected: true,
},
{
scanScope: reporthandling.ScopeCloudEKS,
controlScope: reporthandling.ScopeCloudEKS,
expected: true,
},
{
scanScope: reporthandling.ScopeCloudGKE,
controlScope: reporthandling.ScopeCloudGKE,
expected: true,
},
{
scanScope: ScopeCluster,
controlScope: reporthandling.ScopeCloud,
expected: false,
},
{
scanScope: reporthandling.ScopeCloud,
controlScope: ScopeCluster,
expected: true,
},
{
scanScope: reporthandling.ScopeCloudAKS,
controlScope: ScopeCluster,
expected: true,
},
{
scanScope: reporthandling.ScopeCloudEKS,
controlScope: ScopeCluster,
expected: true,
},
{
scanScope: reporthandling.ScopeCloudGKE,
controlScope: ScopeCluster,
expected: true,
},
{
scanScope: reporthandling.ScopeCloud,
controlScope: reporthandling.ScopeCloudAKS,
expected: false,
},
{
scanScope: reporthandling.ScopeCloudAKS,
controlScope: reporthandling.ScopeCloud,
expected: true,
},
{
scanScope: reporthandling.ScopeCloudEKS,
controlScope: reporthandling.ScopeCloud,
expected: true,
},
{
scanScope: reporthandling.ScopeCloudGKE,
controlScope: reporthandling.ScopeCloud,
expected: true,
},
{
scanScope: ScopeCluster,
controlScope: reporthandling.ScopeCloudAKS,
expected: false,
},
{
scanScope: ScopeCluster,
controlScope: reporthandling.ScopeCloudEKS,
expected: false,
},
{
scanScope: ScopeCluster,
controlScope: reporthandling.ScopeCloudGKE,
expected: false,
},
{
scanScope: reporthandling.ScopeFile,
controlScope: ScopeCluster,
expected: false,
},
{
scanScope: reporthandling.ScopeFile,
controlScope: reporthandling.ScopeCloud,
expected: false,
},
{
scanScope: reporthandling.ScopeFile,
controlScope: reporthandling.ScopeCloudAKS,
expected: false,
},
{
scanScope: reporthandling.ScopeFile,
controlScope: reporthandling.ScopeCloudEKS,
expected: false,
},
{
scanScope: reporthandling.ScopeFile,
controlScope: reporthandling.ScopeCloudGKE,
expected: false,
},
{
scanScope: reporthandling.ScopeCloud,
controlScope: reporthandling.ScopeCloudEKS,
expected: false,
},
{
scanScope: reporthandling.ScopeCloud,
controlScope: reporthandling.ScopeCloudGKE,
expected: false,
},
{
scanScope: reporthandling.ScopeCloudAKS,
controlScope: reporthandling.ScopeCloudEKS,
expected: false,
},
{
scanScope: reporthandling.ScopeCloudAKS,
controlScope: reporthandling.ScopeCloudGKE,
expected: false,
},
{
scanScope: reporthandling.ScopeCloudEKS,
controlScope: reporthandling.ScopeCloudAKS,
expected: false,
},
{
scanScope: reporthandling.ScopeCloudEKS,
controlScope: reporthandling.ScopeCloudGKE,
expected: false,
},
{
scanScope: reporthandling.ScopeCloudGKE,
controlScope: reporthandling.ScopeCloudAKS,
expected: false,
},
{
scanScope: reporthandling.ScopeCloudGKE,
controlScope: reporthandling.ScopeCloudEKS,
expected: false,
},
}
for _, test := range tests {
result := isScanningScopeMatchToControlScope(test.scanScope, test.controlScope)
assert.Equal(t, test.expected, result, fmt.Sprintf("scanScope: %v, controlScope: %v", test.scanScope, test.controlScope))
}
}
func TestIsFrameworkFitToScanScope(t *testing.T) {
tests := []struct {
name string
framework reporthandling.Framework
scanScopeMatch reporthandling.ScanningScopeType
want bool
}{
{
name: "Framework with nil ScanningScope should return true",
framework: reporthandling.Framework{
PortalBase: armotypes.PortalBase{
Name: "test-framework",
},
},
scanScopeMatch: reporthandling.ScopeFile,
want: true,
},
{
name: "Framework with empty ScanningScope.Matches should return true",
framework: reporthandling.Framework{
PortalBase: armotypes.PortalBase{
Name: "test-framework",
}, ScanningScope: &reporthandling.ScanningScope{},
},
scanScopeMatch: reporthandling.ScopeFile,
want: true,
},
{
name: "Framework with matching ScanningScope.Matches should return true",
framework: reporthandling.Framework{
PortalBase: armotypes.PortalBase{
Name: "test-framework",
}, ScanningScope: &reporthandling.ScanningScope{
Matches: []reporthandling.ScanningScopeType{reporthandling.ScopeFile},
},
},
scanScopeMatch: reporthandling.ScopeFile,
want: true,
},
{
name: "Framework with non-matching ScanningScope.Matches should return false",
framework: reporthandling.Framework{
PortalBase: armotypes.PortalBase{
Name: "test-framework",
}, ScanningScope: &reporthandling.ScanningScope{
Matches: []reporthandling.ScanningScopeType{reporthandling.ScopeCluster},
},
},
scanScopeMatch: reporthandling.ScopeFile,
want: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := isFrameworkFitToScanScope(tt.framework, tt.scanScopeMatch); got != tt.want {
t.Errorf("isFrameworkFitToScanScope() = %v, want %v", got, tt.want)
}
})
assert.Equal(t, isControlFitToScanScope(tests[i].Control, GetScanningScope(tests[i].scanInfo)), tests[i].expected_res, fmt.Sprintf("tests_true index %d", i))
}
}

View File

@@ -0,0 +1,7 @@
package cautils
// Kubescape Cloud environment vars
var (
CustomerGUID = ""
ClusterName = ""
)

View File

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

View File

@@ -5,7 +5,6 @@ import (
"strings"
"github.com/armosec/armoapi-go/armotypes"
"github.com/kubescape/opa-utils/reporthandling"
"github.com/kubescape/opa-utils/reporthandling/attacktrack/v1alpha1"

View File

@@ -9,19 +9,11 @@ import (
"strings"
"testing"
jsoniter "github.com/json-iterator/go"
"github.com/kubescape/kubescape/v2/internal/testutils"
jsoniter "github.com/json-iterator/go"
"github.com/stretchr/testify/require"
)
func min(a, b int64) int64 {
if a < b {
return a
}
return b
}
func TestReleasedPolicy(t *testing.T) {
t.Parallel()

View File

@@ -0,0 +1,42 @@
package getter
import (
"context"
"os"
containeranalysis "cloud.google.com/go/containeranalysis/apiv1"
)
type GCPCloudAPI struct {
credentialsPath string
context context.Context
client *containeranalysis.Client
projectID string
credentialsCheck bool
}
func GetGlobalGCPCloudAPIConnector() *GCPCloudAPI {
if os.Getenv("KS_GCP_CREDENTIALS_PATH") == "" || os.Getenv("KS_GCP_PROJECT_ID") == "" {
return &GCPCloudAPI{
credentialsCheck: false,
}
} else {
return &GCPCloudAPI{
context: context.Background(),
credentialsPath: os.Getenv("KS_GCP_CREDENTIALS_PATH"),
projectID: os.Getenv("KS_GCP_PROJECT_ID"),
credentialsCheck: true,
}
}
}
func (api *GCPCloudAPI) SetClient(client *containeranalysis.Client) {
api.client = client
}
func (api *GCPCloudAPI) GetCredentialsPath() string { return api.credentialsPath }
func (api *GCPCloudAPI) GetClient() *containeranalysis.Client { return api.client }
func (api *GCPCloudAPI) GetProjectID() string { return api.projectID }
func (api *GCPCloudAPI) GetCredentialsCheck() bool { return api.credentialsCheck }
func (api *GCPCloudAPI) GetContext() context.Context { return api.context }

View File

@@ -6,7 +6,6 @@ import (
"path/filepath"
"testing"
beClient "github.com/kubescape/backend/pkg/client/v1"
"github.com/stretchr/testify/require"
)
@@ -73,7 +72,7 @@ func TestHttpMethods(t *testing.T) {
client := http.DefaultClient
hdrs := map[string]string{"key": "value"}
srv := beClient.MockAPIServer(t)
srv := mockAPIServer(t)
t.Cleanup(srv.Close)
t.Run("HttpGetter should GET", func(t *testing.T) {

View File

@@ -31,4 +31,25 @@ type (
IAttackTracksGetter interface {
GetAttackTracks() ([]v1alpha1.AttackTrack, error)
}
// IBackend knows how to configure a KS Cloud client
IBackend interface {
GetAccountID() string
GetClientID() string
GetSecretKey() string
GetCloudReportURL() string
GetCloudAPIURL() string
GetCloudUIURL() string
GetCloudAuthURL() string
SetAccountID(accountID string)
SetClientID(clientID string)
SetSecretKey(secretKey string)
SetCloudReportURL(cloudReportURL string)
SetCloudAPIURL(cloudAPIURL string)
SetCloudUIURL(cloudUIURL string)
SetCloudAuthURL(cloudAuthURL string)
GetTenant() (*TenantResponse, error)
}
)

View File

@@ -1,49 +1,833 @@
package getter
import (
v1 "github.com/kubescape/backend/pkg/client/v1"
"github.com/kubescape/go-logger"
"github.com/kubescape/go-logger/helpers"
"bytes"
"errors"
"fmt"
"io"
"net/http"
"strings"
)
const (
// Kubescape API endpoints
// production
ksCloudERURL = "report.armo.cloud" // API reports URL
ksCloudBEURL = "api.armosec.io" // API backend URL
ksCloudFEURL = "cloud.armosec.io" // API frontend (UI) URIL
ksCloudAUTHURL = "auth.armosec.io" // API login URL
// staging
ksCloudStageERURL = "report-ks.eustage2.cyberarmorsoft.com"
ksCloudStageBEURL = "api-stage.armosec.io"
ksCloudStageFEURL = "armoui-stage.armosec.io"
ksCloudStageAUTHURL = "eggauth-stage.armosec.io"
// dev
ksCloudDevERURL = "report.eudev3.cyberarmorsoft.com"
ksCloudDevBEURL = "api-dev.armosec.io"
ksCloudDevFEURL = "cloud-dev.armosec.io"
ksCloudDevAUTHURL = "eggauth-dev.armosec.io"
// Kubescape API routes
pathAttackTracks = "/api/v1/attackTracks"
pathFrameworks = "/api/v1/armoFrameworks"
pathExceptions = "/api/v1/armoPostureExceptions"
pathTenant = "/api/v1/tenants/createTenant"
pathExceptionPolicy = "/api/v1/postureExceptionPolicy"
pathCustomerConfig = "/api/v1/armoCustomerConfiguration"
pathLogin = "/identity/resources/auth/v1/api-token"
pathToken = "/api/v1/openid_customers" //nolint:gosec
// reports upload route
pathReport = "/k8s/v2/postureReport"
// Kubescape UI routes
pathUIScan = "/compliance/%s"
pathUIRBAC = "/rbac-visualizer"
pathUIRepository = "/repository-scanning/%s"
pathUIDashboard = "/dashboard/"
pathUISign = "/account/sign-up"
)
const (
// default dummy GUID when not defined
fallbackGUID = "11111111-1111-1111-1111-111111111111"
// URL query parameters
queryParamGUID = "customerGUID"
queryParamScope = "scope"
queryParamFrameworkName = "frameworkName"
queryParamPolicyName = "policyName"
queryParamClusterName = "clusterName"
queryParamContextName = "contextName"
queryParamUTMSource = "utm_source"
queryParamUTMMedium = "utm_medium"
// queryParamUTMCampaign = "utm_campaign"
queryParamReport = "reportGUID"
queryParamInvitationToken = "invitationToken"
authenticationCookie = "auth"
)
var (
// Errors returned by the API
ErrLoginMissingAccountID = errors.New("failed to login, missing accountID")
ErrLoginMissingClientID = errors.New("failed to login, missing clientID")
ErrLoginMissingSecretKey = errors.New("failed to login, missing secretKey")
ErrAPINotPublic = errors.New("control api is not public")
)
var (
// globalKSCloudAPIConnector is a static global instance of the KS Cloud client,
// to be initialized with SetKSCloudAPIConnector.
globalKSCloudAPIConnector *v1.KSCloudAPI
globalKSCloudAPIConnector *KSCloudAPI
_ IPolicyGetter = &v1.KSCloudAPI{}
_ IExceptionsGetter = &v1.KSCloudAPI{}
_ IAttackTracksGetter = &v1.KSCloudAPI{}
_ IControlsInputsGetter = &v1.KSCloudAPI{}
_ IPolicyGetter = &KSCloudAPI{}
_ IExceptionsGetter = &KSCloudAPI{}
_ IAttackTracksGetter = &KSCloudAPI{}
_ IControlsInputsGetter = &KSCloudAPI{}
)
// KSCloudAPI allows to access the API of the Kubescape Cloud offering.
type KSCloudAPI struct {
authCookie *http.Cookie
*ksCloudOptions
authhost string
cloudAPIURL string
secretKey string
accountID string
cloudAuthURL string
invitationToken string
reporthost string
scheme string
host string
authscheme string
clientID string
uischeme string
uihost string
reportscheme string
feToken feLoginResponse
loggedIn bool
}
// SetKSCloudAPIConnector registers a global instance of the KS Cloud client.
//
// NOTE: cannot be used concurrently.
func SetKSCloudAPIConnector(ksCloudAPI *v1.KSCloudAPI) {
if ksCloudAPI != nil {
logger.L().Debug("setting global KS Cloud API connector",
helpers.String("accountID", ksCloudAPI.GetAccountID()),
helpers.String("cloudAPIURL", ksCloudAPI.GetCloudAPIURL()),
helpers.String("cloudReportURL", ksCloudAPI.GetCloudReportURL()))
} else {
logger.L().Debug("setting global KS Cloud API connector (nil)")
}
func SetKSCloudAPIConnector(ksCloudAPI *KSCloudAPI) {
globalKSCloudAPIConnector = ksCloudAPI
}
// GetKSCloudAPIConnector returns a shallow clone of the KS Cloud client registered for this package.
//
// NOTE: cannot be used concurrently with SetKSCloudAPIConnector.
func GetKSCloudAPIConnector() *v1.KSCloudAPI {
func GetKSCloudAPIConnector() *KSCloudAPI {
if globalKSCloudAPIConnector == nil {
SetKSCloudAPIConnector(v1.NewEmptyKSCloudAPI())
SetKSCloudAPIConnector(NewKSCloudAPIProd())
}
// we return a shallow clone that may be freely modified by the caller.
client := *globalKSCloudAPIConnector
options := *globalKSCloudAPIConnector.KsCloudOptions
client.KsCloudOptions = &options
options := *globalKSCloudAPIConnector.ksCloudOptions
client.ksCloudOptions = &options
return &client
}
// NewKSCloudAPIDev returns a KS Cloud client pointing to a development environment.
func NewKSCloudAPIDev(opts ...KSCloudOption) *KSCloudAPI {
devOpts := []KSCloudOption{
WithFrontendURL(ksCloudDevFEURL),
WithReportURL(ksCloudDevERURL),
}
devOpts = append(devOpts, opts...)
apiObj := newKSCloudAPI(
ksCloudDevBEURL,
ksCloudDevAUTHURL,
devOpts...,
)
return apiObj
}
// NewKSCloudAPIDProd returns a KS Cloud client pointing to a production environment.
func NewKSCloudAPIProd(opts ...KSCloudOption) *KSCloudAPI {
prodOpts := []KSCloudOption{
WithFrontendURL(ksCloudFEURL),
WithReportURL(ksCloudERURL),
}
prodOpts = append(prodOpts, opts...)
return newKSCloudAPI(
ksCloudBEURL,
ksCloudAUTHURL,
prodOpts...,
)
}
// NewKSCloudAPIStaging returns a KS Cloud client pointing to a testing environment.
func NewKSCloudAPIStaging(opts ...KSCloudOption) *KSCloudAPI {
stagingOpts := []KSCloudOption{
WithFrontendURL(ksCloudStageFEURL),
WithReportURL(ksCloudStageERURL),
}
stagingOpts = append(stagingOpts, opts...)
return newKSCloudAPI(
ksCloudStageBEURL,
ksCloudStageAUTHURL,
stagingOpts...,
)
}
// NewKSCloudAPICustomed returns a KS Cloud client with configurable API and authentication endpoints.
func NewKSCloudAPICustomized(ksCloudAPIURL, ksCloudAuthURL string, opts ...KSCloudOption) *KSCloudAPI {
return newKSCloudAPI(
ksCloudAPIURL,
ksCloudAuthURL,
opts...,
)
}
func newKSCloudAPI(apiURL, authURL string, opts ...KSCloudOption) *KSCloudAPI {
api := &KSCloudAPI{
cloudAPIURL: apiURL,
cloudAuthURL: authURL,
ksCloudOptions: ksCloudOptionsWithDefaults(opts),
}
api.SetCloudAPIURL(apiURL)
api.SetCloudAuthURL(authURL)
api.SetCloudUIURL(api.cloudUIURL)
api.SetCloudReportURL(api.cloudReportURL)
return api
}
// Get retrieves an API resource.
//
// The response is serialized as a string.
//
// The caller may specify extra headers.
//
// By default, all authentication headers are added.
func (api *KSCloudAPI) Get(fullURL string, headers map[string]string) (string, error) {
rdr, size, err := api.get(fullURL, withExtraHeaders(headers))
if err != nil {
return "", err
}
defer rdr.Close()
return readString(rdr, size)
}
// Post creates an API resource.
//
// The response is serialized as a string.
//
// The caller may specify extra headers.
//
// By default, the body content type is set to JSON and all authentication headers are added.
func (api *KSCloudAPI) Post(fullURL string, headers map[string]string, body []byte) (string, error) {
rdr, size, err := api.post(fullURL, body, withContentJSON(true), withExtraHeaders(headers))
if err != nil {
return "", err
}
defer rdr.Close()
return readString(rdr, size)
}
// Delete an API resource.
//
// The response is serialized as a string.
//
// The caller may specify extra headers.
//
// By default, all authentication headers are added.
func (api *KSCloudAPI) Delete(fullURL string, headers map[string]string) (string, error) {
rdr, size, err := api.delete(fullURL, withExtraHeaders(headers))
if err != nil {
return "", err
}
defer rdr.Close()
return readString(rdr, size)
}
// GetAccountID returns the customer account's GUID.
func (api *KSCloudAPI) GetAccountID() string { return api.accountID }
// IsLoggedIn indicates if the client has sucessfully authenticated.
func (api *KSCloudAPI) IsLoggedIn() bool { return api.loggedIn }
func (api *KSCloudAPI) GetClientID() string { return api.clientID }
func (api *KSCloudAPI) GetSecretKey() string { return api.secretKey }
func (api *KSCloudAPI) GetCloudReportURL() string { return api.cloudReportURL }
func (api *KSCloudAPI) GetCloudAPIURL() string { return api.cloudAPIURL }
func (api *KSCloudAPI) GetCloudUIURL() string { return api.cloudUIURL }
func (api *KSCloudAPI) GetCloudAuthURL() string { return api.cloudAuthURL }
func (api *KSCloudAPI) GetInvitationToken() string { return api.invitationToken }
func (api *KSCloudAPI) SetAccountID(accountID string) { api.accountID = accountID }
func (api *KSCloudAPI) SetClientID(clientID string) { api.clientID = clientID }
func (api *KSCloudAPI) SetSecretKey(secretKey string) { api.secretKey = secretKey }
func (api *KSCloudAPI) SetInvitationToken(token string) { api.invitationToken = token }
func (api *KSCloudAPI) SetCloudAPIURL(cloudAPIURL string) {
api.cloudAPIURL = cloudAPIURL
api.scheme, api.host = parseHost(cloudAPIURL)
}
func (api *KSCloudAPI) SetCloudUIURL(cloudUIURL string) {
api.cloudUIURL = cloudUIURL
api.uischeme, api.uihost = parseHost(cloudUIURL)
}
func (api *KSCloudAPI) SetCloudAuthURL(cloudAuthURL string) {
api.cloudAuthURL = cloudAuthURL
api.authscheme, api.authhost = parseHost(cloudAuthURL)
}
func (api *KSCloudAPI) SetCloudReportURL(cloudReportURL string) {
api.cloudReportURL = cloudReportURL
api.reportscheme, api.reporthost = parseHost(cloudReportURL)
}
func (api *KSCloudAPI) GetAttackTracks() ([]AttackTrack, error) {
rdr, _, err := api.get(api.getAttackTracksURL())
if err != nil {
return nil, err
}
defer rdr.Close()
attackTracks, err := decode[[]AttackTrack](rdr)
if err != nil {
return nil, err
}
return attackTracks, nil
}
func (api *KSCloudAPI) getAttackTracksURL() string {
return api.buildAPIURL(
pathAttackTracks,
api.paramsWithGUID()...,
)
}
// GetFramework retrieves a framework by name.
func (api *KSCloudAPI) GetFramework(frameworkName string) (*Framework, error) {
rdr, _, err := api.get(api.getFrameworkURL(frameworkName))
if err != nil {
return nil, err
}
defer rdr.Close()
framework, err := decode[Framework](rdr)
if err != nil {
return nil, err
}
return &framework, err
}
func (api *KSCloudAPI) getFrameworkURL(frameworkName string) string {
if isNativeFramework(frameworkName) {
// Native framework name is normalized as upper case, but for a custom framework the name remains unaltered
frameworkName = strings.ToUpper(frameworkName)
}
return api.buildAPIURL(
pathFrameworks,
append(
api.paramsWithGUID(),
queryParamFrameworkName, frameworkName,
)...,
)
}
// GetFrameworks returns all registered frameworks.
func (api *KSCloudAPI) GetFrameworks() ([]Framework, error) {
rdr, _, err := api.get(api.getListFrameworkURL())
if err != nil {
return nil, err
}
defer rdr.Close()
frameworks, err := decode[[]Framework](rdr)
if err != nil {
return nil, err
}
return frameworks, err
}
func (api *KSCloudAPI) getListFrameworkURL() string {
return api.buildAPIURL(
pathFrameworks,
api.paramsWithGUID()...,
)
}
// ListCustomFrameworks lists the names of all non-native frameworks that have been registered for this account.
func (api *KSCloudAPI) ListCustomFrameworks() ([]string, error) {
frameworks, err := api.GetFrameworks()
if err != nil {
return nil, err
}
frameworkList := make([]string, 0, len(frameworks))
for _, framework := range frameworks {
if isNativeFramework(framework.Name) {
continue
}
frameworkList = append(frameworkList, framework.Name)
}
return frameworkList, nil
}
// ListFrameworks list the names of all registered frameworks.
func (api *KSCloudAPI) ListFrameworks() ([]string, error) {
frameworks, err := api.GetFrameworks()
if err != nil {
return nil, err
}
frameworkList := make([]string, 0, len(frameworks))
for _, framework := range frameworks {
name := framework.Name
if isNativeFramework(framework.Name) {
name = strings.ToLower(framework.Name)
}
frameworkList = append(frameworkList, name)
}
return frameworkList, nil
}
// GetExceptions returns exception policies.
func (api *KSCloudAPI) GetExceptions(clusterName string) ([]PostureExceptionPolicy, error) {
rdr, _, err := api.get(api.getExceptionsURL(clusterName))
if err != nil {
return nil, err
}
defer rdr.Close()
exceptions, err := decode[[]PostureExceptionPolicy](rdr)
if err != nil {
return nil, err
}
return exceptions, nil
}
func (api *KSCloudAPI) getExceptionsURL(clusterName string) string {
return api.buildAPIURL(
pathExceptions,
api.paramsWithGUID()...,
)
// queryParamClusterName, clusterName, // TODO - fix customer name support in Armo BE
}
// GetTenant retrieves the credentials for the calling tenant.
//
// The tenant ID overides any already provided account ID.
func (api *KSCloudAPI) GetTenant() (*TenantResponse, error) {
rdr, _, err := api.get(api.getTenantURL())
if err != nil {
return nil, err
}
defer rdr.Close()
tenant, err := decode[TenantResponse](rdr)
if err != nil {
return nil, err
}
if tenant.TenantID != "" {
api.accountID = tenant.TenantID
}
return &tenant, nil
}
func (api *KSCloudAPI) getTenantURL() string {
var params []string
if api.accountID != "" {
params = []string{
queryParamGUID, api.accountID, // NOTE: no fallback in this case
}
}
return api.buildAPIURL(
pathTenant,
params...,
)
}
// GetAccountConfig yields the account configuration.
func (api *KSCloudAPI) GetAccountConfig(clusterName string) (*CustomerConfig, error) {
if api.accountID == "" {
return &CustomerConfig{}, nil
}
rdr, _, err := api.get(api.getAccountConfig(clusterName))
if err != nil {
return nil, err
}
defer rdr.Close()
accountConfig, err := decode[CustomerConfig](rdr)
if err != nil {
// retry with default scope
rdr, _, err = api.get(api.getAccountConfigDefault(clusterName))
if err != nil {
return nil, err
}
defer rdr.Close()
accountConfig, err = decode[CustomerConfig](rdr)
if err != nil {
return nil, err
}
}
return &accountConfig, nil
}
func (api *KSCloudAPI) getAccountConfig(clusterName string) string {
params := api.paramsWithGUID()
if clusterName != "" { // TODO - fix customer name support in Armo BE
params = append(params, queryParamClusterName, clusterName)
}
return api.buildAPIURL(
pathCustomerConfig,
params...,
)
}
func (api *KSCloudAPI) getAccountConfigDefault(clusterName string) string {
params := append(
api.paramsWithGUID(),
queryParamScope, "customer",
)
if clusterName != "" { // TODO - fix customer name support in Armo BE
params = append(params, queryParamClusterName, clusterName)
}
return api.buildAPIURL(
pathCustomerConfig,
params...,
)
}
// GetControlsInputs returns the controls inputs configured in the account configuration.
func (api *KSCloudAPI) GetControlsInputs(clusterName string) (map[string][]string, error) {
accountConfig, err := api.GetAccountConfig(clusterName)
if err != nil {
return nil, err
}
return accountConfig.Settings.PostureControlInputs, nil
}
// GetControl is currently not exposed as a public API endpoint.
func (api *KSCloudAPI) GetControl(ID string) (*Control, error) {
return nil, ErrAPINotPublic
}
// ListControls is currently not exposed as a public API endpoint.
func (api *KSCloudAPI) ListControls() ([]string, error) {
return nil, ErrAPINotPublic
}
// PostExceptions registers a list of exceptions.
func (api *KSCloudAPI) PostExceptions(exceptions []PostureExceptionPolicy) error {
target := api.exceptionsURL("")
for i := range exceptions {
jazon, err := json.Marshal(exceptions[i])
if err != nil {
return err
}
_, _, err = api.post(target, jazon, withContentJSON(true))
if err != nil {
return err
}
}
return nil
}
// Delete exception removes a registered exception rule.
func (api *KSCloudAPI) DeleteException(exceptionName string) error {
_, _, err := api.delete(api.exceptionsURL(exceptionName))
return err
}
func (api *KSCloudAPI) exceptionsURL(exceptionsPolicyName string) string {
params := api.paramsWithGUID()
if exceptionsPolicyName != "" { // for delete
params = append(params, queryParamPolicyName, exceptionsPolicyName)
}
return api.buildAPIURL(
pathExceptionPolicy,
params...,
)
}
// SubmitReport uploads a posture report.
func (api *KSCloudAPI) SubmitReport(report *PostureReport) error {
jazon, err := json.Marshal(report)
if err != nil {
return err
}
_, _, err = api.post(api.postReportURL(report.ClusterName, report.ReportID), jazon, withContentJSON(true), withToken(api.invitationToken))
return err
}
func (api *KSCloudAPI) postReportURL(cluster, reportID string) string {
return api.buildReportURL(pathReport,
append(
api.paramsWithGUID(),
queryParamContextName, cluster,
queryParamClusterName, cluster, // deprecated
queryParamReport, reportID,
)...,
)
}
// ViewReportURL yields the frontend URL to view a posture report (e.g. from a repository scan).
func (api *KSCloudAPI) ViewReportURL(reportID string) string {
return api.buildUIURL(
fmt.Sprintf(pathUIRepository, reportID),
)
}
// ViewDashboardURL yields the frontend URL for the dashboard.
func (api *KSCloudAPI) ViewDashboardURL() string {
return api.buildUIURL(
pathUIDashboard,
)
}
// ViewRBACURL yields the frontend URL to visualize RBAC.
func (api *KSCloudAPI) ViewRBACURL() string {
return api.buildUIURL(
pathUIRBAC,
)
}
// ViewRBACURL yields the frontend URL to check the compliance of a scanned cluster.
func (api *KSCloudAPI) ViewScanURL(cluster string) string {
return api.buildUIURL(
fmt.Sprintf(pathUIScan, cluster),
)
}
// ViewSignURL yields the frontend login page.
func (api *KSCloudAPI) ViewSignURL() string {
params := api.paramsWithGUID()
params = append(params, api.paramsWithUTM()...)
params = append(params, queryParamInvitationToken, api.invitationToken)
return api.buildUIURL(
pathUISign,
params...,
)
}
// Login to the KS Cloud using the caller's accountID, clientID and secret key.
func (api *KSCloudAPI) Login() error {
if err := api.loginRequirements(); err != nil {
return err
}
// 1. acquire auth token
body, err := json.Marshal(feLoginData{ClientId: api.clientID, Secret: api.secretKey})
if err != nil {
return err
}
rdr, _, err := api.post(api.authTokenURL(), body, withContentJSON(true))
if err != nil {
return err
}
defer rdr.Close()
resp, err := decode[feLoginResponse](rdr)
if err != nil {
return err
}
api.feToken = resp
// 2. acquire auth cookie
// Now that we have the JWT token, acquire a cookie from the API
api.authCookie, err = api.getAuthCookie()
if err != nil {
return err
}
api.loggedIn = true
return nil
}
func (api *KSCloudAPI) authTokenURL() string {
return api.buildAuthURL(pathLogin)
}
func (api *KSCloudAPI) getOpenidURL() string {
return api.buildAPIURL(pathToken)
}
func (api *KSCloudAPI) getAuthCookie() (*http.Cookie, error) {
selectCustomer := ksCloudSelectCustomer{SelectedCustomerGuid: api.accountID}
body, err := json.Marshal(selectCustomer)
if err != nil {
return nil, err
}
target := api.getOpenidURL()
o := api.defaultRequestOptions([]requestOption{withContentJSON(true), withCookie(nil)})
req, err := http.NewRequestWithContext(o.reqContext, http.MethodPost, target, bytes.NewBuffer(body))
if err != nil {
return nil, err
}
o.setHeaders(req)
o.traceReq(req)
resp, err := api.httpClient.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
o.traceResp(resp)
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("failed to get cookie from %s: status %d", target, resp.StatusCode)
}
for _, cookie := range resp.Cookies() {
if cookie.Name == authenticationCookie {
return cookie, nil
}
}
return nil, fmt.Errorf("no auth cookie in response from %s", target)
}
func (api *KSCloudAPI) loginRequirements() error {
if api.accountID == "" {
return ErrLoginMissingAccountID
}
if api.clientID == "" {
return ErrLoginMissingClientID
}
if api.secretKey == "" {
return ErrLoginMissingSecretKey
}
return nil
}
// defaultRequestOptions adds standard authentication headers to all requests
func (api *KSCloudAPI) defaultRequestOptions(opts []requestOption) *requestOptions {
optionsWithDefaults := append(make([]requestOption, 0, 4),
withToken(api.feToken.Token),
withCookie(api.authCookie),
withTrace(api.withTrace),
)
optionsWithDefaults = append(optionsWithDefaults, opts...)
return requestOptionsWithDefaults(optionsWithDefaults)
}
func (api *KSCloudAPI) get(fullURL string, opts ...requestOption) (io.ReadCloser, int64, error) {
o := api.defaultRequestOptions(opts)
req, err := http.NewRequestWithContext(o.reqContext, http.MethodGet, fullURL, nil)
if err != nil {
return nil, 0, err
}
return api.do(req, o)
}
func (api *KSCloudAPI) post(fullURL string, body []byte, opts ...requestOption) (io.ReadCloser, int64, error) {
o := api.defaultRequestOptions(opts)
req, err := http.NewRequestWithContext(o.reqContext, http.MethodPost, fullURL, bytes.NewBuffer(body))
if err != nil {
return nil, 0, err
}
return api.do(req, o)
}
func (api *KSCloudAPI) delete(fullURL string, opts ...requestOption) (io.ReadCloser, int64, error) {
o := api.defaultRequestOptions(opts)
req, err := http.NewRequestWithContext(o.reqContext, http.MethodDelete, fullURL, nil)
if err != nil {
return nil, 0, err
}
return api.do(req, o)
}
func (api *KSCloudAPI) do(req *http.Request, o *requestOptions) (io.ReadCloser, int64, error) {
o.setHeaders(req)
o.traceReq(req)
resp, err := api.httpClient.Do(req)
if err != nil {
return nil, 0, err
}
o.traceResp(resp)
if resp.StatusCode >= 400 {
if req.URL.Path == pathLogin {
return nil, 0, errAuth(resp)
}
return nil, 0, errAPI(resp)
}
return resp.Body, resp.ContentLength, err
}
func (api *KSCloudAPI) paramsWithGUID() []string {
return append(make([]string, 0, 6),
queryParamGUID, api.getCustomerGUIDFallBack(),
)
}
func (api *KSCloudAPI) paramsWithUTM() []string {
return append(make([]string, 0, 6),
queryParamUTMSource, "ARMOgithub",
queryParamUTMMedium, "createaccount",
)
}
func (api *KSCloudAPI) getCustomerGUIDFallBack() string {
if api.accountID != "" {
return api.accountID
}
return fallbackGUID
}

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -3,87 +3,20 @@
package cautils
import (
"fmt"
"path/filepath"
"time"
"errors"
"github.com/kubescape/go-git-url/apis"
"github.com/kubescape/go-logger"
"github.com/kubescape/go-logger/helpers"
"github.com/matthyx/go-gitlog"
)
var ErrFatalNotSupportedByBuild = errors.New(`git scan not supported by this build. Build with tag "gitenabled" to enable the git scan feature`)
type gitRepository struct {
gitLogDisabled bool
gitRepo gitlog.GitLog
fileToLastCommit map[string]*gitlog.Commit
}
func newGitRepository(root string) (*gitRepository, error) {
gitRepo := gitlog.New(&gitlog.Config{Path: root})
return &gitRepository{
gitRepo: gitRepo,
}, nil
return &gitRepository{}, ErrWarnNotSupportedByBuild
}
func (g *gitRepository) GetFileLastCommit(filePath string) (*apis.Commit, error) {
if len(g.fileToLastCommit) == 0 && !g.gitLogDisabled {
g.buildCommitMap()
}
if relevantCommit, exists := g.fileToLastCommit[filepath.ToSlash(filePath)]; exists {
return g.getCommit(relevantCommit), nil
}
return nil, fmt.Errorf("failed to get commit information for file: %s", filePath)
}
func (g *gitRepository) buildCommitMap() {
filePathToCommitTime := map[string]time.Time{}
filePathToCommit := map[string]*gitlog.Commit{}
allCommits, err := g.gitRepo.Log(nil, &gitlog.Params{IgnoreMerges: true})
if err != nil {
logger.L().Warning("git not found in PATH: git metadata (author and hash) will not be available", helpers.Error(err))
g.gitLogDisabled = true
return
}
// builds a map of all files to their last commit
for _, commit := range allCommits {
for _, file := range commit.Files {
commitTime := commit.Author.Date
// In case we have the commit information for the file which is not the latest - we override it
if currentCommitTime, exists := filePathToCommitTime[file]; exists {
if currentCommitTime.Before(commitTime) {
filePathToCommitTime[file] = commitTime
filePathToCommit[file] = commit
}
} else {
filePathToCommitTime[file] = commitTime
filePathToCommit[file] = commit
}
}
}
g.fileToLastCommit = filePathToCommit
}
func (g *gitRepository) getCommit(commit *gitlog.Commit) *apis.Commit {
return &apis.Commit{
SHA: commit.Hash.Long,
Author: apis.Committer{
Name: commit.Author.Name,
Email: commit.Author.Email,
Date: commit.Author.Date,
},
Message: commit.Subject + "\n" + commit.Body,
Committer: apis.Committer{
Name: commit.Committer.Name,
Email: commit.Committer.Email,
Date: commit.Committer.Date,
},
Files: []apis.Files{},
}
return nil, ErrFatalNotSupportedByBuild
}

View File

@@ -0,0 +1,11 @@
//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,5 +1,4 @@
//go:build gitenabled
package cautils
import (
@@ -28,7 +27,64 @@ func newGitRepository(root string) (*gitRepository, error) {
func (g *gitRepository) GetFileLastCommit(filePath string) (*apis.Commit, error) {
if len(g.fileToLastCommit) == 0 {
g.buildCommitMap()
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 {
@@ -38,67 +94,6 @@ func (g *gitRepository) GetFileLastCommit(filePath string) (*apis.Commit, error)
return nil, fmt.Errorf("failed to get commit information for file: %s", filePath)
}
func (g *gitRepository) buildCommitMap() {
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
}
func (g *gitRepository) getAllCommits() ([]*git2go.Commit, error) {
logItr, itrErr := g.git2GoRepo.Walk()
if itrErr != nil {

View File

@@ -1,18 +1,13 @@
//go:build gitenabled
package cautils
import (
"testing"
"github.com/stretchr/testify/assert"
)
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(int64(1653235917), commit.Author.Date.Unix())
s.Equal("2022-05-22 19:11:57 +0300 +0300", commit.Author.Date.String())
s.Equal("added file B\n", commit.Message)
}
}
@@ -26,7 +21,7 @@ func (s *LocalGitRepositoryTestSuite) TestGetFileLastCommit() {
s.Equal("9fae4be19624297947d2b605cefbff516628612d", commit.SHA)
s.Equal("Amir Malka", commit.Author.Name)
s.Equal("amirm@armosec.io", commit.Author.Email)
s.Equal(int64(1653234948), commit.Author.Date.Unix())
s.Equal("2022-05-22 18:55:48 +0300 +0300", commit.Author.Date.String())
s.Equal("added file A\n", commit.Message)
}
@@ -40,19 +35,10 @@ func (s *LocalGitRepositoryTestSuite) TestGetFileLastCommit() {
s.Equal("7e09312b8017695fadcd606882e3779f10a5c832", commit.SHA)
s.Equal("Amir Malka", commit.Author.Name)
s.Equal("amirm@armosec.io", commit.Author.Email)
s.Equal(int64(1653235917), commit.Author.Date.Unix())
s.Equal("2022-05-22 19:11:57 +0300 +0300", commit.Author.Date.String())
s.Equal("added file B\n", commit.Message)
}
}
})
}
func BenchmarkBuildCommitMap(b *testing.B) {
localRepo, err := NewLocalGitRepository("testdata/temp/localrepo")
assert.NoError(b, err)
for i := 0; i < b.N; i++ {
localRepo.buildCommitMap()
}
b.ReportAllocs()
}

View File

@@ -1,144 +0,0 @@
package cautils
import (
"errors"
"testing"
"github.com/armosec/armoapi-go/apis"
apisv1 "github.com/kubescape/opa-utils/httpserver/apis/v1"
utilsmetav1 "github.com/kubescape/opa-utils/httpserver/meta/v1"
"github.com/stretchr/testify/assert"
)
func newFalse() *bool {
f := false
return &f
}
func Test_GetRequestPayload(t *testing.T) {
testCases := []struct {
name string
clusterName string
OperatorScanInfo
result *apis.Commands
}{
{
name: "scan kubescape config",
OperatorScanInfo: &ConfigScanInfo{
ExcludedNamespaces: []string{"1111"},
IncludedNamespaces: []string{"2222"},
HostScanner: false,
Frameworks: []string{"any", "many"},
},
result: &apis.Commands{
Commands: []apis.Command{
{
CommandName: apis.TypeRunKubescape,
Args: map[string]interface{}{
KubescapeScanV1: utilsmetav1.PostScanRequest{
ExcludedNamespaces: []string{"1111"},
IncludeNamespaces: []string{"2222"},
TargetType: apisv1.KindFramework,
TargetNames: []string{"any", "many"},
HostScanner: newFalse(),
},
},
},
},
},
},
{
name: "scan kubescape vulns",
OperatorScanInfo: &VulnerabilitiesScanInfo{
ClusterName: "any",
IncludeNamespaces: []string{""},
},
result: &apis.Commands{
Commands: []apis.Command{
{
CommandName: apis.TypeScanImages,
WildWlid: "wlid://cluster-any",
},
},
},
},
{
name: "scan kubescape vulns with namespace",
OperatorScanInfo: &VulnerabilitiesScanInfo{
ClusterName: "any",
IncludeNamespaces: []string{"123"},
},
result: &apis.Commands{
Commands: []apis.Command{
{
CommandName: apis.TypeScanImages,
WildWlid: "wlid://cluster-any/namespace-123",
},
},
},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
result := tc.OperatorScanInfo.GetRequestPayload()
assert.Equal(t, tc.result, result)
})
}
}
func Test_ValidatePayload(t *testing.T) {
testCases := []struct {
name string
clusterName string
OperatorScanInfo
result error
}{
{
name: "ConfigScanInfo first happy case",
OperatorScanInfo: &ConfigScanInfo{
ExcludedNamespaces: []string{"1111"},
IncludedNamespaces: []string{},
HostScanner: false,
Frameworks: []string{"any", "many"},
},
result: nil,
},
{
name: "ConfigScanInfo second happy case",
OperatorScanInfo: &ConfigScanInfo{
ExcludedNamespaces: []string{},
IncludedNamespaces: []string{"1111"},
HostScanner: false,
Frameworks: []string{"any", "many"},
},
result: nil,
},
{
name: "ConfigScanInfo returned error",
OperatorScanInfo: &ConfigScanInfo{
ExcludedNamespaces: []string{"1111"},
IncludedNamespaces: []string{"2222"},
HostScanner: false,
Frameworks: []string{"any", "many"},
},
result: errors.New("invalid arguments: include-namespaces and exclude-namespaces can't pass together to the CLI"),
},
{
name: "VulnerabilitiesScanInfo happy case",
OperatorScanInfo: &VulnerabilitiesScanInfo{
ClusterName: "any",
IncludeNamespaces: []string{""},
},
result: nil,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
payload := tc.OperatorScanInfo.GetRequestPayload()
result := tc.OperatorScanInfo.ValidatePayload(payload)
assert.Equal(t, tc.result, result)
})
}
}

View File

@@ -1,107 +0,0 @@
package cautils
import (
"errors"
"github.com/armosec/armoapi-go/apis"
"github.com/armosec/utils-k8s-go/wlid"
apisv1 "github.com/kubescape/opa-utils/httpserver/apis/v1"
utilsmetav1 "github.com/kubescape/opa-utils/httpserver/meta/v1"
)
type OperatorSubCommand string
const (
ScanCommand OperatorSubCommand = "scan"
ScanConfigCommand OperatorSubCommand = "config"
ScanVulnerabilitiesCommand OperatorSubCommand = "vulnerabilities"
KubescapeScanV1 string = "scanV1"
)
type VulnerabilitiesScanInfo struct {
IncludeNamespaces []string
ClusterName string
}
type ConfigScanInfo struct {
ExcludedNamespaces []string
IncludedNamespaces []string
HostScanner bool
Frameworks []string // Load frameworks for config scan
}
type OperatorInfo struct {
Subcommands []OperatorSubCommand
OperatorScanInfo
}
type OperatorConnector interface {
StartPortForwarder() error
StopPortForwarder()
GetPortForwardLocalhost() string
}
type OperatorScanInfo interface {
GetRequestPayload() *apis.Commands
ValidatePayload(*apis.Commands) error
}
func (v *VulnerabilitiesScanInfo) ValidatePayload(commands *apis.Commands) error {
return nil
}
func (v *VulnerabilitiesScanInfo) GetRequestPayload() *apis.Commands {
var commands []apis.Command
clusterName := v.ClusterName
if len(v.IncludeNamespaces) == 0 {
wildWlid := wlid.GetWLID(clusterName, "", "", "")
command := apis.Command{
CommandName: apis.TypeScanImages,
WildWlid: wildWlid,
}
commands = append(commands, command)
} else {
for i := range v.IncludeNamespaces {
wildWlid := wlid.GetWLID(clusterName, v.IncludeNamespaces[i], "", "")
command := apis.Command{
CommandName: apis.TypeScanImages,
WildWlid: wildWlid,
}
commands = append(commands, command)
}
}
return &apis.Commands{
Commands: commands,
}
}
func (c *ConfigScanInfo) ValidatePayload(commands *apis.Commands) error {
if len(c.IncludedNamespaces) != 0 && len(c.ExcludedNamespaces) != 0 {
return errors.New("invalid arguments: include-namespaces and exclude-namespaces can't pass together to the CLI")
}
return nil
}
func (c *ConfigScanInfo) GetRequestPayload() *apis.Commands {
if len(c.Frameworks) == 0 {
c.Frameworks = append(c.Frameworks, "all")
}
return &apis.Commands{
Commands: []apis.Command{
{
CommandName: apis.TypeRunKubescape,
Args: map[string]interface{}{
KubescapeScanV1: utilsmetav1.PostScanRequest{
ExcludedNamespaces: c.ExcludedNamespaces,
IncludeNamespaces: c.IncludedNamespaces,
TargetType: apisv1.KindFramework,
TargetNames: c.Frameworks,
HostScanner: &c.HostScanner,
},
},
},
},
}
}

View File

@@ -1,86 +0,0 @@
package cautils
import (
"bytes"
"fmt"
"net/http"
"net/url"
"os"
"strings"
"github.com/kubescape/k8s-interface/k8sinterface"
v1 "k8s.io/api/core/v1"
"k8s.io/client-go/tools/portforward"
"k8s.io/client-go/transport/spdy"
)
const (
DefaultPortForwardPortEnv string = "DEFAULT_PORT_FORWARDER_PORT"
DefaultPortForwardPortValue string = "4444"
)
type portForward struct {
*portforward.PortForwarder
localPort string
stopChan chan struct{}
readyChan chan struct{}
out *bytes.Buffer
errOut *bytes.Buffer
}
func getPortForwardingPort() string {
if port, exist := os.LookupEnv(DefaultPortForwardPortEnv); exist {
return port
}
return DefaultPortForwardPortValue
}
func CreatePortForwarder(k8sClient *k8sinterface.KubernetesApi, pod *v1.Pod, forwardingPort, namespace string) (OperatorConnector, error) {
path := fmt.Sprintf("/api/v1/namespaces/%s/pods/%s/portforward", namespace, pod.Name)
hostIP := strings.TrimLeft(k8sClient.K8SConfig.Host, "htps:/")
serverURL := &url.URL{Scheme: "https", Path: path, Host: hostIP}
roundTripper, upgrader, err := spdy.RoundTripperFor(k8sClient.K8SConfig)
if err != nil {
return nil, err
}
dialer := spdy.NewDialer(upgrader, &http.Client{Transport: roundTripper}, http.MethodPost, serverURL)
stopChan, readyChan := make(chan struct{}, 1), make(chan struct{})
out, errOut := new(bytes.Buffer), new(bytes.Buffer)
forwarder, err := portforward.NewOnAddresses(dialer, []string{"localhost"}, []string{fmt.Sprintf("%s:%s", getPortForwardingPort(), forwardingPort)}, stopChan, readyChan, out, errOut)
if err != nil {
return nil, err
}
return &portForward{
PortForwarder: forwarder,
localPort: getPortForwardingPort(),
stopChan: stopChan,
readyChan: readyChan,
out: out,
errOut: errOut,
}, nil
}
func (p *portForward) waitForPortForwardReadiness() struct{} {
return <-p.readyChan
}
func (p *portForward) GetPortForwardLocalhost() string {
return "localhost:" + getPortForwardingPort()
}
func (p *portForward) StopPortForwarder() {
p.stopChan <- struct{}{}
}
func (p *portForward) StartPortForwarder() error {
go func() {
p.PortForwarder.ForwardPorts()
}()
p.waitForPortForwardReadiness()
return nil
}

View File

@@ -1,134 +0,0 @@
package cautils
import (
"context"
"testing"
"github.com/kubescape/k8s-interface/k8sinterface"
"github.com/stretchr/testify/assert"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/discovery"
"k8s.io/client-go/kubernetes/fake"
"k8s.io/client-go/rest"
)
type FakeCachedDiscoveryClient struct {
discovery.DiscoveryInterface
Groups []*metav1.APIGroup
Resources []*metav1.APIResourceList
PreferredResources []*metav1.APIResourceList
Invalidations int
}
func Test_getPortForwardingPort(t *testing.T) {
testCases := []struct {
name string
createNewPort bool
port string
expectedPort string
}{
{
name: "test default port",
port: "",
expectedPort: DefaultPortForwardPortValue,
},
{
name: "test set port",
createNewPort: true,
port: "1234",
expectedPort: "1234",
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if tc.createNewPort {
t.Setenv(DefaultPortForwardPortEnv, tc.port)
}
assert.Equal(t, tc.expectedPort, getPortForwardingPort())
})
}
}
func Test_CreatePortForwarder(t *testing.T) {
testCases := []struct {
name string
expectedError error
}{
{
name: "test creation",
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
k8sClient := k8sinterface.KubernetesApi{
KubernetesClient: fake.NewSimpleClientset(),
K8SConfig: &rest.Config{
Host: "any",
},
Context: context.TODO(),
}
operatorPod := v1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "first",
Labels: map[string]string{
"app": "operator",
},
},
}
createdOperatorPod, err := k8sClient.KubernetesClient.CoreV1().Pods(kubescapeNamespace).Create(k8sClient.Context, &operatorPod, metav1.CreateOptions{})
assert.Equal(t, nil, err)
_, err = CreatePortForwarder(&k8sClient, createdOperatorPod, "1234", "any")
assert.Equal(t, nil, err)
})
}
}
func Test_GetPortForwardLocalhost(t *testing.T) {
testCases := []struct {
name string
port string
result string
}{
{
name: "test creation",
port: "1234",
result: "localhost",
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
k8sClient := k8sinterface.KubernetesApi{
KubernetesClient: fake.NewSimpleClientset(),
K8SConfig: &rest.Config{
Host: "any",
},
Context: context.TODO(),
}
operatorPod := v1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "first",
Labels: map[string]string{
"app": "operator",
},
},
}
createdOperatorPod, err := k8sClient.KubernetesClient.CoreV1().Pods(kubescapeNamespace).Create(k8sClient.Context, &operatorPod, metav1.CreateOptions{})
assert.Equal(t, nil, err)
t.Setenv(DefaultPortForwardPortEnv, tc.port)
pf, err := CreatePortForwarder(&k8sClient, createdOperatorPod, "1234", "any")
assert.Equal(t, nil, err)
result := pf.GetPortForwardLocalhost()
assert.Equal(t, tc.result+":"+getPortForwardingPort(), result)
})
}
}

View File

@@ -7,24 +7,43 @@ import (
)
type RootInfo struct {
Logger string // logger level
LoggerName string // logger name ("pretty"/"zap"/"none")
CacheDir string // cached dir
DisableColor bool // Disable Color
EnableColor bool // Force enable Color
DiscoveryServerURL string // Discovery Server URL (See https://github.com/kubescape/backend/tree/main/pkg/servicediscovery)
KubeContext string // context name
Logger string // logger level
LoggerName string // logger name ("pretty"/"zap"/"none")
CacheDir string // cached dir
DisableColor bool // Disable Color
EnableColor bool // Force enable Color
KSCloudBEURLs string // Kubescape Cloud URL
KSCloudBEURLsDep string // Kubescape Cloud URL
}
type CloudURLs struct {
CloudReportURL string
CloudAPIURL string
CloudUIURL string
CloudAuthURL string
}
// To check if the provided account ID is valid
func ValidateAccountID(accountID string) error {
type Credentials struct {
Account string
ClientID string
SecretKey string
}
// To check if the user's credentials: accountID / clientID / secretKey are valid.
func (credentials *Credentials) Validate() error {
// Check if the Account-ID is valid
if _, err := uuid.Parse(accountID); accountID != "" && err != nil {
return fmt.Errorf("bad argument: accound ID must be a valid UUID")
if _, err := uuid.Parse(credentials.Account); credentials.Account != "" && err != nil {
return fmt.Errorf("bad argument: account must be a valid UUID")
}
// Check if the Client-ID is valid
if _, err := uuid.Parse(credentials.ClientID); credentials.ClientID != "" && err != nil {
return fmt.Errorf("bad argument: account must be a valid UUID")
}
// Check if the Secret-Key is valid
if _, err := uuid.Parse(credentials.SecretKey); credentials.SecretKey != "" && err != nil {
return fmt.Errorf("bad argument: account must be a valid UUID")
}
return nil

View File

@@ -2,9 +2,11 @@ package cautils
import "testing"
func TestValidateAccountID(t *testing.T) {
func TestCredentials_Validate(t *testing.T) {
type fields struct {
Account string
Account string
ClientID string
SecretKey string
}
tests := []struct {
name string
@@ -25,11 +27,44 @@ func TestValidateAccountID(t *testing.T) {
},
wantErr: true,
},
{
name: "valid client ID",
fields: fields{
ClientID: "22019933-feac-4012-a8eb-e81461ba6655",
},
wantErr: false,
},
{
name: "invalid client ID",
fields: fields{
ClientID: "22019933-feac-4012-a8eb-e81461ba665",
},
wantErr: true,
},
{
name: "valid secret key",
fields: fields{
SecretKey: "22019933-feac-4012-a8eb-e81461ba6655",
},
wantErr: false,
},
{
name: "invalid secret key",
fields: fields{
SecretKey: "22019933-feac-4012-a8eb-e81461ba665",
},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if err := ValidateAccountID(tt.fields.Account); (err != nil) != tt.wantErr {
t.Errorf("ValidateAccountID() error = %v, wantErr %v", err, tt.wantErr)
credentials := &Credentials{
Account: tt.fields.Account,
ClientID: tt.fields.ClientID,
SecretKey: tt.fields.SecretKey,
}
if err := credentials.Validate(); (err != nil) != tt.wantErr {
t.Errorf("Credentials.Validate() error = %v, wantErr %v", err, tt.wantErr)
}
})
}

View File

@@ -124,17 +124,18 @@ type ScanInfo struct {
ComplianceThreshold float32 // Compliance score threshold
FailThresholdSeverity string // Severity at and above which the command should fail
Submit bool // Submit results to Kubescape Cloud BE
CreateAccount bool // Create account in Kubescape Cloud BE if no account found in local cache
ScanID string // Report id of the current scan
HostSensorEnabled BoolPtrFlag // Deploy Kubescape K8s host scanner to collect data from certain controls
HostSensorYamlPath string // Path to hostsensor file
Local bool // Do not submit results
AccountID string // account ID
Credentials Credentials // account ID
KubeContext string // context name
FrameworkScan bool // false if scanning control
ScanAll bool // true if scan all frameworks
OmitRawResources bool // true if omit raw resources from the output
PrintAttackTree bool // true if print attack tree
ScanObject *objectsenvelopes.ScanObject // identifies a single resource (k8s object) to be scanned
IsDeletedScanObject bool // indicates whether the ScanObject is a deleted K8S resource
ScanType ScanTypes
ScanImages bool
ChartPath string
@@ -295,10 +296,11 @@ func scanInfoToScanMetadata(ctx context.Context, scanInfo *ScanInfo) *reporthand
}
func (scanInfo *ScanInfo) GetScanningContext() ScanningContext {
input := ""
if len(scanInfo.InputPatterns) > 0 {
return GetScanningContext(scanInfo.InputPatterns[0])
input = scanInfo.InputPatterns[0]
}
return GetScanningContext("")
return GetScanningContext(input)
}
// GetScanningContext get scanning context from the input param

View File

@@ -2,8 +2,6 @@ package cautils
import (
"context"
"os"
"path/filepath"
"testing"
reporthandlingv2 "github.com/kubescape/opa-utils/reporthandling/v2"
@@ -25,11 +23,13 @@ func TestSetContextMetadata(t *testing.T) {
/*{
ctx := reporthandlingv2.ContextMetadata{}
setContextMetadata(&ctx, "https://github.com/kubescape/kubescape")
assert.Nil(t, ctx.ClusterContextMetadata)
assert.Nil(t, ctx.DirectoryContextMetadata)
assert.Nil(t, ctx.FileContextMetadata)
assert.Nil(t, ctx.HelmContextMetadata)
assert.NotNil(t, ctx.RepoContextMetadata)
assert.Equal(t, "kubescape", ctx.RepoContextMetadata.Repo)
assert.Equal(t, "kubescape", ctx.RepoContextMetadata.Owner)
assert.Equal(t, "master", ctx.RepoContextMetadata.Branch)
@@ -37,18 +37,12 @@ func TestSetContextMetadata(t *testing.T) {
}
func TestGetHostname(t *testing.T) {
// Test that the hostname is not empty
assert.NotEqual(t, "", getHostname())
}
func TestGetScanningContext(t *testing.T) {
// Test with empty input
assert.Equal(t, ContextCluster, GetScanningContext(""))
// Test with Git URL input
assert.Equal(t, ContextGitURL, GetScanningContext("https://github.com/kubescape/kubescape"))
// TODO: Add more tests with other input types
}
func TestScanInfoFormats(t *testing.T) {
@@ -77,30 +71,3 @@ func TestScanInfoFormats(t *testing.T) {
})
}
}
func TestGetScanningContextWithFile(t *testing.T) {
// Test with a file
dir, err := os.MkdirTemp("", "example")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(dir)
filePath := filepath.Join(dir, "file.txt")
if _, err := os.Create(filePath); err != nil {
t.Fatal(err)
}
assert.Equal(t, ContextFile, GetScanningContext(filePath))
}
func TestGetScanningContextWithDir(t *testing.T) {
// Test with a directory
dir, err := os.MkdirTemp("", "example")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(dir)
assert.Equal(t, ContextDir, GetScanningContext(dir))
}

View File

@@ -63,17 +63,3 @@ func ParseIntEnvVar(varName string, defaultValue int) (int, error) {
return intValue, nil
}
func ParseBoolEnvVar(varName string, defaultValue bool) (bool, error) {
varValue, exists := os.LookupEnv(varName)
if !exists {
return defaultValue, nil
}
boolValue, err := strconv.ParseBool(varValue)
if err != nil {
return defaultValue, fmt.Errorf("failed to parse %s env var as bool: %w", varName, err)
}
return boolValue, nil
}

View File

@@ -2,6 +2,7 @@ package cautils
import (
"fmt"
"os"
"strings"
"testing"
@@ -74,7 +75,9 @@ func TestParseIntEnvVar(t *testing.T) {
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if tc.varValue != "" {
t.Setenv(tc.varName, tc.varValue)
os.Setenv(tc.varName, tc.varValue)
} else {
os.Unsetenv(tc.varName)
}
actual, err := ParseIntEnvVar(tc.varName, tc.defaultValue)
@@ -132,57 +135,3 @@ func TestStringSlicesAreEqual(t *testing.T) {
})
}
}
func TestParseBoolEnvVar(t *testing.T) {
testCases := []struct {
expectedErr string
name string
varName string
varValue string
defaultValue bool
expected bool
}{
{
name: "Variable does not exist",
varName: "DOES_NOT_EXIST",
varValue: "",
defaultValue: true,
expected: true,
expectedErr: "",
},
{
name: "Variable exists and is a valid bool",
varName: "MY_VAR",
varValue: "true",
defaultValue: false,
expected: true,
expectedErr: "",
},
{
name: "Variable exists but is not a valid bool",
varName: "MY_VAR",
varValue: "not_a_boolean",
defaultValue: false,
expected: false,
expectedErr: "failed to parse MY_VAR env var as bool",
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if tc.varValue != "" {
t.Setenv(tc.varName, tc.varValue)
}
actual, err := ParseBoolEnvVar(tc.varName, tc.defaultValue)
if tc.expectedErr != "" {
assert.NotNil(t, err)
assert.ErrorContains(t, err, tc.expectedErr)
} else {
assert.Nil(t, err)
}
assert.Equalf(t, tc.expected, actual, "unexpected result")
})
}
}

View File

@@ -31,7 +31,7 @@ type IVersionCheckHandler interface {
func NewIVersionCheckHandler(ctx context.Context) IVersionCheckHandler {
if BuildNumber == "" {
logger.L().Ctx(ctx).Warning("Unknown build number, this might affect your scan results. Please make sure that you are running the latest version")
logger.L().Ctx(ctx).Warning("Unknown build number, this might affect your scan results. Please make sure you are updated to latest version")
}
if v, ok := os.LookupEnv(CLIENT_ENV); ok && v != "" {

View File

@@ -4,35 +4,47 @@ import (
"context"
"fmt"
"github.com/kubescape/kubescape/v2/core/cautils"
metav1 "github.com/kubescape/kubescape/v2/core/meta/datastructures/v1"
)
func (ks *Kubescape) SetCachedConfig(setConfig *metav1.SetConfig) error {
tenant := cautils.GetTenantConfig("", "", "", nil)
tenant := getTenantConfig(nil, "", "", nil)
if setConfig.Account != "" {
tenant.GetConfigObj().AccountID = setConfig.Account
}
if setConfig.SecretKey != "" {
tenant.GetConfigObj().SecretKey = setConfig.SecretKey
}
if setConfig.ClientID != "" {
tenant.GetConfigObj().ClientID = setConfig.ClientID
}
if setConfig.CloudAPIURL != "" {
tenant.GetConfigObj().CloudAPIURL = setConfig.CloudAPIURL
}
if setConfig.CloudAuthURL != "" {
tenant.GetConfigObj().CloudAuthURL = setConfig.CloudAuthURL
}
if setConfig.CloudReportURL != "" {
tenant.GetConfigObj().CloudReportURL = setConfig.CloudReportURL
}
if setConfig.CloudUIURL != "" {
tenant.GetConfigObj().CloudUIURL = setConfig.CloudUIURL
}
return tenant.UpdateCachedConfig()
}
// View cached configurations
func (ks *Kubescape) ViewCachedConfig(viewConfig *metav1.ViewConfig) error {
tenant := cautils.GetTenantConfig("", "", "", getKubernetesApi()) // change k8sinterface
tenant := getTenantConfig(nil, "", "", getKubernetesApi()) // change k8sinterface
fmt.Fprintf(viewConfig.Writer, "%s\n", tenant.GetConfigObj().Config())
return nil
}
func (ks *Kubescape) DeleteCachedConfig(ctx context.Context, deleteConfig *metav1.DeleteConfig) error {
tenant := cautils.GetTenantConfig("", "", "", nil) // change k8sinterface
tenant := getTenantConfig(nil, "", "", nil) // change k8sinterface
return tenant.DeleteCachedConfig(ctx)
}

View File

@@ -1,99 +0,0 @@
package core
import (
"encoding/json"
"errors"
"fmt"
"net/http"
"net/url"
"github.com/armosec/armoapi-go/apis"
"github.com/armosec/utils-go/httputils"
"github.com/kubescape/k8s-interface/k8sinterface"
"github.com/kubescape/kubescape/v2/core/cautils"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
const (
operatorServicePort string = "4002"
operatorTriggerPath string = "v1/triggerAction"
kubescapeNamespace string = "kubescape"
)
type OperatorAdapter struct {
httpPostFunc func(httputils.IHttpClient, string, map[string]string, []byte) (*http.Response, error)
cautils.OperatorScanInfo
cautils.OperatorConnector
}
func getOperatorPod(k8sClient *k8sinterface.KubernetesApi) (*v1.Pod, error) {
listOptions := metav1.ListOptions{
LabelSelector: "app=operator",
}
pods, err := k8sClient.KubernetesClient.CoreV1().Pods(kubescapeNamespace).List(k8sClient.Context, listOptions)
if err != nil {
return nil, err
}
if len(pods.Items) != 1 {
return nil, errors.New("Could not find the Kubescape-Operator chart, please validate that the Kubescape-Operator helm chart is installed and running -> https://github.com/kubescape/helm-charts")
}
return &pods.Items[0], nil
}
func NewOperatorAdapter(scanInfo cautils.OperatorScanInfo) (*OperatorAdapter, error) {
k8sClient := getKubernetesApi()
pod, err := getOperatorPod(k8sClient)
if err != nil {
return nil, err
}
operatorConnector, err := cautils.CreatePortForwarder(k8sClient, pod, operatorServicePort, kubescapeNamespace)
if err != nil {
return nil, err
}
return &OperatorAdapter{
httpPostFunc: httputils.HttpPost,
OperatorScanInfo: scanInfo,
OperatorConnector: operatorConnector,
}, nil
}
func (a *OperatorAdapter) httpPostOperatorScanRequest(body apis.Commands) (string, error) {
reqBody, err := json.Marshal(body)
if err != nil {
return "", fmt.Errorf("in 'httpPostOperatorScanRequest' failed to json.Marshal, reason: %v", err)
}
err = a.StartPortForwarder()
if err != nil {
return "", err
}
defer a.StopPortForwarder()
urlQuery := url.URL{
Scheme: "http",
Host: a.GetPortForwardLocalhost(),
Path: operatorTriggerPath,
}
resp, err := a.httpPostFunc(http.DefaultClient, urlQuery.String(), map[string]string{"Content-Type": "application/json"}, reqBody)
if err != nil {
return "", err
}
defer resp.Body.Close()
return httputils.HttpRespToString(resp)
}
func (a *OperatorAdapter) OperatorScan() (string, error) {
payload := a.OperatorScanInfo.GetRequestPayload()
if err := a.OperatorScanInfo.ValidatePayload(payload); err != nil {
return "", err
}
res, err := a.httpPostOperatorScanRequest(*payload)
if err != nil {
return "", err
}
return res, nil
}

View File

@@ -1,83 +0,0 @@
package core
import (
"context"
"fmt"
"testing"
"github.com/kubescape/k8s-interface/k8sinterface"
"github.com/stretchr/testify/assert"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes/fake"
)
func Test_getOperatorPod(t *testing.T) {
testCases := []struct {
name string
createOperatorPod bool
createAnotherOperatorPodWithSameLabel bool
expectedError error
}{
{
name: "test error no operator exist",
createOperatorPod: false,
createAnotherOperatorPodWithSameLabel: false,
expectedError: fmt.Errorf("Could not find the Kubescape-Operator chart, please validate that the Kubescape-Operator helm chart is installed and running -> https://github.com/kubescape/helm-charts"),
},
{
name: "test error several operators exist",
createOperatorPod: true,
createAnotherOperatorPodWithSameLabel: true,
expectedError: fmt.Errorf("Could not find the Kubescape-Operator chart, please validate that the Kubescape-Operator helm chart is installed and running -> https://github.com/kubescape/helm-charts"),
},
{
name: "test no error",
createOperatorPod: true,
createAnotherOperatorPodWithSameLabel: false,
expectedError: nil,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
k8sClient := k8sinterface.KubernetesApi{
KubernetesClient: fake.NewSimpleClientset(),
Context: context.TODO(),
}
var createdOperatorPod *v1.Pod
if tc.createOperatorPod {
operatorPod := v1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "first",
Labels: map[string]string{
"app": "operator",
},
},
}
var err error
createdOperatorPod, err = k8sClient.KubernetesClient.CoreV1().Pods(kubescapeNamespace).Create(k8sClient.Context, &operatorPod, metav1.CreateOptions{})
assert.Equal(t, nil, err)
}
if tc.createAnotherOperatorPodWithSameLabel {
operatorPod := v1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "second",
Labels: map[string]string{
"app": "operator",
},
},
}
_, err := k8sClient.KubernetesClient.CoreV1().Pods(kubescapeNamespace).Create(k8sClient.Context, &operatorPod, metav1.CreateOptions{})
assert.Equal(t, nil, err)
}
pod, err := getOperatorPod(&k8sClient)
assert.Equal(t, err, tc.expectedError)
if tc.expectedError == nil {
assert.Equal(t, pod, createdOperatorPod)
}
})
}
}

36
core/core/delete.go Normal file
View File

@@ -0,0 +1,36 @@
package core
import (
"fmt"
logger "github.com/kubescape/go-logger"
"github.com/kubescape/go-logger/helpers"
"github.com/kubescape/kubescape/v2/core/cautils/getter"
v1 "github.com/kubescape/kubescape/v2/core/meta/datastructures/v1"
)
func (ks *Kubescape) DeleteExceptions(delExceptions *v1.DeleteExceptions) error {
// load cached config
getTenantConfig(&delExceptions.Credentials, "", "", getKubernetesApi())
// login kubescape SaaS
ksCloudAPI := getter.GetKSCloudAPIConnector()
if err := ksCloudAPI.Login(); err != nil {
return err
}
for i := range delExceptions.Exceptions {
exceptionName := delExceptions.Exceptions[i]
if exceptionName == "" {
continue
}
logger.L().Info("Deleting exception", helpers.String("name", exceptionName))
if err := ksCloudAPI.DeleteException(exceptionName); err != nil {
return fmt.Errorf("failed to delete exception '%s', reason: %s", exceptionName, err.Error())
}
logger.L().Success("Exception deleted successfully")
}
return nil
}

View File

@@ -9,7 +9,6 @@ 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"
metav1 "github.com/kubescape/kubescape/v2/core/meta/datastructures/v1"
)
@@ -92,7 +91,7 @@ func downloadArtifacts(ctx context.Context, downloadInfo *metav1.DownloadInfo) e
}
func downloadConfigInputs(ctx context.Context, downloadInfo *metav1.DownloadInfo) error {
tenant := cautils.GetTenantConfig(downloadInfo.AccountID, "", "", getKubernetesApi())
tenant := getTenantConfig(&downloadInfo.Credentials, "", "", getKubernetesApi())
controlsInputsGetter := getConfigInputsGetter(ctx, downloadInfo.Identifier, tenant.GetAccountID(), nil)
controlInputs, err := controlsInputsGetter.GetControlsInputs(tenant.GetContextName())
@@ -115,7 +114,7 @@ func downloadConfigInputs(ctx context.Context, downloadInfo *metav1.DownloadInfo
}
func downloadExceptions(ctx context.Context, downloadInfo *metav1.DownloadInfo) error {
tenant := cautils.GetTenantConfig(downloadInfo.AccountID, "", "", getKubernetesApi())
tenant := getTenantConfig(&downloadInfo.Credentials, "", "", getKubernetesApi())
exceptionsGetter := getExceptionsGetter(ctx, "", tenant.GetAccountID(), nil)
exceptions, err := exceptionsGetter.GetExceptions(tenant.GetContextName())
@@ -137,7 +136,7 @@ func downloadExceptions(ctx context.Context, downloadInfo *metav1.DownloadInfo)
func downloadAttackTracks(ctx context.Context, downloadInfo *metav1.DownloadInfo) error {
var err error
tenant := cautils.GetTenantConfig(downloadInfo.AccountID, "", "", getKubernetesApi())
tenant := getTenantConfig(&downloadInfo.Credentials, "", "", getKubernetesApi())
attackTracksGetter := getAttackTracksGetter(ctx, "", tenant.GetAccountID(), nil)
@@ -161,9 +160,9 @@ func downloadAttackTracks(ctx context.Context, downloadInfo *metav1.DownloadInfo
func downloadFramework(ctx context.Context, downloadInfo *metav1.DownloadInfo) error {
tenant := cautils.GetTenantConfig(downloadInfo.AccountID, "", "", getKubernetesApi())
tenant := getTenantConfig(&downloadInfo.Credentials, "", "", getKubernetesApi())
g := getPolicyGetter(ctx, nil, tenant.GetAccountID(), true, nil)
g := getPolicyGetter(ctx, nil, tenant.GetTenantEmail(), true, nil)
if downloadInfo.Identifier == "" {
// if framework name not specified - download all frameworks
@@ -203,9 +202,9 @@ func downloadFramework(ctx context.Context, downloadInfo *metav1.DownloadInfo) e
func downloadControl(ctx context.Context, downloadInfo *metav1.DownloadInfo) error {
tenant := cautils.GetTenantConfig(downloadInfo.AccountID, "", "", getKubernetesApi())
tenant := getTenantConfig(&downloadInfo.Credentials, "", "", getKubernetesApi())
g := getPolicyGetter(ctx, nil, tenant.GetAccountID(), false, nil)
g := getPolicyGetter(ctx, nil, tenant.GetTenantEmail(), false, nil)
if downloadInfo.Identifier == "" {
// TODO - support

View File

@@ -30,6 +30,12 @@ func getKubernetesApi() *k8sinterface.KubernetesApi {
}
return k8sinterface.NewKubernetesApi()
}
func getTenantConfig(credentials *cautils.Credentials, clusterName string, customClusterName string, k8s *k8sinterface.KubernetesApi) cautils.ITenantConfig {
if !k8sinterface.IsConnectedToCluster() || k8s == nil {
return cautils.NewLocalConfig(getter.GetKSCloudAPIConnector(), credentials, clusterName, customClusterName)
}
return cautils.NewClusterConfig(k8s, getter.GetKSCloudAPIConnector(), credentials, clusterName, customClusterName)
}
func getExceptionsGetter(ctx context.Context, useExceptions string, accountID string, downloadReleasedPolicy *getter.DownloadReleasedPolicy) getter.IExceptionsGetter {
if useExceptions != "" {
@@ -68,7 +74,7 @@ func getReporter(ctx context.Context, tenantConfig cautils.ITenantConfig, report
if scanInfo.GetScanningContext() != cautils.ContextCluster {
submitData = reporterv2.SubmitContextRepository
}
return reporterv2.NewReportEventReceiver(tenantConfig, reportID, submitData)
return reporterv2.NewReportEventReceiver(tenantConfig.GetConfigObj(), reportID, submitData)
}
if tenantConfig.GetAccountID() == "" {
// Add link only when scanning a cluster using a framework
@@ -83,7 +89,7 @@ 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) resourcehandler.IResourceHandler {
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()
@@ -94,7 +100,7 @@ func getResourceHandler(ctx context.Context, scanInfo *cautils.ScanInfo, tenantC
getter.GetKSCloudAPIConnector()
rbacObjects := getRBACHandler(tenantConfig, k8s, scanInfo.Submit)
return resourcehandler.NewK8sResourceHandler(k8s, hostSensorHandler, rbacObjects, tenantConfig.GetContextName())
return resourcehandler.NewK8sResourceHandler(k8s, hostSensorHandler, rbacObjects, registryAdaptors)
}
// getHostSensorHandler yields a IHostSensor that knows how to collect a host's scanned resources.
@@ -147,59 +153,57 @@ func policyIdentifierIdentities(pi []cautils.PolicyIdentifier) string {
func setSubmitBehavior(scanInfo *cautils.ScanInfo, tenantConfig cautils.ITenantConfig) {
/*
If keep-local OR scan type which is not submittable - Do not send report
If CloudReportURL not set - Do not send report
If CloudReportURL is set
If There is no account -
Generate Account & Submit report
If There is no account - Do not send report
If There is account -
Invalid Account ID - Do not send report
Valid Account - Submit report
If There is account -
keep-local - Do not send report
Default - Submit report
*/
// do not submit control/workload scanning
if !isScanTypeForSubmission(scanInfo.ScanType) || scanInfo.Local {
if getter.GetKSCloudAPIConnector().GetCloudAPIURL() == "" {
scanInfo.Submit = false
return
}
if getter.GetKSCloudAPIConnector().GetCloudReportURL() == "" {
// do not submit control scanning
if !scanInfo.FrameworkScan {
scanInfo.Submit = false
return
}
// a new account will be created if a report URL is set and there is no account ID
if tenantConfig.GetAccountID() == "" {
if scanInfo.Local {
scanInfo.Submit = false
return
}
// do not submit single resource scan to BE
if scanInfo.ScanObject != nil {
scanInfo.Submit = false
return
}
// If There is no account, or if the account is not legal, do not submit
if _, err := uuid.Parse(tenantConfig.GetAccountID()); err != nil {
scanInfo.Submit = false
} else {
scanInfo.Submit = true
return
}
_, err := uuid.Parse(tenantConfig.GetAccountID())
if err != nil {
logger.L().Warning("account is not a valid UUID", helpers.Error(err))
if scanInfo.CreateAccount {
scanInfo.Submit = true
}
// submit if account is valid
scanInfo.Submit = err == nil
}
func isScanTypeForSubmission(scanType cautils.ScanTypes) bool {
if scanType == cautils.ScanTypeControl || scanType == cautils.ScanTypeWorkload {
return false
}
return true
}
// setPolicyGetter set the policy getter - local file/github release/Kubescape Cloud API
func getPolicyGetter(ctx context.Context, loadPoliciesFromFile []string, accountID string, frameworkScope bool, downloadReleasedPolicy *getter.DownloadReleasedPolicy) getter.IPolicyGetter {
func getPolicyGetter(ctx context.Context, loadPoliciesFromFile []string, tenantEmail string, frameworkScope bool, downloadReleasedPolicy *getter.DownloadReleasedPolicy) getter.IPolicyGetter {
if len(loadPoliciesFromFile) > 0 {
return getter.NewLoadPolicy(loadPoliciesFromFile)
}
if accountID != "" && getter.GetKSCloudAPIConnector().GetCloudAPIURL() != "" && frameworkScope {
if tenantEmail != "" && getter.GetKSCloudAPIConnector().GetCloudAPIURL() != "" && frameworkScope {
g := getter.GetKSCloudAPIConnector() // download policy from Kubescape Cloud backend
return g
}
@@ -273,12 +277,12 @@ func getAttackTracksGetter(ctx context.Context, attackTracks, accountID string,
}
// getUIPrinter returns a printer that will be used to print to the programs UI (terminal)
func GetUIPrinter(ctx context.Context, scanInfo *cautils.ScanInfo, clusterName string) printer.IPrinter {
func GetUIPrinter(ctx context.Context, scanInfo *cautils.ScanInfo) printer.IPrinter {
var p printer.IPrinter
if helpers.ToLevel(logger.L().GetLevel()) >= helpers.WarningLevel {
p = &printerv2.SilentPrinter{}
} else {
p = printerv2.NewPrettyPrinter(scanInfo.VerboseMode, scanInfo.FormatVersion, scanInfo.PrintAttackTree, cautils.ViewTypes(scanInfo.View), scanInfo.ScanType, scanInfo.InputPatterns, clusterName)
p = printerv2.NewPrettyPrinter(scanInfo.VerboseMode, scanInfo.FormatVersion, scanInfo.PrintAttackTree, cautils.ViewTypes(scanInfo.View), scanInfo.ScanType, scanInfo.InputPatterns)
// 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())

View File

@@ -88,7 +88,7 @@ func Test_getUIPrinter(t *testing.T) {
View: string(tt.args.viewType),
}
got := GetUIPrinter(tt.args.ctx, scanInfo, "test-cluster")
got := GetUIPrinter(tt.args.ctx, scanInfo)
assert.Equal(t, tt.want.structType, reflect.TypeOf(got).String())
@@ -183,49 +183,3 @@ func TestGetSensorHandler(t *testing.T) {
// TODO(fredbi): need to share the k8s client mock to test a happy path / deployment failure path
}
func TestIsScanTypeForSubmission(t *testing.T) {
test := []struct {
name string
scanType cautils.ScanTypes
want bool
}{
{
name: "cluster scan",
scanType: cautils.ScanTypeCluster,
want: true,
},
{
name: "repo scan",
scanType: cautils.ScanTypeRepo,
want: true,
},
{
name: "workload scan",
scanType: cautils.ScanTypeWorkload,
want: false,
},
{
name: "control scan",
scanType: cautils.ScanTypeControl,
want: false,
},
{
name: "framework scan",
scanType: cautils.ScanTypeFramework,
want: true,
},
{
name: "image scan",
scanType: cautils.ScanTypeImage,
want: true,
},
}
for _, tt := range test {
t.Run(tt.name, func(t *testing.T) {
got := isScanTypeForSubmission(tt.scanType)
assert.Equal(t, tt.want, got)
})
}
}

View File

@@ -7,7 +7,6 @@ import (
"sort"
"strings"
"github.com/jwalton/gchalk"
"github.com/kubescape/kubescape/v2/core/cautils"
metav1 "github.com/kubescape/kubescape/v2/core/meta/datastructures/v1"
"github.com/kubescape/kubescape/v2/core/pkg/resultshandling/printer"
@@ -54,22 +53,22 @@ func (ks *Kubescape) List(ctx context.Context, listPolicies *metav1.ListPolicies
}
func listFrameworks(ctx context.Context, listPolicies *metav1.ListPolicies) ([]string, error) {
tenant := cautils.GetTenantConfig(listPolicies.AccountID, "", "", getKubernetesApi()) // change k8sinterface
policyGetter := getPolicyGetter(ctx, nil, tenant.GetAccountID(), true, nil)
tenant := getTenantConfig(&listPolicies.Credentials, "", "", getKubernetesApi()) // change k8sinterface
policyGetter := getPolicyGetter(ctx, nil, tenant.GetTenantEmail(), true, nil)
return listFrameworksNames(policyGetter), nil
}
func listControls(ctx context.Context, listPolicies *metav1.ListPolicies) ([]string, error) {
tenant := cautils.GetTenantConfig(listPolicies.AccountID, "", "", getKubernetesApi()) // change k8sinterface
tenant := getTenantConfig(&listPolicies.Credentials, "", "", getKubernetesApi()) // change k8sinterface
policyGetter := getPolicyGetter(ctx, nil, tenant.GetAccountID(), false, nil)
policyGetter := getPolicyGetter(ctx, nil, tenant.GetTenantEmail(), false, nil)
return policyGetter.ListControls()
}
func listExceptions(ctx context.Context, listPolicies *metav1.ListPolicies) ([]string, error) {
// load tenant metav1
tenant := cautils.GetTenantConfig(listPolicies.AccountID, "", "", getKubernetesApi())
tenant := getTenantConfig(&listPolicies.Credentials, "", "", getKubernetesApi())
var exceptionsNames []string
ksCloudAPI := getExceptionsGetter(ctx, "", tenant.GetAccountID(), nil)
@@ -96,10 +95,8 @@ func prettyPrintListFormat(ctx context.Context, targetPolicy string, policies []
policyTable.SetHeader([]string{header})
policyTable.SetHeaderLine(true)
policyTable.SetRowLine(true)
policyTable.SetHeaderAlignment(tablewriter.ALIGN_LEFT)
policyTable.SetAutoFormatHeaders(false)
policyTable.SetAlignment(tablewriter.ALIGN_CENTER)
policyTable.SetUnicodeHVC(tablewriter.Regular, tablewriter.Regular, gchalk.Ansi256(238))
policyTable.SetUnicodeHV(tablewriter.Regular, tablewriter.Regular)
data := v2.Matrix{}
controlRows := generatePolicyRows(policies)
@@ -128,9 +125,7 @@ func prettyPrintControls(ctx context.Context, policies []string) {
controlsTable.SetAutoWrapText(false)
controlsTable.SetHeaderLine(true)
controlsTable.SetRowLine(true)
controlsTable.SetHeaderAlignment(tablewriter.ALIGN_LEFT)
controlsTable.SetAutoFormatHeaders(false)
controlsTable.SetUnicodeHVC(tablewriter.Regular, tablewriter.Regular, gchalk.Ansi256(238))
controlsTable.SetUnicodeHV(tablewriter.Regular, tablewriter.Regular)
controlRows := generateControlRows(policies)

View File

@@ -6,6 +6,7 @@ import (
"github.com/kubescape/go-logger"
"github.com/kubescape/go-logger/helpers"
"github.com/kubescape/go-logger/iconlogger"
"github.com/kubescape/k8s-interface/k8sinterface"
"github.com/kubescape/k8s-interface/workloadinterface"
"github.com/kubescape/kubescape/v2/core/cautils"
@@ -49,17 +50,23 @@ func getInterfaces(ctx context.Context, scanInfo *cautils.ScanInfo) componentInt
}
// ================== setup tenant object ======================================
tenantConfig := cautils.GetTenantConfig(scanInfo.AccountID, k8sinterface.GetContextName(), scanInfo.CustomClusterName, k8s)
ctxTenant, spanTenant := otel.Tracer("").Start(ctx, "setup tenant")
tenantConfig := getTenantConfig(&scanInfo.Credentials, k8sinterface.GetContextName(), scanInfo.CustomClusterName, k8s)
// Set submit behavior AFTER loading tenant config
setSubmitBehavior(scanInfo, tenantConfig)
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")
}
}
spanTenant.End()
// ================== version testing ======================================
@@ -75,9 +82,13 @@ func getInterfaces(ctx context.Context, scanInfo *cautils.ScanInfo) componentInt
}
spanHostScanner.End()
// ================== setup registry adaptors ======================================
registryAdaptors, _ := resourcehandler.NewRegistryAdaptors()
// ================== setup resource collector object ======================================
resourceHandler := getResourceHandler(ctx, scanInfo, tenantConfig, k8s, hostSensorHandler)
resourceHandler := getResourceHandler(ctx, scanInfo, tenantConfig, k8s, hostSensorHandler, registryAdaptors)
// ================== setup reporter & printer objects ======================================
@@ -85,9 +96,9 @@ func getInterfaces(ctx context.Context, scanInfo *cautils.ScanInfo) componentInt
reportHandler := getReporter(ctx, tenantConfig, scanInfo.ScanID, scanInfo.Submit, scanInfo.FrameworkScan, *scanInfo)
// setup printers
outputPrinters := GetOutputPrinters(scanInfo, ctx, tenantConfig.GetContextName())
outputPrinters := GetOutputPrinters(scanInfo, ctx)
uiPrinter := GetUIPrinter(ctx, scanInfo, tenantConfig.GetContextName())
uiPrinter := GetUIPrinter(ctx, scanInfo)
// ================== return interface ======================================
@@ -101,16 +112,12 @@ func getInterfaces(ctx context.Context, scanInfo *cautils.ScanInfo) componentInt
}
}
func GetOutputPrinters(scanInfo *cautils.ScanInfo, ctx context.Context, clusterName string) []printer.IPrinter {
func GetOutputPrinters(scanInfo *cautils.ScanInfo, ctx context.Context) []printer.IPrinter {
formats := scanInfo.Formats()
outputPrinters := make([]printer.IPrinter, 0)
for _, format := range formats {
if err := resultshandling.ValidatePrinter(scanInfo.ScanType, scanInfo.GetScanningContext(), format); err != nil {
logger.L().Ctx(ctx).Fatal(err.Error())
}
printerHandler := resultshandling.NewPrinter(ctx, format, scanInfo.FormatVersion, scanInfo.PrintAttackTree, scanInfo.VerboseMode, cautils.ViewTypes(scanInfo.View), clusterName)
printerHandler := resultshandling.NewPrinter(ctx, format, scanInfo.FormatVersion, scanInfo.PrintAttackTree, scanInfo.VerboseMode, cautils.ViewTypes(scanInfo.View))
printerHandler.SetWriter(ctx, scanInfo.Output)
outputPrinters = append(outputPrinters, printerHandler)
}
@@ -119,18 +126,23 @@ func GetOutputPrinters(scanInfo *cautils.ScanInfo, ctx context.Context, clusterN
func (ks *Kubescape) Scan(ctx context.Context, scanInfo *cautils.ScanInfo) (*resultshandling.ResultsHandler, error) {
ctxInit, spanInit := otel.Tracer("").Start(ctx, "initialization")
logger.InitLogger(iconlogger.LoggerName)
logger.L().Start("Kubescape scanner initializing")
// ===================== Initialization =====================
scanInfo.Init(ctxInit) // initialize scan info
interfaces := getInterfaces(ctxInit, scanInfo)
interfaces.report.SetTenantConfig(interfaces.tenantConfig)
cautils.ClusterName = interfaces.tenantConfig.GetContextName() // TODO - Deprecated
cautils.CustomerGUID = interfaces.tenantConfig.GetAccountID() // TODO - Deprecated
interfaces.report.SetClusterName(interfaces.tenantConfig.GetContextName())
interfaces.report.SetCustomerGUID(interfaces.tenantConfig.GetAccountID())
downloadReleasedPolicy := getter.NewDownloadReleasedPolicy() // download config inputs from github release
// set policy getter only after setting the customerGUID
scanInfo.Getters.PolicyGetter = getPolicyGetter(ctxInit, scanInfo.UseFrom, interfaces.tenantConfig.GetAccountID(), scanInfo.FrameworkScan, downloadReleasedPolicy)
scanInfo.Getters.PolicyGetter = getPolicyGetter(ctxInit, scanInfo.UseFrom, interfaces.tenantConfig.GetTenantEmail(), scanInfo.FrameworkScan, downloadReleasedPolicy)
scanInfo.Getters.ControlsInputsGetter = getConfigInputsGetter(ctxInit, scanInfo.ControlsInputs, interfaces.tenantConfig.GetAccountID(), downloadReleasedPolicy)
scanInfo.Getters.ExceptionsGetter = getExceptionsGetter(ctxInit, scanInfo.UseExceptions, interfaces.tenantConfig.GetAccountID(), downloadReleasedPolicy)
scanInfo.Getters.AttackTracksGetter = getAttackTracksGetter(ctxInit, scanInfo.AttackTracks, interfaces.tenantConfig.GetAccountID(), downloadReleasedPolicy)
@@ -153,7 +165,7 @@ func (ks *Kubescape) Scan(ctx context.Context, scanInfo *cautils.ScanInfo) (*res
// ===================== policies =====================
ctxPolicies, spanPolicies := otel.Tracer("").Start(ctxInit, "policies")
policyHandler := policyhandler.NewPolicyHandler(interfaces.tenantConfig.GetContextName())
policyHandler := policyhandler.NewPolicyHandler()
scanData, err := policyHandler.CollectPolicies(ctxPolicies, scanInfo.PolicyIdentifier, scanInfo)
if err != nil {
spanInit.End()
@@ -176,8 +188,8 @@ func (ks *Kubescape) Scan(ctx context.Context, scanInfo *cautils.ScanInfo) (*res
defer spanOpa.End()
deps := resources.NewRegoDependenciesData(k8sinterface.GetK8sConfig(), interfaces.tenantConfig.GetContextName())
reportResults := opaprocessor.NewOPAProcessor(scanData, deps, interfaces.tenantConfig.GetContextName())
if err = reportResults.ProcessRulesListener(ctxOpa, cautils.NewProgressHandler(""), scanInfo); err != nil {
reportResults := opaprocessor.NewOPAProcessor(scanData, deps)
if err := reportResults.ProcessRulesListener(ctxOpa, cautils.NewProgressHandler(""), scanInfo); err != nil {
// TODO - do something
return resultsHandling, fmt.Errorf("%w", err)
}

68
core/core/submit.go Normal file
View File

@@ -0,0 +1,68 @@
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"
logger "github.com/kubescape/go-logger"
"github.com/kubescape/go-logger/helpers"
)
func (ks *Kubescape) Submit(ctx context.Context, submitInterfaces cliinterfaces.SubmitInterfaces) error {
// list resources
report, err := submitInterfaces.SubmitObjects.SetResourcesReport()
if err != nil {
return err
}
allresources, err := submitInterfaces.SubmitObjects.ListAllResources()
if err != nil {
return err
}
// report
o := &cautils.OPASessionObj{
Report: report,
AllResources: allresources,
Metadata: &report.Metadata,
}
if err := submitInterfaces.Reporter.Submit(ctx, o); err != nil {
return err
}
logger.L().Success("Data has been submitted successfully")
submitInterfaces.Reporter.DisplayReportURL()
return nil
}
func (ks *Kubescape) SubmitExceptions(ctx context.Context, 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).Warning("failed setting account ID", helpers.Error(err))
}
// load exceptions from file
loader := getter.NewLoadPolicy([]string{excPath})
exceptions, err := loader.GetExceptions("")
if err != nil {
return err
}
// login kubescape SaaS
ksCloudAPI := getter.GetKSCloudAPIConnector()
if err := ksCloudAPI.Login(); err != nil {
return err
}
if err := ksCloudAPI.PostExceptions(exceptions); err != nil {
return err
}
logger.L().Success("Exceptions submitted successfully")
return nil
}

View File

@@ -4,8 +4,12 @@ import "io"
type SetConfig struct {
Account string
ClientID string
SecretKey string
CloudReportURL string
CloudAPIURL string
CloudUIURL string
CloudAuthURL string
}
type ViewConfig struct {

View File

@@ -0,0 +1,8 @@
package v1
import "github.com/kubescape/kubescape/v2/core/cautils"
type DeleteExceptions struct {
Credentials cautils.Credentials
Exceptions []string
}

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