Compare commits

..

10 Commits

Author SHA1 Message Date
Rotem Refael
54020d317e Merge pull request #528 from armosec/rotemamsa-patch-1
Update README.md
2022-06-06 13:45:35 +03:00
Rotem Refael
91d1ec6c2f Update README.md 2022-06-06 13:30:22 +03:00
Rotem Refael
1d3fd0dc9d Merge pull request #527 from vladklokun/add-maintenance-message 2022-06-03 19:43:27 +03:00
Vlad Klokun
8a7511cecb chore: add maintenance message 2022-06-03 19:35:33 +03:00
David Wertenteil
0af0c01ec0 Merge pull request #524 from armosec/dev
Quick fix
2022-05-26 12:21:29 +03:00
David Wertenteil
4f1971a63d Merge pull request #520 from armosec/dev
Extend microservice support
2022-05-24 11:07:24 +03:00
Rotem Refael
afb6ea1d9c Merge pull request #507 from armosec/dev
- Adding Lens extension to readme
- Supporting --view flag
2022-05-12 10:26:14 +03:00
David Wertenteil
b3d16875d6 Merge pull request #493 from armosec/dev
Microservice support
2022-05-03 15:14:24 +03:00
Rotem Refael
cf086e6614 Merge pull request #487 from armosec/dev
Remove binary from repo
2022-04-11 17:20:18 +03:00
Rotem Refael
a27d2d41f2 Merge pull request #484 from armosec/dev
CLI improvement release
2022-04-11 15:48:10 +03:00
124 changed files with 2119 additions and 5902 deletions

View File

@@ -28,76 +28,29 @@ jobs:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
steps:
- uses: actions/checkout@v3
with:
submodules: recursive
- name: Cache Go modules (Linux)
if: matrix.os == 'ubuntu-latest'
uses: actions/cache@v3
with:
path: |
~/.cache/go-build
~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-
- name: Cache Go modules (macOS)
if: matrix.os == 'macos-latest'
uses: actions/cache@v3
with:
path: |
~/Library/Caches/go-build
~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-
- name: Cache Go modules (Windows)
if: matrix.os == 'windows-latest'
uses: actions/cache@v3
with:
path: |
~\AppData\Local\go-build
~\go\pkg\mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-
- uses: actions/checkout@v1
- name: Set up Go
uses: actions/setup-go@v3
uses: actions/setup-go@v2
with:
go-version: 1.18
go-version: 1.17
# - name: Test cmd pkg
# run: cd cmd && go test -v ./...
- name: Install MSYS2 & libgit2 (Windows)
shell: cmd
run: .\build.bat all
if: matrix.os == 'windows-latest'
- name: Install libgit2 (Linux/macOS)
run: make libgit2
if: matrix.os != 'windows-latest'
- name: Test core pkg
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: go test -tags=static -v ./...
run: go test -v ./...
- name: Test httphandler pkg
run: cd httphandler && go test -tags=static -v ./...
run: cd httphandler && go test -v ./...
- name: Build
env:
RELEASE: v2.0.${{ github.run_number }}
CLIENT: release
ArmoBEServer: api.armo.cloud
ArmoAuthServer: auth.armo.cloud
ArmoERServer: report.armo.cloud
ArmoWebsite: portal.armo.cloud
CGO_ENABLED: 1
CGO_ENABLED: 0
run: python3 --version && python3 build.py
- name: Smoke Testing
@@ -139,8 +92,6 @@ jobs:
steps:
- uses: actions/checkout@v2
with:
submodules: recursive
- name: Set image version
id: image-version
@@ -163,7 +114,7 @@ jobs:
run: docker login -u="${QUAY_USERNAME}" -p="${QUAY_PASSWORD}" quay.io
- name: Build the Docker image
run: docker buildx build . --file build/Dockerfile --tag ${{ steps.image-name.outputs.IMAGE_NAME }}:${{ steps.image-version.outputs.IMAGE_VERSION }} --tag ${{ steps.image-name.outputs.IMAGE_NAME }}:latest --build-arg image_version=${{ steps.image-version.outputs.IMAGE_VERSION }} --build-arg client=image-release --push --platform linux/amd64,linux/arm64
run: docker buildx build . --file build/Dockerfile --tag ${{ steps.image-name.outputs.IMAGE_NAME }}:${{ steps.image-version.outputs.IMAGE_VERSION }} --tag ${{ steps.image-name.outputs.IMAGE_NAME }}:latest --build-arg image_version=${{ steps.image-version.outputs.IMAGE_VERSION }} --push --platform linux/amd64,linux/arm64
# - name: Login to GitHub Container Registry
# uses: docker/login-action@v1

View File

@@ -11,47 +11,12 @@ jobs:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
steps:
- uses: actions/checkout@v3
with:
submodules: recursive
- name: Cache Go modules (Linux)
if: matrix.os == 'ubuntu-latest'
uses: actions/cache@v3
with:
path: |
~/.cache/go-build
~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-
- name: Cache Go modules (macOS)
if: matrix.os == 'macos-latest'
uses: actions/cache@v3
with:
path: |
~/Library/Caches/go-build
~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-
- name: Cache Go modules (Windows)
if: matrix.os == 'windows-latest'
uses: actions/cache@v3
with:
path: |
~\AppData\Local\go-build
~\go\pkg\mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-
- uses: actions/checkout@v1
- name: Set up Go
uses: actions/setup-go@v3
uses: actions/setup-go@v2
with:
go-version: 1.18
go-version: 1.17
# - name: Test cmd pkg
# run: cd cmd && go test -v ./...
@@ -64,32 +29,22 @@ jobs:
# - name: Test cmd pkg
# run: cd cmd && go test -v ./...
- name: Install MSYS2 & libgit2 (Windows)
shell: cmd
run: .\build.bat all
if: matrix.os == 'windows-latest'
- name: Install libgit2 (Linux/macOS)
run: make libgit2
if: matrix.os != 'windows-latest'
- name: Test core pkg
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: go test -tags=static -v ./...
run: go test -v ./...
- name: Test httphandler pkg
run: cd httphandler && go test -tags=static -v ./...
run: cd httphandler && go test -v ./...
- name: Build
env:
RELEASE: v2.0.${{ github.run_number }}
CLIENT: release-dev
ArmoBEServer: api.armo.cloud
ArmoAuthServer: auth.armo.cloud
ArmoERServer: report.armo.cloud
ArmoWebsite: portal.armo.cloud
CGO_ENABLED: 1
CGO_ENABLED: 0
run: python3 --version && python3 build.py
- name: Smoke Testing
@@ -116,8 +71,6 @@ jobs:
steps:
- uses: actions/checkout@v2
with:
submodules: recursive
- name: Set image version
id: image-version
@@ -140,4 +93,4 @@ jobs:
run: docker login -u="${QUAY_USERNAME}" -p="${QUAY_PASSWORD}" quay.io
- name: Build the Docker image
run: docker buildx build . --file build/Dockerfile --tag ${{ steps.image-name.outputs.IMAGE_NAME }}:${{ steps.image-version.outputs.IMAGE_VERSION }} --build-arg image_version=${{ steps.image-version.outputs.IMAGE_VERSION }} --build-arg client=image-dev --push --platform linux/amd64,linux/arm64
run: docker buildx build . --file build/Dockerfile --tag ${{ steps.image-name.outputs.IMAGE_NAME }}:${{ steps.image-version.outputs.IMAGE_VERSION }} --build-arg image_version=${{ steps.image-version.outputs.IMAGE_VERSION }} --push --platform linux/amd64,linux/arm64

48
.github/workflows/master_pr_checks.yaml vendored Normal file
View File

@@ -0,0 +1,48 @@
name: master-pr
on:
pull_request:
branches: [ master ]
types: [ edited, opened, synchronize, reopened ]
jobs:
build:
name: Create cross-platform build
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
steps:
- uses: actions/checkout@v1
- name: Set up Go
uses: actions/setup-go@v2
with:
go-version: 1.17
# - name: Test cmd pkg
# run: cd cmd && go test -v ./...
- name: Test core pkg
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: go test -v ./...
- name: Test httphandler pkg
run: cd httphandler && go test -v ./...
- name: Build
env:
RELEASE: v2.0.${{ github.run_number }}
ArmoBEServer: api.armo.cloud
ArmoAuthServer: auth.armo.cloud
ArmoERServer: report.armo.cloud
ArmoWebsite: portal.armo.cloud
CGO_ENABLED: 0
run: python3 --version && python3 build.py
- name: Smoke Testing
env:
RELEASE: v2.0.${{ github.run_number }}
KUBESCAPE_SKIP_UPDATE_CHECK: "true"
run: python3 smoke_testing/init.py ${PWD}/build/${{ matrix.os }}/kubescape

View File

@@ -1,93 +0,0 @@
name: pr-checks
on:
pull_request:
branches: [ master, dev ]
types: [ edited, opened, synchronize, reopened ]
jobs:
build:
name: Create cross-platform build
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
steps:
- uses: actions/checkout@v3
with:
submodules: recursive
- name: Cache Go modules (Linux)
if: matrix.os == 'ubuntu-latest'
uses: actions/cache@v3
with:
path: |
~/.cache/go-build
~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-
- name: Cache Go modules (macOS)
if: matrix.os == 'macos-latest'
uses: actions/cache@v3
with:
path: |
~/Library/Caches/go-build
~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-
- name: Cache Go modules (Windows)
if: matrix.os == 'windows-latest'
uses: actions/cache@v3
with:
path: |
~\AppData\Local\go-build
~\go\pkg\mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-
- name: Set up Go
uses: actions/setup-go@v3
with:
go-version: 1.18
- name: Install MSYS2 & libgit2 (Windows)
shell: cmd
run: .\build.bat all
if: matrix.os == 'windows-latest'
- name: Install libgit2 (Linux/macOS)
run: make libgit2
if: matrix.os != 'windows-latest'
# - name: Test cmd pkg
# run: cd cmd && go test -v ./...
- name: Test core pkg
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: go test -tags=static -v ./...
- name: Test httphandler pkg
run: cd httphandler && go test -tags=static -v ./...
- name: Build
env:
RELEASE: v2.0.${{ github.run_number }}
CLIENT: test
ArmoBEServer: api.armo.cloud
ArmoAuthServer: auth.armo.cloud
ArmoERServer: report.armo.cloud
ArmoWebsite: portal.armo.cloud
CGO_ENABLED: 1
run: python3 --version && python3 build.py
- name: Smoke Testing
env:
RELEASE: v2.0.${{ github.run_number }}
KUBESCAPE_SKIP_UPDATE_CHECK: "true"
run: python3 smoke_testing/init.py ${PWD}/build/${{ matrix.os }}/kubescape

1
.gitignore vendored
View File

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

3
.gitmodules vendored
View File

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

View File

@@ -1,20 +0,0 @@
.PHONY: test all build libgit2
# default task invoked while running make
all: libgit2 build
export CGO_ENABLED=1
# build and install libgit2
libgit2:
git submodule update --init --recursive
cd git2go; make install-static
# go build tags
TAGS = "static"
build:
go build -v -tags=$(TAGS) .
test:
go test -v -tags=$(TAGS) ./...

229
README.md
View File

@@ -6,7 +6,7 @@
Kubescape is a K8s open-source tool providing a multi-cloud K8s single pane of glass, including risk analysis, security compliance, RBAC visualizer and image vulnerabilities scanning.
Kubescape scans K8s clusters, YAML files, and HELM charts, detecting misconfigurations according to multiple frameworks (such as the [NSA-CISA](https://www.armosec.io/blog/kubernetes-hardening-guidance-summary-by-armo/?utm_source=github&utm_medium=repository), [MITRE ATT&CK®](https://www.microsoft.com/security/blog/2021/03/23/secure-containerized-environments-with-updated-threat-matrix-for-kubernetes/)), software vulnerabilities, and RBAC (role-based-access-control) violations at early stages of the CI/CD pipeline, calculates risk score instantly and shows risk trends over time.
Kubescape scans K8s clusters, YAML files, and HELM charts, detecting misconfigurations according to multiple frameworks (such as the [NSA-CISA](https://www.armosec.io/blog/kubernetes-hardening-guidance-summary-by-armo) , [MITRE ATT&CK®](https://www.microsoft.com/security/blog/2021/03/23/secure-containerized-environments-with-updated-threat-matrix-for-kubernetes/)), software vulnerabilities, and RBAC (role-based-access-control) violations at early stages of the CI/CD pipeline, calculates risk score instantly and shows risk trends over time.
It became one of the fastest-growing Kubernetes tools among developers due to its easy-to-use CLI interface, flexible output formats, and automated scanning capabilities, saving Kubernetes users and admins precious time, effort, and resources.
Kubescape integrates natively with other DevOps tools, including Jenkins, CircleCI, Github workflows, Prometheus, and Slack, and supports multi-cloud K8s deployments like EKS, GKE, and AKS.
@@ -28,7 +28,7 @@ Kubescape integrates natively with other DevOps tools, including Jenkins, Circle
# TL;DR
## Install:
```sh
```
curl -s https://raw.githubusercontent.com/armosec/kubescape/master/install.sh | /bin/bash
```
@@ -36,12 +36,8 @@ curl -s https://raw.githubusercontent.com/armosec/kubescape/master/install.sh |
[Install on macOS](#install-on-macos)
[Install on NixOS or Linux/macOS via nix](#install-on-nixos-or-with-nix-community)
[Install using Go](#install-using-go)
## Run:
```sh
```
kubescape scan --submit --enable-host-scan --verbose
```
@@ -66,7 +62,7 @@ Want to contribute? Want to discuss something? Have an issue?
* Feel free to pick a task from the [roadmap](docs/roadmap.md) or suggest a feature of your own. [Contact us](MAINTAINERS.md) directly for more information :)
* Open a issue, we are trying to respond within 48 hours
* [Join us](https://armosec.github.io/kubescape) in a discussion on our discord server!
* [Join us](https://armosec.github.io/kubescape/) in a discussion on our discord server!
[<img src="docs/discord-banner.png" width="100" alt="logo" align="center">](https://armosec.github.io/kubescape/)
![discord](https://img.shields.io/discord/893048809884643379)
@@ -74,10 +70,10 @@ Want to contribute? Want to discuss something? Have an issue?
# Options and examples
[Kubescape docs](https://hub.armosec.io/docs?utm_source=github&utm_medium=repository)
[Kubescape docs](https://hub.armo.cloud/docs)
## Playground
* [Kubescape playground](https://killercoda.com/saiyampathak/scenario/kubescape)
* [Kubescape playground](https://www.katacoda.com/pathaksaiyam/scenarios/kubescape)
## Tutorials
@@ -89,8 +85,6 @@ Want to contribute? Want to discuss something? Have an issue?
* [Configure and run customized frameworks](https://youtu.be/12Sanq_rEhs)
* Customize controls configurations. [Kubescape CLI](https://youtu.be/955psg6TVu4), [Kubescape SaaS](https://youtu.be/lIMVSVhH33o)
<details><summary>Windows</summary>
## Install on Windows
**Requires powershell v5.0+**
@@ -104,81 +98,41 @@ Note: if you get an error you might need to change the execution policy (i.e. en
``` powershell
Set-ExecutionPolicy RemoteSigned -scope CurrentUser
```
</details>
<details><summary>MacOS</summary>
## Install on macOS
1. ```sh
1. ```
brew tap armosec/kubescape
```
2. ```sh
2. ```
brew install kubescape
```
</details>
<details><summary>Nix/NixOS</summary>
## Install on NixOS or with nix (Community)
Direct issues installing `kubescape` via `nix` through the channels mentioned [here](https://nixos.wiki/wiki/Support)
You can use `nix` on Linux or macOS and on other platforms unofficially.
Try it out in an ephemeral shell: `nix-shell -p kubescape`
Install declarative as usual
NixOS:
```nix
# your other config ...
environment.systemPackages = with pkgs; [
# your other packages ...
kubescape
];
```
home-manager:
```nix
# your other config ...
home.packages = with pkgs; [
# your other packages ...
kubescape
];
```
Or to your profile (not preferred): `nix-env --install -A nixpkgs.kubescape`
</details>
## Usage & Examples
### Examples
#### Scan a running Kubernetes cluster and submit results to the [Kubescape SaaS version](https://cloud.armosec.io?utm_source=github&utm_medium=repository)
#### Scan a running Kubernetes cluster and submit results to the [Kubescape SaaS version](https://portal.armo.cloud/)
```
kubescape scan --submit --enable-host-scan --verbose
```
> Read [here](https://hub.armosec.io/docs/host-sensor?utm_source=github&utm_medium=repository) more about the `enable-host-scan` flag
> Read [here](https://hub.armo.cloud/docs/host-sensor) more about the `enable-host-scan` flag
#### Scan a running Kubernetes cluster with [`nsa`](https://www.nsa.gov/Press-Room/News-Highlights/Article/Article/2716980/nsa-cisa-release-kubernetes-hardening-guidance/) framework and submit results to the [Kubescape SaaS version](https://cloud.armosec.io?utm_source=github&utm_medium=repository)
#### Scan a running Kubernetes cluster with [`nsa`](https://www.nsa.gov/Press-Room/News-Highlights/Article/Article/2716980/nsa-cisa-release-kubernetes-hardening-guidance/) framework and submit results to the [Kubescape SaaS version](https://portal.armo.cloud/)
```
kubescape scan framework nsa --submit
```
#### Scan a running Kubernetes cluster with [`MITRE ATT&CK®`](https://www.microsoft.com/security/blog/2021/03/23/secure-containerized-environments-with-updated-threat-matrix-for-kubernetes/) framework and submit results to the [Kubescape SaaS version](https://cloud.armosec.io?utm_source=github&utm_medium=repository)
#### Scan a running Kubernetes cluster with [`MITRE ATT&CK®`](https://www.microsoft.com/security/blog/2021/03/23/secure-containerized-environments-with-updated-threat-matrix-for-kubernetes/) framework and submit results to the [Kubescape SaaS version](https://portal.armo.cloud/)
```
kubescape scan framework mitre --submit
```
#### Scan a running Kubernetes cluster with a specific control using the control name or control ID. [List of controls](https://hub.armosec.io/docs/controls?utm_source=github&utm_medium=repository)
#### Scan a running Kubernetes cluster with a specific control using the control name or control ID. [List of controls](https://hub.armo.cloud/docs/controls)
```
kubescape scan control "Privileged container"
```
@@ -193,14 +147,14 @@ kubescape scan --include-namespaces development,staging,production
kubescape scan --exclude-namespaces kube-system,kube-public
```
#### Scan local `yaml`/`json` files before deploying. [Take a look at the demonstration](https://youtu.be/Ox6DaR7_4ZI) Submit the results in case the directory is a git repo. [docs](https://hub.armosec.io/docs/repository-scanning?utm_source=github&utm_medium=repository)
#### Scan local `yaml`/`json` files before deploying. [Take a look at the demonstration](https://youtu.be/Ox6DaR7_4ZI)
```
kubescape scan *.yaml --submit
kubescape scan *.yaml
```
#### Scan kubernetes manifest files from a git repository [and submit the results](https://hub.armosec.io/docs/repository-scanning?utm_source=github&utm_medium=repository)
#### Scan kubernetes manifest files from a public github repository
```
kubescape scan https://github.com/armosec/kubescape --submit
kubescape scan https://github.com/armosec/kubescape
```
#### Display all scanned resources (including the resources who passed)
@@ -239,11 +193,16 @@ kubescape scan --format prometheus
kubescape scan --exceptions examples/exceptions/exclude-kube-namespaces.json
```
#### Scan Helm charts
#### Scan Helm charts - Render the helm chart using [`helm template`](https://helm.sh/docs/helm/helm_template/) and pass to stdout
```
kubescape scan </path/to/directory> --submit
helm template [NAME] [CHART] [flags] --dry-run | kubescape scan -
```
> Kubescape will load the default values file
e.g.
```
helm template bitnami/mysql --generate-name --dry-run | kubescape scan -
```
### Offline/Air-gaped Environment Support
@@ -279,10 +238,36 @@ kubescape scan framework nsa --use-from /path/nsa.json
```
## Scan Periodically using Helm
[Please follow the instructions here](https://hub.armosec.io/docs/installation-of-armo-in-cluster?utm_source=github&utm_medium=repository)
## Scan Periodically using Helm - Contributed by [@yonahd](https://github.com/yonahd)
[Please follow the instructions here](https://hub.armo.cloud/docs/installation-of-armo-in-cluster)
[helm chart repo](https://github.com/armosec/armo-helm)
## Scan using docker image
Official Docker image `quay.io/armosec/kubescape`
```
docker run -v "$(pwd)/example.yaml:/app/example.yaml quay.io/armosec/kubescape scan /app/example.yaml
```
If you wish, you can [build the docker image on your own](build/README.md)
# Submit data manually
Use the `submit` command if you wish to submit data manually
## Submit scan results manually
> Support forward compatibility by using the `--format-version v2` flag
First, scan your cluster using the `json` format flag: `kubescape scan framework <name> --format json --format-version v2 --output path/to/results.json`.
Now you can submit the results to the Kubescape SaaS version -
```
kubescape submit results path/to/results.json
```
# Integrations
## VS Code Extension
@@ -295,116 +280,12 @@ Scan the YAML files while writing them using the [vs code extension](https://git
View Kubescape scan results directly in [Lens IDE](https://k8slens.dev/) using kubescape [Lens extension](https://github.com/armosec/lens-kubescape/blob/master/README.md)
# Building Kubescape
<details><summary>Windows</summary>
## Windows
1. Install MSYS2 & build libgit _(needed only for the first time)_
```
build.bat all
```
> You can install MSYS2 separately by running `build.bat install` and build libgit2 separately by running `build.bat build`
2. Build kubescape
```
make build
```
OR
```
go build -tags=static .
```
</details>
<details><summary>Linux / MacOS</summary>
## Linux / MacOS
1. Install libgit2 dependency _(needed only for the first time)_
```
make libgit2
```
> `cmake` is required to build libgit2. You can install it by running `sudo apt-get install cmake` (Linux) or `brew install cmake` (macOS)
2. Build kubescape
```
make build
```
OR
```
go build -tags=static .
```
3. Test
```
make test
```
</details>
## VS code configuration samples
You can use the samples files below to setup your VS code environment for building and debugging purposes.
<details><summary>.vscode/settings.json</summary>
```json5
// .vscode/settings.json
{
"go.testTags": "static",
"go.buildTags": "static",
"go.toolsEnvVars": {
"CGO_ENABLED": "1"
}
}
```
</details>
<details><summary>.vscode/launch.json</summary>
```json5
// .vscode/launch.json
{
"version": "0.2.0",
"configurations": [
{
"name": "Launch Package",
"type": "go",
"request": "launch",
"mode": "auto",
"program": "${workspaceFolder}/main.go",
"args": [
"scan",
"--logger",
"debug"
],
"buildFlags": "-tags=static"
}
]
}
```
</details>
# Under the hood
## Technology
Kubescape based on [OPA engine](https://github.com/open-policy-agent/opa) and ARMO's posture controls.
Kubescape based on OPA engine: https://github.com/open-policy-agent/opa and ARMO's posture controls.
The tools retrieves Kubernetes objects from the API server and runs a set of [rego's snippets](https://www.openpolicyagent.org/docs/latest/policy-language/) developed by [ARMO](https://www.armosec.io?utm_source=github&utm_medium=repository).
The tools retrieves Kubernetes objects from the API server and runs a set of [rego's snippets](https://www.openpolicyagent.org/docs/latest/policy-language/) developed by [ARMO](https://www.armosec.io/).
The results by default printed in a pretty "console friendly" manner, but they can be retrieved in JSON format for further processing.

View File

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

View File

@@ -5,65 +5,79 @@ import platform
import subprocess
BASE_GETTER_CONST = "github.com/armosec/kubescape/v2/core/cautils/getter"
BE_SERVER_CONST = BASE_GETTER_CONST + ".ArmoBEURL"
ER_SERVER_CONST = BASE_GETTER_CONST + ".ArmoERURL"
WEBSITE_CONST = BASE_GETTER_CONST + ".ArmoFEURL"
AUTH_SERVER_CONST = BASE_GETTER_CONST + ".armoAUTHURL"
def check_status(status, msg):
def checkStatus(status, msg):
if status != 0:
sys.stderr.write(msg)
exit(status)
def get_build_dir():
current_platform = platform.system()
build_dir = "./build/"
def getBuildDir():
currentPlatform = platform.system()
buildDir = "./build/"
if current_platform == "Windows": build_dir += "windows-latest"
elif current_platform == "Linux": build_dir += "ubuntu-latest"
elif current_platform == "Darwin": build_dir += "macos-latest"
else: raise OSError("Platform %s is not supported!" % (current_platform))
if currentPlatform == "Windows": buildDir += "windows-latest"
elif currentPlatform == "Linux": buildDir += "ubuntu-latest"
elif currentPlatform == "Darwin": buildDir += "macos-latest"
else: raise OSError("Platform %s is not supported!" % (currentPlatform))
return build_dir
return buildDir
def get_package_name():
package_name = "kubescape"
# if platform.system() == "Windows": package_name += ".exe"
def getPackageName():
packageName = "kubescape"
# if platform.system() == "Windows": packageName += ".exe"
return package_name
return packageName
def main():
print("Building Kubescape")
# print environment variables
# print(os.environ)
# Set some variables
package_name = get_package_name()
build_url = "github.com/armosec/kubescape/v2/core/cautils.BuildNumber"
release_version = os.getenv("RELEASE")
packageName = getPackageName()
buildUrl = "github.com/armosec/kubescape/v2/core/cautils.BuildNumber"
releaseVersion = os.getenv("RELEASE")
ArmoBEServer = os.getenv("ArmoBEServer")
ArmoERServer = os.getenv("ArmoERServer")
ArmoWebsite = os.getenv("ArmoWebsite")
ArmoAuthServer = os.getenv("ArmoAuthServer")
client_var = "github.com/armosec/kubescape/v2/core/cautils.Client"
client_name = os.getenv("CLIENT")
# Create build directory
build_dir = get_build_dir()
buildDir = getBuildDir()
ks_file = os.path.join(build_dir, package_name)
ks_file = os.path.join(buildDir, packageName)
hash_file = ks_file + ".sha256"
if not os.path.isdir(build_dir):
os.makedirs(build_dir)
if not os.path.isdir(buildDir):
os.makedirs(buildDir)
# Build kubescape
ldflags = "-w -s"
if release_version:
ldflags += " -X {}={}".format(build_url, release_version)
if client_name:
ldflags += " -X {}={}".format(client_var, client_name)
if releaseVersion:
ldflags += " -X {}={}".format(buildUrl, releaseVersion)
if ArmoBEServer:
ldflags += " -X {}={}".format(BE_SERVER_CONST, ArmoBEServer)
if ArmoERServer:
ldflags += " -X {}={}".format(ER_SERVER_CONST, ArmoERServer)
if ArmoWebsite:
ldflags += " -X {}={}".format(WEBSITE_CONST, ArmoWebsite)
if ArmoAuthServer:
ldflags += " -X {}={}".format(AUTH_SERVER_CONST, ArmoAuthServer)
build_command = ["go", "build", "-tags=static", "-o", ks_file, "-ldflags" ,ldflags]
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)))
status = subprocess.call(build_command)
check_status(status, "Failed to build kubescape")
checkStatus(status, "Failed to build kubescape")
sha256 = hashlib.sha256()
with open(ks_file, "rb") as kube:

View File

@@ -1,28 +1,23 @@
FROM golang:1.18-alpine as builder
FROM golang:1.17-alpine as builder
#ENV GOPROXY=https://goproxy.io,direct
ARG image_version
ARG client
ENV RELEASE=$image_version
ENV CLIENT=$client
ENV GO111MODULE=
ENV CGO_ENABLED=1
ENV CGO_ENABLED=0
# Install required python/pip
ENV PYTHONUNBUFFERED=1
RUN apk add --update --no-cache python3 git openssl-dev musl-dev gcc make cmake pkgconfig && ln -sf python3 /usr/bin/python
RUN apk add --update --no-cache python3 && ln -sf python3 /usr/bin/python
RUN python3 -m ensurepip
RUN pip3 install --no-cache --upgrade pip setuptools
WORKDIR /work
ADD . .
# install libgit2
WORKDIR /work
RUN rm -rf git2go && make libgit2
# build kubescape server
WORKDIR /work/httphandler
RUN python build.py

View File

@@ -1,9 +1,9 @@
package config
import (
"github.com/armosec/kubescape/v2/core/cautils/logger"
"github.com/armosec/kubescape/v2/core/meta"
v1 "github.com/armosec/kubescape/v2/core/meta/datastructures/v1"
logger "github.com/dwertent/go-logger"
"github.com/spf13/cobra"
)

View File

@@ -4,9 +4,9 @@ import (
"fmt"
"strings"
"github.com/armosec/kubescape/v2/core/cautils/logger"
"github.com/armosec/kubescape/v2/core/meta"
metav1 "github.com/armosec/kubescape/v2/core/meta/datastructures/v1"
logger "github.com/dwertent/go-logger"
"github.com/spf13/cobra"
)

View File

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

View File

@@ -25,8 +25,8 @@ func GetDeleteCmd(ks meta.IKubescape) *cobra.Command {
},
}
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.PersistentFlags().StringVarP(&deleteInfo.Credentials.ClientID, "client-id", "", "", "Kubescape SaaS client ID. Default will load client ID from cache, read more - https://hub.armo.cloud/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.armo.cloud/docs/authentication")
deleteCmd.AddCommand(getExceptionsCmd(ks, &deleteInfo))

View File

@@ -4,9 +4,9 @@ import (
"fmt"
"strings"
"github.com/armosec/kubescape/v2/core/cautils/logger"
"github.com/armosec/kubescape/v2/core/meta"
v1 "github.com/armosec/kubescape/v2/core/meta/datastructures/v1"
logger "github.com/dwertent/go-logger"
"github.com/spf13/cobra"
)

View File

@@ -6,10 +6,10 @@ import (
"strings"
"github.com/armosec/kubescape/v2/core/cautils"
"github.com/armosec/kubescape/v2/core/cautils/logger"
"github.com/armosec/kubescape/v2/core/core"
"github.com/armosec/kubescape/v2/core/meta"
v1 "github.com/armosec/kubescape/v2/core/meta/datastructures/v1"
logger "github.com/dwertent/go-logger"
"github.com/spf13/cobra"
)
@@ -74,8 +74,8 @@ func GeDownloadCmd(ks meta.IKubescape) *cobra.Command {
}
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.PersistentFlags().StringVarP(&downloadInfo.Credentials.ClientID, "client-id", "", "", "Kubescape SaaS client ID. Default will load client ID from cache, read more - https://hub.armo.cloud/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.armo.cloud/docs/authentication")
downloadCmd.Flags().StringVarP(&downloadInfo.Path, "output", "o", "", "Output file. If not specified, will save in `~/.kubescape/<policy name>.json`")
return downloadCmd

View File

@@ -5,10 +5,10 @@ import (
"strings"
"github.com/armosec/kubescape/v2/core/cautils"
"github.com/armosec/kubescape/v2/core/cautils/logger"
"github.com/armosec/kubescape/v2/core/core"
"github.com/armosec/kubescape/v2/core/meta"
v1 "github.com/armosec/kubescape/v2/core/meta/datastructures/v1"
logger "github.com/dwertent/go-logger"
"github.com/spf13/cobra"
)
@@ -27,7 +27,7 @@ var (
kubescape list controls --id
Control documentation:
https://hub.armosec.io/docs/controls
https://hub.armo.cloud/docs/controls
`
)
@@ -60,8 +60,8 @@ func GetListCmd(ks meta.IKubescape) *cobra.Command {
},
}
listCmd.PersistentFlags().StringVarP(&listPolicies.Credentials.Account, "account", "", "", "Kubescape SaaS account ID. Default will load account ID from cache")
listCmd.PersistentFlags().StringVarP(&listPolicies.Credentials.ClientID, "client-id", "", "", "Kubescape SaaS client ID. Default will load client ID from cache, read more - https://hub.armosec.io/docs/authentication")
listCmd.PersistentFlags().StringVarP(&listPolicies.Credentials.SecretKey, "secret-key", "", "", "Kubescape SaaS secret key. Default will load secret key from cache, read more - https://hub.armosec.io/docs/authentication")
listCmd.PersistentFlags().StringVarP(&listPolicies.Credentials.ClientID, "client-id", "", "", "Kubescape SaaS client ID. Default will load client ID from cache, read more - https://hub.armo.cloud/docs/authentication")
listCmd.PersistentFlags().StringVarP(&listPolicies.Credentials.SecretKey, "secret-key", "", "", "Kubescape SaaS secret key. Default will load secret key from cache, read more - https://hub.armo.cloud/docs/authentication")
listCmd.PersistentFlags().StringVar(&listPolicies.Format, "format", "pretty-print", "output format. supported: 'pretty-printer'/'json'")
listCmd.PersistentFlags().BoolVarP(&listPolicies.ListIDs, "id", "", false, "List control ID's instead of controls names")

View File

@@ -14,10 +14,10 @@ import (
"github.com/armosec/kubescape/v2/cmd/version"
"github.com/armosec/kubescape/v2/core/cautils"
"github.com/armosec/kubescape/v2/core/cautils/getter"
"github.com/armosec/kubescape/v2/core/cautils/logger"
"github.com/armosec/kubescape/v2/core/cautils/logger/helpers"
"github.com/armosec/kubescape/v2/core/core"
"github.com/armosec/kubescape/v2/core/meta"
logger "github.com/dwertent/go-logger"
"github.com/dwertent/go-logger/helpers"
"github.com/spf13/cobra"
)
@@ -47,7 +47,8 @@ func getRootCmd(ks meta.IKubescape) *cobra.Command {
rootCmd := &cobra.Command{
Use: "kubescape",
Short: "Kubescape is a tool for testing Kubernetes security posture. Docs: https://hub.armosec.io/docs",
Version: cautils.BuildNumber,
Short: "Kubescape is a tool for testing Kubernetes security posture. Docs: https://hub.armo.cloud/docs",
Example: ksExamples,
}

View File

@@ -6,8 +6,8 @@ import (
"strings"
"github.com/armosec/kubescape/v2/core/cautils/getter"
logger "github.com/dwertent/go-logger"
"github.com/dwertent/go-logger/helpers"
"github.com/armosec/kubescape/v2/core/cautils/logger"
"github.com/armosec/kubescape/v2/core/cautils/logger/helpers"
"github.com/mattn/go-isatty"
)

View File

@@ -9,10 +9,9 @@ import (
apisv1 "github.com/armosec/opa-utils/httpserver/apis/v1"
"github.com/armosec/kubescape/v2/core/cautils"
"github.com/armosec/kubescape/v2/core/cautils/logger"
"github.com/armosec/kubescape/v2/core/cautils/logger/helpers"
"github.com/armosec/kubescape/v2/core/meta"
logger "github.com/dwertent/go-logger"
"github.com/dwertent/go-logger/helpers"
"github.com/enescakir/emoji"
"github.com/spf13/cobra"
)
@@ -31,7 +30,7 @@ var (
Run 'kubescape list controls' for the list of supported controls
Control documentation:
https://hub.armosec.io/docs/controls
https://hub.armo.cloud/docs/controls
`
)

View File

@@ -9,10 +9,9 @@ import (
apisv1 "github.com/armosec/opa-utils/httpserver/apis/v1"
"github.com/armosec/kubescape/v2/core/cautils"
"github.com/armosec/kubescape/v2/core/cautils/logger"
"github.com/armosec/kubescape/v2/core/cautils/logger/helpers"
"github.com/armosec/kubescape/v2/core/meta"
logger "github.com/dwertent/go-logger"
"github.com/dwertent/go-logger/helpers"
"github.com/enescakir/emoji"
"github.com/spf13/cobra"
)

View File

@@ -64,15 +64,15 @@ func GetScanCommand(ks meta.IKubescape) *cobra.Command {
}
scanCmd.PersistentFlags().StringVarP(&scanInfo.Credentials.Account, "account", "", "", "Kubescape SaaS account ID. Default will load account ID from cache")
scanCmd.PersistentFlags().StringVarP(&scanInfo.Credentials.ClientID, "client-id", "", "", "Kubescape SaaS client ID. Default will load client ID from cache, read more - https://hub.armosec.io/docs/authentication")
scanCmd.PersistentFlags().StringVarP(&scanInfo.Credentials.SecretKey, "secret-key", "", "", "Kubescape SaaS secret key. Default will load secret key from cache, read more - https://hub.armosec.io/docs/authentication")
scanCmd.PersistentFlags().StringVarP(&scanInfo.Credentials.ClientID, "client-id", "", "", "Kubescape SaaS client ID. Default will load client ID from cache, read more - https://hub.armo.cloud/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.armo.cloud/docs/authentication")
scanCmd.PersistentFlags().StringVarP(&scanInfo.KubeContext, "kube-context", "", "", "Kube context. Default will use the current-context")
scanCmd.PersistentFlags().StringVar(&scanInfo.ControlsInputs, "controls-config", "", "Path to an controls-config obj. If not set will download controls-config from ARMO management portal")
scanCmd.PersistentFlags().StringVar(&scanInfo.UseExceptions, "exceptions", "", "Path to an exceptions obj. If not set will download exceptions from ARMO management portal")
scanCmd.PersistentFlags().StringVar(&scanInfo.UseArtifactsFrom, "use-artifacts-from", "", "Load artifacts from local directory. If not used will download them")
scanCmd.PersistentFlags().StringVarP(&scanInfo.ExcludedNamespaces, "exclude-namespaces", "e", "", "Namespaces to exclude from scanning. Recommended: kube-system,kube-public")
scanCmd.PersistentFlags().Float32VarP(&scanInfo.FailThreshold, "fail-threshold", "t", 100, "Failure threshold is the percent above which the command fails and returns exit code 1")
scanCmd.PersistentFlags().StringVarP(&scanInfo.Format, "format", "f", "pretty-printer", `Output format. Supported formats: "pretty-printer", "json", "junit", "prometheus", "pdf", "html"`)
scanCmd.PersistentFlags().StringVarP(&scanInfo.Format, "format", "f", "pretty-printer", `Output format. Supported formats: "pretty-printer","json","junit","prometheus","pdf"`)
scanCmd.PersistentFlags().StringVar(&scanInfo.IncludeNamespaces, "include-namespaces", "", "scan specific namespaces. e.g: --include-namespaces ns-a,ns-b")
scanCmd.PersistentFlags().BoolVarP(&scanInfo.Local, "keep-local", "", false, "If you do not want your Kubescape results reported to ARMO backend. Use this flag if you ran with the '--submit' flag in the past and you do not want to submit your current scan results")
scanCmd.PersistentFlags().StringVarP(&scanInfo.Output, "output", "o", "", "Output file. Print output to file and not stdout")

View File

@@ -3,16 +3,16 @@ package submit
import (
"fmt"
"github.com/armosec/kubescape/v2/core/cautils/logger"
"github.com/armosec/kubescape/v2/core/meta"
metav1 "github.com/armosec/kubescape/v2/core/meta/datastructures/v1"
logger "github.com/dwertent/go-logger"
"github.com/spf13/cobra"
)
func getExceptionsCmd(ks meta.IKubescape, submitInfo *metav1.Submit) *cobra.Command {
return &cobra.Command{
Use: "exceptions <full path to exceptions file>",
Use: "exceptions <full path to exceptins file>",
Short: "Submit exceptions to the Kubescape SaaS version",
Args: func(cmd *cobra.Command, args []string) error {
if len(args) != 1 {

View File

@@ -1,40 +1,27 @@
package submit
import (
"fmt"
"github.com/armosec/k8s-interface/k8sinterface"
"github.com/armosec/kubescape/v2/core/cautils"
"github.com/armosec/kubescape/v2/core/cautils/getter"
"github.com/armosec/kubescape/v2/core/cautils/logger"
"github.com/armosec/kubescape/v2/core/cautils/logger/helpers"
"github.com/armosec/kubescape/v2/core/meta"
"github.com/armosec/kubescape/v2/core/meta/cliinterfaces"
v1 "github.com/armosec/kubescape/v2/core/meta/datastructures/v1"
reporterv2 "github.com/armosec/kubescape/v2/core/pkg/resultshandling/reporter/v2"
logger "github.com/dwertent/go-logger"
"github.com/dwertent/go-logger/helpers"
"github.com/google/uuid"
reporterv1 "github.com/armosec/kubescape/v2/core/pkg/resultshandling/reporter/v1"
"github.com/armosec/rbac-utils/rbacscanner"
"github.com/spf13/cobra"
)
var (
rbacExamples = `
# Submit cluster's Role-Based Access Control(RBAC)
kubescape submit rbac
# Submit cluster's Role-Based Access Control(RBAC) with account ID
kubescape submit rbac --account <account-id>
`
)
// getRBACCmd represents the RBAC command
func getRBACCmd(ks meta.IKubescape, submitInfo *v1.Submit) *cobra.Command {
return &cobra.Command{
Use: "rbac",
Example: rbacExamples,
Short: "Submit cluster's Role-Based Access Control(RBAC)",
Long: ``,
Use: "rbac \nExample:\n$ kubescape submit rbac",
Short: "Submit cluster's Role-Based Access Control(RBAC)",
Long: ``,
RunE: func(cmd *cobra.Command, args []string) error {
k8s := k8sinterface.NewKubernetesApi()
@@ -45,15 +32,11 @@ func getRBACCmd(ks meta.IKubescape, submitInfo *v1.Submit) *cobra.Command {
logger.L().Error("failed setting account ID", helpers.Error(err))
}
if clusterConfig.GetAccountID() == "" {
return fmt.Errorf("account ID is not set, run 'kubescape submit rbac --account <account-id>'")
}
// list RBAC
rbacObjects := cautils.NewRBACObjects(rbacscanner.NewRbacScannerFromK8sAPI(k8s, clusterConfig.GetAccountID(), clusterConfig.GetContextName()))
// submit resources
r := reporterv2.NewReportEventReceiver(clusterConfig.GetConfigObj(), uuid.NewString(), reporterv2.SubmitContextRBAC)
r := reporterv1.NewReportEventReceiver(clusterConfig.GetConfigObj())
submitInterfaces := cliinterfaces.SubmitInterfaces{
ClusterConfig: clusterConfig,

View File

@@ -4,18 +4,20 @@ import (
"encoding/json"
"fmt"
"os"
reporthandlingv2 "github.com/armosec/opa-utils/reporthandling/v2"
"github.com/google/uuid"
"time"
"github.com/armosec/k8s-interface/workloadinterface"
"github.com/armosec/kubescape/v2/core/cautils/logger"
"github.com/armosec/kubescape/v2/core/cautils/logger/helpers"
"github.com/armosec/kubescape/v2/core/meta"
"github.com/armosec/kubescape/v2/core/meta/cliinterfaces"
v1 "github.com/armosec/kubescape/v2/core/meta/datastructures/v1"
"github.com/armosec/kubescape/v2/core/pkg/resultshandling/reporter"
reporterv1 "github.com/armosec/kubescape/v2/core/pkg/resultshandling/reporter/v1"
reporterv2 "github.com/armosec/kubescape/v2/core/pkg/resultshandling/reporter/v2"
logger "github.com/dwertent/go-logger"
"github.com/dwertent/go-logger/helpers"
"github.com/armosec/opa-utils/reporthandling"
"github.com/google/uuid"
"github.com/spf13/cobra"
)
@@ -35,13 +37,19 @@ func NewResultsObject(customerGUID, clusterName, filePath string) *ResultsObject
}
}
func (resultsObject *ResultsObject) SetResourcesReport() (*reporthandlingv2.PostureReport, error) {
func (resultsObject *ResultsObject) SetResourcesReport() (*reporthandling.PostureReport, error) {
// load framework results from json file
report, err := loadResultsFromFile(resultsObject.filePath)
frameworkReports, err := loadResultsFromFile(resultsObject.filePath)
if err != nil {
return nil, err
}
return report, nil
return &reporthandling.PostureReport{
FrameworkReports: frameworkReports,
ReportID: uuid.NewString(),
ReportGenerationTime: time.Now().UTC(),
CustomerGUID: resultsObject.customerGUID,
ClusterName: resultsObject.clusterName,
}, nil
}
func (resultsObject *ResultsObject) ListAllResources() (map[string]workloadinterface.IMetadata, error) {
@@ -68,7 +76,15 @@ func getResultsCmd(ks meta.IKubescape, submitInfo *v1.Submit) *cobra.Command {
resultsObjects := NewResultsObject(clusterConfig.GetAccountID(), clusterConfig.GetContextName(), args[0])
r := reporterv2.NewReportEventReceiver(clusterConfig.GetConfigObj(), uuid.NewString(), reporterv2.SubmitContextScan)
// submit resources
var r reporter.IReport
switch formatVersion {
case "v2":
r = reporterv2.NewReportEventReceiver(clusterConfig.GetConfigObj(), "")
default:
logger.L().Warning("Deprecated results version. run with '--format-version' flag", helpers.String("your version", formatVersion), helpers.String("latest version", "v2"))
r = reporterv1.NewReportEventReceiver(clusterConfig.GetConfigObj())
}
submitInterfaces := cliinterfaces.SubmitInterfaces{
ClusterConfig: clusterConfig,
@@ -86,14 +102,18 @@ func getResultsCmd(ks meta.IKubescape, submitInfo *v1.Submit) *cobra.Command {
return resultsCmd
}
func loadResultsFromFile(filePath string) (*reporthandlingv2.PostureReport, error) {
report := &reporthandlingv2.PostureReport{}
func loadResultsFromFile(filePath string) ([]reporthandling.FrameworkReport, error) {
frameworkReports := []reporthandling.FrameworkReport{}
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())
if err = json.Unmarshal(f, &frameworkReports); err != nil {
frameworkReport := reporthandling.FrameworkReport{}
if err = json.Unmarshal(f, &frameworkReport); err != nil {
return frameworkReports, err
}
frameworkReports = append(frameworkReports, frameworkReport)
}
return report, nil
return frameworkReports, nil
}

View File

@@ -21,8 +21,8 @@ func GetSubmitCmd(ks meta.IKubescape) *cobra.Command {
},
}
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.PersistentFlags().StringVarP(&submitInfo.Credentials.ClientID, "client-id", "", "", "Kubescape SaaS client ID. Default will load client ID from cache, read more - https://hub.armo.cloud/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.armo.cloud/docs/authentication")
submitCmd.AddCommand(getExceptionsCmd(ks, &submitInfo))
submitCmd.AddCommand(getResultsCmd(ks, &submitInfo))

View File

@@ -11,7 +11,7 @@ import (
"github.com/armosec/k8s-interface/k8sinterface"
"github.com/armosec/kubescape/v2/core/cautils/getter"
logger "github.com/dwertent/go-logger"
"github.com/armosec/kubescape/v2/core/cautils/logger"
corev1 "k8s.io/api/core/v1"
)
@@ -487,13 +487,13 @@ func getConfigMapNamespace() string {
func getAccountFromEnv(credentials *Credentials) {
// load from env
if accountID := os.Getenv("KS_ACCOUNT_ID"); credentials.Account == "" && accountID != "" {
if accountID := os.Getenv("KS_ACCOUNT_ID"); credentials.Account != "" && accountID != "" {
credentials.Account = accountID
}
if clientID := os.Getenv("KS_CLIENT_ID"); credentials.ClientID == "" && clientID != "" {
if clientID := os.Getenv("KS_CLIENT_ID"); credentials.ClientID != "" && clientID != "" {
credentials.ClientID = clientID
}
if secretKey := os.Getenv("KS_SECRET_KEY"); credentials.SecretKey == "" && secretKey != "" {
if secretKey := os.Getenv("KS_SECRET_KEY"); credentials.SecretKey != "" && secretKey != "" {
credentials.SecretKey = secretKey
}
}

View File

@@ -19,7 +19,8 @@ type OPASessionObj struct {
Policies []reporthandling.Framework // list of frameworks to scan
AllResources map[string]workloadinterface.IMetadata // all scanned resources, map[<rtesource ID>]<resource>
ResourcesResult map[string]resourcesresults.Result // resources scan results, map[<rtesource ID>]<resource result>
ResourceSource map[string]reporthandling.Source // resources sources, map[<rtesource ID>]<resource result>
ResourceSource map[string]string // resources sources, map[<rtesource ID>]<resource result>
PostureReport *reporthandling.PostureReport // scan results v1 - Remove
Report *reporthandlingv2.PostureReport // scan results v2 - Remove
Exceptions []armotypes.PostureExceptionPolicy // list of exceptions to apply on scan results
RegoInputData RegoInputData // input passed to rgo for scanning. map[<control name>][<input arguments>]
@@ -38,9 +39,13 @@ func NewOPASessionObj(frameworks []reporthandling.Framework, k8sResources *K8SRe
ResourcesResult: make(map[string]resourcesresults.Result),
InfoMap: make(map[string]apis.StatusInfo),
ResourceToControlsMap: make(map[string][]string),
ResourceSource: make(map[string]reporthandling.Source),
ResourceSource: make(map[string]string),
SessionID: scanInfo.ScanID,
Metadata: scanInfoToScanMetadata(scanInfo),
PostureReport: &reporthandling.PostureReport{
ClusterName: ClusterName,
CustomerGUID: CustomerGUID,
},
Metadata: scanInfoToScanMetadata(scanInfo),
}
}
@@ -51,10 +56,11 @@ func NewOPASessionObjMock() *OPASessionObj {
AllResources: make(map[string]workloadinterface.IMetadata),
ResourcesResult: make(map[string]resourcesresults.Result),
Report: &reporthandlingv2.PostureReport{},
Metadata: &reporthandlingv2.Metadata{
ScanMetadata: reporthandlingv2.ScanMetadata{
ScanningTarget: 0,
},
PostureReport: &reporthandling.PostureReport{
ClusterName: "",
CustomerGUID: "",
ReportID: "",
JobID: "",
},
}
}

View File

@@ -9,12 +9,8 @@ import (
"strings"
"github.com/armosec/k8s-interface/workloadinterface"
"github.com/dwertent/go-logger/helpers"
"github.com/armosec/kubescape/v2/core/cautils/logger"
"github.com/armosec/opa-utils/objectsenvelopes"
"github.com/armosec/opa-utils/objectsenvelopes/localworkload"
logger "github.com/dwertent/go-logger"
"gopkg.in/yaml.v2"
)
@@ -30,55 +26,23 @@ const (
JSON_FILE_FORMAT FileFormat = "json"
)
// LoadResourcesFromHelmCharts scans a given path (recuresively) for helm charts, renders the templates and returns a map of workloads and a map of chart names
func LoadResourcesFromHelmCharts(basePath string) (map[string][]workloadinterface.IMetadata, map[string]string) {
directories, _ := listDirs(basePath)
helmDirectories := make([]string, 0)
for _, dir := range directories {
if ok, _ := IsHelmDirectory(dir); ok {
helmDirectories = append(helmDirectories, dir)
}
}
sourceToWorkloads := map[string][]workloadinterface.IMetadata{}
sourceToChartName := map[string]string{}
for _, helmDir := range helmDirectories {
chart, err := NewHelmChart(helmDir)
if err == nil {
wls, errs := chart.GetWorkloadsWithDefaultValues()
if len(errs) > 0 {
logger.L().Error(fmt.Sprintf("Rendering of Helm chart template failed: %v", errs))
continue
}
chartName := chart.GetName()
for k, v := range wls {
sourceToWorkloads[k] = v
sourceToChartName[k] = chartName
}
}
}
return sourceToWorkloads, sourceToChartName
}
func LoadResourcesFromFiles(input, rootPath string) map[string][]workloadinterface.IMetadata {
files, errs := listFiles(input)
func LoadResourcesFromFiles(inputPatterns []string) (map[string][]workloadinterface.IMetadata, error) {
files, errs := listFiles(inputPatterns)
if len(errs) > 0 {
logger.L().Error(fmt.Sprintf("%v", errs))
}
if len(files) == 0 {
return nil
return nil, nil
}
workloads, errs := loadFiles(rootPath, files)
workloads, errs := loadFiles(files)
if len(errs) > 0 {
logger.L().Error(fmt.Sprintf("%v", errs))
}
return workloads
return workloads, nil
}
func loadFiles(rootPath string, filePaths []string) (map[string][]workloadinterface.IMetadata, []error) {
func loadFiles(filePaths []string) (map[string][]workloadinterface.IMetadata, []error) {
workloads := make(map[string][]workloadinterface.IMetadata, 0)
errs := []error{}
for i := range filePaths {
@@ -87,30 +51,15 @@ func loadFiles(rootPath string, filePaths []string) (map[string][]workloadinterf
errs = append(errs, err)
continue
}
if len(f) == 0 {
continue // empty file
}
w, e := ReadFile(f, GetFileFormat(filePaths[i]))
if e != nil {
logger.L().Debug("failed to read file", helpers.String("file", filePaths[i]), helpers.Error(e))
}
if len(w) != 0 {
path := filePaths[i]
if _, ok := workloads[path]; !ok {
workloads[path] = []workloadinterface.IMetadata{}
errs = append(errs, e...)
if w != nil {
if _, ok := workloads[filePaths[i]]; !ok {
workloads[filePaths[i]] = []workloadinterface.IMetadata{}
}
wSlice := workloads[path]
for j := range w {
lw := localworkload.NewLocalWorkload(w[j].GetObject())
if relPath, err := filepath.Rel(rootPath, path); err == nil {
lw.SetPath(relPath)
} else {
lw.SetPath(path)
}
wSlice = append(wSlice, lw)
}
workloads[path] = wSlice
wSlice := workloads[filePaths[i]]
wSlice = append(wSlice, w...)
workloads[filePaths[i]] = wSlice
}
}
return workloads, errs
@@ -119,64 +68,45 @@ func loadFiles(rootPath string, filePaths []string) (map[string][]workloadinterf
func loadFile(filePath string) ([]byte, error) {
return os.ReadFile(filePath)
}
func ReadFile(fileContent []byte, fileFormat FileFormat) ([]workloadinterface.IMetadata, error) {
func ReadFile(fileContent []byte, fileFromat FileFormat) ([]workloadinterface.IMetadata, []error) {
switch fileFormat {
switch fileFromat {
case YAML_FILE_FORMAT:
return readYamlFile(fileContent)
case JSON_FILE_FORMAT:
return readJsonFile(fileContent)
default:
return nil, nil
return nil, nil // []error{fmt.Errorf("file extension %s not supported", fileFromat)}
}
}
// listFiles returns the list of absolute paths, full file path and list of errors. The list of abs paths and full path have the same length
func listFiles(pattern string) ([]string, []error) {
return listFilesOrDirectories(pattern, false)
}
// listDirs returns the list of absolute paths, full directories path and list of errors. The list of abs paths and full path have the same length
func listDirs(pattern string) ([]string, []error) {
return listFilesOrDirectories(pattern, true)
}
func listFilesOrDirectories(pattern string, onlyDirectories bool) ([]string, []error) {
var paths []string
func listFiles(patterns []string) ([]string, []error) {
files := []string{}
errs := []error{}
if !filepath.IsAbs(pattern) {
o, _ := os.Getwd()
pattern = filepath.Join(o, pattern)
for i := range patterns {
if strings.HasPrefix(patterns[i], "http") {
continue
}
if !filepath.IsAbs(patterns[i]) {
o, _ := os.Getwd()
patterns[i] = filepath.Join(o, patterns[i])
}
if IsFile(patterns[i]) {
files = append(files, patterns[i])
} else {
f, err := glob(filepath.Split(patterns[i])) //filepath.Glob(patterns[i])
if err != nil {
errs = append(errs, err)
} else {
files = append(files, f...)
}
}
}
if !onlyDirectories && IsFile(pattern) {
paths = append(paths, pattern)
return paths, errs
}
root, shouldMatch := filepath.Split(pattern)
if IsDir(pattern) {
root = pattern
shouldMatch = "*"
}
if shouldMatch == "" {
shouldMatch = "*"
}
f, err := glob(root, shouldMatch, onlyDirectories)
if err != nil {
errs = append(errs, err)
} else {
paths = append(paths, f...)
}
return paths, errs
return files, errs
}
func readYamlFile(yamlFile []byte) ([]workloadinterface.IMetadata, error) {
defer recover()
func readYamlFile(yamlFile []byte) ([]workloadinterface.IMetadata, []error) {
errs := []error{}
r := bytes.NewReader(yamlFile)
dec := yaml.NewDecoder(r)
@@ -196,17 +126,19 @@ func readYamlFile(yamlFile []byte) ([]workloadinterface.IMetadata, error) {
yamlObjs = append(yamlObjs, o)
}
}
} else {
errs = append(errs, fmt.Errorf("failed to convert yaml file to map[string]interface, file content: %v", j))
}
}
return yamlObjs, nil
return yamlObjs, errs
}
func readJsonFile(jsonFile []byte) ([]workloadinterface.IMetadata, error) {
func readJsonFile(jsonFile []byte) ([]workloadinterface.IMetadata, []error) {
workloads := []workloadinterface.IMetadata{}
var jsonObj interface{}
if err := json.Unmarshal(jsonFile, &jsonObj); err != nil {
return workloads, err
return workloads, []error{err}
}
convertJsonToWorkload(jsonObj, &workloads)
@@ -252,40 +184,21 @@ func IsJson(filePath string) bool {
return StringInSlice(JSON_PREFIX, strings.ReplaceAll(filepath.Ext(filePath), ".", "")) != ValueNotFound
}
func glob(root, pattern string, onlyDirectories bool) ([]string, error) {
func glob(root, pattern string) ([]string, error) {
var matches []string
err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
// listing only directotries
if onlyDirectories {
if info.IsDir() {
if matched, err := filepath.Match(pattern, filepath.Base(path)); err != nil {
return err
} else if matched {
matches = append(matches, path)
}
}
return nil
}
// listing only files
if info.IsDir() {
return nil
}
fileFormat := GetFileFormat(path)
if !(fileFormat == JSON_FILE_FORMAT || fileFormat == YAML_FILE_FORMAT) {
return nil
}
if matched, err := filepath.Match(pattern, filepath.Base(path)); err != nil {
return err
} else if matched {
matches = append(matches, path)
}
return nil
})
if err != nil {
@@ -293,8 +206,6 @@ func glob(root, pattern string, onlyDirectories bool) ([]string, error) {
}
return matches, nil
}
// IsFile checks if a given path is a file
func IsFile(name string) bool {
if fi, err := os.Stat(name); err == nil {
if fi.Mode().IsRegular() {
@@ -304,16 +215,6 @@ func IsFile(name string) bool {
return false
}
// IsDir checks if a given path is a directory
func IsDir(name string) bool {
if info, err := os.Stat(name); err == nil {
if info.IsDir() {
return true
}
}
return false
}
func GetFileFormat(filePath string) FileFormat {
if IsYaml(filePath) {
return YAML_FILE_FORMAT

View File

@@ -6,100 +6,59 @@ import (
"strings"
"testing"
"github.com/armosec/opa-utils/objectsenvelopes/localworkload"
"github.com/stretchr/testify/assert"
)
func onlineBoutiquePath() string {
o, _ := os.Getwd()
return filepath.Join(filepath.Dir(o), "..", "examples", "online-boutique")
}
func helmChartPath() string {
o, _ := os.Getwd()
return filepath.Join(filepath.Dir(o), "..", "examples", "helm_chart")
return filepath.Join(filepath.Dir(o), "../examples/online-boutique/*")
}
func TestListFiles(t *testing.T) {
filesPath := onlineBoutiquePath()
files, errs := listFiles(filesPath)
files, errs := listFiles([]string{filesPath})
assert.Equal(t, 0, len(errs))
assert.Equal(t, 12, len(files))
}
func TestLoadResourcesFromFiles(t *testing.T) {
workloads := LoadResourcesFromFiles(onlineBoutiquePath(), "")
workloads, err := LoadResourcesFromFiles([]string{onlineBoutiquePath()})
assert.NoError(t, err)
assert.Equal(t, 12, len(workloads))
for i, w := range workloads {
switch filepath.Base(i) {
case "adservice.yaml":
assert.Equal(t, 2, len(w))
assert.Equal(t, "apps/v1//Deployment/adservice", getRelativePath(w[0].GetID()))
assert.Equal(t, "/v1//Service/adservice", getRelativePath(w[1].GetID()))
assert.Equal(t, "apps/v1//Deployment/adservice", w[0].GetID())
assert.Equal(t, "/v1//Service/adservice", w[1].GetID())
}
}
}
func TestLoadResourcesFromHelmCharts(t *testing.T) {
sourceToWorkloads, sourceToChartName := LoadResourcesFromHelmCharts(helmChartPath())
assert.Equal(t, 6, len(sourceToWorkloads))
for file, workloads := range sourceToWorkloads {
assert.Equalf(t, 1, len(workloads), "expected 1 workload in file %s", file)
w := workloads[0]
assert.True(t, localworkload.IsTypeLocalWorkload(w.GetObject()), "Expected localworkload as object type")
assert.Equal(t, "kubescape", sourceToChartName[file])
switch filepath.Base(file) {
case "serviceaccount.yaml":
assert.Equal(t, "/v1//ServiceAccount/kubescape-discovery", getRelativePath(w.GetID()))
case "clusterrole.yaml":
assert.Equal(t, "rbac.authorization.k8s.io/v1//ClusterRole/-kubescape", getRelativePath(w.GetID()))
case "cronjob.yaml":
assert.Equal(t, "batch/v1//CronJob/-kubescape", getRelativePath(w.GetID()))
case "role.yaml":
assert.Equal(t, "rbac.authorization.k8s.io/v1//Role/-kubescape", getRelativePath(w.GetID()))
case "rolebinding.yaml":
assert.Equal(t, "rbac.authorization.k8s.io/v1//RoleBinding/-kubescape", getRelativePath(w.GetID()))
case "clusterrolebinding.yaml":
assert.Equal(t, "rbac.authorization.k8s.io/v1//ClusterRoleBinding/-kubescape", getRelativePath(w.GetID()))
default:
assert.Failf(t, "missing case for file: %s", filepath.Base(file))
}
}
}
func TestLoadFiles(t *testing.T) {
files, _ := listFiles(onlineBoutiquePath())
_, err := loadFiles("", files)
files, _ := listFiles([]string{onlineBoutiquePath()})
_, err := loadFiles(files)
assert.Equal(t, 0, len(err))
}
func TestListDirs(t *testing.T) {
dirs, _ := listDirs(filepath.Join(onlineBoutiquePath(), "adservice.yaml"))
assert.Equal(t, 0, len(dirs))
expectedDirs := []string{filepath.Join("examples", "helm_chart"), filepath.Join("examples", "helm_chart", "templates")}
dirs, _ = listDirs(helmChartPath())
assert.Equal(t, len(expectedDirs), len(dirs))
for i := range expectedDirs {
assert.Contains(t, dirs[i], expectedDirs[i])
}
}
func TestLoadFile(t *testing.T) {
files, _ := listFiles(filepath.Join(onlineBoutiquePath(), "adservice.yaml"))
files, _ := listFiles([]string{strings.Replace(onlineBoutiquePath(), "*", "adservice.yaml", 1)})
assert.Equal(t, 1, len(files))
_, err := loadFile(files[0])
assert.NoError(t, err)
}
func getRelativePath(p string) string {
pp := strings.SplitAfter(p, "api=")
return pp[1]
func TestMapResources(t *testing.T) {
// policyHandler := &PolicyHandler{}
// k8sResources, err := policyHandler.loadResources(opaSessionObj.Frameworks, scanInfo)
// files, _ := listFiles([]string{onlineBoutiquePath()})
// bb, err := loadFile(files[0])
// if len(err) > 0 {
// t.Errorf("%v", err)
// }
// for i := range bb {
// t.Errorf("%s", bb[i].ToString())
// }
}

View File

@@ -10,9 +10,9 @@ import (
"time"
"github.com/armosec/armoapi-go/armotypes"
"github.com/armosec/kubescape/v2/core/cautils/logger"
"github.com/armosec/kubescape/v2/core/cautils/logger/helpers"
"github.com/armosec/opa-utils/reporthandling"
logger "github.com/dwertent/go-logger"
"github.com/dwertent/go-logger/helpers"
)
// =======================================================================================================================
@@ -23,19 +23,19 @@ var (
// ATTENTION!!!
// Changes in this URLs variable names, or in the usage is affecting the build process! BE CAREFUL
armoERURL = "report.armo.cloud"
armoBEURL = "api.armosec.io"
armoFEURL = "cloud.armosec.io"
armoAUTHURL = "auth.armosec.io"
armoBEURL = "api.armo.cloud"
armoFEURL = "portal.armo.cloud"
armoAUTHURL = "auth.armo.cloud"
armoStageERURL = "report-ks.eustage2.cyberarmorsoft.com"
armoStageBEURL = "api-stage.armosec.io"
armoStageFEURL = "armoui-stage.armosec.io"
armoStageAUTHURL = "eggauth-stage.armosec.io"
armoStageBEURL = "api-stage.armo.cloud"
armoStageFEURL = "armoui.eustage2.cyberarmorsoft.com"
armoStageAUTHURL = "eggauth.eustage2.cyberarmorsoft.com"
armoDevERURL = "report.eudev3.cyberarmorsoft.com"
armoDevBEURL = "api-dev.armosec.io"
armoDevFEURL = "cloud-dev.armosec.io"
armoDevAUTHURL = "eggauth-dev.armosec.io"
armoDevBEURL = "api-dev.armo.cloud"
armoDevFEURL = "armoui-dev.eudev3.cyberarmorsoft.com"
armoDevAUTHURL = "eggauth.eudev3.cyberarmorsoft.com"
)
// Armo API for downloading policies

View File

@@ -1,92 +0,0 @@
package cautils
import (
"path/filepath"
"strings"
"github.com/armosec/k8s-interface/workloadinterface"
"github.com/armosec/opa-utils/objectsenvelopes/localworkload"
logger "github.com/dwertent/go-logger"
"github.com/dwertent/go-logger/helpers"
helmchart "helm.sh/helm/v3/pkg/chart"
helmloader "helm.sh/helm/v3/pkg/chart/loader"
helmchartutil "helm.sh/helm/v3/pkg/chartutil"
helmengine "helm.sh/helm/v3/pkg/engine"
)
type HelmChart struct {
chart *helmchart.Chart
path string
}
func IsHelmDirectory(path string) (bool, error) {
return helmchartutil.IsChartDir(path)
}
func NewHelmChart(path string) (*HelmChart, error) {
chart, err := helmloader.Load(path)
if err != nil {
return nil, err
}
return &HelmChart{
chart: chart,
path: path,
}, nil
}
func (hc *HelmChart) GetName() string {
return hc.chart.Name()
}
func (hc *HelmChart) GetDefaultValues() map[string]interface{} {
return hc.chart.Values
}
// GetWorkloads renders chart template using the default values and returns a map of source file to its workloads
func (hc *HelmChart) GetWorkloadsWithDefaultValues() (map[string][]workloadinterface.IMetadata, []error) {
return hc.GetWorkloads(hc.GetDefaultValues())
}
// GetWorkloads renders chart template using the provided values and returns a map of source (absolute) file path to its workloads
func (hc *HelmChart) GetWorkloads(values map[string]interface{}) (map[string][]workloadinterface.IMetadata, []error) {
vals, err := helmchartutil.ToRenderValues(hc.chart, values, helmchartutil.ReleaseOptions{}, nil)
if err != nil {
return nil, []error{err}
}
sourceToFile, err := helmengine.Render(hc.chart, vals)
if err != nil {
return nil, []error{err}
}
workloads := make(map[string][]workloadinterface.IMetadata, 0)
errs := []error{}
for path, renderedYaml := range sourceToFile {
if !IsYaml(strings.ToLower(path)) {
continue
}
wls, e := ReadFile([]byte(renderedYaml), YAML_FILE_FORMAT)
if e != nil {
logger.L().Debug("failed to read rendered yaml file", helpers.String("file", path), helpers.Error(e))
}
if len(wls) == 0 {
continue
}
// separate base path and file name. We do not use the os.Separator because the paths returned from the helm engine are not OS specific (e.g. mychart/templates/myfile.yaml)
if firstPathSeparatorIndex := strings.Index(path, string("/")); firstPathSeparatorIndex != -1 {
absPath := filepath.Join(hc.path, path[firstPathSeparatorIndex:])
workloads[absPath] = []workloadinterface.IMetadata{}
for i := range wls {
lw := localworkload.NewLocalWorkload(wls[i].GetObject())
lw.SetPath(absPath)
workloads[absPath] = append(workloads[absPath], lw)
}
}
}
return workloads, errs
}

View File

@@ -1,133 +0,0 @@
package cautils
import (
_ "embed"
"encoding/json"
"io/ioutil"
"os"
"path/filepath"
"strings"
"testing"
"github.com/armosec/opa-utils/objectsenvelopes/localworkload"
"github.com/stretchr/testify/suite"
)
type HelmChartTestSuite struct {
suite.Suite
helmChartPath string
expectedFiles []string
expectedDefaultValues map[string]interface{}
}
func TestHelmChartTestSuite(t *testing.T) {
suite.Run(t, new(HelmChartTestSuite))
}
func (s *HelmChartTestSuite) SetupSuite() {
o, _ := os.Getwd()
s.helmChartPath = filepath.Join(filepath.Dir(o), "..", "examples", "helm_chart")
s.expectedFiles = []string{
filepath.Join(s.helmChartPath, "templates", "clusterrolebinding.yaml"),
filepath.Join(s.helmChartPath, "templates", "clusterrole.yaml"),
filepath.Join(s.helmChartPath, "templates", "serviceaccount.yaml"),
filepath.Join(s.helmChartPath, "templates", "rolebinding.yaml"),
filepath.Join(s.helmChartPath, "templates", "role.yaml"),
filepath.Join(s.helmChartPath, "templates", "cronjob.yaml"),
}
var obj interface{}
file, _ := ioutil.ReadFile(filepath.Join("testdata", "helm_expected_default_values.json"))
_ = json.Unmarshal([]byte(file), &obj)
s.expectedDefaultValues = obj.(map[string]interface{})
}
func (s *HelmChartTestSuite) TestInvalidHelmDirectory() {
_, err := NewHelmChart("/invalid_path")
s.Error(err)
}
func (s *HelmChartTestSuite) TestValidHelmDirectory() {
chart, err := NewHelmChart(s.helmChartPath)
s.NoError(err)
s.NotNil(chart)
}
func (s *HelmChartTestSuite) TestGetName() {
chart, _ := NewHelmChart(s.helmChartPath)
s.Equal("kubescape", chart.GetName())
}
func (s *HelmChartTestSuite) TestGetDefaultValues() {
chart, _ := NewHelmChart(s.helmChartPath)
values := chart.GetDefaultValues()
valuesJson, _ := json.Marshal(values)
expectedValuesJson, _ := json.Marshal(s.expectedDefaultValues)
s.JSONEq(string(valuesJson), string(expectedValuesJson))
}
func (s *HelmChartTestSuite) TestGetWorkloadsWithOverride() {
chart, err := NewHelmChart(s.helmChartPath)
s.NoError(err, "Expected a valid helm chart")
values := chart.GetDefaultValues()
// Default pullPolicy value = Always
pullPolicyValue := values["image"].(map[string]interface{})["pullPolicy"].(string)
s.Equal(pullPolicyValue, "Always")
// Override default value
values["image"].(map[string]interface{})["pullPolicy"] = "Never"
fileToWorkloads, errs := chart.GetWorkloads(values)
s.Len(errs, 0)
s.Lenf(fileToWorkloads, len(s.expectedFiles), "Expected %d files", len(s.expectedFiles))
for _, expectedFile := range s.expectedFiles {
s.Contains(fileToWorkloads, expectedFile)
s.FileExists(expectedFile)
s.GreaterOrEqualf(len(fileToWorkloads[expectedFile]), 1, "Expected at least one workload in %q", expectedFile)
for i := range fileToWorkloads[expectedFile] {
pathInWorkload := fileToWorkloads[expectedFile][i].(*localworkload.LocalWorkload).GetPath()
s.Equal(pathInWorkload, expectedFile, "Expected GetPath() to return a valid path on workload")
}
if strings.Contains(expectedFile, "cronjob.yaml") {
jsonBytes, _ := json.Marshal(fileToWorkloads[expectedFile][0].GetObject())
s.Contains(string(jsonBytes), "\"imagePullPolicy\":\"Never\"", "Expected to overriden value of imagePullPolicy to be 'Never'")
}
}
}
func (s *HelmChartTestSuite) TestGetWorkloadsMissingValue() {
chart, _ := NewHelmChart(s.helmChartPath)
values := chart.GetDefaultValues()
delete(values, "image")
fileToWorkloads, errs := chart.GetWorkloads(values)
s.Nil(fileToWorkloads)
s.Len(errs, 1, "Expected an error due to missing value")
expectedErrMsg := "<.Values.image.repository>: nil pointer"
s.Containsf(errs[0].Error(), expectedErrMsg, "expected error containing %q, got %q", expectedErrMsg, errs[0])
}
func (s *HelmChartTestSuite) TestIsHelmDirectory() {
ok, err := IsHelmDirectory(s.helmChartPath)
s.True(ok)
s.NoError(err)
o, _ := os.Getwd()
nonHelmDir := filepath.Join(filepath.Dir(o), "../examples/online-boutique")
ok, err = IsHelmDirectory(nonHelmDir)
s.False(ok)
s.Contains(err.Error(), "no Chart.yaml exists in directory")
}

View File

@@ -1,246 +0,0 @@
package cautils
import (
"fmt"
"path"
"strings"
"time"
"github.com/armosec/go-git-url/apis"
gitv5 "github.com/go-git/go-git/v5"
configv5 "github.com/go-git/go-git/v5/config"
plumbingv5 "github.com/go-git/go-git/v5/plumbing"
git2go "github.com/libgit2/git2go/v33"
)
type LocalGitRepository struct {
goGitRepo *gitv5.Repository
git2GoRepo *git2go.Repository
head *plumbingv5.Reference
config *configv5.Config
fileToLastCommit map[string]*git2go.Commit
}
func NewLocalGitRepository(path string) (*LocalGitRepository, error) {
goGitRepo, err := gitv5.PlainOpenWithOptions(path, &gitv5.PlainOpenOptions{DetectDotGit: true})
if err != nil {
return nil, err
}
head, err := goGitRepo.Head()
if err != nil {
return nil, err
}
if !head.Name().IsBranch() {
return nil, fmt.Errorf("current HEAD reference is not a branch")
}
config, err := goGitRepo.Config()
if err != nil {
return nil, err
}
if len(config.Remotes) == 0 {
return nil, fmt.Errorf("no remotes found")
}
l := &LocalGitRepository{
goGitRepo: goGitRepo,
head: head,
config: config,
}
if repoRoot, err := l.GetRootDir(); err == nil {
git2GoRepo, err := git2go.OpenRepository(repoRoot)
if err != nil {
return l, err
}
l.git2GoRepo = git2GoRepo
}
return l, nil
}
// GetBranchName get current branch name
func (g *LocalGitRepository) GetBranchName() string {
return g.head.Name().Short()
}
// GetRemoteUrl get default remote URL
func (g *LocalGitRepository) GetRemoteUrl() (string, error) {
branchName := g.GetBranchName()
if branchRef, branchFound := g.config.Branches[branchName]; branchFound {
remoteName := branchRef.Remote
if len(g.config.Remotes[remoteName].URLs) == 0 {
return "", fmt.Errorf("expected to find URLs for remote '%s', branch '%s'", remoteName, branchName)
}
return g.config.Remotes[remoteName].URLs[0], nil
}
const defaultRemoteName string = "origin"
if len(g.config.Remotes[defaultRemoteName].URLs) == 0 {
return "", fmt.Errorf("expected to find URLs for remote '%s'", defaultRemoteName)
}
return g.config.Remotes[defaultRemoteName].URLs[0], nil
}
// GetName get origin name without the .git suffix
func (g *LocalGitRepository) GetName() (string, error) {
originUrl, err := g.GetRemoteUrl()
if err != nil {
return "", err
}
baseName := path.Base(originUrl)
// remove .git
return strings.TrimSuffix(baseName, ".git"), nil
}
// GetLastCommit get latest commit object
func (g *LocalGitRepository) GetLastCommit() (*apis.Commit, error) {
cIter, err := g.goGitRepo.Log(&gitv5.LogOptions{})
if err != nil {
return nil, err
}
commit, err := cIter.Next()
defer cIter.Close()
if err != nil {
return nil, err
}
return &apis.Commit{
SHA: commit.Hash.String(),
Author: apis.Committer{
Name: commit.Author.Name,
Email: commit.Author.Email,
Date: commit.Author.When,
},
Message: commit.Message,
Committer: apis.Committer{},
Files: []apis.Files{},
}, nil
}
func (g *LocalGitRepository) getAllCommits() ([]*git2go.Commit, error) {
logItr, itrErr := g.git2GoRepo.Walk()
if itrErr != nil {
return nil, itrErr
}
pushErr := logItr.PushHead()
if pushErr != nil {
return nil, pushErr
}
var allCommits []*git2go.Commit
err := logItr.Iterate(func(commit *git2go.Commit) bool {
if commit != nil {
allCommits = append(allCommits, commit)
return true
}
return false
})
if err != nil {
return nil, err
}
if err != nil {
return nil, err
}
return allCommits, nil
}
func (g *LocalGitRepository) GetFileLastCommit(filePath string) (*apis.Commit, error) {
if len(g.fileToLastCommit) == 0 {
filePathToCommitTime := map[string]time.Time{}
filePathToCommit := map[string]*git2go.Commit{}
allCommits, _ := g.getAllCommits()
// builds a map of all files to their last commit
for _, commit := range allCommits {
// Ignore merge commits (2+ parents)
if commit.ParentCount() <= 1 {
tree, err := commit.Tree()
if err != nil {
continue
}
// ParentCount can be either 1 or 0 (initial commit)
// In case it's the initial commit, prevTree is nil
var prevTree *git2go.Tree
if commit.ParentCount() == 1 {
prevCommit := commit.Parent(0)
prevTree, err = prevCommit.Tree()
if err != nil {
continue
}
}
diff, err := g.git2GoRepo.DiffTreeToTree(prevTree, tree, nil)
if err != nil {
continue
}
numDeltas, err := diff.NumDeltas()
if err != nil {
continue
}
for i := 0; i < numDeltas; i++ {
delta, err := diff.Delta(i)
if err != nil {
continue
}
deltaFilePath := delta.NewFile.Path
commitTime := commit.Author().When
// In case we have the commit information for the file which is not the latest - we override it
if currentCommitTime, exists := filePathToCommitTime[deltaFilePath]; exists {
if currentCommitTime.Before(commitTime) {
filePathToCommitTime[deltaFilePath] = commitTime
filePathToCommit[deltaFilePath] = commit
}
} else {
filePathToCommitTime[deltaFilePath] = commitTime
filePathToCommit[deltaFilePath] = commit
}
}
}
}
g.fileToLastCommit = filePathToCommit
}
if relevantCommit, exists := g.fileToLastCommit[filePath]; exists {
return g.getCommit(relevantCommit), nil
}
return nil, fmt.Errorf("failed to get commit information for file: %s", filePath)
}
func (g *LocalGitRepository) getCommit(commit *git2go.Commit) *apis.Commit {
return &apis.Commit{
SHA: commit.Id().String(),
Author: apis.Committer{
Name: commit.Author().Name,
Email: commit.Author().Email,
Date: commit.Author().When,
},
Message: commit.Message(),
Committer: apis.Committer{},
Files: []apis.Files{},
}
}
func (g *LocalGitRepository) GetRootDir() (string, error) {
wt, err := g.goGitRepo.Worktree()
if err != nil {
return "", fmt.Errorf("failed to get repo root")
}
return wt.Filesystem.Root(), nil
}

View File

@@ -1,175 +0,0 @@
package cautils
import (
"archive/zip"
"fmt"
"io"
"os"
"path/filepath"
"strings"
"testing"
"github.com/stretchr/testify/suite"
)
var TEST_REPOS = [...]string{"localrepo", "withoutremotes"}
type LocalGitRepositoryTestSuite struct {
suite.Suite
archives map[string]*zip.ReadCloser
gitRepositoryPaths map[string]string
destinationPath string
}
func unzipFile(zipPath, destinationFolder string) (*zip.ReadCloser, error) {
archive, err := zip.OpenReader(zipPath)
if err != nil {
return nil, err
}
for _, f := range archive.File {
filePath := filepath.Join(destinationFolder, f.Name)
if !strings.HasPrefix(filePath, filepath.Clean(destinationFolder)+string(os.PathSeparator)) {
return nil, fmt.Errorf("invalid file path")
}
if f.FileInfo().IsDir() {
os.MkdirAll(filePath, os.ModePerm)
continue
}
if err := os.MkdirAll(filepath.Dir(filePath), os.ModePerm); err != nil {
return nil, err
}
dstFile, err := os.OpenFile(filePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
if err != nil {
return nil, err
}
fileInArchive, err := f.Open()
if err != nil {
return nil, err
}
if _, err := io.Copy(dstFile, fileInArchive); err != nil {
return nil, err
}
dstFile.Close()
fileInArchive.Close()
}
return archive, err
}
func (s *LocalGitRepositoryTestSuite) SetupSuite() {
s.archives = make(map[string]*zip.ReadCloser)
s.gitRepositoryPaths = make(map[string]string)
destinationPath := filepath.Join(".", "testdata", "temp")
s.destinationPath = destinationPath
os.RemoveAll(destinationPath)
for _, repo := range TEST_REPOS {
zippedFixturePath := filepath.Join(".", "testdata", repo+".git")
gitRepositoryPath := filepath.Join(destinationPath, repo)
archive, err := unzipFile(zippedFixturePath, destinationPath)
if err == nil {
s.archives[repo] = archive
s.gitRepositoryPaths[repo] = gitRepositoryPath
}
}
}
func TestLocalGitRepositoryTestSuite(t *testing.T) {
suite.Run(t, new(LocalGitRepositoryTestSuite))
}
func (s *LocalGitRepositoryTestSuite) TearDownSuite() {
if s.archives != nil {
for _, archive := range s.archives {
if archive != nil {
archive.Close()
}
}
}
os.RemoveAll(s.destinationPath)
}
func (s *LocalGitRepositoryTestSuite) TestInvalidRepositoryPath() {
if _, err := NewLocalGitRepository("/invalidpath"); s.Error(err) {
s.Equal("repository does not exist", err.Error())
}
}
func (s *LocalGitRepositoryTestSuite) TestRepositoryWithoutRemotes() {
if _, err := NewLocalGitRepository(s.gitRepositoryPaths["withoutremotes"]); s.Error(err) {
s.Equal("no remotes found", err.Error())
}
}
func (s *LocalGitRepositoryTestSuite) TestGetBranchName() {
if localRepo, err := NewLocalGitRepository(s.gitRepositoryPaths["localrepo"]); s.NoError(err) {
s.Equal("master", localRepo.GetBranchName())
}
}
func (s *LocalGitRepositoryTestSuite) TestGetName() {
if localRepo, err := NewLocalGitRepository(s.gitRepositoryPaths["localrepo"]); s.NoError(err) {
if name, err := localRepo.GetName(); s.NoError(err) {
s.Equal("localrepo", name)
}
}
}
func (s *LocalGitRepositoryTestSuite) TestGetOriginUrl() {
if localRepo, err := NewLocalGitRepository(s.gitRepositoryPaths["localrepo"]); s.NoError(err) {
if url, err := localRepo.GetRemoteUrl(); s.NoError(err) {
s.Equal("git@github.com:testuser/localrepo", url)
}
}
}
func (s *LocalGitRepositoryTestSuite) TestGetLastCommit() {
if localRepo, err := NewLocalGitRepository(s.gitRepositoryPaths["localrepo"]); s.NoError(err) {
if commit, err := localRepo.GetLastCommit(); s.NoError(err) {
s.Equal("7e09312b8017695fadcd606882e3779f10a5c832", commit.SHA)
s.Equal("Amir Malka", commit.Author.Name)
s.Equal("amirm@armosec.io", commit.Author.Email)
s.Equal("2022-05-22 19:11:57 +0300 +0300", commit.Author.Date.String())
s.Equal("added file B\n", commit.Message)
}
}
}
func (s *LocalGitRepositoryTestSuite) TestGetFileLastCommit() {
s.Run("fileA", func() {
if localRepo, err := NewLocalGitRepository(s.gitRepositoryPaths["localrepo"]); s.NoError(err) {
if commit, err := localRepo.GetFileLastCommit("fileA"); s.NoError(err) {
s.Equal("9fae4be19624297947d2b605cefbff516628612d", commit.SHA)
s.Equal("Amir Malka", commit.Author.Name)
s.Equal("amirm@armosec.io", commit.Author.Email)
s.Equal("2022-05-22 18:55:48 +0300 +0300", commit.Author.Date.String())
s.Equal("added file A\n", commit.Message)
}
}
})
s.Run("fileB", func() {
if localRepo, err := NewLocalGitRepository(s.gitRepositoryPaths["localrepo"]); s.NoError(err) {
if commit, err := localRepo.GetFileLastCommit("dirA/fileB"); s.NoError(err) {
s.Equal("7e09312b8017695fadcd606882e3779f10a5c832", commit.SHA)
s.Equal("Amir Malka", commit.Author.Name)
s.Equal("amirm@armosec.io", commit.Author.Email)
s.Equal("2022-05-22 19:11:57 +0300 +0300", commit.Author.Date.String())
s.Equal("added file B\n", commit.Message)
}
}
})
}

View File

@@ -0,0 +1,31 @@
package helpers
import "time"
type StringObj struct {
key string
value string
}
type ErrorObj struct {
key string
value error
}
type IntObj struct {
key string
value int
}
type InterfaceObj struct {
key string
value interface{}
}
func Error(e error) *ErrorObj { return &ErrorObj{key: "error", value: e} }
func Int(k string, v int) *IntObj { return &IntObj{key: k, value: v} }
func String(k, v string) *StringObj { return &StringObj{key: k, value: v} }
func Interface(k string, v interface{}) *InterfaceObj { return &InterfaceObj{key: k, value: v} }
func Time() *StringObj {
return &StringObj{key: "time", value: time.Now().Format("2006-01-02 15:04:05")}
}

View File

@@ -0,0 +1,69 @@
package helpers
import (
"strings"
)
type Level int8
const (
UnknownLevel Level = iota - -1
DebugLevel
InfoLevel //default
SuccessLevel
WarningLevel
ErrorLevel
FatalLevel
_defaultLevel = InfoLevel
_minLevel = DebugLevel
_maxLevel = FatalLevel
)
func ToLevel(level string) Level {
switch strings.ToLower(level) {
case "debug":
return DebugLevel
case "info":
return InfoLevel
case "success":
return SuccessLevel
case "warning", "warn":
return WarningLevel
case "error":
return ErrorLevel
case "fatal":
return FatalLevel
default:
return UnknownLevel
}
}
func (l Level) String() string {
switch l {
case DebugLevel:
return "debug"
case InfoLevel:
return "info"
case SuccessLevel:
return "success"
case WarningLevel:
return "warning"
case ErrorLevel:
return "error"
case FatalLevel:
return "fatal"
}
return ""
}
func (l Level) Skip(l2 Level) bool {
return l < l2
}
func SupportedLevels() []string {
levels := []string{}
for i := _minLevel; i <= _maxLevel; i++ {
levels = append(levels, i.String())
}
return levels
}

View File

@@ -0,0 +1,62 @@
package helpers
type IDetails interface {
Key() string
Value() interface{}
}
// ======================================================================================
// ============================== String ================================================
// ======================================================================================
// Key
func (s *StringObj) Key() string {
return s.key
}
// Value
func (s *StringObj) Value() interface{} {
return s.value
}
// ======================================================================================
// =============================== Error ================================================
// ======================================================================================
// Key
func (s *ErrorObj) Key() string {
return s.key
}
// Value
func (s *ErrorObj) Value() interface{} {
return s.value
}
// ======================================================================================
// ================================= Int ================================================
// ======================================================================================
// Key
func (s *IntObj) Key() string {
return s.key
}
// Value
func (s *IntObj) Value() interface{} {
return s.value
}
// ======================================================================================
// =========================== Interface ================================================
// ======================================================================================
// Key
func (s *InterfaceObj) Key() string {
return s.key
}
// Value
func (s *InterfaceObj) Value() interface{} {
return s.value
}

View File

@@ -0,0 +1,81 @@
package logger
import (
"os"
"strings"
"github.com/armosec/kubescape/v2/core/cautils/logger/helpers"
"github.com/armosec/kubescape/v2/core/cautils/logger/nonelogger"
"github.com/armosec/kubescape/v2/core/cautils/logger/prettylogger"
"github.com/armosec/kubescape/v2/core/cautils/logger/zaplogger"
)
type ILogger interface {
Fatal(msg string, details ...helpers.IDetails) // print log and exit 1
Error(msg string, details ...helpers.IDetails)
Success(msg string, details ...helpers.IDetails)
Warning(msg string, details ...helpers.IDetails)
Info(msg string, details ...helpers.IDetails)
Debug(msg string, details ...helpers.IDetails)
SetLevel(level string) error
GetLevel() string
SetWriter(w *os.File)
GetWriter() *os.File
LoggerName() string
}
var l ILogger
// Return initialized logger. If logger not initialized, will call InitializeLogger() with the default value
func L() ILogger {
if l == nil {
InitDefaultLogger()
}
return l
}
/* InitLogger initialize desired logger
Use:
InitLogger("<logger name>")
Supported logger names (call ListLoggersNames() for listing supported loggers)
- "zap": Logger from package "go.uber.org/zap"
- "pretty", "colorful": Human friendly colorful logger
- "none", "mock", "empty", "ignore": Logger will not print anything
Default:
- "pretty"
e.g.
InitLogger("none") -> will initialize the mock logger
*/
func InitLogger(loggerName string) {
switch strings.ToLower(loggerName) {
case zaplogger.LoggerName:
l = zaplogger.NewZapLogger()
case prettylogger.LoggerName, "colorful":
l = prettylogger.NewPrettyLogger()
case nonelogger.LoggerName, "mock", "empty", "ignore":
l = nonelogger.NewNoneLogger()
default:
InitDefaultLogger()
}
}
func InitDefaultLogger() {
l = prettylogger.NewPrettyLogger()
}
func DisableColor(flag bool) {
prettylogger.DisableColor(flag)
}
func ListLoggersNames() []string {
return []string{prettylogger.LoggerName, zaplogger.LoggerName, nonelogger.LoggerName}
}

View File

@@ -0,0 +1,28 @@
package nonelogger
import (
"os"
"github.com/armosec/kubescape/v2/core/cautils/logger/helpers"
)
const LoggerName string = "none"
type NoneLogger struct {
}
func NewNoneLogger() *NoneLogger {
return &NoneLogger{}
}
func (nl *NoneLogger) GetLevel() string { return "" }
func (nl *NoneLogger) LoggerName() string { return LoggerName }
func (nl *NoneLogger) SetWriter(w *os.File) {}
func (nl *NoneLogger) GetWriter() *os.File { return nil }
func (nl *NoneLogger) SetLevel(level string) error { return nil }
func (nl *NoneLogger) Fatal(msg string, details ...helpers.IDetails) {}
func (nl *NoneLogger) Error(msg string, details ...helpers.IDetails) {}
func (nl *NoneLogger) Warning(msg string, details ...helpers.IDetails) {}
func (nl *NoneLogger) Success(msg string, details ...helpers.IDetails) {}
func (nl *NoneLogger) Info(msg string, details ...helpers.IDetails) {}
func (nl *NoneLogger) Debug(msg string, details ...helpers.IDetails) {}

View File

@@ -0,0 +1,37 @@
package prettylogger
import (
"io"
"github.com/armosec/kubescape/v2/core/cautils/logger/helpers"
"github.com/fatih/color"
)
var prefixError = color.New(color.Bold, color.FgHiRed).FprintfFunc()
var prefixWarning = color.New(color.Bold, color.FgHiYellow).FprintfFunc()
var prefixInfo = color.New(color.Bold, color.FgCyan).FprintfFunc()
var prefixSuccess = color.New(color.Bold, color.FgHiGreen).FprintfFunc()
var prefixDebug = color.New(color.Bold, color.FgWhite).FprintfFunc()
var message = color.New().FprintfFunc()
func prefix(l helpers.Level) func(w io.Writer, format string, a ...interface{}) {
switch l {
case helpers.DebugLevel:
return prefixDebug
case helpers.InfoLevel:
return prefixInfo
case helpers.SuccessLevel:
return prefixSuccess
case helpers.WarningLevel:
return prefixWarning
case helpers.ErrorLevel, helpers.FatalLevel:
return prefixError
}
return message
}
func DisableColor(flag bool) {
if flag {
color.NoColor = true
}
}

View File

@@ -0,0 +1,82 @@
package prettylogger
import (
"fmt"
"os"
"sync"
"github.com/armosec/kubescape/v2/core/cautils/logger/helpers"
)
const LoggerName string = "pretty"
type PrettyLogger struct {
writer *os.File
level helpers.Level
mutex sync.Mutex
}
func NewPrettyLogger() *PrettyLogger {
return &PrettyLogger{
writer: os.Stderr, // default to stderr
level: helpers.InfoLevel,
mutex: sync.Mutex{},
}
}
func (pl *PrettyLogger) GetLevel() string { return pl.level.String() }
func (pl *PrettyLogger) SetWriter(w *os.File) { pl.writer = w }
func (pl *PrettyLogger) GetWriter() *os.File { return pl.writer }
func (pl *PrettyLogger) LoggerName() string { return LoggerName }
func (pl *PrettyLogger) SetLevel(level string) error {
pl.level = helpers.ToLevel(level)
if pl.level == helpers.UnknownLevel {
return fmt.Errorf("level '%s' unknown", level)
}
return nil
}
func (pl *PrettyLogger) Fatal(msg string, details ...helpers.IDetails) {
pl.print(helpers.FatalLevel, msg, details...)
os.Exit(1)
}
func (pl *PrettyLogger) Error(msg string, details ...helpers.IDetails) {
pl.print(helpers.ErrorLevel, msg, details...)
}
func (pl *PrettyLogger) Warning(msg string, details ...helpers.IDetails) {
pl.print(helpers.WarningLevel, msg, details...)
}
func (pl *PrettyLogger) Info(msg string, details ...helpers.IDetails) {
pl.print(helpers.InfoLevel, msg, details...)
}
func (pl *PrettyLogger) Debug(msg string, details ...helpers.IDetails) {
pl.print(helpers.DebugLevel, msg, details...)
}
func (pl *PrettyLogger) Success(msg string, details ...helpers.IDetails) {
pl.print(helpers.SuccessLevel, msg, details...)
}
func (pl *PrettyLogger) print(level helpers.Level, msg string, details ...helpers.IDetails) {
if !level.Skip(pl.level) {
pl.mutex.Lock()
prefix(level)(pl.writer, "[%s] ", level.String())
if d := detailsToString(details); d != "" {
msg = fmt.Sprintf("%s. %s", msg, d)
}
message(pl.writer, fmt.Sprintf("%s\n", msg))
pl.mutex.Unlock()
}
}
func detailsToString(details []helpers.IDetails) string {
s := ""
for i := range details {
s += fmt.Sprintf("%s: %v", details[i].Key(), details[i].Value())
if i < len(details)-1 {
s += "; "
}
}
return s
}

View File

@@ -0,0 +1,79 @@
package zaplogger
import (
"os"
"github.com/armosec/kubescape/v2/core/cautils/logger/helpers"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
const LoggerName string = "zap"
type ZapLogger struct {
zapL *zap.Logger
cfg zap.Config
}
func NewZapLogger() *ZapLogger {
ec := zap.NewProductionEncoderConfig()
ec.EncodeTime = zapcore.RFC3339TimeEncoder
cfg := zap.NewProductionConfig()
cfg.DisableCaller = true
cfg.DisableStacktrace = true
cfg.Encoding = "json"
cfg.EncoderConfig = ec
zapLogger, err := cfg.Build()
if err != nil {
panic(err)
}
return &ZapLogger{
zapL: zapLogger,
cfg: cfg,
}
}
func (zl *ZapLogger) GetLevel() string { return zl.cfg.Level.Level().String() }
func (zl *ZapLogger) SetWriter(w *os.File) {}
func (zl *ZapLogger) GetWriter() *os.File { return nil }
func (zl *ZapLogger) LoggerName() string { return LoggerName }
func (zl *ZapLogger) SetLevel(level string) error {
l := zapcore.Level(1)
err := l.Set(level)
if err == nil {
zl.cfg.Level.SetLevel(l)
}
return err
}
func (zl *ZapLogger) Fatal(msg string, details ...helpers.IDetails) {
zl.zapL.Fatal(msg, detailsToZapFields(details)...)
}
func (zl *ZapLogger) Error(msg string, details ...helpers.IDetails) {
zl.zapL.Error(msg, detailsToZapFields(details)...)
}
func (zl *ZapLogger) Warning(msg string, details ...helpers.IDetails) {
zl.zapL.Warn(msg, detailsToZapFields(details)...)
}
func (zl *ZapLogger) Success(msg string, details ...helpers.IDetails) {
zl.zapL.Info(msg, detailsToZapFields(details)...)
}
func (zl *ZapLogger) Info(msg string, details ...helpers.IDetails) {
zl.zapL.Info(msg, detailsToZapFields(details)...)
}
func (zl *ZapLogger) Debug(msg string, details ...helpers.IDetails) {
zl.zapL.Debug(msg, detailsToZapFields(details)...)
}
func detailsToZapFields(details []helpers.IDetails) []zapcore.Field {
zapFields := []zapcore.Field{}
for i := range details {
zapFields = append(zapFields, zap.Any(details[i].Key(), details[i].Value()))
}
return zapFields
}

View File

@@ -4,9 +4,8 @@ import (
"encoding/json"
"time"
reporthandlingv2 "github.com/armosec/opa-utils/reporthandling/v2"
"github.com/armosec/k8s-interface/workloadinterface"
"github.com/armosec/opa-utils/reporthandling"
"github.com/armosec/rbac-utils/rbacscanner"
"github.com/armosec/rbac-utils/rbacutils"
"github.com/google/uuid"
@@ -20,19 +19,12 @@ func NewRBACObjects(scanner *rbacscanner.RbacScannerFromK8sAPI) *RBACObjects {
return &RBACObjects{scanner: scanner}
}
func (rbacObjects *RBACObjects) SetResourcesReport() (*reporthandlingv2.PostureReport, error) {
return &reporthandlingv2.PostureReport{
func (rbacObjects *RBACObjects) SetResourcesReport() (*reporthandling.PostureReport, error) {
return &reporthandling.PostureReport{
ReportID: uuid.NewString(),
ReportGenerationTime: time.Now().UTC(),
CustomerGUID: rbacObjects.scanner.CustomerGUID,
ClusterName: rbacObjects.scanner.ClusterName,
Metadata: reporthandlingv2.Metadata{
ContextMetadata: reporthandlingv2.ContextMetadata{
ClusterContextMetadata: &reporthandlingv2.ClusterMetadata{
ContextName: rbacObjects.scanner.ClusterName,
},
},
},
}, nil
}
@@ -61,7 +53,7 @@ func (rbacObjects *RBACObjects) rbacObjectsToResources(resources *rbacutils.Rbac
************************************************************************************************************************
*/
// wrap rbac aggregated objects in IMetadata and add to AllResources
// wrap rbac aggregated objects in IMetadata and add to allresources
// TODO - DEPRECATE SA2WLIDmap
SA2WLIDmapIMeta, err := rbacutils.SA2WLIDmapIMetadataWrapper(resources.SA2WLIDmap)
if err != nil {

View File

@@ -7,8 +7,10 @@ import (
"github.com/armosec/opa-utils/reporthandling/results/v1/reportsummary"
)
func ReportV2ToV1(opaSessionObj *OPASessionObj) *reporthandling.PostureReport {
report := &reporthandling.PostureReport{}
func ReportV2ToV1(opaSessionObj *OPASessionObj) {
if len(opaSessionObj.PostureReport.FrameworkReports) > 0 {
return // report already converted
}
// opaSessionObj.PostureReport.ClusterCloudProvider = opaSessionObj.Report.ClusterCloudProvider
frameworks := []reporthandling.FrameworkReport{}
@@ -51,8 +53,7 @@ func ReportV2ToV1(opaSessionObj *OPASessionObj) *reporthandling.PostureReport {
// scoreutil := score.NewScore(opaSessionObj.AllResources)
// scoreutil.Calculate(frameworks)
report.FrameworkReports = frameworks
return report
opaSessionObj.PostureReport.FrameworkReports = frameworks
}
func controlReportV2ToV1(opaSessionObj *OPASessionObj, frameworkName string, controls map[string]reportsummary.ControlSummary) []reporthandling.ControlReport {

View File

@@ -14,31 +14,16 @@ import (
giturl "github.com/armosec/go-git-url"
"github.com/armosec/k8s-interface/k8sinterface"
"github.com/armosec/kubescape/v2/core/cautils/getter"
"github.com/armosec/kubescape/v2/core/cautils/logger"
"github.com/armosec/kubescape/v2/core/cautils/logger/helpers"
"github.com/armosec/opa-utils/reporthandling"
reporthandlingv2 "github.com/armosec/opa-utils/reporthandling/v2"
logger "github.com/dwertent/go-logger"
"github.com/dwertent/go-logger/helpers"
"github.com/google/uuid"
)
type ScanningContext string
const (
ContextCluster ScanningContext = "cluster"
ContextFile ScanningContext = "single-file"
ContextDir ScanningContext = "local-dir"
ContextGitURL ScanningContext = "git-url"
ContextGitLocal ScanningContext = "git-local"
)
const ( // deprecated
ScopeCluster = "cluster"
ScopeYAML = "yaml"
)
const (
// ScanCluster string = "cluster"
// ScanLocalFiles string = "yaml"
ScanCluster string = "cluster"
ScanLocalFiles string = "yaml"
localControlInputsFilename string = "controls-inputs.json"
localExceptionsFilename string = "exceptions.json"
)
@@ -205,12 +190,12 @@ func (scanInfo *ScanInfo) setOutputFile() {
}
}
// func (scanInfo *ScanInfo) GetScanningEnvironment() string {
// if len(scanInfo.InputPatterns) != 0 {
// return ScanLocalFiles
// }
// return ScanCluster
// }
func (scanInfo *ScanInfo) GetScanningEnvironment() string {
if len(scanInfo.InputPatterns) != 0 {
return ScanLocalFiles
}
return ScanCluster
}
func (scanInfo *ScanInfo) SetPolicyIdentifiers(policies []string, kind apisv1.NotificationPolicyKind) {
for _, policy := range policies {
@@ -263,189 +248,71 @@ func scanInfoToScanMetadata(scanInfo *ScanInfo) *reporthandlingv2.Metadata {
metadata.ScanMetadata.VerboseMode = scanInfo.VerboseMode
metadata.ScanMetadata.ControlsInputs = scanInfo.ControlsInputs
metadata.ScanMetadata.ScanningTarget = reporthandlingv2.Cluster
if scanInfo.GetScanningEnvironment() == ScanLocalFiles {
metadata.ScanMetadata.ScanningTarget = reporthandlingv2.File
}
inputFiles := ""
if len(scanInfo.InputPatterns) > 0 {
inputFiles = scanInfo.InputPatterns[0]
}
switch GetScanningContext(inputFiles) {
case ContextCluster:
// cluster
metadata.ScanMetadata.ScanningTarget = reporthandlingv2.Cluster
case ContextFile:
// local file
metadata.ScanMetadata.ScanningTarget = reporthandlingv2.File
case ContextGitURL:
// url
metadata.ScanMetadata.ScanningTarget = reporthandlingv2.Repo
case ContextGitLocal:
// local-git
metadata.ScanMetadata.ScanningTarget = reporthandlingv2.GitLocal
case ContextDir:
// directory
metadata.ScanMetadata.ScanningTarget = reporthandlingv2.Directory
}
setContextMetadata(&metadata.ContextMetadata, inputFiles)
return metadata
}
func (scanInfo *ScanInfo) GetScanningContext() ScanningContext {
input := ""
if len(scanInfo.InputPatterns) > 0 {
input = scanInfo.InputPatterns[0]
}
return GetScanningContext(input)
}
// GetScanningContext get scanning context from the input param
func GetScanningContext(input string) ScanningContext {
func setContextMetadata(contextMetadata *reporthandlingv2.ContextMetadata, input string) {
// cluster
if input == "" {
return ContextCluster
contextMetadata.ClusterContextMetadata = &reporthandlingv2.ClusterMetadata{
ContextName: k8sinterface.GetContextName(),
}
return
}
// url
if _, err := giturl.NewGitURL(input); err == nil {
return ContextGitURL
if gitParser, err := giturl.NewGitURL(input); err == nil {
if gitParser.GetBranch() == "" {
gitParser.SetDefaultBranch()
}
contextMetadata.RepoContextMetadata = &reporthandlingv2.RepoContextMetadata{
Repo: gitParser.GetRepo(),
Owner: gitParser.GetOwner(),
Branch: gitParser.GetBranch(),
}
return
}
if !filepath.IsAbs(input) { // parse path
if !filepath.IsAbs(input) {
if o, err := os.Getwd(); err == nil {
input = filepath.Join(o, input)
}
}
// local git repo
if _, err := NewLocalGitRepository(input); err == nil {
return ContextGitLocal
}
// single file
if IsFile(input) {
return ContextFile
contextMetadata.FileContextMetadata = &reporthandlingv2.FileContextMetadata{
FilePath: input,
HostName: getHostname(),
}
return
}
// dir/glob
return ContextDir
}
func setContextMetadata(contextMetadata *reporthandlingv2.ContextMetadata, input string) {
switch GetScanningContext(input) {
case ContextCluster:
contextMetadata.ClusterContextMetadata = &reporthandlingv2.ClusterMetadata{
ContextName: k8sinterface.GetContextName(),
}
case ContextGitURL:
// url
context, err := metadataGitURL(input)
if err != nil {
logger.L().Warning("in setContextMetadata", helpers.Interface("case", ContextGitURL), helpers.Error(err))
}
contextMetadata.RepoContextMetadata = context
case ContextDir:
if !IsFile(input) {
contextMetadata.DirectoryContextMetadata = &reporthandlingv2.DirectoryContextMetadata{
BasePath: getAbsPath(input),
BasePath: input,
HostName: getHostname(),
}
case ContextFile:
contextMetadata.FileContextMetadata = &reporthandlingv2.FileContextMetadata{
FilePath: getAbsPath(input),
HostName: getHostname(),
}
case ContextGitLocal:
// local
context, err := metadataGitLocal(input)
if err != nil {
logger.L().Warning("in setContextMetadata", helpers.Interface("case", ContextGitURL), helpers.Error(err))
}
contextMetadata.RepoContextMetadata = context
return
}
}
func metadataGitURL(input string) (*reporthandlingv2.RepoContextMetadata, error) {
context := &reporthandlingv2.RepoContextMetadata{}
gitParser, err := giturl.NewGitAPI(input)
if err != nil {
return context, fmt.Errorf("%w", err)
}
if gitParser.GetBranchName() == "" {
gitParser.SetDefaultBranchName()
}
context.Provider = gitParser.GetProvider()
context.Repo = gitParser.GetRepoName()
context.Owner = gitParser.GetOwnerName()
context.Branch = gitParser.GetBranchName()
context.RemoteURL = gitParser.GetURL().String()
commit, err := gitParser.GetLatestCommit()
if err != nil {
return context, fmt.Errorf("%w", err)
}
context.LastCommit = reporthandling.LastCommit{
Hash: commit.SHA,
Date: commit.Committer.Date,
CommitterName: commit.Committer.Name,
}
return context, nil
}
func metadataGitLocal(input string) (*reporthandlingv2.RepoContextMetadata, error) {
gitParser, err := NewLocalGitRepository(input)
if err != nil {
return nil, fmt.Errorf("%w", err)
}
remoteURL, err := gitParser.GetRemoteUrl()
if err != nil {
return nil, fmt.Errorf("%w", err)
}
context := &reporthandlingv2.RepoContextMetadata{}
gitParserURL, err := giturl.NewGitURL(remoteURL)
if err != nil {
return context, fmt.Errorf("%w", err)
}
gitParserURL.SetBranchName(gitParser.GetBranchName())
context.Provider = gitParserURL.GetProvider()
context.Repo = gitParserURL.GetRepoName()
context.Owner = gitParserURL.GetOwnerName()
context.Branch = gitParserURL.GetBranchName()
context.RemoteURL = gitParserURL.GetURL().String()
commit, err := gitParser.GetLastCommit()
if err != nil {
return context, fmt.Errorf("%w", err)
}
context.LastCommit = reporthandling.LastCommit{
Hash: commit.SHA,
Date: commit.Committer.Date,
CommitterName: commit.Committer.Name,
}
return context, nil
}
func getHostname() string {
if h, e := os.Hostname(); e == nil {
return h
}
return ""
}
func getAbsPath(p string) string {
if !filepath.IsAbs(p) { // parse path
if o, err := os.Getwd(); err == nil {
return filepath.Join(o, p)
}
}
return p
}
// ScanningContextToScanningScope convert the context to the deprecated scope
func ScanningContextToScanningScope(scanningContext ScanningContext) string {
if scanningContext == ContextCluster {
return ScopeCluster
}
return ScopeYAML
}

View File

@@ -18,6 +18,34 @@ func TestSetContextMetadata(t *testing.T) {
assert.Nil(t, ctx.HelmContextMetadata)
assert.Nil(t, ctx.RepoContextMetadata)
}
{
ctx := reporthandlingv2.ContextMetadata{}
setContextMetadata(&ctx, "file")
assert.Nil(t, ctx.ClusterContextMetadata)
assert.NotNil(t, ctx.DirectoryContextMetadata)
assert.Nil(t, ctx.FileContextMetadata)
assert.Nil(t, ctx.HelmContextMetadata)
assert.Nil(t, ctx.RepoContextMetadata)
hostName := getHostname()
assert.Contains(t, ctx.DirectoryContextMetadata.BasePath, "file")
assert.Equal(t, hostName, ctx.DirectoryContextMetadata.HostName)
}
{
ctx := reporthandlingv2.ContextMetadata{}
setContextMetadata(&ctx, "scaninfo_test.go")
assert.Nil(t, ctx.ClusterContextMetadata)
assert.Nil(t, ctx.DirectoryContextMetadata)
assert.NotNil(t, ctx.FileContextMetadata)
assert.Nil(t, ctx.HelmContextMetadata)
assert.Nil(t, ctx.RepoContextMetadata)
hostName := getHostname()
assert.Contains(t, ctx.FileContextMetadata.FilePath, "scaninfo_test.go")
assert.Equal(t, hostName, ctx.FileContextMetadata.HostName)
}
{
ctx := reporthandlingv2.ContextMetadata{}
setContextMetadata(&ctx, "https://github.com/armosec/kubescape")
@@ -37,11 +65,3 @@ func TestSetContextMetadata(t *testing.T) {
func TestGetHostname(t *testing.T) {
assert.NotEqual(t, "", getHostname())
}
func TestGetScanningContext(t *testing.T) {
assert.Equal(t, ContextCluster, GetScanningContext(""))
// assert.Equal(t, ContextDir, GetScanningContext("/"))
assert.Equal(t, ContextGitURL, GetScanningContext("https://github.com/armosec/kubescape"))
// assert.Equal(t, ContextFile, GetScanningContext(path.Join(".", "testdata", "localrepo.git")))
// assert.Equal(t, ContextGitLocal, GetScanningContext(path.Join(".", "testdata")))
}

View File

@@ -1,40 +0,0 @@
{
"affinity": {},
"configMap": {
"create": false,
"params": {
"clusterName": "<MyK8sClusterName>",
"customerGUID": "<MyGUID>"
}
},
"fullnameOverride": "",
"image": {
"imageName": "kubescape",
"pullPolicy": "Always",
"repository": "quay.io/armosec",
"tag": "latest"
},
"imagePullSecrets": [],
"nameOverride": "",
"nodeSelector": {},
"podAnnotations": {},
"podSecurityContext": {},
"resources": {
"limits": {
"cpu": "500m",
"memory": "512Mi"
},
"requests": {
"cpu": "200m",
"memory": "256Mi"
}
},
"schedule": "* * 1 * *",
"securityContext": {},
"serviceAccount": {
"annotations": {},
"create": true,
"name": "kubescape-discovery"
},
"tolerations": []
}

Binary file not shown.

Binary file not shown.

View File

@@ -7,10 +7,9 @@ import (
"os"
"github.com/armosec/kubescape/v2/core/cautils/getter"
"github.com/armosec/kubescape/v2/core/cautils/logger"
"github.com/armosec/kubescape/v2/core/cautils/logger/helpers"
"github.com/armosec/utils-go/boolutils"
logger "github.com/dwertent/go-logger"
"github.com/dwertent/go-logger/helpers"
"golang.org/x/mod/semver"
)
@@ -18,7 +17,6 @@ const SKIP_VERSION_CHECK_DEPRECATED = "KUBESCAPE_SKIP_UPDATE_CHECK"
const SKIP_VERSION_CHECK = "KS_SKIP_UPDATE_CHECK"
var BuildNumber string
var Client string
const UnknownBuildNumber = "unknown"
@@ -50,12 +48,10 @@ type VersionCheckHandler struct {
}
type VersionCheckRequest struct {
Client string `json:"client"` // kubescape
ClientBuild string `json:"clientBuild"` // client build environment
ClientVersion string `json:"clientVersion"` // kubescape version
Framework string `json:"framework"` // framework name
FrameworkVersion string `json:"frameworkVersion"` // framework version
ScanningTarget string `json:"target"` // Deprecated
ScanningContext string `json:"context"` // scanning context- cluster/file/gitURL/localGit/dir
ScanningTarget string `json:"target"` // scanning target- cluster/yaml
}
type VersionCheckResponse struct {
@@ -78,12 +74,8 @@ func NewVersionCheckRequest(buildNumber, frameworkName, frameworkVersion, scanni
if scanningTarget == "" {
scanningTarget = "unknown"
}
if Client == "" {
Client = "local-build"
}
return &VersionCheckRequest{
Client: "kubescape",
ClientBuild: Client,
ClientVersion: buildNumber,
Framework: frameworkName,
FrameworkVersion: frameworkVersion,

View File

@@ -14,10 +14,7 @@ var (
"KernelVersion",
"LinuxSecurityHardeningStatus",
"OpenPortsList",
"LinuxKernelVariables",
"KubeletInfo",
"KubeProxyInfo",
}
"LinuxKernelVariables"}
CloudResources = []string{"ClusterDescribe"}
)

View File

@@ -4,9 +4,9 @@ import (
"fmt"
"github.com/armosec/kubescape/v2/core/cautils/getter"
"github.com/armosec/kubescape/v2/core/cautils/logger"
"github.com/armosec/kubescape/v2/core/cautils/logger/helpers"
v1 "github.com/armosec/kubescape/v2/core/meta/datastructures/v1"
logger "github.com/dwertent/go-logger"
"github.com/dwertent/go-logger/helpers"
)
func (ks *Kubescape) DeleteExceptions(delExceptions *v1.DeleteExceptions) error {

View File

@@ -8,9 +8,9 @@ import (
"github.com/armosec/armoapi-go/armotypes"
"github.com/armosec/kubescape/v2/core/cautils/getter"
"github.com/armosec/kubescape/v2/core/cautils/logger"
"github.com/armosec/kubescape/v2/core/cautils/logger/helpers"
metav1 "github.com/armosec/kubescape/v2/core/meta/datastructures/v1"
logger "github.com/dwertent/go-logger"
"github.com/dwertent/go-logger/helpers"
)
var downloadFunc = map[string]func(*metav1.DownloadInfo) error{

View File

@@ -6,14 +6,12 @@ import (
"github.com/armosec/k8s-interface/k8sinterface"
"github.com/armosec/kubescape/v2/core/cautils"
"github.com/armosec/kubescape/v2/core/cautils/getter"
"github.com/armosec/kubescape/v2/core/cautils/logger"
"github.com/armosec/kubescape/v2/core/cautils/logger/helpers"
"github.com/armosec/kubescape/v2/core/pkg/hostsensorutils"
"github.com/armosec/kubescape/v2/core/pkg/resourcehandler"
"github.com/armosec/kubescape/v2/core/pkg/resultshandling/reporter"
reporterv2 "github.com/armosec/kubescape/v2/core/pkg/resultshandling/reporter/v2"
logger "github.com/dwertent/go-logger"
"github.com/dwertent/go-logger/helpers"
"github.com/google/uuid"
"github.com/armosec/rbac-utils/rbacscanner"
)
@@ -48,13 +46,9 @@ func getRBACHandler(tenantConfig cautils.ITenantConfig, k8s *k8sinterface.Kubern
return nil
}
func getReporter(tenantConfig cautils.ITenantConfig, reportID string, submit, fwScan bool, scanningContext cautils.ScanningContext) reporter.IReport {
func getReporter(tenantConfig cautils.ITenantConfig, reportID string, submit, fwScan bool) reporter.IReport {
if submit {
submitData := reporterv2.SubmitContextScan
if scanningContext != cautils.ContextCluster {
submitData = reporterv2.SubmitContextRepository
}
return reporterv2.NewReportEventReceiver(tenantConfig.GetConfigObj(), reportID, submitData)
return reporterv2.NewReportEventReceiver(tenantConfig.GetConfigObj(), reportID)
}
if tenantConfig.GetAccountID() == "" {
// Add link only when scanning a cluster using a framework
@@ -128,6 +122,7 @@ func policyIdentifierNames(pi []cautils.PolicyIdentifier) string {
func setSubmitBehavior(scanInfo *cautils.ScanInfo, tenantConfig cautils.ITenantConfig) {
/*
If "First run (local config not found)" -
Default/keep-local - Do not send report
Submit - Create tenant & Submit report
@@ -144,20 +139,8 @@ func setSubmitBehavior(scanInfo *cautils.ScanInfo, tenantConfig cautils.ITenantC
return
}
scanningContext := scanInfo.GetScanningContext()
if scanningContext == cautils.ContextFile || scanningContext == cautils.ContextDir {
scanInfo.Submit = false
return
}
if tenantConfig.IsConfigFound() { // config found in cache (submitted)
if !scanInfo.Local {
if tenantConfig.GetAccountID() != "" {
if _, err := uuid.Parse(tenantConfig.GetAccountID()); err != nil {
scanInfo.Submit = false
return
}
}
// Submit report
scanInfo.Submit = true
}
@@ -181,6 +164,20 @@ func getPolicyGetter(loadPoliciesFromFile []string, tennatEmail string, framewor
}
// func setGetArmoAPIConnector(scanInfo *cautils.ScanInfo, customerGUID string) {
// g := getter.GetArmoAPIConnector() // download policy from ARMO backend
// g.SetCustomerGUID(customerGUID)
// scanInfo.PolicyGetter = g
// if scanInfo.ScanAll {
// frameworks, err := g.ListCustomFrameworks(customerGUID)
// if err != nil {
// glog.Error("failed to get custom frameworks") // handle error
// return
// }
// scanInfo.SetPolicyIdentifiers(frameworks, reporthandling.KindFramework)
// }
// }
// setConfigInputsGetter sets the config input getter - local file/github release/ArmoAPI
func getConfigInputsGetter(ControlsInputs string, accountID string, downloadReleasedPolicy *getter.DownloadReleasedPolicy) getter.IControlsInputsGetter {
if len(ControlsInputs) > 0 {

View File

@@ -9,6 +9,8 @@ import (
"github.com/armosec/kubescape/v2/core/cautils"
"github.com/armosec/kubescape/v2/core/cautils/getter"
"github.com/armosec/kubescape/v2/core/cautils/logger"
"github.com/armosec/kubescape/v2/core/cautils/logger/helpers"
"github.com/armosec/kubescape/v2/core/pkg/hostsensorutils"
"github.com/armosec/kubescape/v2/core/pkg/opaprocessor"
"github.com/armosec/kubescape/v2/core/pkg/policyhandler"
@@ -16,8 +18,6 @@ import (
"github.com/armosec/kubescape/v2/core/pkg/resultshandling"
"github.com/armosec/kubescape/v2/core/pkg/resultshandling/printer"
"github.com/armosec/kubescape/v2/core/pkg/resultshandling/reporter"
logger "github.com/dwertent/go-logger"
"github.com/dwertent/go-logger/helpers"
"github.com/armosec/opa-utils/resources"
)
@@ -34,7 +34,7 @@ func getInterfaces(scanInfo *cautils.ScanInfo) componentInterfaces {
// ================== setup k8s interface object ======================================
var k8s *k8sinterface.KubernetesApi
if scanInfo.GetScanningContext() == cautils.ContextCluster {
if scanInfo.GetScanningEnvironment() == cautils.ScanCluster {
k8s = getKubernetesApi()
if k8s == nil {
logger.L().Fatal("failed connecting to Kubernetes cluster")
@@ -48,6 +48,11 @@ func getInterfaces(scanInfo *cautils.ScanInfo) componentInterfaces {
// Set submit behavior AFTER loading tenant config
setSubmitBehavior(scanInfo, tenantConfig)
// Do not submit yaml scanning
if len(scanInfo.InputPatterns) > 0 {
scanInfo.Submit = false
}
if scanInfo.Submit {
// submit - Create tenant & Submit report
if err := tenantConfig.SetTenant(); err != nil {
@@ -58,7 +63,7 @@ func getInterfaces(scanInfo *cautils.ScanInfo) componentInterfaces {
// ================== version testing ======================================
v := cautils.NewIVersionCheckHandler()
v.CheckLatestVersion(cautils.NewVersionCheckRequest(cautils.BuildNumber, policyIdentifierNames(scanInfo.PolicyIdentifier), "", cautils.ScanningContextToScanningScope(scanInfo.GetScanningContext())))
v.CheckLatestVersion(cautils.NewVersionCheckRequest(cautils.BuildNumber, policyIdentifierNames(scanInfo.PolicyIdentifier), "", scanInfo.GetScanningEnvironment()))
// ================== setup host scanner object ======================================
@@ -86,7 +91,7 @@ func getInterfaces(scanInfo *cautils.ScanInfo) componentInterfaces {
// ================== setup reporter & printer objects ======================================
// reporting behavior - setup reporter
reportHandler := getReporter(tenantConfig, scanInfo.ScanID, scanInfo.Submit, scanInfo.FrameworkScan, scanInfo.GetScanningContext())
reportHandler := getReporter(tenantConfig, scanInfo.ScanID, scanInfo.Submit, scanInfo.FrameworkScan)
// setup printer
printerHandler := resultshandling.NewPrinter(scanInfo.Format, scanInfo.FormatVersion, scanInfo.VerboseMode, cautils.ViewTypes(scanInfo.View))
@@ -149,7 +154,7 @@ func (ks *Kubescape) Scan(scanInfo *cautils.ScanInfo) (*resultshandling.ResultsH
reportResults := opaprocessor.NewOPAProcessor(scanData, deps)
if err := reportResults.ProcessRulesListenner(); err != nil {
// TODO - do something
return resultsHandling, fmt.Errorf("%w", err)
return resultsHandling, err
}
// ========================= results handling =====================
@@ -161,3 +166,25 @@ func (ks *Kubescape) Scan(scanInfo *cautils.ScanInfo) (*resultshandling.ResultsH
return resultsHandling, nil
}
// func askUserForHostSensor() bool {
// return false
// if !isatty.IsTerminal(os.Stdin.Fd()) {
// return false
// }
// if ssss, err := os.Stdin.Stat(); err == nil {
// // fmt.Printf("Found stdin type: %s\n", ssss.Mode().Type())
// if ssss.Mode().Type()&(fs.ModeDevice|fs.ModeCharDevice) > 0 { //has TTY
// fmt.Fprintf(os.Stderr, "Would you like to scan K8s nodes? [y/N]. This is required to collect valuable data for certain controls\n")
// fmt.Fprintf(os.Stderr, "Use --enable-host-scan flag to suppress this message\n")
// var b []byte = make([]byte, 1)
// if n, err := os.Stdin.Read(b); err == nil {
// if n > 0 && len(b) > 0 && (b[0] == 'y' || b[0] == 'Y') {
// return true
// }
// }
// }
// }
// return false
// }

View File

@@ -3,16 +3,15 @@ package core
import (
"github.com/armosec/kubescape/v2/core/cautils"
"github.com/armosec/kubescape/v2/core/cautils/getter"
"github.com/armosec/kubescape/v2/core/cautils/logger"
"github.com/armosec/kubescape/v2/core/cautils/logger/helpers"
"github.com/armosec/kubescape/v2/core/meta/cliinterfaces"
logger "github.com/dwertent/go-logger"
"github.com/dwertent/go-logger/helpers"
)
func (ks *Kubescape) Submit(submitInterfaces cliinterfaces.SubmitInterfaces) error {
// list resources
report, err := submitInterfaces.SubmitObjects.SetResourcesReport()
postureReport, err := submitInterfaces.SubmitObjects.SetResourcesReport()
if err != nil {
return err
}
@@ -21,12 +20,7 @@ func (ks *Kubescape) Submit(submitInterfaces cliinterfaces.SubmitInterfaces) err
return err
}
// report
o := &cautils.OPASessionObj{
Report: report,
AllResources: allresources,
Metadata: &report.Metadata,
}
if err := submitInterfaces.Reporter.Submit(o); err != nil {
if err := submitInterfaces.Reporter.Submit(&cautils.OPASessionObj{PostureReport: postureReport, AllResources: allresources}); err != nil {
return err
}
logger.L().Success("Data has been submitted successfully")

View File

@@ -4,11 +4,11 @@ import (
"github.com/armosec/k8s-interface/workloadinterface"
"github.com/armosec/kubescape/v2/core/cautils"
"github.com/armosec/kubescape/v2/core/pkg/resultshandling/reporter"
reporthandlingv2 "github.com/armosec/opa-utils/reporthandling/v2"
"github.com/armosec/opa-utils/reporthandling"
)
type ISubmitObjects interface {
SetResourcesReport() (*reporthandlingv2.PostureReport, error)
SetResourcesReport() (*reporthandling.PostureReport, error)
ListAllResources() (map[string]workloadinterface.IMetadata, error)
}

View File

@@ -11,9 +11,8 @@ import (
"github.com/armosec/k8s-interface/k8sinterface"
"github.com/armosec/k8s-interface/workloadinterface"
"github.com/armosec/kubescape/v2/core/cautils"
logger "github.com/dwertent/go-logger"
"github.com/dwertent/go-logger/helpers"
"github.com/armosec/kubescape/v2/core/cautils/logger"
"github.com/armosec/kubescape/v2/core/cautils/logger/helpers"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -36,7 +35,6 @@ type HostSensorHandler struct {
DaemonSet *appsv1.DaemonSet
podListLock sync.RWMutex
gracePeriod int64
workerPool workerPool
}
func NewHostSensorHandler(k8sObj *k8sinterface.KubernetesApi, hostSensorYAMLFile string) (*HostSensorHandler, error) {
@@ -56,7 +54,6 @@ func NewHostSensorHandler(k8sObj *k8sinterface.KubernetesApi, hostSensorYAMLFile
HostSensorPodNames: map[string]string{},
HostSensorUnscheduledPodNames: map[string]string{},
gracePeriod: int64(15),
workerPool: NewWorkerPool(),
}
// Don't deploy on cluster with no nodes. Some cloud providers prevents termination of K8s objects for cluster with no nodes!!!
if nodeList, err := k8sObj.KubernetesClient.CoreV1().Nodes().List(k8sObj.Context, metav1.ListOptions{}); err != nil || len(nodeList.Items) == 0 {
@@ -92,7 +89,7 @@ func (hsh *HostSensorHandler) Init() error {
func (hsh *HostSensorHandler) applyYAML() error {
workloads, err := cautils.ReadFile([]byte(hostSensorYAML), cautils.YAML_FILE_FORMAT)
if err != nil {
if len(err) != 0 {
return fmt.Errorf("failed to read YAML files, reason: %v", err)
}

View File

@@ -6,11 +6,10 @@ import (
"sync"
"github.com/armosec/k8s-interface/k8sinterface"
"github.com/armosec/kubescape/v2/core/cautils/logger"
"github.com/armosec/kubescape/v2/core/cautils/logger/helpers"
"github.com/armosec/opa-utils/objectsenvelopes/hostsensor"
"github.com/armosec/opa-utils/reporthandling/apis"
logger "github.com/dwertent/go-logger"
"github.com/dwertent/go-logger/helpers"
"sigs.k8s.io/yaml"
)
@@ -33,23 +32,7 @@ func (hsh *HostSensorHandler) HTTPGetToPod(podName, path string) ([]byte, error)
restProxy := hsh.k8sObj.KubernetesClient.CoreV1().Pods(hsh.DaemonSet.Namespace).ProxyGet("http", podName, fmt.Sprintf("%d", hsh.HostSensorPort), path, map[string]string{})
return restProxy.DoRaw(hsh.k8sObj.Context)
}
func (hsh *HostSensorHandler) getResourcesFromPod(podName, nodeName, resourceKind, path string) (hostsensor.HostSensorDataEnvelope, error) {
// send the request and pack the response as an hostSensorDataEnvelope
resBytes, err := hsh.HTTPGetToPod(podName, path)
if err != nil {
return hostsensor.HostSensorDataEnvelope{}, err
}
hostSensorDataEnvelope := hostsensor.HostSensorDataEnvelope{}
hostSensorDataEnvelope.SetApiVersion(k8sinterface.JoinGroupVersion(hostsensor.GroupHostSensor, hostsensor.Version))
hostSensorDataEnvelope.SetKind(resourceKind)
hostSensorDataEnvelope.SetName(nodeName)
hostSensorDataEnvelope.SetData(resBytes)
return hostSensorDataEnvelope, nil
}
func (hsh *HostSensorHandler) ForwardToPod(podName, path string) ([]byte, error) {
@@ -76,26 +59,35 @@ func (hsh *HostSensorHandler) ForwardToPod(podName, path string) ([]byte, error)
// sendAllPodsHTTPGETRequest fills the raw byte response in the envelope and the node name, but not the GroupVersionKind
// so the caller is responsible to convert the raw data to some structured data and add the GroupVersionKind details
//
// The function produces a worker-pool with a fixed number of workers.
// For each node the request is pushed to the jobs channel, the worker sends the request and pushes the result to the result channel.
// When all workers have finished, the function returns a list of results
func (hsh *HostSensorHandler) sendAllPodsHTTPGETRequest(path, requestKind string) ([]hostsensor.HostSensorDataEnvelope, error) {
podList, err := hsh.getPodList()
if err != nil {
return nil, fmt.Errorf("failed to sendAllPodsHTTPGETRequest: %v", err)
}
res := make([]hostsensor.HostSensorDataEnvelope, 0, len(podList))
var wg sync.WaitGroup
// initialization of the channels
hsh.workerPool.init(len(podList))
hsh.workerPool.hostSensorApplyJobs(podList, path, requestKind)
hsh.workerPool.hostSensorGetResults(&res)
hsh.workerPool.createWorkerPool(hsh, &wg)
hsh.workerPool.waitForDone(&wg)
resLock := sync.Mutex{}
wg := sync.WaitGroup{}
wg.Add(len(podList))
for podName := range podList {
go func(podName, path string) {
defer wg.Done()
resBytes, err := hsh.HTTPGetToPod(podName, path)
if err != nil {
logger.L().Error("failed to get data", helpers.String("path", path), helpers.String("podName", podName), helpers.Error(err))
} else {
resLock.Lock()
defer resLock.Unlock()
hostSensorDataEnvelope := hostsensor.HostSensorDataEnvelope{}
hostSensorDataEnvelope.SetApiVersion(k8sinterface.JoinGroupVersion(hostsensor.GroupHostSensor, hostsensor.Version))
hostSensorDataEnvelope.SetKind(requestKind)
hostSensorDataEnvelope.SetName(podList[podName])
hostSensorDataEnvelope.SetData(resBytes)
res = append(res, hostSensorDataEnvelope)
}
}(podName, path)
}
wg.Wait()
return res, nil
}
@@ -117,18 +109,6 @@ func (hsh *HostSensorHandler) GetLinuxSecurityHardeningStatus() ([]hostsensor.Ho
return hsh.sendAllPodsHTTPGETRequest("/linuxSecurityHardening", "LinuxSecurityHardeningStatus")
}
// return list of KubeletInfo
func (hsh *HostSensorHandler) GetKubeletInfo() ([]hostsensor.HostSensorDataEnvelope, error) {
// loop over pods and port-forward it to each of them
return hsh.sendAllPodsHTTPGETRequest("/kubeletInfo", "KubeletInfo")
}
// return list of KubeProxyInfo
func (hsh *HostSensorHandler) GetKubeProxyInfo() ([]hostsensor.HostSensorDataEnvelope, error) {
// loop over pods and port-forward it to each of them
return hsh.sendAllPodsHTTPGETRequest("/kubeProxyInfo", "KubeProxyInfo")
}
// return list of KubeletCommandLine
func (hsh *HostSensorHandler) GetKubeletCommandLine() ([]hostsensor.HostSensorDataEnvelope, error) {
// loop over pods and port-forward it to each of them
@@ -248,27 +228,6 @@ func (hsh *HostSensorHandler) CollectResources() ([]hostsensor.HostSensorDataEnv
if len(kcData) > 0 {
res = append(res, kcData...)
}
// GetKubeletInfo
kcData, err = hsh.GetKubeletInfo()
if err != nil {
addInfoToMap(KubeletInfo, infoMap, err)
logger.L().Warning(err.Error())
}
if len(kcData) > 0 {
res = append(res, kcData...)
}
// GetKubeProxyInfo
kcData, err = hsh.GetKubeProxyInfo()
if err != nil {
addInfoToMap(KubeProxyInfo, infoMap, err)
logger.L().Warning(err.Error())
}
if len(kcData) > 0 {
res = append(res, kcData...)
}
logger.L().Debug("Done reading information from host scanner")
return res, infoMap, nil
}

View File

@@ -1,96 +0,0 @@
package hostsensorutils
import (
"sync"
"github.com/armosec/opa-utils/objectsenvelopes/hostsensor"
logger "github.com/dwertent/go-logger"
"github.com/dwertent/go-logger/helpers"
)
const noOfWorkers int = 10
type job struct {
podName string
nodeName string
requestKind string
path string
}
type workerPool struct {
jobs chan job
results chan hostsensor.HostSensorDataEnvelope
done chan bool
noOfWorkers int
}
func NewWorkerPool() workerPool {
wp := workerPool{}
wp.noOfWorkers = noOfWorkers
wp.init()
return wp
}
func (wp *workerPool) init(noOfPods ...int) {
if noOfPods != nil && len(noOfPods) > 0 && noOfPods[0] < noOfWorkers {
wp.noOfWorkers = noOfPods[0]
}
// init the channels
wp.jobs = make(chan job, noOfWorkers)
wp.results = make(chan hostsensor.HostSensorDataEnvelope, noOfWorkers)
wp.done = make(chan bool)
}
// The worker takes a job out of the chan, executes the request, and pushes the result to the results chan
func (wp *workerPool) hostSensorWorker(hsh *HostSensorHandler, wg *sync.WaitGroup) {
defer wg.Done()
for job := range wp.jobs {
hostSensorDataEnvelope, err := hsh.getResourcesFromPod(job.podName, job.nodeName, job.requestKind, job.path)
if err != nil {
logger.L().Error("failed to get data", helpers.String("path", job.path), helpers.String("podName", job.podName), helpers.Error(err))
} else {
wp.results <- hostSensorDataEnvelope
}
}
}
func (wp *workerPool) createWorkerPool(hsh *HostSensorHandler, wg *sync.WaitGroup) {
for i := 0; i < noOfWorkers; i++ {
wg.Add(1)
go wp.hostSensorWorker(hsh, wg)
}
}
func (wp *workerPool) waitForDone(wg *sync.WaitGroup) {
// Waiting for workers to finish
wg.Wait()
close(wp.results)
// Waiting for the results to be processed
<-wp.done
}
func (wp *workerPool) hostSensorGetResults(result *[]hostsensor.HostSensorDataEnvelope) {
go func() {
for res := range wp.results {
*result = append(*result, res)
}
wp.done <- true
}()
}
func (wp *workerPool) hostSensorApplyJobs(podList map[string]string, path, requestKind string) {
go func() {
for podName, nodeName := range podList {
job := job{
podName: podName,
nodeName: nodeName,
requestKind: requestKind,
path: path,
}
wp.jobs <- job
}
close(wp.jobs)
}()
}

View File

@@ -13,8 +13,6 @@ var (
OpenPortsList = "OpenPortsList"
LinuxKernelVariables = "LinuxKernelVariables"
KubeletCommandLine = "KubeletCommandLine"
KubeletInfo = "KubeletInfo"
KubeProxyInfo = "KubeProxyInfo"
MapHostSensorResourceToApiGroup = map[string]string{
KubeletConfiguration: "hostdata.kubescape.cloud/v1beta0",
@@ -24,8 +22,6 @@ var (
LinuxSecurityHardeningStatus: "hostdata.kubescape.cloud/v1beta0",
OpenPortsList: "hostdata.kubescape.cloud/v1beta0",
LinuxKernelVariables: "hostdata.kubescape.cloud/v1beta0",
KubeletInfo: "hostdata.kubescape.cloud/v1beta0",
KubeProxyInfo: "hostdata.kubescape.cloud/v1beta0",
}
)

View File

@@ -7,19 +7,17 @@ import (
"github.com/armosec/armoapi-go/armotypes"
"github.com/armosec/kubescape/v2/core/cautils"
"github.com/armosec/kubescape/v2/core/cautils/logger"
"github.com/armosec/kubescape/v2/core/cautils/logger/helpers"
"github.com/armosec/kubescape/v2/core/pkg/score"
"github.com/armosec/opa-utils/objectsenvelopes"
"github.com/armosec/opa-utils/reporthandling"
"github.com/armosec/opa-utils/reporthandling/apis"
"github.com/armosec/opa-utils/reporthandling/results/v1/resourcesresults"
logger "github.com/dwertent/go-logger"
"github.com/dwertent/go-logger/helpers"
"github.com/open-policy-agent/opa/storage"
"github.com/armosec/k8s-interface/workloadinterface"
reporthandlingv2 "github.com/armosec/opa-utils/reporthandling/v2"
"github.com/armosec/opa-utils/resources"
"github.com/open-policy-agent/opa/ast"
"github.com/open-policy-agent/opa/rego"
@@ -64,7 +62,7 @@ func (opap *OPAProcessor) ProcessRulesListenner() error {
}
func (opap *OPAProcessor) Process(policies *cautils.Policies) error {
opap.loggerStartScanning()
logger.L().Info("Scanning", helpers.String("cluster", cautils.ClusterName))
cautils.StartSpinner()
@@ -91,30 +89,11 @@ func (opap *OPAProcessor) Process(policies *cautils.Policies) error {
opap.Report.ReportGenerationTime = time.Now().UTC()
cautils.StopSpinner()
opap.loggerDoneScanning()
logger.L().Success("Done scanning", helpers.String("cluster", cautils.ClusterName))
return errs
}
func (opap *OPAProcessor) loggerStartScanning() {
targetScan := opap.OPASessionObj.Metadata.ScanMetadata.ScanningTarget
if reporthandlingv2.Cluster == targetScan {
logger.L().Info("Scanning", helpers.String(targetScan.String(), cautils.ClusterName))
} else {
logger.L().Info("Scanning " + targetScan.String())
}
}
func (opap *OPAProcessor) loggerDoneScanning() {
targetScan := opap.OPASessionObj.Metadata.ScanMetadata.ScanningTarget
if reporthandlingv2.Cluster == targetScan {
logger.L().Success("Done scanning", helpers.String(targetScan.String(), cautils.ClusterName))
} else {
logger.L().Success("Done scanning " + targetScan.String())
}
}
func (opap *OPAProcessor) processControl(control *reporthandling.Control) (map[string]resourcesresults.ResourceAssociatedControl, error) {
var errs error

View File

@@ -6,10 +6,12 @@ import (
"github.com/armosec/armoapi-go/armotypes"
"github.com/armosec/kubescape/v2/core/cautils"
"github.com/armosec/kubescape/v2/core/mocks"
"github.com/armosec/opa-utils/objectsenvelopes"
"github.com/armosec/opa-utils/reporthandling"
"github.com/armosec/opa-utils/resources"
"github.com/stretchr/testify/assert"
"github.com/armosec/k8s-interface/k8sinterface"
"github.com/armosec/k8s-interface/workloadinterface"
// _ "k8s.io/client-go/plugin/pkg/client/auth"
)
@@ -17,6 +19,43 @@ import (
func NewOPAProcessorMock() *OPAProcessor {
return &OPAProcessor{}
}
func TestProcess(t *testing.T) {
// set k8s
k8sResources := make(cautils.K8SResources)
allResources := make(map[string]workloadinterface.IMetadata)
imetaObj := objectsenvelopes.ListMapToMeta(k8sinterface.ConvertUnstructuredSliceToMap(k8sinterface.V1KubeSystemNamespaceMock().Items))
for i := range imetaObj {
allResources[imetaObj[i].GetID()] = imetaObj[i]
}
k8sResources["/v1/pods"] = workloadinterface.ListMetaIDs(imetaObj)
// set opaSessionObj
opaSessionObj := cautils.NewOPASessionObjMock()
opaSessionObj.Policies = []reporthandling.Framework{*reporthandling.MockFrameworkA()}
policies := ConvertFrameworksToPolicies(opaSessionObj.Policies, "")
opaSessionObj.K8SResources = &k8sResources
opaSessionObj.AllResources = allResources
opap := NewOPAProcessor(opaSessionObj, resources.NewRegoDependenciesDataMock()) // ,
opap.Process(policies)
opap.updateResults()
for _, f := range opap.PostureReport.FrameworkReports {
for _, c := range f.ControlReports {
for _, r := range c.RuleReports {
for _, rr := range r.RuleResponses {
// t.Errorf("AlertMessage: %v", rr.AlertMessage)
if rr.Exception != nil {
t.Errorf("Exception: %v", rr.Exception)
}
}
}
}
}
}
func TestProcessResourcesResult(t *testing.T) {
// set k8s

View File

@@ -2,7 +2,7 @@ package opaprocessor
import (
"github.com/armosec/kubescape/v2/core/cautils"
logger "github.com/dwertent/go-logger"
"github.com/armosec/kubescape/v2/core/cautils/logger"
"github.com/armosec/k8s-interface/k8sinterface"
"github.com/armosec/k8s-interface/workloadinterface"

View File

@@ -8,9 +8,9 @@ import (
"github.com/armosec/kubescape/v2/core/cautils"
"github.com/armosec/kubescape/v2/core/cautils/getter"
"github.com/armosec/kubescape/v2/core/cautils/logger"
"github.com/armosec/kubescape/v2/core/cautils/logger/helpers"
"github.com/armosec/opa-utils/reporthandling"
logger "github.com/dwertent/go-logger"
"github.com/dwertent/go-logger/helpers"
)
func (policyHandler *PolicyHandler) getPolicies(policyIdentifier []cautils.PolicyIdentifier, policiesAndResources *cautils.OPASessionObj) error {
@@ -33,8 +33,6 @@ func (policyHandler *PolicyHandler) getPolicies(policyIdentifier []cautils.Polic
exceptionPolicies, err := policyHandler.getters.ExceptionsGetter.GetExceptions(cautils.ClusterName)
if err == nil {
policiesAndResources.Exceptions = exceptionPolicies
} else {
logger.L().Error("failed to load exceptions", helpers.Error(err))
}
// get account configuration

View File

@@ -7,7 +7,7 @@ For these controls to work properly, it is necessary to
# Integrate With Armosec Server
1. Navigate to the [armosec.io](https://cloud.armosec.io?utm_source=github&utm_medium=repository)
1. Navigate to the [armosec.io](https://portal.armo.cloud/)
2. Click Profile(top right icon)->"User Management"->"API Tokens" and Generate a token
3. Copy the clientID and secretKey and run:
```

View File

@@ -5,10 +5,10 @@ import (
"fmt"
"github.com/armosec/kubescape/v2/core/cautils/getter"
"github.com/armosec/kubescape/v2/core/cautils/logger"
"github.com/armosec/kubescape/v2/core/cautils/logger/helpers"
"github.com/armosec/kubescape/v2/core/pkg/containerscan"
"github.com/armosec/kubescape/v2/core/pkg/registryadaptors/registryvulnerabilities"
logger "github.com/dwertent/go-logger"
"github.com/dwertent/go-logger/helpers"
)
func NewArmoAdaptor(armoAPI *getter.ArmoAPI) *ArmoCivAdaptor {
@@ -83,7 +83,7 @@ func (armoCivAdaptor *ArmoCivAdaptor) GetImageVulnerability(imageID *registryvul
}
func (armoCivAdaptor *ArmoCivAdaptor) DescribeAdaptor() string {
return "armo image vulnerabilities scanner, docs: https://hub.armosec.io/docs/configuration-of-image-vulnerabilities"
return "armo image vulnerabilities scanner, docs: https://hub.armo.cloud/docs/cluster-vulnerability-scanning"
}
func (armoCivAdaptor *ArmoCivAdaptor) GetImagesInformation(imageIDs []registryvulnerabilities.ContainerImageIdentifier) ([]registryvulnerabilities.ContainerImageInformation, error) {

View File

@@ -2,18 +2,15 @@ package resourcehandler
import (
"fmt"
"os"
"path/filepath"
"github.com/armosec/armoapi-go/armotypes"
"github.com/armosec/k8s-interface/workloadinterface"
"github.com/armosec/opa-utils/reporthandling"
"k8s.io/apimachinery/pkg/version"
"github.com/armosec/k8s-interface/k8sinterface"
"github.com/armosec/kubescape/v2/core/cautils"
logger "github.com/dwertent/go-logger"
"github.com/dwertent/go-logger/helpers"
"github.com/armosec/kubescape/v2/core/cautils/logger"
"github.com/armosec/kubescape/v2/core/cautils/logger/helpers"
)
// FileResourceHandler handle resources from files and URLs
@@ -32,139 +29,48 @@ func NewFileResourceHandler(inputPatterns []string, registryAdaptors *RegistryAd
func (fileHandler *FileResourceHandler) GetResources(sessionObj *cautils.OPASessionObj, designator *armotypes.PortalDesignator) (*cautils.K8SResources, map[string]workloadinterface.IMetadata, *cautils.ArmoResources, error) {
//
// build resources map
// map resources based on framework required resources: map["/group/version/kind"][]<k8s workloads ids>
k8sResources := setK8sResourceMap(sessionObj.Policies)
allResources := map[string]workloadinterface.IMetadata{}
workloadIDToSource := make(map[string]reporthandling.Source, 0)
workloadIDToSource := make(map[string]string, 0)
armoResources := &cautils.ArmoResources{}
workloads := []workloadinterface.IMetadata{}
if len(fileHandler.inputPatterns) == 0 {
return nil, nil, nil, fmt.Errorf("missing input")
}
path := fileHandler.inputPatterns[0]
clonedRepo, err := cloneGitRepo(&path)
// load resource from local file system
sourceToWorkloads, err := cautils.LoadResourcesFromFiles(fileHandler.inputPatterns)
if err != nil {
return nil, allResources, nil, err
}
if clonedRepo != "" {
defer os.RemoveAll(clonedRepo)
}
// Get repo root
repoRoot := ""
gitRepo, err := cautils.NewLocalGitRepository(path)
if err == nil && gitRepo != nil {
repoRoot, _ = gitRepo.GetRootDir()
}
// load resource from local file system
logger.L().Info("Accessing local objects")
cautils.StartSpinner()
sourceToWorkloads := cautils.LoadResourcesFromFiles(path, repoRoot)
// update workloads and workloadIDToSource
for source, ws := range sourceToWorkloads {
workloads = append(workloads, ws...)
relSource, err := filepath.Rel(repoRoot, source)
if err == nil {
source = relSource
}
var filetype string
if cautils.IsYaml(source) {
filetype = reporthandling.SourceTypeYaml
} else if cautils.IsJson(source) {
filetype = reporthandling.SourceTypeJson
} else {
continue
}
var lastCommit reporthandling.LastCommit
if gitRepo != nil {
commitInfo, _ := gitRepo.GetFileLastCommit(source)
if commitInfo != nil {
lastCommit = reporthandling.LastCommit{
Hash: commitInfo.SHA,
Date: commitInfo.Author.Date,
CommitterName: commitInfo.Author.Name,
CommitterEmail: commitInfo.Author.Email,
Message: commitInfo.Message,
}
}
}
workloadSource := reporthandling.Source{
RelativePath: source,
FileType: filetype,
LastCommit: lastCommit,
}
for i := range ws {
workloadIDToSource[ws[i].GetID()] = workloadSource
workloadIDToSource[ws[i].GetID()] = source
}
}
logger.L().Debug("files found in local storage", helpers.Int("files", len(sourceToWorkloads)), helpers.Int("workloads", len(workloads)))
if len(workloads) == 0 {
logger.L().Debug("files found in local storage", helpers.Int("files", len(sourceToWorkloads)), helpers.Int("workloads", len(workloads)))
// load resources from url
sourceToWorkloads, err = loadResourcesFromUrl(fileHandler.inputPatterns)
if err != nil {
return nil, allResources, nil, err
}
// load resources from helm charts
helmSourceToWorkloads, helmSourceToChartName := cautils.LoadResourcesFromHelmCharts(path)
for source, ws := range helmSourceToWorkloads {
for source, ws := range sourceToWorkloads {
workloads = append(workloads, ws...)
helmChartName := helmSourceToChartName[source]
relSource, err := filepath.Rel(repoRoot, source)
if err == nil {
source = relSource
}
var lastCommit reporthandling.LastCommit
if gitRepo != nil {
commitInfo, _ := gitRepo.GetFileLastCommit(source)
if commitInfo != nil {
lastCommit = reporthandling.LastCommit{
Hash: commitInfo.SHA,
Date: commitInfo.Author.Date,
CommitterName: commitInfo.Author.Name,
CommitterEmail: commitInfo.Author.Email,
Message: commitInfo.Message,
}
}
}
workloadSource := reporthandling.Source{
RelativePath: source,
FileType: reporthandling.SourceTypeHelmChart,
HelmChartName: helmChartName,
LastCommit: lastCommit,
}
for i := range ws {
workloadIDToSource[ws[i].GetID()] = workloadSource
workloadIDToSource[ws[i].GetID()] = source
}
}
if len(helmSourceToWorkloads) > 0 {
logger.L().Debug("helm templates found in local storage", helpers.Int("helmTemplates", len(helmSourceToWorkloads)), helpers.Int("workloads", len(workloads)))
}
// addCommitData(fileHandler.inputPatterns[0], workloadIDToSource)
if len(workloads) == 0 {
return nil, allResources, nil, fmt.Errorf("empty list of workloads - no workloads found")
}
logger.L().Debug("files found in git repo", helpers.Int("files", len(sourceToWorkloads)), helpers.Int("workloads", len(workloads)))
sessionObj.ResourceSource = workloadIDToSource
// map all resources: map["/apiVersion/version/kind"][]<k8s workloads>
// map all resources: map["/group/version/kind"][]<k8s workloads>
mappedResources := mapResources(workloads)
// save only relevant resources
@@ -183,12 +89,39 @@ func (fileHandler *FileResourceHandler) GetResources(sessionObj *cautils.OPASess
logger.L().Warning("failed to collect images vulnerabilities", helpers.Error(err))
}
cautils.StopSpinner()
logger.L().Success("Accessed to local objects")
return k8sResources, allResources, armoResources, nil
}
func (fileHandler *FileResourceHandler) GetClusterAPIServerInfo() *version.Info {
return nil
}
// build resources map
func mapResources(workloads []workloadinterface.IMetadata) map[string][]workloadinterface.IMetadata {
allResources := map[string][]workloadinterface.IMetadata{}
for i := range workloads {
groupVersionResource, err := k8sinterface.GetGroupVersionResource(workloads[i].GetKind())
if err != nil {
// TODO - print warning
continue
}
if k8sinterface.IsTypeWorkload(workloads[i].GetObject()) {
w := workloadinterface.NewWorkloadObj(workloads[i].GetObject())
if groupVersionResource.Group != w.GetGroup() || groupVersionResource.Version != w.GetVersion() {
// TODO - print warning
continue
}
}
resourceTriplets := k8sinterface.JoinResourceTriplets(groupVersionResource.Group, groupVersionResource.Version, groupVersionResource.Resource)
if r, ok := allResources[resourceTriplets]; ok {
allResources[resourceTriplets] = append(r, workloads[i])
} else {
allResources[resourceTriplets] = []workloadinterface.IMetadata{workloads[i]}
}
}
return allResources
}

View File

@@ -1,86 +0,0 @@
package resourcehandler
import (
"fmt"
"path/filepath"
giturl "github.com/armosec/go-git-url"
"github.com/armosec/k8s-interface/k8sinterface"
"github.com/armosec/k8s-interface/workloadinterface"
"github.com/armosec/kubescape/v2/core/cautils"
"github.com/armosec/opa-utils/reporthandling"
logger "github.com/dwertent/go-logger"
"github.com/dwertent/go-logger/helpers"
)
// Clone git repository
func cloneGitRepo(path *string) (string, error) {
var clonedDir string
// Clone git repository if needed
gitURL, err := giturl.NewGitURL(*path)
if err == nil {
logger.L().Info("cloning", helpers.String("repository url", gitURL.GetURL().String()))
cautils.StartSpinner()
clonedDir, err = cloneRepo(gitURL)
cautils.StopSpinner()
if err != nil {
return "", fmt.Errorf("failed to clone git repo '%s', %w", gitURL.GetURL().String(), err)
}
*path = filepath.Join(clonedDir, gitURL.GetPath())
}
return clonedDir, nil
}
// build resources map
func mapResources(workloads []workloadinterface.IMetadata) map[string][]workloadinterface.IMetadata {
allResources := map[string][]workloadinterface.IMetadata{}
for i := range workloads {
groupVersionResource, err := k8sinterface.GetGroupVersionResource(workloads[i].GetKind())
if err != nil {
// TODO - print warning
continue
}
if k8sinterface.IsTypeWorkload(workloads[i].GetObject()) {
w := workloadinterface.NewWorkloadObj(workloads[i].GetObject())
if groupVersionResource.Group != w.GetGroup() || groupVersionResource.Version != w.GetVersion() {
// TODO - print warning
continue
}
}
resourceTriplets := k8sinterface.JoinResourceTriplets(groupVersionResource.Group, groupVersionResource.Version, groupVersionResource.Resource)
if r, ok := allResources[resourceTriplets]; ok {
allResources[resourceTriplets] = append(r, workloads[i])
} else {
allResources[resourceTriplets] = []workloadinterface.IMetadata{workloads[i]}
}
}
return allResources
}
func addCommitData(input string, workloadIDToSource map[string]reporthandling.Source) {
giRepo, err := cautils.NewLocalGitRepository(input)
if err != nil || giRepo == nil {
return
}
for k := range workloadIDToSource {
sourceObj := workloadIDToSource[k]
lastCommit, err := giRepo.GetFileLastCommit(sourceObj.RelativePath)
if err != nil {
continue
}
sourceObj.LastCommit = reporthandling.LastCommit{
Hash: lastCommit.SHA,
Date: lastCommit.Author.Date,
CommitterName: lastCommit.Author.Name,
CommitterEmail: lastCommit.Author.Email,
Message: lastCommit.Message,
}
workloadIDToSource[k] = sourceObj
}
}

View File

@@ -6,11 +6,11 @@ import (
"strings"
"github.com/armosec/kubescape/v2/core/cautils"
"github.com/armosec/kubescape/v2/core/cautils/logger"
"github.com/armosec/kubescape/v2/core/cautils/logger/helpers"
"github.com/armosec/kubescape/v2/core/pkg/hostsensorutils"
"github.com/armosec/opa-utils/objectsenvelopes"
"github.com/armosec/opa-utils/reporthandling/apis"
logger "github.com/dwertent/go-logger"
"github.com/dwertent/go-logger/helpers"
"github.com/armosec/k8s-interface/cloudsupport"
"github.com/armosec/k8s-interface/k8sinterface"
@@ -86,10 +86,10 @@ func (k8sHandler *K8sResourceHandler) GetResources(sessionObj *cautils.OPASessio
if len(imgVulnResources) > 0 {
if err := k8sHandler.registryAdaptors.collectImagesVulnerabilities(k8sResourcesMap, allResources, armoResourceMap); err != nil {
logger.L().Warning("failed to collect image vulnerabilities", helpers.Error(err))
cautils.SetInfoMapForResources(fmt.Sprintf("failed to pull image scanning data: %s. for more information: https://hub.armosec.io/docs/configuration-of-image-vulnerabilities", err.Error()), imgVulnResources, sessionObj.InfoMap)
cautils.SetInfoMapForResources(fmt.Sprintf("failed to pull image scanning data: %s", err.Error()), imgVulnResources, sessionObj.InfoMap)
} else {
if isEmptyImgVulns(*armoResourceMap) {
cautils.SetInfoMapForResources("image scanning is not configured. for more information: https://hub.armosec.io/docs/configuration-of-image-vulnerabilities", imgVulnResources, sessionObj.InfoMap)
cautils.SetInfoMapForResources("image scanning is not configured. for more information: https://hub.armo.cloud/docs/cluster-vulnerability-scanning", imgVulnResources, sessionObj.InfoMap)
}
}
}
@@ -109,7 +109,7 @@ func (k8sHandler *K8sResourceHandler) GetResources(sessionObj *cautils.OPASessio
sessionObj.InfoMap = infoMap
}
} else {
cautils.SetInfoMapForResources("enable-host-scan flag not used. For more information: https://hub.armosec.io/docs/host-sensor", hostResources, sessionObj.InfoMap)
cautils.SetInfoMapForResources("enable-host-scan flag not used. For more information: https://hub.armo.cloud/docs/host-sensor", hostResources, sessionObj.InfoMap)
}
}
@@ -283,7 +283,7 @@ func getCloudProviderDescription(allResources map[string]workloadinterface.IMeta
if err != nil {
// Return error with useful info on how to configure credentials for getting cloud provider info
logger.L().Debug("failed to get descriptive information", helpers.Error(err))
return provider, fmt.Errorf("failed to get %s descriptive information. Read more: https://hub.armosec.io/docs/kubescape-integration-with-cloud-providers", strings.ToUpper(provider))
return provider, fmt.Errorf("failed to get %s descriptive information. Read more: https://hub.armo.cloud/docs/kubescape-integration-with-cloud-providers", strings.ToUpper(provider))
}
allResources[wl.GetID()] = wl
(*armoResourceMap)[fmt.Sprintf("%s/%s", wl.GetApiVersion(), wl.GetKind())] = []string{wl.GetID()}

View File

@@ -20,8 +20,6 @@ var (
LinuxKernelVariables = "LinuxKernelVariables"
KubeletCommandLine = "KubeletCommandLine"
ImageVulnerabilities = "ImageVulnerabilities"
KubeletInfo = "KubeletInfo"
KubeProxyInfo = "KubeProxyInfo"
MapResourceToApiGroup = map[string]string{
KubeletConfiguration: "hostdata.kubescape.cloud/v1beta0",
@@ -31,8 +29,6 @@ var (
LinuxSecurityHardeningStatus: "hostdata.kubescape.cloud/v1beta0",
OpenPortsList: "hostdata.kubescape.cloud/v1beta0",
LinuxKernelVariables: "hostdata.kubescape.cloud/v1beta0",
KubeletInfo: "hostdata.kubescape.cloud/v1beta0",
KubeProxyInfo: "hostdata.kubescape.cloud/v1beta0",
}
MapResourceToApiGroupVuln = map[string][]string{
ImageVulnerabilities: {"armo.vuln.images/v1", "image.vulnscan.com/v1"}}

View File

@@ -7,9 +7,9 @@ import (
"github.com/armosec/k8s-interface/workloadinterface"
"github.com/armosec/kubescape/v2/core/cautils"
"github.com/armosec/kubescape/v2/core/cautils/getter"
"github.com/armosec/kubescape/v2/core/cautils/logger"
armosecadaptorv1 "github.com/armosec/kubescape/v2/core/pkg/registryadaptors/armosec/v1"
"github.com/armosec/kubescape/v2/core/pkg/registryadaptors/registryvulnerabilities"
logger "github.com/dwertent/go-logger"
"github.com/armosec/opa-utils/shared"
)
@@ -38,16 +38,6 @@ func NewRegistryAdaptors() (*RegistryAdaptors, error) {
func (registryAdaptors *RegistryAdaptors) collectImagesVulnerabilities(k8sResourcesMap *cautils.K8SResources, allResources map[string]workloadinterface.IMetadata, armoResourceMap *cautils.ArmoResources) error {
logger.L().Debug("Collecting images vulnerabilities")
if len(registryAdaptors.adaptors) == 0 {
return fmt.Errorf("credentials are not configured for any registry adaptor")
}
for i := range registryAdaptors.adaptors { // login and and get vulnerabilities
if err := registryAdaptors.adaptors[i].Login(); err != nil {
return fmt.Errorf("failed to login, adaptor: '%s', reason: '%s'", registryAdaptors.adaptors[i].DescribeAdaptor(), err.Error())
}
}
// list cluster images
images := listImagesTags(k8sResourcesMap, allResources)
imagesIdentifiers := imageTagsToContainerImageIdentifier(images)
@@ -55,6 +45,11 @@ func (registryAdaptors *RegistryAdaptors) collectImagesVulnerabilities(k8sResour
imagesVulnerability := map[string][]registryvulnerabilities.Vulnerability{}
for i := range registryAdaptors.adaptors { // login and and get vulnerabilities
if err := registryAdaptors.adaptors[i].Login(); err != nil {
if err != nil {
return fmt.Errorf("failed to login, adaptor: '%s', reason: '%s'", registryAdaptors.adaptors[i].DescribeAdaptor(), err.Error())
}
}
vulnerabilities, err := registryAdaptors.adaptors[i].GetImagesVulnerabilities(imagesIdentifiers)
if err != nil {
return err
@@ -67,10 +62,6 @@ func (registryAdaptors *RegistryAdaptors) collectImagesVulnerabilities(k8sResour
// convert result to IMetadata object
metaObjs := vulnerabilitiesToIMetadata(imagesVulnerability)
if len(metaObjs) == 0 {
return fmt.Errorf("no vulnerabilities found for any of the images")
}
// save in resources map
for i := range metaObjs {
allResources[metaObjs[i].GetID()] = metaObjs[i]

View File

@@ -1,36 +0,0 @@
package resourcehandler
import (
"fmt"
"os"
giturl "github.com/armosec/go-git-url"
"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/plumbing"
)
// cloneRepo clones a repository to a local temporary directory and returns the directory
func cloneRepo(gitURL giturl.IGitURL) (string, error) {
// Create temp directory
tmpDir, err := os.MkdirTemp("", "")
if err != nil {
return "", fmt.Errorf("failed to create temporary directory: %w", err)
}
// Clone option
cloneURL := gitURL.GetHttpCloneURL()
cloneOpts := git.CloneOptions{URL: cloneURL}
if gitURL.GetBranchName() != "" {
cloneOpts.ReferenceName = plumbing.NewBranchReferenceName(gitURL.GetBranchName())
cloneOpts.SingleBranch = true
}
// Actual clone
_, err = git.PlainClone(tmpDir, false, &cloneOpts)
if err != nil {
return "", fmt.Errorf("failed to clone %s. %w", gitURL.GetRepoName(), err)
}
return tmpDir, nil
}

View File

@@ -4,15 +4,15 @@ import (
giturl "github.com/armosec/go-git-url"
"github.com/armosec/k8s-interface/workloadinterface"
"github.com/armosec/kubescape/v2/core/cautils"
logger "github.com/dwertent/go-logger"
"github.com/dwertent/go-logger/helpers"
"github.com/armosec/kubescape/v2/core/cautils/logger"
"github.com/armosec/kubescape/v2/core/cautils/logger/helpers"
)
func loadResourcesFromUrl(inputPatterns []string) (map[string][]workloadinterface.IMetadata, error) {
if len(inputPatterns) == 0 {
return nil, nil
}
g, err := giturl.NewGitAPI(inputPatterns[0])
g, err := giturl.NewGitURL(inputPatterns[0])
if err != nil {
return nil, nil
}
@@ -33,7 +33,7 @@ func loadResourcesFromUrl(inputPatterns []string) (map[string][]workloadinterfac
for i, j := range files {
w, e := cautils.ReadFile(j, cautils.GetFileFormat(i))
if e != nil || len(w) == 0 {
if len(e) != 0 || len(w) == 0 {
continue
}
if _, ok := workloads[i]; !ok {

View File

@@ -6,7 +6,7 @@ import (
"path/filepath"
"github.com/armosec/kubescape/v2/core/cautils"
logger "github.com/dwertent/go-logger"
"github.com/armosec/kubescape/v2/core/cautils/logger"
)
var INDENT = " "
@@ -17,7 +17,6 @@ const (
JunitResultFormat string = "junit"
PrometheusFormat string = "prometheus"
PdfFormat string = "pdf"
HtmlFormat string = "html"
)
type IPrinter interface {

View File

@@ -6,8 +6,8 @@ import (
"os"
"github.com/armosec/kubescape/v2/core/cautils"
"github.com/armosec/kubescape/v2/core/cautils/logger"
"github.com/armosec/kubescape/v2/core/pkg/resultshandling/printer"
logger "github.com/dwertent/go-logger"
)
type JsonPrinter struct {
@@ -27,15 +27,15 @@ func (jsonPrinter *JsonPrinter) Score(score float32) {
}
func (jsonPrinter *JsonPrinter) ActionPrint(opaSessionObj *cautils.OPASessionObj) {
report := cautils.ReportV2ToV1(opaSessionObj)
cautils.ReportV2ToV1(opaSessionObj)
var postureReportStr []byte
var err error
if len(report.FrameworkReports) == 1 {
postureReportStr, err = json.Marshal(report.FrameworkReports[0])
if len(opaSessionObj.PostureReport.FrameworkReports) == 1 {
postureReportStr, err = json.Marshal(opaSessionObj.PostureReport.FrameworkReports[0])
} else {
postureReportStr, err = json.Marshal(report.FrameworkReports)
postureReportStr, err = json.Marshal(opaSessionObj.PostureReport.FrameworkReports)
}
if err != nil {

View File

@@ -6,9 +6,9 @@ import (
"github.com/armosec/k8s-interface/workloadinterface"
"github.com/armosec/kubescape/v2/core/cautils"
"github.com/armosec/kubescape/v2/core/cautils/logger"
"github.com/armosec/kubescape/v2/core/pkg/resultshandling/printer"
"github.com/armosec/opa-utils/reporthandling"
logger "github.com/dwertent/go-logger"
)
type PrometheusPrinter struct {
@@ -87,9 +87,9 @@ func (printer *PrometheusPrinter) printReports(allResources map[string]workloadi
}
func (printer *PrometheusPrinter) ActionPrint(opaSessionObj *cautils.OPASessionObj) {
report := cautils.ReportV2ToV1(opaSessionObj)
cautils.ReportV2ToV1(opaSessionObj)
err := printer.printReports(opaSessionObj.AllResources, report.FrameworkReports)
err := printer.printReports(opaSessionObj.AllResources, opaSessionObj.PostureReport.FrameworkReports)
if err != nil {
logger.L().Fatal(err.Error())
}

View File

@@ -1,154 +0,0 @@
<!DOCTYPE html>
<html lang="en-US">
<head>
<meta charset="UTF-8">
<title>Kubescape Scan Report</title>
</head>
<style>
:root {
--cell-padding-vertical: 0.25em;
--cell-padding-horizontal: 0.25em;
--font-family-sans: system-ui, -apple-system, sans-serif;
}
body {
max-width: 60em;
margin: auto;
font-family: var(--font-family-sans);
}
table {
width: 100%;
border-top: 0.1em solid black;
border-bottom: 0.1em solid black;
border-collapse: collapse;
table-layout: fixed;
}
th {
text-align: left;
}
td, th {
padding-top: var(--cell-padding-vertical);
padding-bottom: var(--cell-padding-vertical);
padding-right: var(--cell-padding-horizontal);
vertical-align: top;
}
td > p {
margin: 0;
word-break: break-all;
hyphens: auto;
}
thead {
border-bottom: 0.01em solid black;
}
.numericCell {
text-align: right;
}
.controlSeverityCell {
width: 10%;
}
.controlNameCell {
width: 50%;
}
.controlRiskCell {
width: 10%;
}
.resourceSeverityCell {
width: 10%;
}
.resourceNameCell {
width: 30%;
}
.resourceURLCell {
width: 10%;
}
.resourceRemediationCell {
width: 50%;
}
.logo {
width: 25%;
float: right;
}
</style>
<body>
<img class="logo" src="https://raw.githubusercontent.com/armosec/kubescape/master/core/pkg/resultshandling/printer/v2/pdf/logo.png">
<h1>Kubescape Scan Report</h1>
{{ with .OPASessionObj.Report.SummaryDetails }}
<h2>By Controls</h2>
<h3>Summary</h3>
<table>
<thead>
<tr>
<th>All</th>
<th>Failed</th>
<th>Excluded</th>
<th>Skipped</th>
</tr>
</thead>
<tbody>
<tr>
<td>{{ .NumberOfControls.All }}</td>
<td>{{ .NumberOfControls.Failed }}</td>
<td>{{ .NumberOfControls.Excluded }}</td>
<td>{{ .NumberOfControls.Skipped }}</td>
</tr>
</tbody>
</table>
<h3>Details</h3>
<table>
<thead>
<tr>
<th class="controlSeverityCell">Severity</th>
<th class="controlNameCell">Control Name</th>
<th class="controlRiskCell">Failed Resources</th>
<th class="controlRiskCell">Excluded Resources</th>
<th class="controlRiskCell">All Resources</th>
<th class="controlRiskCell">Risk Score, %</th>
</tr>
</thead>
<tbody>
{{ $sorted := sortBySeverityName .Controls }}
{{ range $control := $sorted }}
<tr>
<td class="controlSeverityCell">{{ controlSeverityToString $control.ScoreFactor }}</td>
<td class="controlNameCell">{{ $control.Name }}</td>
<td class="controlRiskCell numericCell">{{ $control.ResourceCounters.FailedResources }}</td>
<td class="controlRiskCell numericCell">{{ $control.ResourceCounters.ExcludedResources }}</td>
<td class="controlRiskCell numericCell">{{ sum $control.ResourceCounters.ExcludedResources $control.ResourceCounters.FailedResources $control.ResourceCounters.PassedResources }}</td>
<td class="controlRiskCell numericCell">{{ float32ToInt $control.Score }}</td>
</tr>
</tr>
{{ end }}
<tbody>
</table>
{{ end }}
<h2>By Resource</h2>
{{ $sortedResourceTableView := sortByNamespace .ResourceTableView }}
{{ range $sortedResourceTableView }}
<h3>Name: {{ .Resource.GetName }}</h3>
<p>ApiVersion: {{ .Resource.GetApiVersion }}</p>
<p>Kind: {{ .Resource.GetKind }}</p>
<p>Name: {{ .Resource.GetName }}</p>
<p>Namespace: {{ .Resource.GetNamespace }}</p>
<table>
<thead>
<tr>
<th class="resourceSeverityCell">Severity</th>
<th class="resourceNameCell">Name</th>
<th class="resourceURLCell">Docs</th>
<th class="resourceRemediationCell">Assistant Remediation</th>
</tr>
</thead>
<tbody>
{{ range .ControlsResult }}
<tr>
<td class="resourceSeverityCell">{{ .Severity }}</td>
<td class="resourceNameCell">{{ .Name }}</td>
<td class="resourceURLCell"><a href="https://hub.armosec.io/docs/{{ lower .URL }}">{{ .URL }}</a></td>
<td class="resourceRemediationCell">{{ range .FailedPaths }} <p>{{ . }}</p> {{ end }}</td>
</tr>
{{ end }}
</tbody>
</table>
</div>
{{ end }}
</body>
</html>

View File

@@ -1,151 +0,0 @@
package v2
import (
_ "embed"
"html/template"
"os"
"path/filepath"
"sort"
"strings"
"github.com/armosec/kubescape/v2/core/cautils"
"github.com/armosec/kubescape/v2/core/pkg/resultshandling/printer"
"github.com/armosec/opa-utils/reporthandling/apis"
"github.com/armosec/opa-utils/reporthandling/results/v1/reportsummary"
"github.com/armosec/opa-utils/reporthandling/results/v1/resourcesresults"
logger "github.com/dwertent/go-logger"
"github.com/dwertent/go-logger/helpers"
)
const (
htmlOutputFile = "report"
htmlOutputExt = ".html"
)
//go:embed html/report.gohtml
var reportTemplate string
type HTMLReportingCtx struct {
OPASessionObj *cautils.OPASessionObj
ResourceTableView ResourceTableView
}
type HtmlPrinter struct {
writer *os.File
}
func NewHtmlPrinter() *HtmlPrinter {
return &HtmlPrinter{}
}
func (htmlPrinter *HtmlPrinter) SetWriter(outputFile string) {
if outputFile == "" {
outputFile = htmlOutputFile
}
if filepath.Ext(strings.TrimSpace(outputFile)) != htmlOutputExt {
outputFile = outputFile + htmlOutputExt
}
htmlPrinter.writer = printer.GetWriter(outputFile)
}
func (htmlPrinter *HtmlPrinter) ActionPrint(opaSessionObj *cautils.OPASessionObj) {
tplFuncMap := template.FuncMap{
"sum": func(nums ...int) int {
total := 0
for _, n := range nums {
total += n
}
return total
},
"float32ToInt": cautils.Float32ToInt,
"lower": strings.ToLower,
"sortByNamespace": func(resourceTableView ResourceTableView) ResourceTableView {
sortedResourceTableView := make(ResourceTableView, len(resourceTableView))
copy(sortedResourceTableView, resourceTableView)
sort.SliceStable(
sortedResourceTableView,
func(i, j int) bool {
return sortedResourceTableView[i].Resource.GetNamespace() < sortedResourceTableView[j].Resource.GetNamespace()
},
)
return sortedResourceTableView
},
"controlSeverityToString": apis.ControlSeverityToString,
"sortBySeverityName": func(controlSummaries map[string]reportsummary.ControlSummary) []reportsummary.ControlSummary {
sortedSlice := make([]reportsummary.ControlSummary, 0, len(controlSummaries))
for _, val := range controlSummaries {
sortedSlice = append(sortedSlice, val)
}
sort.SliceStable(
sortedSlice,
func(i, j int) bool {
//First sort by Severity descending
iSeverity := apis.ControlSeverityToInt(sortedSlice[i].GetScoreFactor())
jSeverity := apis.ControlSeverityToInt(sortedSlice[j].GetScoreFactor())
if iSeverity > jSeverity {
return true
}
if iSeverity < jSeverity {
return false
}
//And then by Name ascending
return sortedSlice[i].GetName() < sortedSlice[j].GetName()
},
)
return sortedSlice
},
}
tpl := template.Must(
template.New("htmlReport").Funcs(tplFuncMap).Parse(reportTemplate),
)
resourceTableView := buildResourceTableView(opaSessionObj)
reportingCtx := HTMLReportingCtx{opaSessionObj, resourceTableView}
err := tpl.Execute(htmlPrinter.writer, reportingCtx)
if err != nil {
logger.L().Error("failed to render template", helpers.Error(err))
}
}
func (htmlPrinter *HtmlPrinter) Score(score float32) {
return
}
func buildResourceTableView(opaSessionObj *cautils.OPASessionObj) ResourceTableView {
resourceTableView := make(ResourceTableView, 0)
for resourceID, result := range opaSessionObj.ResourcesResult {
if result.GetStatus(nil).IsFailed() {
resource := opaSessionObj.AllResources[resourceID]
ctlResults := buildResourceControlResultTable(result.AssociatedControls, &opaSessionObj.Report.SummaryDetails)
resourceTableView = append(resourceTableView, ResourceResult{resource, ctlResults})
}
}
return resourceTableView
}
func buildResourceControlResult(resourceControl resourcesresults.ResourceAssociatedControl, control reportsummary.IControlSummary) ResourceControlResult {
ctlSeverity := apis.ControlSeverityToString(control.GetScoreFactor())
ctlName := resourceControl.GetName()
ctlURL := resourceControl.GetID()
failedPaths := failedPathsToString(&resourceControl)
return ResourceControlResult{ctlSeverity, ctlName, ctlURL, failedPaths}
}
func buildResourceControlResultTable(resourceControls []resourcesresults.ResourceAssociatedControl, summaryDetails *reportsummary.SummaryDetails) []ResourceControlResult {
var ctlResults []ResourceControlResult
for _, resourceControl := range resourceControls {
if resourceControl.GetStatus(nil).IsFailed() {
control := summaryDetails.Controls.GetControl(reportsummary.EControlCriteriaName, resourceControl.GetName())
ctlResult := buildResourceControlResult(resourceControl, control)
ctlResults = append(ctlResults, ctlResult)
}
}
return ctlResults
}

View File

@@ -6,9 +6,9 @@ import (
"os"
"github.com/armosec/kubescape/v2/core/cautils"
"github.com/armosec/kubescape/v2/core/cautils/logger"
"github.com/armosec/kubescape/v2/core/cautils/logger/helpers"
"github.com/armosec/kubescape/v2/core/pkg/resultshandling/printer"
logger "github.com/dwertent/go-logger"
"github.com/dwertent/go-logger/helpers"
)
type JsonPrinter struct {

View File

@@ -9,11 +9,11 @@ import (
"github.com/armosec/k8s-interface/workloadinterface"
"github.com/armosec/kubescape/v2/core/cautils"
"github.com/armosec/kubescape/v2/core/cautils/logger"
"github.com/armosec/kubescape/v2/core/cautils/logger/helpers"
"github.com/armosec/kubescape/v2/core/pkg/resultshandling/printer"
"github.com/armosec/opa-utils/reporthandling/results/v1/reportsummary"
"github.com/armosec/opa-utils/shared"
logger "github.com/dwertent/go-logger"
"github.com/dwertent/go-logger/helpers"
)
/*
@@ -49,7 +49,7 @@ type JUnitTestSuite struct {
XMLName xml.Name `xml:"testsuite"`
Name string `xml:"name,attr"` // Full (class) name of the test for non-aggregated testsuite documents. Class name without the package for aggregated testsuites documents. Required
Disabled int `xml:"disabled,attr"` // The total number of disabled tests in the suite. optional. not supported by maven surefire.
Errors int `xml:"errors,attr"` // The total number of tests in the suite that errors
Errors int `xml:"errors,attr"` // The total number of tests in the suite that errored
Failures int `xml:"failures,attr"` // The total number of tests in the suite that failed
Hostname string `xml:"hostname,attr"` // Host on which the tests were executed ? cluster name ?
ID int `xml:"id,attr"` // Starts at 0 for the first testsuite and is incremented by 1 for each following testsuite
@@ -181,7 +181,7 @@ func testsCases(results *cautils.OPASessionObj, controls reportsummary.IControls
testCase.Failure = &testCaseFailure
} else if control.GetStatus().IsSkipped() {
testCase.SkipMessage = &JUnitSkipMessage{
Message: "", // TODO - fill after statusInfo is supported
Message: "", // TODO - fill after statusInfo is supportred
}
}

View File

@@ -10,11 +10,10 @@ import (
"time"
"github.com/armosec/kubescape/v2/core/cautils"
"github.com/armosec/kubescape/v2/core/cautils/logger"
"github.com/armosec/kubescape/v2/core/cautils/logger/helpers"
"github.com/armosec/kubescape/v2/core/pkg/resultshandling/printer"
"github.com/armosec/opa-utils/reporthandling/results/v1/reportsummary"
logger "github.com/dwertent/go-logger"
"github.com/dwertent/go-logger/helpers"
"github.com/johnfercher/maroto/pkg/color"
"github.com/johnfercher/maroto/pkg/consts"
"github.com/johnfercher/maroto/pkg/pdf"

View File

@@ -33,7 +33,7 @@ func NewPrettyPrinter(verboseMode bool, formatVersion string, viewType cautils.V
}
func (prettyPrinter *PrettyPrinter) ActionPrint(opaSessionObj *cautils.OPASessionObj) {
fmt.Fprintf(prettyPrinter.writer, "\n"+getSeparator("^")+"\n")
fmt.Fprintf(prettyPrinter.writer, "\n"+getSperator("^")+"\n")
sortedControlNames := getSortedControlsNames(opaSessionObj.Report.SummaryDetails.Controls) // ListControls().All())
@@ -188,10 +188,6 @@ func generateFooter(summaryDetails *reportsummary.SummaryDetails) []string {
}
func (prettyPrinter *PrettyPrinter) printSummaryTable(summaryDetails *reportsummary.SummaryDetails, sortedControlNames [][]string) {
if summaryDetails.NumberOfControls().All() == 0 {
fmt.Fprintf(prettyPrinter.writer, "\nKubescape did not scan any of the resources, make sure you are scanning valid kubernetes manifests (Deployments, Pods, etc.)\n")
return
}
cautils.InfoTextDisplay(prettyPrinter.writer, "\n"+controlCountersForSummary(summaryDetails.NumberOfControls())+"\n\n")
summaryTable := tablewriter.NewWriter(prettyPrinter.writer)
@@ -200,16 +196,10 @@ func (prettyPrinter *PrettyPrinter) printSummaryTable(summaryDetails *reportsumm
summaryTable.SetHeaderLine(true)
summaryTable.SetColumnAlignment(getColumnsAlignments())
printAll := prettyPrinter.verboseMode
if summaryDetails.NumberOfResources().Failed() == 0 {
// if there are no failed controls, print the resource table and detailed information
printAll = true
}
infoToPrintInfo := mapInfoToPrintInfo(summaryDetails.Controls)
for i := len(sortedControlNames) - 1; i >= 0; i-- {
for _, c := range sortedControlNames[i] {
row := generateRow(summaryDetails.Controls.GetControl(reportsummary.EControlCriteriaName, c), infoToPrintInfo, printAll)
row := generateRow(summaryDetails.Controls.GetControl(reportsummary.EControlCriteriaName, c), infoToPrintInfo, prettyPrinter.verboseMode)
if len(row) > 0 {
summaryTable.Append(row)
}
@@ -253,7 +243,7 @@ func frameworksScoresToString(frameworks []reportsummary.IFrameworkSummary) stri
}
func getControlLink(controlID string) string {
return fmt.Sprintf("https://hub.armosec.io/docs/%s", strings.ToLower(controlID))
return fmt.Sprintf("https://hub.armo.cloud/docs/%s", strings.ToLower(controlID))
}
func controlCountersForSummary(counters reportsummary.ICounters) string {
@@ -263,7 +253,7 @@ func controlCountersForSummary(counters reportsummary.ICounters) string {
func controlCountersForResource(l *helpersv1.AllLists) string {
return fmt.Sprintf("Controls: %d (Failed: %d, Excluded: %d)", len(l.All()), len(l.Failed()), len(l.Excluded()))
}
func getSeparator(sep string) string {
func getSperator(sep string) string {
s := ""
for i := 0; i < 80; i++ {
s += sep

View File

@@ -6,11 +6,11 @@ import (
"github.com/armosec/k8s-interface/workloadinterface"
"github.com/armosec/kubescape/v2/core/cautils"
"github.com/armosec/kubescape/v2/core/cautils/logger"
"github.com/armosec/kubescape/v2/core/cautils/logger/helpers"
"github.com/armosec/kubescape/v2/core/pkg/resultshandling/printer"
"github.com/armosec/opa-utils/reporthandling/results/v1/reportsummary"
"github.com/armosec/opa-utils/reporthandling/results/v1/resourcesresults"
logger "github.com/dwertent/go-logger"
"github.com/dwertent/go-logger/helpers"
)
type PrometheusPrinter struct {

View File

@@ -1,19 +0,0 @@
package v2
import (
"github.com/armosec/k8s-interface/workloadinterface"
)
type ResourceTableView []ResourceResult
type ResourceResult struct {
Resource workloadinterface.IMetadata
ControlsResult []ResourceControlResult
}
type ResourceControlResult struct {
Severity string
Name string
URL string
FailedPaths []string
}

View File

@@ -29,10 +29,10 @@ func (prettyPrinter *PrettyPrinter) resourceTable(opaSessionObj *cautils.OPASess
if !ok {
continue
}
fmt.Fprintf(prettyPrinter.writer, "\n%s\n", getSeparator("#"))
fmt.Fprintf(prettyPrinter.writer, "\n"+getSperator("#")+"\n\n")
if source, ok := opaSessionObj.ResourceSource[resourceID]; ok {
fmt.Fprintf(prettyPrinter.writer, "Source: %s\n", source.RelativePath)
fmt.Fprintf(prettyPrinter.writer, "Source: %s\n", source)
}
fmt.Fprintf(prettyPrinter.writer, "ApiVersion: %s\n", resource.GetApiVersion())
fmt.Fprintf(prettyPrinter.writer, "Kind: %s\n", resource.GetKind())
@@ -73,7 +73,7 @@ func generateResourceRows(controls []resourcesresults.ResourceAssociatedControl,
continue
}
row[resourceColumnURL] = fmt.Sprintf("https://hub.armosec.io/docs/%s", strings.ToLower(controls[i].GetID()))
row[resourceColumnURL] = fmt.Sprintf("https://hub.armo.cloud/docs/%s", strings.ToLower(controls[i].GetID()))
row[resourceColumnPath] = strings.Join(failedPathsToString(&controls[i]), "\n")
row[resourceColumnName] = controls[i].GetName()

View File

@@ -3,12 +3,12 @@ package v2
import (
"github.com/armosec/k8s-interface/workloadinterface"
"github.com/armosec/kubescape/v2/core/cautils"
"github.com/armosec/kubescape/v2/core/cautils/logger"
"github.com/armosec/kubescape/v2/core/cautils/logger/helpers"
"github.com/armosec/opa-utils/reporthandling"
"github.com/armosec/opa-utils/reporthandling/results/v1/reportsummary"
"github.com/armosec/opa-utils/reporthandling/results/v1/resourcesresults"
reporthandlingv2 "github.com/armosec/opa-utils/reporthandling/v2"
logger "github.com/dwertent/go-logger"
"github.com/dwertent/go-logger/helpers"
)
// finalizeV2Report finalize the results objects by copying data from map to lists
@@ -62,13 +62,13 @@ func mapInfoToPrintInfo(controls reportsummary.ControlSummaries) []infoStars {
return infoToPrintInfo
}
func finalizeResources(results []resourcesresults.Result, allResources map[string]workloadinterface.IMetadata, resourcesSource map[string]reporthandling.Source) []reporthandling.Resource {
func finalizeResources(results []resourcesresults.Result, allResources map[string]workloadinterface.IMetadata, resourcesSource map[string]string) []reporthandling.Resource {
resources := make([]reporthandling.Resource, 0)
for i := range results {
if obj, ok := allResources[results[i].ResourceID]; ok {
resource := *reporthandling.NewResourceIMetadata(obj)
if r, ok := resourcesSource[results[i].ResourceID]; ok {
resource.SetSource(&r)
resource.SetSource(&reporthandling.Source{Path: r})
}
resources = append(resources, resource)
}

View File

@@ -0,0 +1,184 @@
package v1
import (
"encoding/json"
"fmt"
"net/http"
"net/url"
"os"
"github.com/armosec/k8s-interface/workloadinterface"
"github.com/armosec/kubescape/v2/core/cautils"
"github.com/armosec/kubescape/v2/core/cautils/getter"
"github.com/armosec/kubescape/v2/core/cautils/logger"
"github.com/armosec/kubescape/v2/core/cautils/logger/helpers"
v2 "github.com/armosec/kubescape/v2/core/pkg/resultshandling/reporter/v2"
"github.com/armosec/opa-utils/reporthandling"
"github.com/google/uuid"
)
const MAX_REPORT_SIZE = 2097152 // 2 MB
type ReportEventReceiver struct {
httpClient *http.Client
clusterName string
customerGUID string
eventReceiverURL *url.URL
token string
customerAdminEMail string
message string
}
func NewReportEventReceiver(tenantConfig *cautils.ConfigObj) *ReportEventReceiver {
return &ReportEventReceiver{
httpClient: &http.Client{},
clusterName: tenantConfig.ClusterName,
customerGUID: tenantConfig.AccountID,
token: tenantConfig.Token,
customerAdminEMail: tenantConfig.CustomerAdminEMail,
}
}
func (report *ReportEventReceiver) Submit(opaSessionObj *cautils.OPASessionObj) error {
if opaSessionObj.PostureReport == nil && opaSessionObj.Report != nil {
cautils.ReportV2ToV1(opaSessionObj)
}
if report.customerGUID == "" {
report.message = "WARNING: Failed to publish results. Reason: Unknown accout ID. Run kubescape with the '--account <account ID>' flag. Please feel free to contact ARMO team for more details"
return nil
}
if report.clusterName == "" {
report.message = "WARNING: Failed to publish results because the cluster name is Unknown. If you are scanning YAML files the results are not submitted to the Kubescape SaaS.Please feel free to contact ARMO team for more details"
return nil
}
opaSessionObj.PostureReport.ReportID = uuid.NewString()
opaSessionObj.PostureReport.CustomerGUID = report.customerGUID
opaSessionObj.PostureReport.ClusterName = report.clusterName
if err := report.prepareReport(opaSessionObj.PostureReport, opaSessionObj.AllResources); err != nil {
report.message = err.Error()
return nil
}
return nil
}
func (report *ReportEventReceiver) SetCustomerGUID(customerGUID string) {
report.customerGUID = customerGUID
}
func (report *ReportEventReceiver) SetClusterName(clusterName string) {
report.clusterName = cautils.AdoptClusterName(clusterName) // clean cluster name
}
func (report *ReportEventReceiver) prepareReport(postureReport *reporthandling.PostureReport, allResources map[string]workloadinterface.IMetadata) error {
report.initEventReceiverURL()
host := hostToString(report.eventReceiverURL, postureReport.ReportID)
cautils.StartSpinner()
// send framework results
if err := report.sendReport(host, postureReport); err != nil {
cautils.StopSpinner()
return err
}
// send resources
if err := report.sendResources(host, postureReport, allResources); err != nil {
cautils.StopSpinner()
return err
}
cautils.StopSpinner()
report.generateMessage()
return nil
}
func (report *ReportEventReceiver) sendResources(host string, postureReport *reporthandling.PostureReport, allResources map[string]workloadinterface.IMetadata) error {
splittedPostureReport := setPaginationReport(postureReport)
counter := 0
for _, v := range allResources {
r, err := json.Marshal(*iMetaToResource(v))
if err != nil {
return fmt.Errorf("failed to unmarshal resource '%s', reason: %v", v.GetID(), err)
}
if counter+len(r) >= MAX_REPORT_SIZE && len(splittedPostureReport.Resources) > 0 {
// send report
if err := report.sendReport(host, splittedPostureReport); err != nil {
return err
}
// delete resources
splittedPostureReport.Resources = []reporthandling.Resource{}
// restart counter
counter = 0
}
counter += len(r)
splittedPostureReport.Resources = append(splittedPostureReport.Resources, *iMetaToResource(v))
}
return report.sendReport(host, splittedPostureReport)
}
func (report *ReportEventReceiver) sendReport(host string, postureReport *reporthandling.PostureReport) error {
reqBody, err := json.Marshal(postureReport)
if err != nil {
return fmt.Errorf("in 'sendReport' failed to json.Marshal, reason: %v", err)
}
msg, err := getter.HttpPost(report.httpClient, host, nil, reqBody)
if err != nil {
return fmt.Errorf("%s, %v:%s", host, err, msg)
}
return nil
}
func (report *ReportEventReceiver) generateMessage() {
message := "You can see the results in a user-friendly UI, choose your preferred compliance framework, check risk results history and trends, manage exceptions, get remediation recommendations and much more by registering here:"
u := url.URL{}
u.Host = getter.GetArmoAPIConnector().GetFrontendURL()
v2.ParseHost(&u)
if report.customerAdminEMail != "" {
logger.L().Debug("", helpers.String("account ID", report.customerGUID))
report.message = fmt.Sprintf("%s %s/configuration-scanning/%s", message, u.String(), report.clusterName)
return
}
u.Path = "account/sign-up"
q := u.Query()
q.Add("invitationToken", report.token)
q.Add("customerGUID", report.customerGUID)
u.RawQuery = q.Encode()
report.message = fmt.Sprintf("%s %s", message, u.String())
}
func (report *ReportEventReceiver) GetURL() string {
return getter.GetArmoAPIConnector().GetFrontendURL()
}
func (report *ReportEventReceiver) DisplayReportURL() {
cautils.InfoTextDisplay(os.Stderr, fmt.Sprintf("\n\n%s\n\n", report.message))
}
// func maskID(id string) string {
// sep := "-"
// splitted := strings.Split(id, sep)
// if len(splitted) != 5 {
// return ""
// }
// str := splitted[0][:4]
// splitted[0] = splitted[0][4:]
// for i := range splitted {
// for j := 0; j < len(splitted[i]); j++ {
// str += "X"
// }
// str += sep
// }
// return strings.TrimSuffix(str, sep)
// }

View File

@@ -0,0 +1,48 @@
package v1
import (
"net/url"
"github.com/armosec/k8s-interface/workloadinterface"
"github.com/armosec/kubescape/v2/core/cautils/getter"
v2 "github.com/armosec/kubescape/v2/core/pkg/resultshandling/reporter/v2"
"github.com/armosec/opa-utils/reporthandling"
"github.com/google/uuid"
)
func (report *ReportEventReceiver) initEventReceiverURL() {
urlObj := url.URL{}
urlObj.Host = getter.GetArmoAPIConnector().GetReportReceiverURL()
v2.ParseHost(&urlObj)
urlObj.Path = "/k8s/postureReport"
q := urlObj.Query()
q.Add("customerGUID", uuid.MustParse(report.customerGUID).String())
q.Add("clusterName", report.clusterName)
urlObj.RawQuery = q.Encode()
report.eventReceiverURL = &urlObj
}
func hostToString(host *url.URL, reportID string) string {
q := host.Query()
q.Add("reportID", reportID) // TODO - do we add the reportID?
host.RawQuery = q.Encode()
return host.String()
}
func setPaginationReport(postureReport *reporthandling.PostureReport) *reporthandling.PostureReport {
return &reporthandling.PostureReport{
CustomerGUID: postureReport.CustomerGUID,
ClusterName: postureReport.ClusterName,
ReportID: postureReport.ReportID,
ReportGenerationTime: postureReport.ReportGenerationTime,
}
}
func iMetaToResource(obj workloadinterface.IMetadata) *reporthandling.Resource {
return &reporthandling.Resource{
ResourceID: obj.GetID(),
Object: obj.GetObject(),
}
}

View File

@@ -0,0 +1,20 @@
package v1
import (
"net/url"
"testing"
)
func TestHostToString(t *testing.T) {
host := url.URL{
Scheme: "https",
Host: "report.eudev3.cyberarmorsoft.com",
Path: "k8srestapi/v1/postureReport",
RawQuery: "cluster=openrasty_seal-7fvz&customerGUID=5d817063-096f-4d91-b39b-8665240080af",
}
expectedHost := "https://report.eudev3.cyberarmorsoft.com/k8srestapi/v1/postureReport?cluster=openrasty_seal-7fvz&customerGUID=5d817063-096f-4d91-b39b-8665240080af&reportID=ffdd2a00-4dc8-4bf3-b97a-a6d4fd198a41"
receivedHost := hostToString(&host, "ffdd2a00-4dc8-4bf3-b97a-a6d4fd198a41")
if receivedHost != expectedHost {
t.Errorf("%s != %s", receivedHost, expectedHost)
}
}

View File

@@ -0,0 +1,48 @@
package v2
import (
"testing"
"github.com/armosec/kubescape/v2/core/cautils"
"github.com/stretchr/testify/assert"
)
func TestGetURL(t *testing.T) {
// Test submit and registered url
{
reporter := NewReportEventReceiver(
&cautils.ConfigObj{
AccountID: "1234",
Token: "token",
CustomerAdminEMail: "my@email",
ClusterName: "test",
},
"",
)
assert.Equal(t, "https://portal.armo.cloud/configuration-scanning/test?utm_campaign=Submit&utm_medium=CLI&utm_source=GitHub", reporter.GetURL())
}
// Test submit and NOT registered url
{
reporter := NewReportEventReceiver(
&cautils.ConfigObj{
AccountID: "1234",
Token: "token",
ClusterName: "test",
},
"",
)
assert.Equal(t, "https://portal.armo.cloud/account/sign-up?customerGUID=1234&invitationToken=token&utm_campaign=Submit&utm_medium=CLI&utm_source=GitHub", reporter.GetURL())
}
// Test None submit url
{
reporter := NewReportMock(NO_SUBMIT_QUERY, "")
assert.Equal(t, "https://portal.armo.cloud/account/sign-up?utm_source=GitHub&utm_medium=CLI&utm_campaign=no_submit", reporter.GetURL())
}
// Test None report url
{
reporter := NewReportMock("", "")
assert.Equal(t, "https://portal.armo.cloud/account/sign-up", reporter.GetURL())
}
}

View File

@@ -7,27 +7,19 @@ import (
"net/url"
"os"
"github.com/armosec/armoapi-go/apis"
"github.com/armosec/k8s-interface/workloadinterface"
"github.com/armosec/kubescape/v2/core/cautils"
"github.com/armosec/kubescape/v2/core/cautils/getter"
"github.com/armosec/kubescape/v2/core/cautils/logger"
"github.com/armosec/kubescape/v2/core/cautils/logger/helpers"
"github.com/armosec/opa-utils/reporthandling"
"github.com/armosec/opa-utils/reporthandling/results/v1/resourcesresults"
reporthandlingv2 "github.com/armosec/opa-utils/reporthandling/v2"
logger "github.com/dwertent/go-logger"
"github.com/dwertent/go-logger/helpers"
)
const MAX_REPORT_SIZE = 2097152 // 2 MB
type SubmitContext string
const (
SubmitContextScan SubmitContext = "scan"
SubmitContextRBAC SubmitContext = "rbac"
SubmitContextRepository SubmitContext = "repository"
)
type ReportEventReceiver struct {
httpClient *http.Client
clusterName string
@@ -37,10 +29,9 @@ type ReportEventReceiver struct {
customerAdminEMail string
message string
reportID string
submitContext SubmitContext
}
func NewReportEventReceiver(tenantConfig *cautils.ConfigObj, reportID string, submitContext SubmitContext) *ReportEventReceiver {
func NewReportEventReceiver(tenantConfig *cautils.ConfigObj, reportID string) *ReportEventReceiver {
return &ReportEventReceiver{
httpClient: &http.Client{},
clusterName: tenantConfig.ClusterName,
@@ -48,7 +39,6 @@ func NewReportEventReceiver(tenantConfig *cautils.ConfigObj, reportID string, su
token: tenantConfig.Token,
customerAdminEMail: tenantConfig.CustomerAdminEMail,
reportID: reportID,
submitContext: submitContext,
}
}
@@ -84,12 +74,6 @@ func (report *ReportEventReceiver) SetClusterName(clusterName string) {
}
func (report *ReportEventReceiver) prepareReport(opaSessionObj *cautils.OPASessionObj) error {
// All scans whose target is not a cluster, currently their target is a file, which is what the backend expects
// (e.g. local-git, directory, etc)
if opaSessionObj.Metadata.ScanMetadata.ScanningTarget != reporthandlingv2.Cluster {
opaSessionObj.Metadata.ScanMetadata.ScanningTarget = reporthandlingv2.File
}
report.initEventReceiverURL()
host := hostToString(report.eventReceiverURL, report.reportID)
@@ -105,11 +89,17 @@ func (report *ReportEventReceiver) prepareReport(opaSessionObj *cautils.OPASessi
func (report *ReportEventReceiver) GetURL() string {
u := url.URL{}
u.Host = getter.GetArmoAPIConnector().GetFrontendURL()
parseHost(&u)
report.addPathURL(&u)
ParseHost(&u)
q := u.Query()
if report.customerAdminEMail != "" || report.token == "" { // data has been submitted
u.Path = fmt.Sprintf("configuration-scanning/%s", report.clusterName)
} else {
u.Path = "account/sign-up"
q.Add("invitationToken", report.token)
q.Add("customerGUID", report.customerGUID)
}
q.Add("utm_source", "GitHub")
q.Add("utm_medium", "CLI")
q.Add("utm_campaign", "Submit")
@@ -130,7 +120,6 @@ func (report *ReportEventReceiver) sendResources(host string, opaSessionObj *cau
if err := report.setResults(splittedPostureReport, opaSessionObj.ResourcesResult, &counter, &reportCounter, host); err != nil {
return err
}
return report.sendReport(host, splittedPostureReport, reportCounter, true)
}
func (report *ReportEventReceiver) setResults(reportObj *reporthandlingv2.PostureReport, results map[string]resourcesresults.Result, counter, reportCounter *int, host string) error {
@@ -162,11 +151,11 @@ func (report *ReportEventReceiver) setResults(reportObj *reporthandlingv2.Postur
return nil
}
func (report *ReportEventReceiver) setResources(reportObj *reporthandlingv2.PostureReport, allResources map[string]workloadinterface.IMetadata, resourcesSource map[string]reporthandling.Source, counter, reportCounter *int, host string) error {
func (report *ReportEventReceiver) setResources(reportObj *reporthandlingv2.PostureReport, allResources map[string]workloadinterface.IMetadata, resourcesSource map[string]string, counter, reportCounter *int, host string) error {
for resourceID, v := range allResources {
resource := reporthandling.NewResourceIMetadata(v)
if r, ok := resourcesSource[resourceID]; ok {
resource.SetSource(&r)
resource.SetSource(&reporthandling.Source{Path: r})
}
r, err := json.Marshal(resource)
if err != nil {
@@ -195,7 +184,7 @@ func (report *ReportEventReceiver) setResources(reportObj *reporthandlingv2.Post
return nil
}
func (report *ReportEventReceiver) sendReport(host string, postureReport *reporthandlingv2.PostureReport, counter int, isLastReport bool) error {
postureReport.PaginationInfo = apis.PaginationMarks{
postureReport.PaginationInfo = reporthandlingv2.PaginationMarks{
ReportNumber: counter,
IsLastReport: isLastReport,
}
@@ -226,26 +215,3 @@ func (report *ReportEventReceiver) DisplayReportURL() {
cautils.InfoTextDisplay(os.Stderr, fmt.Sprintf("\n\n%s\n\n", report.message))
}
}
func (report *ReportEventReceiver) addPathURL(urlObj *url.URL) {
if report.customerAdminEMail != "" || report.token == "" { // data has been submitted
switch report.submitContext {
case SubmitContextScan:
urlObj.Path = fmt.Sprintf("configuration-scanning/%s", report.clusterName)
case SubmitContextRBAC:
urlObj.Path = "rbac-visualizer"
case SubmitContextRepository:
urlObj.Path = fmt.Sprintf("repositories-scan/%s", report.reportID)
default:
urlObj.Path = "dashboard"
}
return
}
urlObj.Path = "account/sign-up"
q := urlObj.Query()
q.Add("invitationToken", report.token)
q.Add("customerGUID", report.customerGUID)
urlObj.RawQuery = q.Encode()
}

View File

@@ -1,118 +0,0 @@
package v2
import (
"net/url"
"testing"
"github.com/armosec/kubescape/v2/core/cautils"
"github.com/stretchr/testify/assert"
)
func TestReportEventReceiver_addPathURL(t *testing.T) {
tests := []struct {
name string
report *ReportEventReceiver
urlObj *url.URL
want *url.URL
}{
{
name: "add scan path",
report: &ReportEventReceiver{
clusterName: "test",
customerGUID: "FFFF",
token: "XXXX",
customerAdminEMail: "test@test",
reportID: "1234",
submitContext: SubmitContextScan,
},
urlObj: &url.URL{
Scheme: "https",
Host: "localhost:8080",
},
want: &url.URL{
Scheme: "https",
Host: "localhost:8080",
Path: "configuration-scanning/test",
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tt.report.addPathURL(tt.urlObj)
assert.Equal(t, tt.want.String(), tt.urlObj.String())
})
}
}
func TestGetURL(t *testing.T) {
// Test submit and registered url
{
reporter := NewReportEventReceiver(
&cautils.ConfigObj{
AccountID: "1234",
Token: "token",
CustomerAdminEMail: "my@email",
ClusterName: "test",
},
"",
SubmitContextScan,
)
assert.Equal(t, "https://cloud.armosec.io/configuration-scanning/test?utm_campaign=Submit&utm_medium=CLI&utm_source=GitHub", reporter.GetURL())
}
// Test rbac submit and registered url
{
reporter := NewReportEventReceiver(
&cautils.ConfigObj{
AccountID: "1234",
Token: "token",
CustomerAdminEMail: "my@email",
ClusterName: "test",
},
"",
SubmitContextRBAC,
)
assert.Equal(t, "https://cloud.armosec.io/rbac-visualizer?utm_campaign=Submit&utm_medium=CLI&utm_source=GitHub", reporter.GetURL())
}
// Test repo submit and registered url
{
reporter := NewReportEventReceiver(
&cautils.ConfigObj{
AccountID: "1234",
Token: "token",
CustomerAdminEMail: "my@email",
ClusterName: "test",
},
"XXXX",
SubmitContextRepository,
)
assert.Equal(t, "https://cloud.armosec.io/repositories-scan/XXXX?utm_campaign=Submit&utm_medium=CLI&utm_source=GitHub", reporter.GetURL())
}
// Test submit and NOT registered url
{
reporter := NewReportEventReceiver(
&cautils.ConfigObj{
AccountID: "1234",
Token: "token",
ClusterName: "test",
},
"",
SubmitContextScan,
)
assert.Equal(t, "https://cloud.armosec.io/account/sign-up?customerGUID=1234&invitationToken=token&utm_campaign=Submit&utm_medium=CLI&utm_source=GitHub", reporter.GetURL())
}
// Test None submit url
{
reporter := NewReportMock(NO_SUBMIT_QUERY, "")
assert.Equal(t, "https://cloud.armosec.io/account/sign-up?utm_source=GitHub&utm_medium=CLI&utm_campaign=no_submit", reporter.GetURL())
}
// Test None report url
{
reporter := NewReportMock("", "")
assert.Equal(t, "https://cloud.armosec.io/account/sign-up", reporter.GetURL())
}
}

View File

@@ -12,13 +12,11 @@ import (
func (report *ReportEventReceiver) initEventReceiverURL() {
urlObj := url.URL{}
urlObj.Host = getter.GetArmoAPIConnector().GetReportReceiverURL()
parseHost(&urlObj)
ParseHost(&urlObj)
urlObj.Path = "/k8s/v2/postureReport"
q := urlObj.Query()
q.Add("customerGUID", uuid.MustParse(report.customerGUID).String())
q.Add("contextName", report.clusterName)
q.Add("clusterName", report.clusterName) // deprecated
q.Add("clusterName", report.clusterName)
urlObj.RawQuery = q.Encode()

View File

@@ -23,7 +23,7 @@ func maskID(id string) string {
return strings.TrimSuffix(str, sep)
}
func parseHost(urlObj *url.URL) {
func ParseHost(urlObj *url.URL) {
if strings.Contains(urlObj.Host, "http://") {
urlObj.Scheme = "http"
urlObj.Host = strings.Replace(urlObj.Host, "http://", "", 1)

View File

@@ -11,27 +11,27 @@ func TestParseHost(t *testing.T) {
urlObj := url.URL{}
urlObj.Host = "http://localhost:7555"
parseHost(&urlObj)
ParseHost(&urlObj)
assert.Equal(t, "http", urlObj.Scheme)
assert.Equal(t, "localhost:7555", urlObj.Host)
urlObj.Host = "https://localhost:7555"
parseHost(&urlObj)
ParseHost(&urlObj)
assert.Equal(t, "https", urlObj.Scheme)
assert.Equal(t, "localhost:7555", urlObj.Host)
urlObj.Host = "http://portal-dev.armo.cloud"
parseHost(&urlObj)
ParseHost(&urlObj)
assert.Equal(t, "http", urlObj.Scheme)
assert.Equal(t, "portal-dev.armo.cloud", urlObj.Host)
urlObj.Host = "https://portal-dev.armo.cloud"
parseHost(&urlObj)
ParseHost(&urlObj)
assert.Equal(t, "https", urlObj.Scheme)
assert.Equal(t, "portal-dev.armo.cloud", urlObj.Host)
urlObj.Host = "portal-dev.armo.cloud"
parseHost(&urlObj)
ParseHost(&urlObj)
assert.Equal(t, "https", urlObj.Scheme)
assert.Equal(t, "portal-dev.armo.cloud", urlObj.Host)

View File

@@ -4,13 +4,13 @@ import (
"encoding/json"
"github.com/armosec/kubescape/v2/core/cautils"
"github.com/armosec/kubescape/v2/core/cautils/logger"
"github.com/armosec/kubescape/v2/core/cautils/logger/helpers"
"github.com/armosec/kubescape/v2/core/pkg/resultshandling/printer"
printerv1 "github.com/armosec/kubescape/v2/core/pkg/resultshandling/printer/v1"
printerv2 "github.com/armosec/kubescape/v2/core/pkg/resultshandling/printer/v2"
"github.com/armosec/kubescape/v2/core/pkg/resultshandling/reporter"
reporthandlingv2 "github.com/armosec/opa-utils/reporthandling/v2"
logger "github.com/dwertent/go-logger"
"github.com/dwertent/go-logger/helpers"
)
type ResultsHandler struct {
@@ -95,8 +95,6 @@ func NewPrinter(printFormat, formatVersion string, verboseMode bool, viewType ca
return printerv2.NewPrometheusPrinter(verboseMode)
case printer.PdfFormat:
return printerv2.NewPdfPrinter()
case printer.HtmlFormat:
return printerv2.NewHtmlPrinter()
default:
return printerv2.NewPrettyPrinter(verboseMode, formatVersion, viewType)
}

View File

@@ -26,6 +26,8 @@ const (
func (su *ScoreWrapper) Calculate(reportVersion PostureReportVersion) error {
switch reportVersion {
case EPostureReportV1:
return su.scoreUtil.Calculate(su.opaSessionObj.PostureReport.FrameworkReports)
case EPostureReportV2:
return su.scoreUtil.CalculatePostureReportV2(su.opaSessionObj.Report)
}

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