mirror of
https://github.com/kubescape/kubescape.git
synced 2026-04-15 06:58:11 +00:00
Compare commits
75 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4c0e358afc | ||
|
|
9ae21b064a | ||
|
|
0b01eb5ee4 | ||
|
|
feb9e3af10 | ||
|
|
ec30ed8439 | ||
|
|
cda9bb0e45 | ||
|
|
17f1c6b647 | ||
|
|
98079ec1ec | ||
|
|
16aaf9b5f8 | ||
|
|
ff0264ee15 | ||
|
|
bea9bd64a4 | ||
|
|
544a19906e | ||
|
|
208bb25118 | ||
|
|
fdb7e278c1 | ||
|
|
a132a49d57 | ||
|
|
23e73f5e88 | ||
|
|
fdcc5e9a66 | ||
|
|
77e7b1a2cb | ||
|
|
db95da3742 | ||
|
|
dc172a1476 | ||
|
|
8694a929cf | ||
|
|
36b3840362 | ||
|
|
d5fcbe842f | ||
|
|
155349dac0 | ||
|
|
7956a849d9 | ||
|
|
0d1c4cdc02 | ||
|
|
8c833a5df8 | ||
|
|
2c5bb977cb | ||
|
|
cddf7dd8f6 | ||
|
|
306c18147e | ||
|
|
84815eb97d | ||
|
|
890c13a91f | ||
|
|
3887ec8091 | ||
|
|
726b06bb70 | ||
|
|
c8e07c283e | ||
|
|
88a5128c03 | ||
|
|
01f2d3b04f | ||
|
|
fef85a4467 | ||
|
|
e0eadc1f2d | ||
|
|
a881b73e8d | ||
|
|
606f5cfb62 | ||
|
|
40737d545b | ||
|
|
990be3afe8 | ||
|
|
7020c2d025 | ||
|
|
a6497c1252 | ||
|
|
9d528a8075 | ||
|
|
5aec8b6f28 | ||
|
|
830ee27169 | ||
|
|
5f2e5c6f4e | ||
|
|
cf4317b5f6 | ||
|
|
2453aea6f3 | ||
|
|
e95b0f840a | ||
|
|
83680d1207 | ||
|
|
fd135e9e49 | ||
|
|
e47eb9cb4e | ||
|
|
d288fdc7f2 | ||
|
|
9de73dab29 | ||
|
|
f0afc20ec6 | ||
|
|
bf75059347 | ||
|
|
78835a58c4 | ||
|
|
fdccae9a1e | ||
|
|
6d97d42f67 | ||
|
|
46001e4761 | ||
|
|
b4d712fcb1 | ||
|
|
7847a4593b | ||
|
|
b2036e64f1 | ||
|
|
fd0bbcccfe | ||
|
|
7caa47f949 | ||
|
|
06b171901d | ||
|
|
e685fe2b7d | ||
|
|
7177e77a8d | ||
|
|
4cda32771b | ||
|
|
f896b65a87 | ||
|
|
3fff1b750a | ||
|
|
2380317953 |
18
.github/workflows/build.yaml
vendored
18
.github/workflows/build.yaml
vendored
@@ -53,7 +53,7 @@ jobs:
|
||||
KUBESCAPE_SKIP_UPDATE_CHECK: "true"
|
||||
run: python3 smoke_testing/init.py ${PWD}/build/${{ matrix.os }}/kubescape
|
||||
|
||||
- name: Upload Release binaries
|
||||
- name: Upload release binaries
|
||||
id: upload-release-asset
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
@@ -64,6 +64,18 @@ jobs:
|
||||
asset_name: kubescape-${{ matrix.os }}
|
||||
asset_content_type: application/octet-stream
|
||||
|
||||
- name: Upload release hash
|
||||
id: upload-release-hash
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ needs.once.outputs.upload_url }}
|
||||
asset_path: build/${{ matrix.os }}/kubescape.sha256
|
||||
asset_name: kubescape-${{ matrix.os }}-sha256
|
||||
asset_content_type: application/octet-stream
|
||||
|
||||
|
||||
|
||||
build-docker:
|
||||
name: Build docker container, tag and upload to registry
|
||||
@@ -80,14 +92,14 @@ jobs:
|
||||
|
||||
- name: Set image version
|
||||
id: image-version
|
||||
run: echo '::set-output version=IMAGE_VERSION::v2.0.${{ github.run_number }}'
|
||||
run: echo '::set-output name=IMAGE_VERSION::v2.0.${{ github.run_number }}'
|
||||
|
||||
- name: Set image name
|
||||
id: image-name
|
||||
run: echo '::set-output name=IMAGE_NAME::quay.io/${{ github.repository_owner }}/kubescape'
|
||||
|
||||
- name: Build the Docker image
|
||||
run: docker build . --file build/Dockerfile --tag ${{ steps.image-name.outputs.IMAGE_NAME }}:${{ steps.image-version.outputs.IMAGE_VERSION }} --build-arg run_number=${{ github.run_number }}
|
||||
run: docker 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 }}
|
||||
|
||||
- name: Re-Tag Image to latest
|
||||
run: docker tag ${{ steps.image-name.outputs.IMAGE_NAME }}:${{ steps.image-version.outputs.IMAGE_VERSION }} ${{ steps.image-name.outputs.IMAGE_NAME }}:latest
|
||||
|
||||
2
.github/workflows/build_dev.yaml
vendored
2
.github/workflows/build_dev.yaml
vendored
@@ -65,7 +65,7 @@ jobs:
|
||||
run: echo '::set-output name=IMAGE_NAME::quay.io/${{ github.repository_owner }}/kubescape'
|
||||
|
||||
- name: Build the Docker image
|
||||
run: docker build . --file build/Dockerfile --tag ${{ steps.image-name.outputs.IMAGE_NAME }}:${{ steps.image-version.outputs.IMAGE_VERSION }} --build-arg run_number=${{ github.run_number }}
|
||||
run: docker 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 }}
|
||||
|
||||
- name: Login to Quay.io
|
||||
env:
|
||||
|
||||
182
README.md
182
README.md
@@ -3,6 +3,8 @@
|
||||
[](https://github.com/armosec/kubescape/actions/workflows/build.yaml)
|
||||
[](https://goreportcard.com/report/github.com/armosec/kubescape)
|
||||
|
||||
|
||||
|
||||
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) , [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.
|
||||
@@ -47,13 +49,17 @@ We invite you to our team! We are excited about this project and want to return
|
||||
Want to contribute? Want to discuss something? Have an issue?
|
||||
|
||||
* 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/)
|
||||

|
||||
|
||||
|
||||
# Options and examples
|
||||
|
||||
[Kubescape docs](https://hub.armo.cloud/docs)
|
||||
|
||||
## Playground
|
||||
* [Kubescape playground](https://www.katacoda.com/pathaksaiyam/scenarios/kubescape)
|
||||
|
||||
@@ -61,9 +67,11 @@ Want to contribute? Want to discuss something? Have an issue?
|
||||
|
||||
* [Overview](https://youtu.be/wdBkt_0Qhbg)
|
||||
* [How To Secure Kubernetes Clusters With Kubescape And Armo](https://youtu.be/ZATGiDIDBQk)
|
||||
* [Scanning Kubernetes YAML files](https://youtu.be/Ox6DaR7_4ZI)
|
||||
* [Scan Kubernetes YAML files](https://youtu.be/Ox6DaR7_4ZI)
|
||||
* [Scan Kubescape on an air-gapped environment (offline support)](https://youtu.be/IGXL9s37smM)
|
||||
* [Managing exceptions in the Kubescape SaaS version](https://youtu.be/OzpvxGmCR80)
|
||||
* [Configure and run customized frameworks](https://youtu.be/12Sanq_rEhs)
|
||||
* Customize controls configurations. [Kubescape CLI](https://youtu.be/955psg6TVu4), [Kubescape SaaS](https://youtu.be/lIMVSVhH33o)
|
||||
|
||||
## Install on Windows
|
||||
|
||||
@@ -88,29 +96,6 @@ Set-ExecutionPolicy RemoteSigned -scope CurrentUser
|
||||
brew install kubescape
|
||||
```
|
||||
|
||||
## Flags
|
||||
|
||||
| flag | default | description | options |
|
||||
|-----------------------------|---------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------|
|
||||
| `-e`/`--exclude-namespaces` | Scan all namespaces | Namespaces to exclude from scanning. Recommended to exclude `kube-system` and `kube-public` namespaces | |
|
||||
| `--include-namespaces` | Scan all namespaces | Scan specific namespaces | |
|
||||
| `-s`/`--silent` | Display progress messages | Silent progress messages | |
|
||||
| `-t`/`--fail-threshold` | `100` (do not fail) | fail command (return exit code 1) if result is above threshold | `0` -> `100` |
|
||||
| `-f`/`--format` | `pretty-printer` | Output format | `pretty-printer`/`json`/`junit`/`prometheus` |
|
||||
| `-o`/`--output` | print to stdout | Save scan result in file | |
|
||||
| `--use-from` | | Load local framework object from specified path. If not used will download latest |
|
||||
| `--use-artifacts-from` | | Load artifacts (frameworks, control-config, exceptions) from local directory. If not used will download them | |
|
||||
| `--use-default` | `false` | Load local framework object from default path. If not used will download latest | `true`/`false` |
|
||||
| `--exceptions` | | Path to an exceptions obj, [examples](examples/exceptions/README.md). Default will download exceptions from Kubescape SaaS |
|
||||
| `--controls-config` | | Path to a controls-config obj. If not set will download controls-config from ARMO management portal | |
|
||||
| `--submit` | `false` | If set, Kubescape will send the scan results to Armo management portal where you can see the results in a user-friendly UI, choose your preferred compliance framework, check risk results history and trends, manage exceptions, get remediation recommendations and much more. By default the results are not sent | `true`/`false` |
|
||||
| `--keep-local` | `false` | Kubescape will not send scan results to Armo management portal. Use this flag if you ran with the `--submit` flag in the past and you do not want to submit your current scan results | `true`/`false` |
|
||||
| `--account` | | Armo portal account ID. Default will load account ID from configMap or config file | |
|
||||
| `--kube-context` | current-context | Cluster context to scan | |
|
||||
| `--verbose` | `false` | Display all of the input resources and not only failed resources | `true`/`false` |
|
||||
| `--logger` | `info` | Set the logger level | `debug`/`info`/`success`/`warning`/`error`/`fatal` |
|
||||
|
||||
|
||||
## Usage & Examples
|
||||
|
||||
### Examples
|
||||
@@ -118,9 +103,11 @@ Set-ExecutionPolicy RemoteSigned -scope CurrentUser
|
||||
|
||||
#### Scan a running Kubernetes cluster and submit results to the [Kubescape SaaS version](https://portal.armo.cloud/)
|
||||
```
|
||||
kubescape scan --submit
|
||||
kubescape scan --submit --enable-host-scan
|
||||
```
|
||||
|
||||
> 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://portal.armo.cloud/)
|
||||
```
|
||||
kubescape scan framework nsa --submit
|
||||
@@ -164,8 +151,11 @@ kubescape scan --verbose
|
||||
```
|
||||
|
||||
#### Output in `json` format
|
||||
|
||||
> Add the `--format-version v2` flag
|
||||
|
||||
```
|
||||
kubescape scan --format json --output results.json
|
||||
kubescape scan --format json --format-version v2 --output results.json
|
||||
```
|
||||
|
||||
#### Output in `junit xml` format
|
||||
@@ -173,7 +163,14 @@ kubescape scan --format json --output results.json
|
||||
kubescape scan --format junit --output results.xml
|
||||
```
|
||||
|
||||
#### Output in `pdf` format - Contributed by [@alegrey91](https://github.com/alegrey91)
|
||||
|
||||
```
|
||||
kubescape scan --format pdf --output results.pdf
|
||||
```
|
||||
|
||||
#### Output in `prometheus` metrics format - Contributed by [@Joibel](https://github.com/Joibel)
|
||||
|
||||
```
|
||||
kubescape scan --format prometheus
|
||||
```
|
||||
@@ -195,38 +192,40 @@ helm template bitnami/mysql --generate-name --dry-run | kubescape scan -
|
||||
```
|
||||
|
||||
|
||||
### Offline Support
|
||||
### Offline/Air-gaped Environment Support
|
||||
|
||||
[Video tutorial](https://youtu.be/IGXL9s37smM)
|
||||
|
||||
It is possible to run Kubescape offline!
|
||||
|
||||
First download the framework and then scan with `--use-from` flag
|
||||
|
||||
1. Download and save in file, if file name not specified, will save in `~/.kubescape/<framework name>.json`
|
||||
```
|
||||
kubescape download framework nsa --output nsa.json
|
||||
```
|
||||
|
||||
2. Scan using the downloaded framework
|
||||
```
|
||||
kubescape scan framework nsa --use-from nsa.json
|
||||
```
|
||||
|
||||
|
||||
|
||||
You can also download all artifacts to a local path and then load them using `--use-artifacts-from` flag
|
||||
#### Download all artifacts
|
||||
|
||||
1. Download and save in local directory, if path not specified, will save all in `~/.kubescape`
|
||||
```
|
||||
kubescape download artifacts --output path/to/local/dir
|
||||
```
|
||||
2. Copy the downloaded artifacts to the air-gaped/offline environment
|
||||
|
||||
2. Scan using the downloaded artifacts
|
||||
3. Scan using the downloaded artifacts
|
||||
```
|
||||
kubescape scan framework nsa --use-artifacts-from path/to/local/dir
|
||||
kubescape scan --use-artifacts-from path/to/local/dir
|
||||
```
|
||||
|
||||
#### Download a single artifacts
|
||||
|
||||
You can also download a single artifacts and scan with the `--use-from` flag
|
||||
|
||||
1. Download and save in file, if file name not specified, will save in `~/.kubescape/<framework name>.json`
|
||||
```
|
||||
kubescape download framework nsa --output /path/nsa.json
|
||||
```
|
||||
2. Copy the downloaded artifacts to the air-gaped/offline environment
|
||||
|
||||
3. Scan using the downloaded framework
|
||||
```
|
||||
kubescape scan framework nsa --use-from /path/nsa.json
|
||||
```
|
||||
|
||||
|
||||
## Scan Periodically using Helm - 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)
|
||||
@@ -239,106 +238,29 @@ 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
|
||||
|
||||
First, scan your cluster using the `json` format flag: `kubescape scan framework <name> --format json --output path/to/results.json`.
|
||||
> Support forward compatibility by using the `--format-version v2` flag
|
||||
|
||||
Now you can submit the results to the Kubaescape SaaS version -
|
||||
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
|
||||
```
|
||||
# How to build
|
||||
|
||||
## Build using python (3.7^) script
|
||||
|
||||
Kubescape can be built using:
|
||||
|
||||
``` sh
|
||||
python build.py
|
||||
```
|
||||
|
||||
Note: In order to built using the above script, one must set the environment
|
||||
variables in this script:
|
||||
|
||||
+ RELEASE
|
||||
+ ArmoBEServer
|
||||
+ ArmoERServer
|
||||
+ ArmoWebsite
|
||||
+ ArmoAuthServer
|
||||
|
||||
|
||||
## Build using go
|
||||
|
||||
Note: development (and the release process) is done with Go `1.17`
|
||||
|
||||
1. Clone Project
|
||||
```
|
||||
git clone https://github.com/armosec/kubescape.git kubescape && cd "$_"
|
||||
```
|
||||
|
||||
2. Build
|
||||
```
|
||||
go build -o kubescape .
|
||||
```
|
||||
|
||||
3. Run
|
||||
```
|
||||
./kubescape scan --submit --enable-host-scan
|
||||
```
|
||||
|
||||
4. Enjoy :zany_face:
|
||||
|
||||
## Docker Build
|
||||
|
||||
### Build your own Docker image
|
||||
|
||||
1. Clone Project
|
||||
```
|
||||
git clone https://github.com/armosec/kubescape.git kubescape && cd "$_"
|
||||
```
|
||||
|
||||
2. Build
|
||||
```
|
||||
docker build -t kubescape -f build/Dockerfile .
|
||||
```
|
||||
|
||||
|
||||
# Under the hood
|
||||
|
||||
## Tests
|
||||
Kubescape is running the following tests according to what is defined by [Kubernetes Hardening Guidance by NSA and CISA](https://www.nsa.gov/Press-Room/News-Highlights/Article/Article/2716980/nsa-cisa-release-kubernetes-hardening-guidance/)
|
||||
* Non-root containers
|
||||
* Immutable container filesystem
|
||||
* Privileged containers
|
||||
* hostPID, hostIPC privileges
|
||||
* hostNetwork access
|
||||
* allowedHostPaths field
|
||||
* Protecting pod service account tokens
|
||||
* Resource policies
|
||||
* Control plane hardening
|
||||
* Exposed dashboard
|
||||
* Allow privilege escalation
|
||||
* Applications credentials in configuration files
|
||||
* Cluster-admin binding
|
||||
* Exec into container
|
||||
* Dangerous capabilities
|
||||
* Insecure capabilities
|
||||
* Linux hardening
|
||||
* Ingress and Egress blocked
|
||||
* Container hostPort
|
||||
* Network policies
|
||||
* Symlink Exchange Can Allow Host Filesystem Access (CVE-2021-25741)
|
||||
|
||||
|
||||
|
||||
## Technology
|
||||
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 [regos snippets](https://www.openpolicyagent.org/docs/latest/policy-language/) developed by [ARMO](https://www.armosec.io/).
|
||||
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.
|
||||
|
||||
|
||||
38
build.py
38
build.py
@@ -52,22 +52,40 @@ def main():
|
||||
# Create build directory
|
||||
buildDir = getBuildDir()
|
||||
|
||||
ks_file = os.path.join(buildDir, packageName)
|
||||
hash_file = ks_file + ".sha256"
|
||||
|
||||
if not os.path.isdir(buildDir):
|
||||
os.makedirs(buildDir)
|
||||
|
||||
# Build kubescape
|
||||
ldflags = "-w -s -X %s=%s -X %s=%s -X %s=%s -X %s=%s -X %s=%s" \
|
||||
% (buildUrl, releaseVersion, BE_SERVER_CONST, ArmoBEServer,
|
||||
ER_SERVER_CONST, ArmoERServer, WEBSITE_CONST, ArmoWebsite,
|
||||
AUTH_SERVER_CONST, ArmoAuthServer)
|
||||
status = subprocess.call(["go", "build", "-o", "%s/%s" % (buildDir, packageName), "-ldflags" ,ldflags])
|
||||
ldflags = "-w -s"
|
||||
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", "-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)
|
||||
checkStatus(status, "Failed to build kubescape")
|
||||
|
||||
sha1 = hashlib.sha1()
|
||||
with open(buildDir + "/" + packageName, "rb") as kube:
|
||||
sha1.update(kube.read())
|
||||
with open(buildDir + "/" + packageName + ".sha1", "w") as kube_sha:
|
||||
kube_sha.write(sha1.hexdigest())
|
||||
sha256 = hashlib.sha256()
|
||||
with open(ks_file, "rb") as kube:
|
||||
sha256.update(kube.read())
|
||||
with open(hash_file, "w") as kube_sha:
|
||||
hash = sha256.hexdigest()
|
||||
print("kubescape hash: {}, file: {}".format(hash, hash_file))
|
||||
kube_sha.write(sha256.hexdigest())
|
||||
|
||||
print("Build Done")
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
FROM golang:1.17-alpine as builder
|
||||
#ENV GOPROXY=https://goproxy.io,direct
|
||||
|
||||
ARG run_number
|
||||
ARG image_version
|
||||
|
||||
ENV RELEASE=v1.0.${run_number}
|
||||
ENV RELEASE=$image_version
|
||||
|
||||
ENV GO111MODULE=
|
||||
|
||||
@@ -21,7 +21,6 @@ ADD . .
|
||||
RUN python build.py
|
||||
|
||||
RUN ls -ltr build/ubuntu-latest
|
||||
RUN cat /work/build/ubuntu-latest/kubescape.sha1
|
||||
|
||||
FROM alpine
|
||||
COPY --from=builder /work/build/ubuntu-latest/kubescape /usr/bin/kubescape
|
||||
|
||||
13
build/README.md
Normal file
13
build/README.md
Normal file
@@ -0,0 +1,13 @@
|
||||
## Docker Build
|
||||
|
||||
### Build your own Docker image
|
||||
|
||||
1. Clone Project
|
||||
```
|
||||
git clone https://github.com/armosec/kubescape.git kubescape && cd "$_"
|
||||
```
|
||||
|
||||
2. Build
|
||||
```
|
||||
docker build -t kubescape -f build/Dockerfile .
|
||||
```
|
||||
@@ -11,7 +11,6 @@ import (
|
||||
|
||||
"github.com/armosec/k8s-interface/k8sinterface"
|
||||
"github.com/armosec/kubescape/cautils/getter"
|
||||
"github.com/armosec/kubescape/cautils/logger"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
)
|
||||
|
||||
@@ -113,12 +112,6 @@ func NewLocalConfig(backendAPI getter.IBackend, customerGUID, clusterName string
|
||||
lc.backendAPI.SetClientID(lc.configObj.ClientID)
|
||||
lc.backendAPI.SetSecretKey(lc.configObj.SecretKey)
|
||||
|
||||
if lc.configObj.AccountID != "" {
|
||||
if err := lc.SetTenant(); err != nil {
|
||||
logger.L().Error(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
return lc
|
||||
}
|
||||
|
||||
@@ -228,12 +221,6 @@ func NewClusterConfig(k8s *k8sinterface.KubernetesApi, backendAPI getter.IBacken
|
||||
c.backendAPI.SetClientID(c.configObj.ClientID)
|
||||
c.backendAPI.SetSecretKey(c.configObj.SecretKey)
|
||||
|
||||
if c.configObj.AccountID != "" {
|
||||
if err := c.SetTenant(); err != nil {
|
||||
logger.L().Error(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ func NewPolicies() *Policies {
|
||||
|
||||
func (policies *Policies) Set(frameworks []reporthandling.Framework, version string) {
|
||||
for i := range frameworks {
|
||||
if frameworks[i].Name != "" {
|
||||
if frameworks[i].Name != "" && len(frameworks[i].Controls) > 0 {
|
||||
policies.Frameworks = append(policies.Frameworks, frameworks[i].Name)
|
||||
}
|
||||
for j := range frameworks[i].Controls {
|
||||
@@ -30,6 +30,7 @@ func (policies *Policies) Set(frameworks []reporthandling.Framework, version str
|
||||
policies.Controls[frameworks[i].Controls[j].ControlID] = frameworks[i].Controls[j]
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
239
cautils/fileutils.go
Normal file
239
cautils/fileutils.go
Normal file
@@ -0,0 +1,239 @@
|
||||
package cautils
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/armosec/k8s-interface/workloadinterface"
|
||||
"github.com/armosec/kubescape/cautils/logger"
|
||||
"github.com/armosec/opa-utils/objectsenvelopes"
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
var (
|
||||
YAML_PREFIX = []string{".yaml", ".yml"}
|
||||
JSON_PREFIX = []string{".json"}
|
||||
)
|
||||
|
||||
type FileFormat string
|
||||
|
||||
const (
|
||||
YAML_FILE_FORMAT FileFormat = "yaml"
|
||||
JSON_FILE_FORMAT FileFormat = "json"
|
||||
)
|
||||
|
||||
func LoadResourcesFromFiles(inputPatterns []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, nil
|
||||
}
|
||||
|
||||
workloads, errs := loadFiles(files)
|
||||
if len(errs) > 0 {
|
||||
logger.L().Error(fmt.Sprintf("%v", errs))
|
||||
}
|
||||
return workloads, nil
|
||||
}
|
||||
|
||||
func loadFiles(filePaths []string) ([]workloadinterface.IMetadata, []error) {
|
||||
workloads := []workloadinterface.IMetadata{}
|
||||
errs := []error{}
|
||||
for i := range filePaths {
|
||||
f, err := loadFile(filePaths[i])
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
continue
|
||||
}
|
||||
w, e := ReadFile(f, GetFileFormat(filePaths[i]))
|
||||
errs = append(errs, e...)
|
||||
if w != nil {
|
||||
workloads = append(workloads, w...)
|
||||
}
|
||||
}
|
||||
return workloads, errs
|
||||
}
|
||||
|
||||
func loadFile(filePath string) ([]byte, error) {
|
||||
return os.ReadFile(filePath)
|
||||
}
|
||||
func ReadFile(fileContent []byte, fileFromat FileFormat) ([]workloadinterface.IMetadata, []error) {
|
||||
|
||||
switch fileFromat {
|
||||
case YAML_FILE_FORMAT:
|
||||
return readYamlFile(fileContent)
|
||||
case JSON_FILE_FORMAT:
|
||||
return readJsonFile(fileContent)
|
||||
default:
|
||||
return nil, nil // []error{fmt.Errorf("file extension %s not supported", fileFromat)}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func listFiles(patterns []string) ([]string, []error) {
|
||||
files := []string{}
|
||||
errs := []error{}
|
||||
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...)
|
||||
}
|
||||
}
|
||||
}
|
||||
return files, errs
|
||||
}
|
||||
|
||||
func readYamlFile(yamlFile []byte) ([]workloadinterface.IMetadata, []error) {
|
||||
errs := []error{}
|
||||
|
||||
r := bytes.NewReader(yamlFile)
|
||||
dec := yaml.NewDecoder(r)
|
||||
yamlObjs := []workloadinterface.IMetadata{}
|
||||
|
||||
var t interface{}
|
||||
for dec.Decode(&t) == nil {
|
||||
j := convertYamlToJson(t)
|
||||
if j == nil {
|
||||
continue
|
||||
}
|
||||
if obj, ok := j.(map[string]interface{}); ok {
|
||||
if o := objectsenvelopes.NewObject(obj); o != nil {
|
||||
if o.GetKind() == "List" {
|
||||
yamlObjs = append(yamlObjs, handleListObject(o)...)
|
||||
} else {
|
||||
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, errs
|
||||
}
|
||||
|
||||
func readJsonFile(jsonFile []byte) ([]workloadinterface.IMetadata, []error) {
|
||||
workloads := []workloadinterface.IMetadata{}
|
||||
var jsonObj interface{}
|
||||
if err := json.Unmarshal(jsonFile, &jsonObj); err != nil {
|
||||
return workloads, []error{err}
|
||||
}
|
||||
|
||||
convertJsonToWorkload(jsonObj, &workloads)
|
||||
|
||||
return workloads, nil
|
||||
}
|
||||
func convertJsonToWorkload(jsonObj interface{}, workloads *[]workloadinterface.IMetadata) {
|
||||
|
||||
switch x := jsonObj.(type) {
|
||||
case map[string]interface{}:
|
||||
if o := objectsenvelopes.NewObject(x); o != nil {
|
||||
(*workloads) = append(*workloads, o)
|
||||
}
|
||||
case []interface{}:
|
||||
for i := range x {
|
||||
convertJsonToWorkload(x[i], workloads)
|
||||
}
|
||||
}
|
||||
}
|
||||
func convertYamlToJson(i interface{}) interface{} {
|
||||
switch x := i.(type) {
|
||||
case map[interface{}]interface{}:
|
||||
m2 := map[string]interface{}{}
|
||||
for k, v := range x {
|
||||
if s, ok := k.(string); ok {
|
||||
m2[s] = convertYamlToJson(v)
|
||||
}
|
||||
}
|
||||
return m2
|
||||
case []interface{}:
|
||||
for i, v := range x {
|
||||
x[i] = convertYamlToJson(v)
|
||||
}
|
||||
}
|
||||
return i
|
||||
}
|
||||
|
||||
func IsYaml(filePath string) bool {
|
||||
return StringInSlice(YAML_PREFIX, filepath.Ext(filePath)) != ValueNotFound
|
||||
}
|
||||
|
||||
func IsJson(filePath string) bool {
|
||||
return StringInSlice(JSON_PREFIX, filepath.Ext(filePath)) != ValueNotFound
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
if info.IsDir() {
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
return matches, nil
|
||||
}
|
||||
func IsFile(name string) bool {
|
||||
if fi, err := os.Stat(name); err == nil {
|
||||
if fi.Mode().IsRegular() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func GetFileFormat(filePath string) FileFormat {
|
||||
if IsYaml(filePath) {
|
||||
return YAML_FILE_FORMAT
|
||||
} else if IsJson(filePath) {
|
||||
return JSON_FILE_FORMAT
|
||||
} else {
|
||||
return FileFormat(filePath)
|
||||
}
|
||||
}
|
||||
|
||||
// handleListObject handle a List manifest
|
||||
func handleListObject(obj workloadinterface.IMetadata) []workloadinterface.IMetadata {
|
||||
yamlObjs := []workloadinterface.IMetadata{}
|
||||
if i, ok := workloadinterface.InspectMap(obj.GetObject(), "items"); ok && i != nil {
|
||||
if items, ok := i.([]interface{}); ok && items != nil {
|
||||
for item := range items {
|
||||
if m, ok := items[item].(map[string]interface{}); ok && m != nil {
|
||||
if o := objectsenvelopes.NewObject(m); o != nil {
|
||||
yamlObjs = append(yamlObjs, o)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return yamlObjs
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package resourcehandler
|
||||
package cautils
|
||||
|
||||
import (
|
||||
"os"
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
|
||||
"github.com/armosec/armoapi-go/armotypes"
|
||||
"github.com/armosec/kubescape/cautils/logger"
|
||||
"github.com/armosec/kubescape/cautils/logger/helpers"
|
||||
"github.com/armosec/opa-utils/reporthandling"
|
||||
)
|
||||
|
||||
@@ -26,6 +27,11 @@ var (
|
||||
armoFEURL = "portal.armo.cloud"
|
||||
armoAUTHURL = "auth.armo.cloud"
|
||||
|
||||
armoStageERURL = "report-ks.eustage2.cyberarmorsoft.com"
|
||||
armoStageBEURL = "api-stage.armo.cloud"
|
||||
armoStageFEURL = "armoui.eustage2.cyberarmorsoft.com"
|
||||
armoStageAUTHURL = "eggauth.eustage2.cyberarmorsoft.com"
|
||||
|
||||
armoDevERURL = "report.eudev3.cyberarmorsoft.com"
|
||||
armoDevBEURL = "api-dev.armo.cloud"
|
||||
armoDevFEURL = "armoui-dev.eudev3.cyberarmorsoft.com"
|
||||
@@ -50,6 +56,7 @@ type ArmoAPI struct {
|
||||
var globalArmoAPIConnector *ArmoAPI
|
||||
|
||||
func SetARMOAPIConnector(armoAPI *ArmoAPI) {
|
||||
logger.L().Debug("Armo URLs", helpers.String("api", armoAPI.apiURL), helpers.String("auth", armoAPI.authURL), helpers.String("report", armoAPI.erURL), helpers.String("UI", armoAPI.feURL))
|
||||
globalArmoAPIConnector = armoAPI
|
||||
}
|
||||
|
||||
@@ -82,6 +89,17 @@ func NewARMOAPIProd() *ArmoAPI {
|
||||
return apiObj
|
||||
}
|
||||
|
||||
func NewARMOAPIStaging() *ArmoAPI {
|
||||
apiObj := newArmoAPI()
|
||||
|
||||
apiObj.apiURL = armoStageBEURL
|
||||
apiObj.erURL = armoStageERURL
|
||||
apiObj.feURL = armoStageFEURL
|
||||
apiObj.authURL = armoStageAUTHURL
|
||||
|
||||
return apiObj
|
||||
}
|
||||
|
||||
func NewARMOAPICustomized(armoERURL, armoBEURL, armoFEURL, armoAUTHURL string) *ArmoAPI {
|
||||
apiObj := newArmoAPI()
|
||||
|
||||
@@ -108,6 +126,13 @@ func (armoAPI *ArmoAPI) Post(fullURL string, headers map[string]string, body []b
|
||||
return HttpPost(armoAPI.httpClient, fullURL, headers, body)
|
||||
}
|
||||
|
||||
func (armoAPI *ArmoAPI) Delete(fullURL string, headers map[string]string) (string, error) {
|
||||
if headers == nil {
|
||||
headers = make(map[string]string)
|
||||
}
|
||||
armoAPI.appendAuthHeaders(headers)
|
||||
return HttpDelete(armoAPI.httpClient, fullURL, headers)
|
||||
}
|
||||
func (armoAPI *ArmoAPI) Get(fullURL string, headers map[string]string) (string, error) {
|
||||
if headers == nil {
|
||||
headers = make(map[string]string)
|
||||
@@ -275,7 +300,7 @@ func (armoAPI *ArmoAPI) PostExceptions(exceptions []armotypes.PostureExceptionPo
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = armoAPI.Post(armoAPI.postExceptionsURL(), map[string]string{"Content-Type": "application/json"}, ex)
|
||||
_, err = armoAPI.Post(armoAPI.exceptionsURL(""), map[string]string{"Content-Type": "application/json"}, ex)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -283,6 +308,14 @@ func (armoAPI *ArmoAPI) PostExceptions(exceptions []armotypes.PostureExceptionPo
|
||||
return nil
|
||||
}
|
||||
|
||||
func (armoAPI *ArmoAPI) DeleteException(exceptionName string) error {
|
||||
|
||||
_, err := armoAPI.Delete(armoAPI.exceptionsURL(exceptionName), nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func (armoAPI *ArmoAPI) Login() error {
|
||||
if armoAPI.accountID == "" {
|
||||
return fmt.Errorf("failed to login, missing accountID")
|
||||
|
||||
@@ -17,7 +17,7 @@ func (armoAPI *ArmoAPI) getFrameworkURL(frameworkName string) string {
|
||||
u.Host = armoAPI.apiURL
|
||||
u.Path = "api/v1/armoFrameworks"
|
||||
q := u.Query()
|
||||
q.Add("customerGUID", armoAPI.accountID)
|
||||
q.Add("customerGUID", armoAPI.getCustomerGUIDFallBack())
|
||||
if isNativeFramework(frameworkName) {
|
||||
q.Add("frameworkName", strings.ToUpper(frameworkName))
|
||||
} else {
|
||||
@@ -35,7 +35,7 @@ func (armoAPI *ArmoAPI) getListFrameworkURL() string {
|
||||
u.Host = armoAPI.apiURL
|
||||
u.Path = "api/v1/armoFrameworks"
|
||||
q := u.Query()
|
||||
q.Add("customerGUID", armoAPI.accountID)
|
||||
q.Add("customerGUID", armoAPI.getCustomerGUIDFallBack())
|
||||
u.RawQuery = q.Encode()
|
||||
|
||||
return u.String()
|
||||
@@ -47,7 +47,7 @@ func (armoAPI *ArmoAPI) getExceptionsURL(clusterName string) string {
|
||||
u.Path = "api/v1/armoPostureExceptions"
|
||||
|
||||
q := u.Query()
|
||||
q.Add("customerGUID", armoAPI.accountID)
|
||||
q.Add("customerGUID", armoAPI.getCustomerGUIDFallBack())
|
||||
// if clusterName != "" { // TODO - fix customer name support in Armo BE
|
||||
// q.Add("clusterName", clusterName)
|
||||
// }
|
||||
@@ -56,14 +56,18 @@ func (armoAPI *ArmoAPI) getExceptionsURL(clusterName string) string {
|
||||
return u.String()
|
||||
}
|
||||
|
||||
func (armoAPI *ArmoAPI) postExceptionsURL() string {
|
||||
func (armoAPI *ArmoAPI) exceptionsURL(exceptionsPolicyName string) string {
|
||||
u := url.URL{}
|
||||
u.Scheme = "https"
|
||||
u.Host = armoAPI.apiURL
|
||||
u.Path = "api/v1/postureExceptionPolicy"
|
||||
|
||||
q := u.Query()
|
||||
q.Add("customerGUID", armoAPI.accountID)
|
||||
q.Add("customerGUID", armoAPI.getCustomerGUIDFallBack())
|
||||
if exceptionsPolicyName != "" { // for delete
|
||||
q.Add("policyName", exceptionsPolicyName)
|
||||
}
|
||||
|
||||
u.RawQuery = q.Encode()
|
||||
|
||||
return u.String()
|
||||
@@ -76,7 +80,7 @@ func (armoAPI *ArmoAPI) getAccountConfig(clusterName string) string {
|
||||
u.Path = "api/v1/armoCustomerConfiguration"
|
||||
|
||||
q := u.Query()
|
||||
q.Add("customerGUID", armoAPI.accountID)
|
||||
q.Add("customerGUID", armoAPI.getCustomerGUIDFallBack())
|
||||
if clusterName != "" { // TODO - fix customer name support in Armo BE
|
||||
q.Add("clusterName", clusterName)
|
||||
}
|
||||
@@ -156,3 +160,10 @@ func (armoAPI *ArmoAPI) appendAuthHeaders(headers map[string]string) {
|
||||
headers["Cookie"] = fmt.Sprintf("auth=%s", armoAPI.authCookie)
|
||||
}
|
||||
}
|
||||
|
||||
func (armoAPI *ArmoAPI) getCustomerGUIDFallBack() string {
|
||||
if armoAPI.accountID != "" {
|
||||
return armoAPI.accountID
|
||||
}
|
||||
return "11111111-1111-1111-1111-111111111111"
|
||||
}
|
||||
|
||||
@@ -13,11 +13,7 @@ import (
|
||||
)
|
||||
|
||||
func GetDefaultPath(name string) string {
|
||||
defaultfilePath := filepath.Join(DefaultLocalStore, name)
|
||||
if homeDir, err := os.UserHomeDir(); err == nil {
|
||||
defaultfilePath = filepath.Join(homeDir, defaultfilePath)
|
||||
}
|
||||
return defaultfilePath
|
||||
return filepath.Join(DefaultLocalStore, name)
|
||||
}
|
||||
|
||||
func SaveInFile(policy interface{}, pathStr string) error {
|
||||
@@ -51,6 +47,24 @@ func JSONDecoder(origin string) *json.Decoder {
|
||||
return dec
|
||||
}
|
||||
|
||||
func HttpDelete(httpClient *http.Client, fullURL string, headers map[string]string) (string, error) {
|
||||
|
||||
req, err := http.NewRequest("DELETE", fullURL, nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
setHeaders(req, headers)
|
||||
|
||||
resp, err := httpClient.Do(req)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
respStr, err := httpRespToString(resp)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return respStr, nil
|
||||
}
|
||||
func HttpGetter(httpClient *http.Client, fullURL string, headers map[string]string) (string, error) {
|
||||
|
||||
req, err := http.NewRequest("GET", fullURL, nil)
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/armosec/armoapi-go/armotypes"
|
||||
@@ -13,7 +14,15 @@ import (
|
||||
// =======================================================================================================================
|
||||
// ============================================== LoadPolicy =============================================================
|
||||
// =======================================================================================================================
|
||||
const DefaultLocalStore = ".kubescape"
|
||||
var DefaultLocalStore = getCacheDir()
|
||||
|
||||
func getCacheDir() string {
|
||||
defaultDirPath := ".kubescape"
|
||||
if homeDir, err := os.UserHomeDir(); err == nil {
|
||||
defaultDirPath = filepath.Join(homeDir, defaultDirPath)
|
||||
}
|
||||
return defaultDirPath
|
||||
}
|
||||
|
||||
// Load policies from a local repository
|
||||
type LoadPolicy struct {
|
||||
|
||||
@@ -2,9 +2,12 @@ package logger
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/armosec/kubescape/cautils/logger/helpers"
|
||||
"github.com/armosec/kubescape/cautils/logger/prettylogger"
|
||||
"github.com/armosec/kubescape/cautils/logger/zaplogger"
|
||||
"github.com/mattn/go-isatty"
|
||||
)
|
||||
|
||||
type ILogger interface {
|
||||
@@ -26,16 +29,23 @@ var l ILogger
|
||||
|
||||
func L() ILogger {
|
||||
if l == nil {
|
||||
InitializeLogger()
|
||||
InitializeLogger("")
|
||||
}
|
||||
return l
|
||||
}
|
||||
|
||||
func InitializeLogger() {
|
||||
initializeLogger()
|
||||
}
|
||||
func InitializeLogger(loggerName string) {
|
||||
|
||||
func initializeLogger() {
|
||||
// TODO - support zap logger
|
||||
l = prettylogger.NewPrettyLogger()
|
||||
switch strings.ToLower(loggerName) {
|
||||
case "zap":
|
||||
l = zaplogger.NewZapLogger()
|
||||
case "pretty":
|
||||
l = prettylogger.NewPrettyLogger()
|
||||
default:
|
||||
if isatty.IsTerminal(os.Stdout.Fd()) {
|
||||
l = prettylogger.NewPrettyLogger()
|
||||
} else {
|
||||
l = zaplogger.NewZapLogger()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,7 +57,10 @@ func (pl *PrettyLogger) print(level helpers.Level, msg string, details ...helper
|
||||
if !level.Skip(pl.level) {
|
||||
pl.mutex.Lock()
|
||||
prefix(level)(pl.writer, "[%s] ", level.String())
|
||||
message(pl.writer, fmt.Sprintf("%s. %s\n", msg, detailsToString(details)))
|
||||
if d := detailsToString(details); d != "" {
|
||||
msg = fmt.Sprintf("%s. %s", msg, d)
|
||||
}
|
||||
message(pl.writer, fmt.Sprintf("%s\n", msg))
|
||||
pl.mutex.Unlock()
|
||||
}
|
||||
|
||||
@@ -68,7 +71,7 @@ func detailsToString(details []helpers.IDetails) string {
|
||||
for i := range details {
|
||||
s += fmt.Sprintf("%s: %s", details[i].Key(), details[i].Value())
|
||||
if i < len(details)-1 {
|
||||
s += ";"
|
||||
s += "; "
|
||||
}
|
||||
}
|
||||
return s
|
||||
|
||||
@@ -10,20 +10,40 @@ import (
|
||||
|
||||
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: zap.L(),
|
||||
zapL: zapLogger,
|
||||
cfg: cfg,
|
||||
}
|
||||
}
|
||||
|
||||
func (zl *ZapLogger) GetLevel() string { return "" }
|
||||
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 GetWriter() *os.File { return nil }
|
||||
|
||||
func (zl *ZapLogger) SetLevel(level string) error {
|
||||
return nil
|
||||
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)...)
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
"github.com/armosec/opa-utils/reporthandling"
|
||||
"github.com/armosec/rbac-utils/rbacscanner"
|
||||
"github.com/armosec/rbac-utils/rbacutils"
|
||||
uuid "github.com/satori/go.uuid"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type RBACObjects struct {
|
||||
@@ -21,7 +21,7 @@ func NewRBACObjects(scanner *rbacscanner.RbacScannerFromK8sAPI) *RBACObjects {
|
||||
|
||||
func (rbacObjects *RBACObjects) SetResourcesReport() (*reporthandling.PostureReport, error) {
|
||||
return &reporthandling.PostureReport{
|
||||
ReportID: uuid.NewV4().String(),
|
||||
ReportID: uuid.NewString(),
|
||||
ReportGenerationTime: time.Now().UTC(),
|
||||
CustomerGUID: rbacObjects.scanner.CustomerGUID,
|
||||
ClusterName: rbacObjects.scanner.ClusterName,
|
||||
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
"github.com/armosec/opa-utils/reporthandling"
|
||||
helpersv1 "github.com/armosec/opa-utils/reporthandling/helpers/v1"
|
||||
"github.com/armosec/opa-utils/reporthandling/results/v1/reportsummary"
|
||||
"github.com/armosec/opa-utils/score"
|
||||
)
|
||||
|
||||
func ReportV2ToV1(opaSessionObj *OPASessionObj) {
|
||||
@@ -22,7 +21,6 @@ func ReportV2ToV1(opaSessionObj *OPASessionObj) {
|
||||
fwv1 := reporthandling.FrameworkReport{}
|
||||
fwv1.Name = fwv2.GetName()
|
||||
fwv1.Score = fwv2.GetScore()
|
||||
|
||||
fwv1.ControlReports = append(fwv1.ControlReports, controlReportV2ToV1(opaSessionObj, fwv2.GetName(), fwv2.Controls)...)
|
||||
frameworks = append(frameworks, fwv1)
|
||||
|
||||
@@ -30,10 +28,10 @@ func ReportV2ToV1(opaSessionObj *OPASessionObj) {
|
||||
} else {
|
||||
fwv1 := reporthandling.FrameworkReport{}
|
||||
fwv1.Name = ""
|
||||
fwv1.Score = 0
|
||||
|
||||
fwv1.ControlReports = append(fwv1.ControlReports, controlReportV2ToV1(opaSessionObj, "", opaSessionObj.Report.SummaryDetails.Controls)...)
|
||||
frameworks = append(frameworks, fwv1)
|
||||
fwv1.Score = opaSessionObj.Report.SummaryDetails.Score
|
||||
}
|
||||
|
||||
// // remove unused data
|
||||
@@ -49,36 +47,14 @@ func ReportV2ToV1(opaSessionObj *OPASessionObj) {
|
||||
reporthandling.SetUniqueResourcesCounter(&frameworks[f])
|
||||
|
||||
// set default score
|
||||
reporthandling.SetDefaultScore(&frameworks[f])
|
||||
// reporthandling.SetDefaultScore(&frameworks[f])
|
||||
}
|
||||
|
||||
// update score
|
||||
scoreutil := score.NewScore(opaSessionObj.AllResources)
|
||||
scoreutil.Calculate(frameworks)
|
||||
// // update score
|
||||
// scoreutil := score.NewScore(opaSessionObj.AllResources)
|
||||
// scoreutil.Calculate(frameworks)
|
||||
|
||||
opaSessionObj.PostureReport.FrameworkReports = frameworks
|
||||
|
||||
// opaSessionObj.Report.SummaryDetails.Score = 0
|
||||
// for i := range frameworks {
|
||||
// for j := range frameworks[i].ControlReports {
|
||||
// // frameworks[i].ControlReports[j].Score
|
||||
// for w := range opaSessionObj.Report.SummaryDetails.Frameworks {
|
||||
// if opaSessionObj.Report.SummaryDetails.Frameworks[w].Name == frameworks[i].Name {
|
||||
// opaSessionObj.Report.SummaryDetails.Frameworks[w].Score = frameworks[i].Score
|
||||
// }
|
||||
// if c, ok := opaSessionObj.Report.SummaryDetails.Frameworks[w].Controls[frameworks[i].ControlReports[j].ControlID]; ok {
|
||||
// c.Score = frameworks[i].ControlReports[j].Score
|
||||
// opaSessionObj.Report.SummaryDetails.Frameworks[w].Controls[frameworks[i].ControlReports[j].ControlID] = c
|
||||
// }
|
||||
// }
|
||||
// if c, ok := opaSessionObj.Report.SummaryDetails.Controls[frameworks[i].ControlReports[j].ControlID]; ok {
|
||||
// c.Score = frameworks[i].ControlReports[j].Score
|
||||
// opaSessionObj.Report.SummaryDetails.Controls[frameworks[i].ControlReports[j].ControlID] = c
|
||||
// }
|
||||
// }
|
||||
// opaSessionObj.Report.SummaryDetails.Score += opaSessionObj.PostureReport.FrameworkReports[i].Score
|
||||
// }
|
||||
// opaSessionObj.Report.SummaryDetails.Score /= float32(len(opaSessionObj.Report.SummaryDetails.Frameworks))
|
||||
}
|
||||
|
||||
func controlReportV2ToV1(opaSessionObj *OPASessionObj, frameworkName string, controls map[string]reportsummary.ControlSummary) []reporthandling.ControlReport {
|
||||
@@ -88,9 +64,9 @@ func controlReportV2ToV1(opaSessionObj *OPASessionObj, frameworkName string, con
|
||||
crv1.ControlID = controlID
|
||||
crv1.BaseScore = crv2.ScoreFactor
|
||||
crv1.Name = crv2.GetName()
|
||||
crv1.Score = crv2.GetScore()
|
||||
crv1.Control_ID = controlID
|
||||
// crv1.Attributes = crv2.
|
||||
crv1.Score = crv2.GetScore()
|
||||
|
||||
// TODO - add fields
|
||||
crv1.Description = crv2.Description
|
||||
|
||||
@@ -4,12 +4,13 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/armosec/kubescape/cautils/getter"
|
||||
"github.com/armosec/kubescape/cautils/logger"
|
||||
"github.com/armosec/kubescape/cautils/logger/helpers"
|
||||
"github.com/armosec/opa-utils/reporthandling"
|
||||
)
|
||||
|
||||
@@ -52,6 +53,10 @@ func (bpf *BoolPtrFlag) Set(val string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type RootInfo struct {
|
||||
Logger string // logger level
|
||||
CacheDir string // cached dir
|
||||
}
|
||||
type ScanInfo struct {
|
||||
Getters
|
||||
PolicyIdentifier []reporthandling.PolicyIdentifier
|
||||
@@ -63,16 +68,17 @@ type ScanInfo struct {
|
||||
VerboseMode bool // Display all of the input resources and not only failed resources
|
||||
Format string // Format results (table, json, junit ...)
|
||||
Output string // Store results in an output file, Output file name
|
||||
FormatVersion string // Output object can be differnet between versions, this is for testing and backward compatibility
|
||||
ExcludedNamespaces string // used for host sensor namespace
|
||||
IncludeNamespaces string // DEPRECATED?
|
||||
InputPatterns []string // Yaml files input patterns
|
||||
Silent bool // Silent mode - Do not print progress logs
|
||||
FailThreshold uint16 // Failure score threshold
|
||||
FailThreshold float32 // Failure score threshold
|
||||
Submit bool // Submit results to Armo BE
|
||||
HostSensor BoolPtrFlag // Deploy ARMO K8s host sensor to collect data from certain controls
|
||||
HostSensorEnabled BoolPtrFlag // Deploy ARMO K8s host sensor to collect data from certain controls
|
||||
HostSensorYamlPath string // Path to hostsensor file
|
||||
Local bool // Do not submit results
|
||||
Account string // account ID
|
||||
Logger string // logger level
|
||||
KubeContext string // context name
|
||||
FrameworkScan bool // false if scanning control
|
||||
ScanAll bool // true if scan all frameworks
|
||||
@@ -104,7 +110,7 @@ func (scanInfo *ScanInfo) setUseArtifactsFrom() {
|
||||
// set frameworks files
|
||||
files, err := ioutil.ReadDir(scanInfo.UseArtifactsFrom)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
logger.L().Fatal("failed to read files from directory", helpers.String("dir", scanInfo.UseArtifactsFrom), helpers.Error(err))
|
||||
}
|
||||
framework := &reporthandling.Framework{}
|
||||
for _, f := range files {
|
||||
@@ -153,6 +159,11 @@ func (scanInfo *ScanInfo) setOutputFile() {
|
||||
scanInfo.Output += ".xml"
|
||||
}
|
||||
}
|
||||
if scanInfo.Format == "pdf" {
|
||||
if filepath.Ext(scanInfo.Output) != ".pdf" {
|
||||
scanInfo.Output += ".pdf"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (scanInfo *ScanInfo) GetScanningEnvironment() string {
|
||||
|
||||
@@ -98,7 +98,7 @@ func (v *VersionCheckHandler) CheckLatestVersion(versionData *VersionCheckReques
|
||||
|
||||
if latestVersion.ClientUpdate != "" {
|
||||
if BuildNumber != "" && BuildNumber < latestVersion.ClientUpdate {
|
||||
logger.L().Warning(warningMessage(latestVersion.Client, latestVersion.ClientUpdate))
|
||||
logger.L().Warning(warningMessage(latestVersion.ClientUpdate))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -133,6 +133,6 @@ func (v *VersionCheckHandler) getLatestVersion(versionData *VersionCheckRequest)
|
||||
return vResp, nil
|
||||
}
|
||||
|
||||
func warningMessage(kind, release string) string {
|
||||
return fmt.Sprintf("'%s' is not updated to the latest release: '%s'", kind, release)
|
||||
func warningMessage(release string) string {
|
||||
return fmt.Sprintf("current version '%s' is not updated to the latest release: '%s'", BuildNumber, release)
|
||||
}
|
||||
|
||||
7
clihandler/cliconfigdelete.go
Normal file
7
clihandler/cliconfigdelete.go
Normal file
@@ -0,0 +1,7 @@
|
||||
package clihandler
|
||||
|
||||
func CliDelete() error {
|
||||
|
||||
tenant := getTenantConfig("", "", getKubernetesApi()) // change k8sinterface
|
||||
return tenant.DeleteCachedConfig()
|
||||
}
|
||||
@@ -1,7 +1,35 @@
|
||||
package clihandler
|
||||
|
||||
func CliDelete() error {
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
tenant := getTenantConfig("", "", getKubernetesApi()) // change k8sinterface
|
||||
return tenant.DeleteCachedConfig()
|
||||
"github.com/armosec/kubescape/cautils/getter"
|
||||
"github.com/armosec/kubescape/cautils/logger"
|
||||
"github.com/armosec/kubescape/cautils/logger/helpers"
|
||||
)
|
||||
|
||||
func DeleteExceptions(accountID string, exceptions []string) error {
|
||||
|
||||
// load cached config
|
||||
getTenantConfig(accountID, "", getKubernetesApi())
|
||||
|
||||
// login kubescape SaaS
|
||||
armoAPI := getter.GetArmoAPIConnector()
|
||||
if err := armoAPI.Login(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for i := range exceptions {
|
||||
exceptionName := exceptions[i]
|
||||
if exceptionName == "" {
|
||||
continue
|
||||
}
|
||||
logger.L().Info("Deleting exception", helpers.String("name", exceptionName))
|
||||
if err := armoAPI.DeleteException(exceptionName); err != nil {
|
||||
return fmt.Errorf("failed to delete exception '%s', reason: %s", exceptionName, err.Error())
|
||||
}
|
||||
logger.L().Success("Exception deleted successfully")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -77,6 +77,7 @@ func downloadArtifacts(downloadInfo *cautils.DownloadInfo) error {
|
||||
|
||||
func downloadConfigInputs(downloadInfo *cautils.DownloadInfo) error {
|
||||
tenant := getTenantConfig(downloadInfo.Account, "", getKubernetesApi())
|
||||
|
||||
controlsInputsGetter := getConfigInputsGetter(downloadInfo.Name, tenant.GetAccountID(), nil)
|
||||
controlInputs, err := controlsInputsGetter.GetControlsInputs(tenant.GetClusterName())
|
||||
if err != nil {
|
||||
@@ -97,6 +98,7 @@ func downloadConfigInputs(downloadInfo *cautils.DownloadInfo) error {
|
||||
func downloadExceptions(downloadInfo *cautils.DownloadInfo) error {
|
||||
var err error
|
||||
tenant := getTenantConfig(downloadInfo.Account, "", getKubernetesApi())
|
||||
|
||||
exceptionsGetter := getExceptionsGetter("")
|
||||
exceptions := []armotypes.PostureExceptionPolicy{}
|
||||
if tenant.GetAccountID() != "" {
|
||||
@@ -120,6 +122,7 @@ func downloadExceptions(downloadInfo *cautils.DownloadInfo) error {
|
||||
func downloadFramework(downloadInfo *cautils.DownloadInfo) error {
|
||||
|
||||
tenant := getTenantConfig(downloadInfo.Account, "", getKubernetesApi())
|
||||
|
||||
g := getPolicyGetter(nil, tenant.GetAccountID(), true, nil)
|
||||
|
||||
if downloadInfo.Name == "" {
|
||||
@@ -129,11 +132,12 @@ func downloadFramework(downloadInfo *cautils.DownloadInfo) error {
|
||||
return err
|
||||
}
|
||||
for _, fw := range frameworks {
|
||||
err = getter.SaveInFile(fw, filepath.Join(downloadInfo.Path, (strings.ToLower(fw.Name)+".json")))
|
||||
downloadTo := filepath.Join(downloadInfo.Path, (strings.ToLower(fw.Name) + ".json"))
|
||||
err = getter.SaveInFile(fw, downloadTo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
logger.L().Success("Downloaded", helpers.String("artifact", downloadInfo.Target), helpers.String("name", fw.Name), helpers.String("path", filepath.Join(downloadInfo.Path, downloadInfo.FileName)))
|
||||
logger.L().Success("Downloaded", helpers.String("artifact", downloadInfo.Target), helpers.String("name", fw.Name), helpers.String("path", downloadTo))
|
||||
}
|
||||
// return fmt.Errorf("missing framework name")
|
||||
} else {
|
||||
@@ -144,11 +148,12 @@ func downloadFramework(downloadInfo *cautils.DownloadInfo) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = getter.SaveInFile(framework, filepath.Join(downloadInfo.Path, downloadInfo.FileName))
|
||||
downloadTo := filepath.Join(downloadInfo.Path, downloadInfo.FileName)
|
||||
err = getter.SaveInFile(framework, downloadTo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
logger.L().Success("Downloaded", helpers.String("artifact", downloadInfo.Target), helpers.String("name", framework.Name), helpers.String("path", filepath.Join(downloadInfo.Path, downloadInfo.FileName)))
|
||||
logger.L().Success("Downloaded", helpers.String("artifact", downloadInfo.Target), helpers.String("name", framework.Name), helpers.String("path", downloadTo))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -156,6 +161,7 @@ func downloadFramework(downloadInfo *cautils.DownloadInfo) error {
|
||||
func downloadControl(downloadInfo *cautils.DownloadInfo) error {
|
||||
|
||||
tenant := getTenantConfig(downloadInfo.Account, "", getKubernetesApi())
|
||||
|
||||
g := getPolicyGetter(nil, tenant.GetAccountID(), false, nil)
|
||||
|
||||
if downloadInfo.Name == "" {
|
||||
@@ -169,10 +175,11 @@ func downloadControl(downloadInfo *cautils.DownloadInfo) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = getter.SaveInFile(controls, filepath.Join(downloadInfo.Path, downloadInfo.FileName))
|
||||
downloadTo := filepath.Join(downloadInfo.Path, downloadInfo.FileName)
|
||||
err = getter.SaveInFile(controls, downloadTo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
logger.L().Success("Downloaded", helpers.String("artifact", downloadInfo.Target), helpers.String("name", downloadInfo.Name), helpers.String("path", filepath.Join(downloadInfo.Path, downloadInfo.FileName)))
|
||||
logger.L().Success("Downloaded", helpers.String("artifact", downloadInfo.Target), helpers.String("name", downloadInfo.Name), helpers.String("path", downloadTo))
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package clihandler
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
@@ -12,6 +13,12 @@ import (
|
||||
var listFunc = map[string]func(*cliobjects.ListPolicies) ([]string, error){
|
||||
"controls": listControls,
|
||||
"frameworks": listFrameworks,
|
||||
"exceptions": listExceptions,
|
||||
}
|
||||
|
||||
var listFormatFunc = map[string]func(*cliobjects.ListPolicies, []string){
|
||||
"pretty-print": prettyPrintListFormat,
|
||||
"json": jsonListFormat,
|
||||
}
|
||||
|
||||
func ListSupportCommands() []string {
|
||||
@@ -29,12 +36,8 @@ func CliList(listPolicies *cliobjects.ListPolicies) error {
|
||||
}
|
||||
sort.Strings(policies)
|
||||
|
||||
sep := "\n * "
|
||||
usageCmd := strings.TrimSuffix(listPolicies.Target, "s")
|
||||
fmt.Printf("Supported %s:%s%s\n", listPolicies.Target, sep, strings.Join(policies, sep))
|
||||
fmt.Printf("\nUseage:\n")
|
||||
fmt.Printf("$ kubescape scan %s \"name\"\n", usageCmd)
|
||||
fmt.Printf("$ kubescape scan %s \"name-0\",\"name-1\"\n\n", usageCmd)
|
||||
listFormatFunc[listPolicies.Format](listPolicies, policies)
|
||||
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("unknown command to download")
|
||||
@@ -49,6 +52,7 @@ func listFrameworks(listPolicies *cliobjects.ListPolicies) ([]string, error) {
|
||||
|
||||
func listControls(listPolicies *cliobjects.ListPolicies) ([]string, error) {
|
||||
tenant := getTenantConfig(listPolicies.Account, "", getKubernetesApi()) // change k8sinterface
|
||||
|
||||
g := getPolicyGetter(nil, tenant.GetAccountID(), false, nil)
|
||||
l := getter.ListName
|
||||
if listPolicies.ListIDs {
|
||||
@@ -56,3 +60,29 @@ func listControls(listPolicies *cliobjects.ListPolicies) ([]string, error) {
|
||||
}
|
||||
return g.ListControls(l)
|
||||
}
|
||||
|
||||
func listExceptions(listPolicies *cliobjects.ListPolicies) ([]string, error) {
|
||||
// load tenant config
|
||||
getTenantConfig(listPolicies.Account, "", getKubernetesApi())
|
||||
|
||||
var exceptionsNames []string
|
||||
armoAPI := getExceptionsGetter("")
|
||||
exceptions, err := armoAPI.GetExceptions("")
|
||||
if err != nil {
|
||||
return exceptionsNames, err
|
||||
}
|
||||
for i := range exceptions {
|
||||
exceptionsNames = append(exceptionsNames, exceptions[i].Name)
|
||||
}
|
||||
return exceptionsNames, nil
|
||||
}
|
||||
|
||||
func prettyPrintListFormat(listPolicies *cliobjects.ListPolicies, policies []string) {
|
||||
sep := "\n * "
|
||||
fmt.Printf("Supported %s:%s%s\n", listPolicies.Target, sep, strings.Join(policies, sep))
|
||||
}
|
||||
|
||||
func jsonListFormat(listPolicies *cliobjects.ListPolicies, policies []string) {
|
||||
j, _ := json.MarshalIndent(policies, "", " ")
|
||||
fmt.Printf("%s\n", j)
|
||||
}
|
||||
|
||||
@@ -4,4 +4,5 @@ type ListPolicies struct {
|
||||
Target string
|
||||
ListIDs bool
|
||||
Account string
|
||||
Format string
|
||||
}
|
||||
|
||||
@@ -3,3 +3,7 @@ package cliobjects
|
||||
type Submit struct {
|
||||
Account string
|
||||
}
|
||||
|
||||
type Delete struct {
|
||||
Account string
|
||||
}
|
||||
|
||||
@@ -33,7 +33,10 @@ func SubmitExceptions(accountID, excPath string) error {
|
||||
logger.L().Info("submitting exceptions", helpers.String("path", excPath))
|
||||
|
||||
// load cached config
|
||||
getTenantConfig(accountID, "", getKubernetesApi())
|
||||
tenantConfig := getTenantConfig(accountID, "", getKubernetesApi())
|
||||
if err := tenantConfig.SetTenant(); err != nil {
|
||||
logger.L().Error("failed setting account ID", helpers.Error(err))
|
||||
}
|
||||
|
||||
// load exceptions from file
|
||||
loader := getter.NewLoadPolicy([]string{excPath})
|
||||
|
||||
43
clihandler/cmd/completion.go
Normal file
43
clihandler/cmd/completion.go
Normal file
@@ -0,0 +1,43 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var completionCmdExamples = `
|
||||
|
||||
# Enable BASH shell autocompletion
|
||||
echo 'source <(kubescape completion bash)' >> ~/.bashrc
|
||||
|
||||
# Enable ZSH shell autocompletion
|
||||
echo 'source <(kubectl completion zsh)' >> "${fpath[1]}/_kubectl"
|
||||
|
||||
`
|
||||
var completionCmd = &cobra.Command{
|
||||
Use: "completion [bash|zsh|fish|powershell]",
|
||||
Short: "Generate autocompletion script",
|
||||
Long: "To load completions",
|
||||
Example: completionCmdExamples,
|
||||
DisableFlagsInUseLine: true,
|
||||
ValidArgs: []string{"bash", "zsh", "fish", "powershell"},
|
||||
Args: cobra.ExactValidArgs(1),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
switch strings.ToLower(args[0]) {
|
||||
case "bash":
|
||||
cmd.Root().GenBashCompletion(os.Stdout)
|
||||
case "zsh":
|
||||
cmd.Root().GenZshCompletion(os.Stdout)
|
||||
case "fish":
|
||||
cmd.Root().GenFishCompletion(os.Stdout, true)
|
||||
case "powershell":
|
||||
cmd.Root().GenPowerShellCompletionWithDesc(os.Stdout)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(completionCmd)
|
||||
}
|
||||
@@ -2,9 +2,9 @@ package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/armosec/kubescape/cautils/logger"
|
||||
"github.com/armosec/kubescape/clihandler"
|
||||
"github.com/armosec/kubescape/clihandler/cliobjects"
|
||||
"github.com/spf13/cobra"
|
||||
@@ -53,8 +53,7 @@ var configSetCmd = &cobra.Command{
|
||||
return err
|
||||
}
|
||||
if err := clihandler.CliSetConfig(&setConfig); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
||||
os.Exit(1)
|
||||
logger.L().Fatal(err.Error())
|
||||
}
|
||||
return nil
|
||||
},
|
||||
@@ -100,8 +99,7 @@ var configDeleteCmd = &cobra.Command{
|
||||
Long: ``,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if err := clihandler.CliDelete(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
||||
os.Exit(1)
|
||||
logger.L().Fatal(err.Error())
|
||||
}
|
||||
},
|
||||
}
|
||||
@@ -113,8 +111,7 @@ var configViewCmd = &cobra.Command{
|
||||
Long: ``,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if err := clihandler.CliView(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
||||
os.Exit(1)
|
||||
logger.L().Fatal(err.Error())
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
@@ -40,8 +40,10 @@ var controlCmd = &cobra.Command{
|
||||
if len(args) > 0 {
|
||||
controls := strings.Split(args[0], ",")
|
||||
if len(controls) > 1 {
|
||||
if controls[1] == "" {
|
||||
return fmt.Errorf("usage: <control-0>,<control-1>")
|
||||
for _, control := range controls {
|
||||
if control == "" {
|
||||
return fmt.Errorf("usage: <control-0>,<control-1>")
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
||||
57
clihandler/cmd/delete.go
Normal file
57
clihandler/cmd/delete.go
Normal file
@@ -0,0 +1,57 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/armosec/kubescape/cautils/logger"
|
||||
"github.com/armosec/kubescape/clihandler"
|
||||
"github.com/armosec/kubescape/clihandler/cliobjects"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var deleteInfo cliobjects.Delete
|
||||
|
||||
var deleteExceptionsExamples = `
|
||||
# Delete single exception
|
||||
kubescape delete exceptions "exception name"
|
||||
|
||||
# Delete multiple exceptions
|
||||
kubescape delete exceptions "first exception;second exception;third exception"
|
||||
`
|
||||
|
||||
var deleteCmd = &cobra.Command{
|
||||
Use: "delete <command>",
|
||||
Short: "Delete configurations in Kubescape SaaS version",
|
||||
Long: ``,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
},
|
||||
}
|
||||
|
||||
var deleteExceptionsCmd = &cobra.Command{
|
||||
Use: "exceptions <exception name>",
|
||||
Short: "Delete exceptions from Kubescape SaaS version. Run 'kubescape list exceptions' for all exceptions names",
|
||||
Example: deleteExceptionsExamples,
|
||||
Args: func(cmd *cobra.Command, args []string) error {
|
||||
if len(args) != 1 {
|
||||
return fmt.Errorf("missing exceptions names")
|
||||
}
|
||||
return nil
|
||||
},
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
exceptionsNames := strings.Split(args[0], ";")
|
||||
if len(exceptionsNames) == 0 {
|
||||
logger.L().Fatal("missing exceptions names")
|
||||
}
|
||||
if err := clihandler.DeleteExceptions(deleteInfo.Account, exceptionsNames); err != nil {
|
||||
logger.L().Fatal(err.Error())
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
deleteCmd.PersistentFlags().StringVarP(&deleteInfo.Account, "account", "", "", "Armo portal account ID. Default will load account ID from configMap or config file")
|
||||
rootCmd.AddCommand(deleteCmd)
|
||||
|
||||
deleteCmd.AddCommand(deleteExceptionsCmd)
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/armosec/kubescape/cautils"
|
||||
@@ -65,10 +66,16 @@ var downloadCmd = &cobra.Command{
|
||||
}
|
||||
|
||||
func init() {
|
||||
// cobra.OnInitialize(initConfig)
|
||||
cobra.OnInitialize(initDownload)
|
||||
|
||||
rootCmd.AddCommand(downloadCmd)
|
||||
downloadCmd.PersistentFlags().StringVarP(&downloadInfo.Account, "account", "", "", "Armo portal account ID. Default will load account ID from configMap or config file")
|
||||
downloadCmd.Flags().StringVarP(&downloadInfo.Path, "output", "o", "", "Output file. If not specified, will save in `~/.kubescape/<policy name>.json`")
|
||||
|
||||
}
|
||||
|
||||
func initDownload() {
|
||||
if filepath.Ext(downloadInfo.Path) == ".json" {
|
||||
downloadInfo.Path, downloadInfo.FileName = filepath.Split(downloadInfo.Path)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ import (
|
||||
var (
|
||||
frameworkExample = `
|
||||
# Scan all frameworks and submit the results
|
||||
kubescape scan --submit
|
||||
kubescape scan framework all --submit
|
||||
|
||||
# Scan the NSA framework
|
||||
kubescape scan framework nsa
|
||||
@@ -30,15 +30,6 @@ var (
|
||||
# Scan kubernetes YAML manifest files
|
||||
kubescape scan framework nsa *.yaml
|
||||
|
||||
# Scan and save the results in the JSON format
|
||||
kubescape scan --format json --output results.json
|
||||
|
||||
# Save scan results in JSON format
|
||||
kubescape scan --format json --output results.json
|
||||
|
||||
# Display all resources
|
||||
kubescape scan --verbose
|
||||
|
||||
Run 'kubescape list frameworks' for the list of supported frameworks
|
||||
`
|
||||
)
|
||||
@@ -51,8 +42,10 @@ var frameworkCmd = &cobra.Command{
|
||||
if len(args) > 0 {
|
||||
frameworks := strings.Split(args[0], ",")
|
||||
if len(frameworks) > 1 {
|
||||
if frameworks[1] == "" {
|
||||
return fmt.Errorf("usage: <framework-0>,<framework-1>")
|
||||
for _, framework := range frameworks {
|
||||
if framework == "" {
|
||||
return fmt.Errorf("usage: <framework-0>,<framework-1>")
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -2,10 +2,10 @@ package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/armosec/kubescape/cautils"
|
||||
"github.com/armosec/kubescape/cautils/logger"
|
||||
"github.com/armosec/kubescape/clihandler"
|
||||
"github.com/armosec/kubescape/clihandler/cliobjects"
|
||||
"github.com/spf13/cobra"
|
||||
@@ -51,8 +51,7 @@ var listCmd = &cobra.Command{
|
||||
listPolicies.Target = args[0]
|
||||
|
||||
if err := clihandler.CliList(&listPolicies); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
||||
os.Exit(1)
|
||||
logger.L().Fatal(err.Error())
|
||||
}
|
||||
return nil
|
||||
},
|
||||
@@ -62,6 +61,7 @@ func init() {
|
||||
// cobra.OnInitialize(initConfig)
|
||||
|
||||
rootCmd.AddCommand(listCmd)
|
||||
listCmd.PersistentFlags().StringVarP(&listPolicies.Account, "account", "", "", "Armo portal account ID. Default will load account ID from configMap or config file")
|
||||
listCmd.PersistentFlags().StringVar(&listPolicies.Account, "account", "", "Armo portal account ID. Default will load account ID from configMap or config file")
|
||||
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")
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"github.com/armosec/kubescape/cautils"
|
||||
"github.com/armosec/kubescape/cautils/getter"
|
||||
"github.com/armosec/kubescape/cautils/logger"
|
||||
"github.com/armosec/kubescape/cautils/logger/helpers"
|
||||
"github.com/armosec/kubescape/clihandler"
|
||||
"github.com/armosec/kubescape/clihandler/cliinterfaces"
|
||||
reporterv1 "github.com/armosec/kubescape/resultshandling/reporter/v1"
|
||||
@@ -23,6 +24,9 @@ var rabcCmd = &cobra.Command{
|
||||
|
||||
// get config
|
||||
clusterConfig := getTenantConfig(submitInfo.Account, "", k8s)
|
||||
if err := clusterConfig.SetTenant(); err != nil {
|
||||
logger.L().Error("failed setting account ID", helpers.Error(err))
|
||||
}
|
||||
|
||||
// list RBAC
|
||||
rbacObjects := cautils.NewRBACObjects(rbacscanner.NewRbacScannerFromK8sAPI(k8s, clusterConfig.GetAccountID(), clusterConfig.GetClusterName()))
|
||||
|
||||
@@ -8,14 +8,19 @@ import (
|
||||
|
||||
"github.com/armosec/k8s-interface/workloadinterface"
|
||||
"github.com/armosec/kubescape/cautils/logger"
|
||||
"github.com/armosec/kubescape/cautils/logger/helpers"
|
||||
"github.com/armosec/kubescape/clihandler"
|
||||
"github.com/armosec/kubescape/clihandler/cliinterfaces"
|
||||
"github.com/armosec/kubescape/resultshandling/reporter"
|
||||
reporterv1 "github.com/armosec/kubescape/resultshandling/reporter/v1"
|
||||
reporterv2 "github.com/armosec/kubescape/resultshandling/reporter/v2"
|
||||
"github.com/armosec/opa-utils/reporthandling"
|
||||
uuid "github.com/satori/go.uuid"
|
||||
"github.com/google/uuid"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var formatVersion string
|
||||
|
||||
type ResultsObject struct {
|
||||
filePath string
|
||||
customerGUID string
|
||||
@@ -36,10 +41,9 @@ func (resultsObject *ResultsObject) SetResourcesReport() (*reporthandling.Postur
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &reporthandling.PostureReport{
|
||||
FrameworkReports: frameworkReports,
|
||||
ReportID: uuid.NewV4().String(),
|
||||
ReportID: uuid.NewString(),
|
||||
ReportGenerationTime: time.Now().UTC(),
|
||||
CustomerGUID: resultsObject.customerGUID,
|
||||
ClusterName: resultsObject.clusterName,
|
||||
@@ -51,7 +55,7 @@ func (resultsObject *ResultsObject) ListAllResources() (map[string]workloadinter
|
||||
}
|
||||
|
||||
var resultsCmd = &cobra.Command{
|
||||
Use: "results <json file>\nExample:\n$ kubescape submit results path/to/results.json",
|
||||
Use: "results <json file>\nExample:\n$ kubescape submit results path/to/results.json --format-version v2",
|
||||
Short: "Submit a pre scanned results file. The file must be in json format",
|
||||
Long: ``,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
@@ -63,11 +67,21 @@ var resultsCmd = &cobra.Command{
|
||||
|
||||
// get config
|
||||
clusterConfig := getTenantConfig(submitInfo.Account, "", k8s)
|
||||
if err := clusterConfig.SetTenant(); err != nil {
|
||||
logger.L().Error("failed setting account ID", helpers.Error(err))
|
||||
}
|
||||
|
||||
resultsObjects := NewResultsObject(clusterConfig.GetAccountID(), clusterConfig.GetClusterName(), args[0])
|
||||
|
||||
// submit resources
|
||||
r := reporterv1.NewReportEventReceiver(clusterConfig.GetConfigObj())
|
||||
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,
|
||||
@@ -84,6 +98,7 @@ var resultsCmd = &cobra.Command{
|
||||
|
||||
func init() {
|
||||
submitCmd.AddCommand(resultsCmd)
|
||||
resultsCmd.PersistentFlags().StringVar(&formatVersion, "format-version", "v1", "Output object can be differnet between versions, this is for maintaining backward and forward compatibility. Supported:'v1'/'v2'")
|
||||
}
|
||||
|
||||
func loadResultsFromFile(filePath string) ([]reporthandling.FrameworkReport, error) {
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/armosec/kubescape/cautils"
|
||||
"github.com/armosec/kubescape/cautils/getter"
|
||||
"github.com/armosec/kubescape/cautils/logger"
|
||||
"github.com/armosec/kubescape/cautils/logger/helpers"
|
||||
@@ -12,6 +13,8 @@ import (
|
||||
)
|
||||
|
||||
var armoBEURLs = ""
|
||||
var armoBEURLsDep = ""
|
||||
var rootInfo cautils.RootInfo
|
||||
|
||||
const envFlagUsage = "Send report results to specific URL. Format:<ReportReceiver>,<Backend>,<Frontend>.\n\t\tExample:report.armo.cloud,api.armo.cloud,portal.armo.cloud"
|
||||
|
||||
@@ -31,30 +34,60 @@ var ksExamples = `
|
||||
|
||||
var rootCmd = &cobra.Command{
|
||||
Use: "kubescape",
|
||||
Version: cautils.BuildNumber,
|
||||
Short: "Kubescape is a tool for testing Kubernetes security posture",
|
||||
Long: `Kubescape is a tool for testing Kubernetes security posture based on NSA \ MITRE ATT&CK® and other frameworks specifications`,
|
||||
Long: `Based on NSA \ MITRE ATT&CK® and other frameworks specifications`,
|
||||
Example: ksExamples,
|
||||
}
|
||||
|
||||
func Execute() {
|
||||
rootCmd.Execute()
|
||||
}
|
||||
func init() {
|
||||
cobra.OnInitialize(initLogger, initEnvironment)
|
||||
|
||||
flag.CommandLine.StringVar(&armoBEURLs, "environment", "", envFlagUsage)
|
||||
rootCmd.PersistentFlags().StringVar(&armoBEURLs, "environment", "", envFlagUsage)
|
||||
func init() {
|
||||
|
||||
cobra.OnInitialize(initLogger, initLoggerLevel, initEnvironment, initCacheDir)
|
||||
|
||||
rootCmd.PersistentFlags().StringVar(&armoBEURLsDep, "environment", "", envFlagUsage)
|
||||
rootCmd.PersistentFlags().StringVar(&armoBEURLs, "env", "", envFlagUsage)
|
||||
rootCmd.PersistentFlags().MarkDeprecated("environment", "use 'env' instead")
|
||||
rootCmd.PersistentFlags().MarkHidden("environment")
|
||||
rootCmd.PersistentFlags().StringVar(&scanInfo.Logger, "logger", "info", fmt.Sprintf("Logger level. Supported: %s", strings.Join(helpers.SupportedLevels(), "/")))
|
||||
flag.Parse()
|
||||
rootCmd.PersistentFlags().MarkHidden("env")
|
||||
|
||||
rootCmd.PersistentFlags().StringVarP(&rootInfo.Logger, "logger", "l", helpers.InfoLevel.String(), fmt.Sprintf("Logger level. Supported: %s [$KS_LOGGER]", strings.Join(helpers.SupportedLevels(), "/")))
|
||||
rootCmd.PersistentFlags().StringVar(&rootInfo.CacheDir, "cache-dir", getter.DefaultLocalStore, "Cache directory [$KS_CACHE_DIR]")
|
||||
}
|
||||
|
||||
func initLogger() {
|
||||
if err := logger.L().SetLevel(scanInfo.Logger); err != nil {
|
||||
if l := os.Getenv("KS_LOGGER_NAME"); l != "" {
|
||||
logger.InitializeLogger(l)
|
||||
}
|
||||
}
|
||||
func initLoggerLevel() {
|
||||
if rootInfo.Logger != helpers.InfoLevel.String() {
|
||||
} else if l := os.Getenv("KS_LOGGER"); l != "" {
|
||||
rootInfo.Logger = l
|
||||
}
|
||||
if err := logger.L().SetLevel(rootInfo.Logger); err != nil {
|
||||
logger.L().Fatal(fmt.Sprintf("supported levels: %s", strings.Join(helpers.SupportedLevels(), "/")), helpers.Error(err))
|
||||
}
|
||||
}
|
||||
|
||||
func initCacheDir() {
|
||||
if rootInfo.CacheDir != getter.DefaultLocalStore {
|
||||
getter.DefaultLocalStore = rootInfo.CacheDir
|
||||
} else if cacheDir := os.Getenv("KS_CACHE_DIR"); cacheDir != "" {
|
||||
getter.DefaultLocalStore = cacheDir
|
||||
} else {
|
||||
return // using default cache di location
|
||||
}
|
||||
|
||||
logger.L().Debug("cache dir updated", helpers.String("path", getter.DefaultLocalStore))
|
||||
}
|
||||
func initEnvironment() {
|
||||
if armoBEURLsDep != "" {
|
||||
armoBEURLs = armoBEURLsDep
|
||||
}
|
||||
urlSlices := strings.Split(armoBEURLs, ",")
|
||||
if len(urlSlices) != 1 && len(urlSlices) < 3 {
|
||||
logger.L().Fatal("expected at least 3 URLs (report, api, frontend, auth)")
|
||||
@@ -62,8 +95,10 @@ func initEnvironment() {
|
||||
switch len(urlSlices) {
|
||||
case 1:
|
||||
switch urlSlices[0] {
|
||||
case "dev":
|
||||
case "dev", "development":
|
||||
getter.SetARMOAPIConnector(getter.NewARMOAPIDev())
|
||||
case "stage", "staging":
|
||||
getter.SetARMOAPIConnector(getter.NewARMOAPIStaging())
|
||||
case "":
|
||||
getter.SetARMOAPIConnector(getter.NewARMOAPIProd())
|
||||
default:
|
||||
|
||||
@@ -8,11 +8,32 @@ import (
|
||||
|
||||
var scanInfo cautils.ScanInfo
|
||||
|
||||
var scanCmdExamples = `
|
||||
Scan command is for scanning an existing cluster or kubernetes manifest files based on pre-defind frameworks
|
||||
|
||||
# Scan current cluster with all frameworks
|
||||
kubescape scan --submit --enable-host-scan
|
||||
|
||||
# Scan kubernetes YAML manifest files
|
||||
kubescape scan *.yaml
|
||||
|
||||
# Scan and save the results in the JSON format
|
||||
kubescape scan --format json --output results.json
|
||||
|
||||
# Display all resources
|
||||
kubescape scan --verbose
|
||||
|
||||
# Scan different clusters from the kubectl context
|
||||
kubescape scan --kube-context <kubernetes context>
|
||||
|
||||
`
|
||||
|
||||
// scanCmd represents the scan command
|
||||
var scanCmd = &cobra.Command{
|
||||
Use: "scan [command]",
|
||||
Short: "Scan the current running cluster or yaml files",
|
||||
Long: `The action you want to perform`,
|
||||
Use: "scan",
|
||||
Short: "Scan the current running cluster or yaml files",
|
||||
Long: `The action you want to perform`,
|
||||
Example: scanCmdExamples,
|
||||
Args: func(cmd *cobra.Command, args []string) error {
|
||||
if len(args) > 0 {
|
||||
if args[0] != "framework" && args[0] != "control" {
|
||||
@@ -34,7 +55,6 @@ var scanCmd = &cobra.Command{
|
||||
func frameworkInitConfig() {
|
||||
k8sinterface.SetClusterContextName(scanInfo.KubeContext)
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
||||
cobra.OnInitialize(frameworkInitConfig)
|
||||
@@ -47,8 +67,8 @@ func init() {
|
||||
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().Uint16VarP(&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"`)
|
||||
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"`)
|
||||
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")
|
||||
@@ -57,8 +77,15 @@ func init() {
|
||||
scanCmd.PersistentFlags().StringSliceVar(&scanInfo.UseFrom, "use-from", nil, "Load local policy object from specified path. If not used will download latest")
|
||||
scanCmd.PersistentFlags().BoolVarP(&scanInfo.Silent, "silent", "s", false, "Silent progress messages")
|
||||
scanCmd.PersistentFlags().BoolVarP(&scanInfo.Submit, "submit", "", false, "Send the scan results to Armo management portal where you can see the results in a user-friendly UI, choose your preferred compliance framework, check risk results history and trends, manage exceptions, get remediation recommendations and much more. By default the results are not submitted")
|
||||
scanCmd.PersistentFlags().StringVar(&scanInfo.HostSensorYamlPath, "host-scan-yaml", "", "Override default host sensor DaemonSet. Use this flag cautiously")
|
||||
scanCmd.PersistentFlags().StringVar(&scanInfo.FormatVersion, "format-version", "v1", "Output object can be differnet between versions, this is for maintaining backward and forward compatibility. Supported:'v1'/'v2'")
|
||||
|
||||
hostF := scanCmd.PersistentFlags().VarPF(&scanInfo.HostSensor, "enable-host-scan", "", "Deploy ARMO K8s host-sensor daemonset in the scanned cluster. Deleting it right after we collecting the data. Required to collect valueable data from cluster nodes for certain controls")
|
||||
// hidden flags
|
||||
scanCmd.PersistentFlags().MarkHidden("host-scan-yaml") // this flag should be used very cautiously. We prefer users will not use it at all unless the DaemoSet can not run pods on the nodes
|
||||
scanCmd.PersistentFlags().MarkHidden("silent") // this flag should be deprecated since we added the --logger support
|
||||
// scanCmd.PersistentFlags().MarkHidden("format-version") // meant for testing different output approaches and not for common use
|
||||
|
||||
hostF := scanCmd.PersistentFlags().VarPF(&scanInfo.HostSensorEnabled, "enable-host-scan", "", "Deploy ARMO K8s host-sensor daemonset in the scanned cluster. Deleting it right after we collecting the data. Required to collect valueable data from cluster nodes for certain controls. Yaml file: https://raw.githubusercontent.com/armosec/kubescape/master/hostsensorutils/hostsensor.yaml")
|
||||
hostF.NoOptDefVal = "true"
|
||||
hostF.DefValue = "false, for no TTY in stdin"
|
||||
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/armosec/kubescape/cautils"
|
||||
"github.com/armosec/kubescape/cautils/logger"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
@@ -13,7 +15,7 @@ var versionCmd = &cobra.Command{
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
v := cautils.NewIVersionCheckHandler()
|
||||
v.CheckLatestVersion(cautils.NewVersionCheckRequest(cautils.BuildNumber, "", "", "version"))
|
||||
logger.L().Info("Your current version is: " + cautils.BuildNumber)
|
||||
fmt.Fprintln(os.Stdout, "Your current version is: "+cautils.BuildNumber)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
@@ -8,9 +8,6 @@ import (
|
||||
"github.com/armosec/armoapi-go/armotypes"
|
||||
"github.com/armosec/k8s-interface/k8sinterface"
|
||||
"github.com/armosec/kubescape/resultshandling/printer"
|
||||
printerv1 "github.com/armosec/kubescape/resultshandling/printer/v1"
|
||||
|
||||
// printerv2 "github.com/armosec/kubescape/resultshandling/printer/v2"
|
||||
|
||||
"github.com/armosec/kubescape/cautils"
|
||||
"github.com/armosec/kubescape/cautils/getter"
|
||||
@@ -36,6 +33,7 @@ type componentInterfaces struct {
|
||||
|
||||
func getInterfaces(scanInfo *cautils.ScanInfo) componentInterfaces {
|
||||
|
||||
// ================== setup k8s interface object ======================================
|
||||
var k8s *k8sinterface.KubernetesApi
|
||||
if scanInfo.GetScanningEnvironment() == cautils.ScanCluster {
|
||||
k8s = getKubernetesApi()
|
||||
@@ -44,11 +42,20 @@ func getInterfaces(scanInfo *cautils.ScanInfo) componentInterfaces {
|
||||
}
|
||||
}
|
||||
|
||||
// ================== setup tenant object ======================================
|
||||
|
||||
tenantConfig := getTenantConfig(scanInfo.Account, scanInfo.KubeContext, k8s)
|
||||
|
||||
// Set submit behavior AFTER loading tenant config
|
||||
setSubmitBehavior(scanInfo, tenantConfig)
|
||||
|
||||
// ================== version testing ======================================
|
||||
|
||||
v := cautils.NewIVersionCheckHandler()
|
||||
v.CheckLatestVersion(cautils.NewVersionCheckRequest(cautils.BuildNumber, policyIdentifierNames(scanInfo.PolicyIdentifier), "", scanInfo.GetScanningEnvironment()))
|
||||
|
||||
// ================== setup host sensor object ======================================
|
||||
|
||||
hostSensorHandler := getHostSensorHandler(scanInfo, k8s)
|
||||
if err := hostSensorHandler.Init(); err != nil {
|
||||
logger.L().Error("failed to init host sensor", helpers.Error(err))
|
||||
@@ -59,24 +66,28 @@ func getInterfaces(scanInfo *cautils.ScanInfo) componentInterfaces {
|
||||
scanInfo.ExcludedNamespaces = fmt.Sprintf("%s,%s", scanInfo.ExcludedNamespaces, hostSensorHandler.GetNamespace())
|
||||
}
|
||||
|
||||
// ================== setup registry adaptors ======================================
|
||||
|
||||
registryAdaptors, err := resourcehandler.NewRegistryAdaptors()
|
||||
if err != nil {
|
||||
logger.L().Error("failed to initialize registry adaptors", helpers.Error(err))
|
||||
}
|
||||
|
||||
// ================== setup resource collector object ======================================
|
||||
|
||||
resourceHandler := getResourceHandler(scanInfo, tenantConfig, k8s, hostSensorHandler, registryAdaptors)
|
||||
|
||||
// reporting behavior - setup reporter
|
||||
reportHandler := getReporter(tenantConfig, scanInfo.Submit)
|
||||
// ================== setup reporter & printer objects ======================================
|
||||
|
||||
v := cautils.NewIVersionCheckHandler()
|
||||
v.CheckLatestVersion(cautils.NewVersionCheckRequest(cautils.BuildNumber, policyIdentifierNames(scanInfo.PolicyIdentifier), "", scanInfo.GetScanningEnvironment()))
|
||||
// reporting behavior - setup reporter
|
||||
reportHandler := getReporter(tenantConfig, scanInfo.Submit, scanInfo.FrameworkScan, len(scanInfo.InputPatterns) == 0)
|
||||
|
||||
// setup printer
|
||||
printerHandler := printerv1.GetPrinter(scanInfo.Format, scanInfo.VerboseMode)
|
||||
// printerHandler = printerv2.GetPrinter(scanInfo.Format, scanInfo.VerboseMode)
|
||||
printerHandler := resultshandling.NewPrinter(scanInfo.Format, scanInfo.FormatVersion, scanInfo.VerboseMode)
|
||||
printerHandler.SetWriter(scanInfo.Output)
|
||||
|
||||
// ================== return interface ======================================
|
||||
|
||||
return componentInterfaces{
|
||||
tenantConfig: tenantConfig,
|
||||
resourceHandler: resourceHandler,
|
||||
@@ -142,7 +153,7 @@ func ScanCliSetup(scanInfo *cautils.ScanInfo) error {
|
||||
interfaces.report.DisplayReportURL()
|
||||
|
||||
if score > float32(scanInfo.FailThreshold) {
|
||||
return fmt.Errorf("scan risk-score %.2f is above permitted threshold %d", score, scanInfo.FailThreshold)
|
||||
return fmt.Errorf("scan risk-score %.2f is above permitted threshold %.2f", score, scanInfo.FailThreshold)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -8,10 +8,10 @@ import (
|
||||
"github.com/armosec/kubescape/cautils"
|
||||
"github.com/armosec/kubescape/cautils/getter"
|
||||
"github.com/armosec/kubescape/cautils/logger"
|
||||
"github.com/armosec/kubescape/cautils/logger/helpers"
|
||||
"github.com/armosec/kubescape/hostsensorutils"
|
||||
"github.com/armosec/kubescape/resourcehandler"
|
||||
"github.com/armosec/kubescape/resultshandling/reporter"
|
||||
reporterv1 "github.com/armosec/kubescape/resultshandling/reporter/v1"
|
||||
reporterv2 "github.com/armosec/kubescape/resultshandling/reporter/v2"
|
||||
|
||||
"github.com/armosec/opa-utils/reporthandling"
|
||||
@@ -48,16 +48,27 @@ func getRBACHandler(tenantConfig cautils.ITenantConfig, k8s *k8sinterface.Kubern
|
||||
return nil
|
||||
}
|
||||
|
||||
func getReporter(tenantConfig cautils.ITenantConfig, submit bool) reporter.IReport {
|
||||
if submit {
|
||||
// return reporterv1.NewReportEventReceiver(tenantConfig.GetConfigObj())
|
||||
func getReporter(tenantConfig cautils.ITenantConfig, submit, fwScan, clusterScan bool) reporter.IReport {
|
||||
if submit && clusterScan {
|
||||
return reporterv2.NewReportEventReceiver(tenantConfig.GetConfigObj())
|
||||
}
|
||||
return reporterv1.NewReportMock()
|
||||
if tenantConfig.GetAccountID() == "" && fwScan && clusterScan {
|
||||
// Add link only when scanning a cluster using a framework
|
||||
return reporterv2.NewReportMock(reporterv2.NO_SUBMIT_QUERY, "run kubescape with the '--submit' flag")
|
||||
}
|
||||
var message string
|
||||
if !fwScan {
|
||||
message = "Kubescape does not submit scan results when scanning controls"
|
||||
}
|
||||
if !clusterScan {
|
||||
message = "Kubescape will submit scan results only when scanning a cluster (not YAML files)"
|
||||
}
|
||||
return reporterv2.NewReportMock("", message)
|
||||
}
|
||||
|
||||
func getResourceHandler(scanInfo *cautils.ScanInfo, tenantConfig cautils.ITenantConfig, k8s *k8sinterface.KubernetesApi, hostSensorHandler hostsensorutils.IHostSensor, registryAdaptors *resourcehandler.RegistryAdaptors) resourcehandler.IResourceHandler {
|
||||
if len(scanInfo.InputPatterns) > 0 || k8s == nil {
|
||||
// scanInfo.HostSensor.SetBool(false)
|
||||
return resourcehandler.NewFileResourceHandler(scanInfo.InputPatterns, registryAdaptors)
|
||||
}
|
||||
getter.GetArmoAPIConnector()
|
||||
@@ -72,12 +83,12 @@ func getHostSensorHandler(scanInfo *cautils.ScanInfo, k8s *k8sinterface.Kubernet
|
||||
|
||||
hasHostSensorControls := true
|
||||
// we need to determined which controls needs host sensor
|
||||
if scanInfo.HostSensor.Get() == nil && hasHostSensorControls {
|
||||
scanInfo.HostSensor.SetBool(askUserForHostSensor())
|
||||
if scanInfo.HostSensorEnabled.Get() == nil && hasHostSensorControls {
|
||||
scanInfo.HostSensorEnabled.SetBool(askUserForHostSensor())
|
||||
logger.L().Warning("Kubernetes cluster nodes scanning is disabled. This is required to collect valuable data for certain controls. You can enable it using the --enable-host-scan flag")
|
||||
}
|
||||
if hostSensorVal := scanInfo.HostSensor.Get(); hostSensorVal != nil && *hostSensorVal {
|
||||
hostSensorHandler, err := hostsensorutils.NewHostSensorHandler(k8s)
|
||||
if hostSensorVal := scanInfo.HostSensorEnabled.Get(); hostSensorVal != nil && *hostSensorVal {
|
||||
hostSensorHandler, err := hostsensorutils.NewHostSensorHandler(k8s, scanInfo.HostSensorYamlPath)
|
||||
if err != nil {
|
||||
logger.L().Warning(fmt.Sprintf("failed to create host sensor: %s", err.Error()))
|
||||
return &hostsensorutils.HostSensorHandlerMock{}
|
||||
@@ -197,7 +208,7 @@ func getConfigInputsGetter(ControlsInputs string, accountID string, downloadRele
|
||||
|
||||
func getDownloadReleasedPolicy(downloadReleasedPolicy *getter.DownloadReleasedPolicy) getter.IPolicyGetter {
|
||||
if err := downloadReleasedPolicy.SetRegoObjects(); err != nil { // if failed to pull policy, fallback to cache
|
||||
cautils.WarningDisplay(os.Stderr, "Warning: failed to get policies from github release, loading policies from cache\n")
|
||||
logger.L().Warning("failed to get policies from github release, loading policies from cache", helpers.Error(err))
|
||||
return getter.NewLoadPolicy(getDefaultFrameworksPaths())
|
||||
} else {
|
||||
return downloadReleasedPolicy
|
||||
@@ -214,8 +225,8 @@ func getDefaultFrameworksPaths() []string {
|
||||
|
||||
func listFrameworksNames(policyGetter getter.IPolicyGetter) []string {
|
||||
fw, err := policyGetter.ListFrameworks()
|
||||
if err != nil {
|
||||
fw = getDefaultFrameworksPaths()
|
||||
if err == nil {
|
||||
return fw
|
||||
}
|
||||
return fw
|
||||
return getter.NativeFrameworks
|
||||
}
|
||||
|
||||
@@ -1,19 +1,49 @@
|
||||
# Kubescape project roadmap
|
||||
|
||||
## Planning principles
|
||||
|
||||
## Proposals
|
||||
* [Container registry integration](/docs/proposals/container-image-vulnerability-adaptor.md)
|
||||
Kubescape roadmap items are labeled based on where the feature is used and by their maturity.
|
||||
|
||||
## Planed features
|
||||
* Image vulnerablity scanning based controls
|
||||
* Assited remidiation (telling where/what to fix)
|
||||
* Git integration for pull requests
|
||||
* Integration with container registries
|
||||
* Custom controls and regos
|
||||
* API server configuration validation
|
||||
The features serve different stages of the workflow of the users:
|
||||
* development phase (writing Kubernetes manifests) - example: VS Code extension is used while editing YAMLs
|
||||
* CI phase (integrating manifests to GIT repo) - example: GitHub action validating HELM charts on PRs
|
||||
* delivery phase (deploying applications in Kubernetes) - example: running cluster scan after a new deployment
|
||||
* monitoring phase (scanning application in Kubernetes) - example: Prometheus scraping the cluster security risk
|
||||
|
||||
Items in Kubescape roadmap are split to 3 major groups based on the feature planning maturity:
|
||||
|
||||
* Planning - we have tickets open for these issues with more or less clear vision of design
|
||||
* Backlog - feature which were discussed at a high level but are not ready for development
|
||||
* Wishlist - features we are dreaming of 😀 and want to push them gradually forward
|
||||
|
||||
|
||||
## Planning 👷
|
||||
* **Integration with image registries**: we want to expand Kubescape to integrate with differnet image registries and read image vulnerability information from there. This will allow Kubescape to give contextual security information about vulnerabilities [Container registry integration](/docs/proposals/container-image-vulnerability-adaptor.md)
|
||||
* **Kubescape as a microservice**: create a REST API for Kubescape so it can run constantly in a cluster and other components like Prometheus can scrape results
|
||||
* **Kubescape CLI control over cluster operations**: add functionality to Kubescape CLI to trigger operations in Kubescape cluster components (example: trigger images scans and etc.)
|
||||
* **Produce md/HTML reports**: create scan reports for different output formats
|
||||
* **Git integration for pull requests**: create insightful GitHub actions for Kubescape
|
||||
|
||||
## Backlog 📅
|
||||
* **JSON path for HELM charts**: today Kubescape can point to issues in the Kubernetes object, we want to develop this feature so Kubescape will be able to point to the misconfigured source file (HELM)
|
||||
* **Create Kubescape HELM plugin**
|
||||
* **Kubescape based admission controller**: Implement admission controller API for Kubescape microservice to enable users to use Kubescape rules as policies
|
||||
|
||||
## Wishlist 💭
|
||||
* **Integrate with other Kubernetes CLI tools** use Kubescape as a YAML validator for `kubectl` and others.
|
||||
* **Kubernetes audit log integration**: connect Kubescape to audit log stream to enable it to produce more contextual security information based on how the API service is used.
|
||||
* **TUI for Kubescape**: interactive terminal based user interface which helps to analyze and fix issues
|
||||
* **Scanning images with GO for vulnerabilities**: Images scanners cannot determine which packages were used to build Go executables and we want to scan them for vulnerabilities
|
||||
* **Scanning Dockerfile-s for security best practices**: Scan image or Dockerfile to determine whether it is using security best practices (like root containers)
|
||||
* **Custom controls and rules**: enable users to define their own Rego base rules
|
||||
* **More CI/CD tool integration**: Jenkins and etc. 😀
|
||||
|
||||
|
||||
## Completed features 🎓
|
||||
* Kubelet configuration validation
|
||||
|
||||
## Completed features
|
||||
* API server configuration validation
|
||||
* Image vulnerability scanning based controls
|
||||
* Assisted remediation (telling where/what to fix)
|
||||
* Integration with Prometheus
|
||||
* Confiugration of controls (customizing rules for a given environment)
|
||||
* Installation in the cluster for continous monitoring
|
||||
|
||||
BIN
docs/summary.png
BIN
docs/summary.png
Binary file not shown.
|
Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 70 KiB |
@@ -19,11 +19,12 @@ e.g. When a `kube-system` resource fails and it is ok, simply add the resource t
|
||||
* `cluster`: k8s cluster name (usually it is the `current-context`) (case-sensitive, regex supported)
|
||||
* resource labels as key value (case-sensitive, regex NOT supported)
|
||||
* `posturePolicies`- An attribute-based declaration {key: value}
|
||||
* `frameworkName` - Framework names can be find [here](https://github.com/armosec/regolibrary/tree/master/frameworks)
|
||||
* `controlName` - Control names can be find [here](https://github.com/armosec/regolibrary/tree/master/controls)
|
||||
* `controlID` - Not yet supported
|
||||
* `ruleName` - Rule names can be find [here](https://github.com/armosec/regolibrary/tree/master/rules)
|
||||
* `frameworkName` - Framework names can be find [here](https://github.com/armosec/regolibrary/tree/master/frameworks) (regex supported)
|
||||
* `controlName` - Control names can be find [here](https://github.com/armosec/regolibrary/tree/master/controls) (regex supported)
|
||||
* `controlID` - Control ID can be find [here](https://github.com/armosec/regolibrary/tree/master/controls) (regex supported)
|
||||
* `ruleName` - Rule names can be find [here](https://github.com/armosec/regolibrary/tree/master/rules) (regex supported)
|
||||
|
||||
You can find [here](https://github.com/armosec/kubescape/tree/master/examples/exceptions) some examples of exceptions files
|
||||
|
||||
## Usage
|
||||
|
||||
@@ -92,7 +93,7 @@ Here are some examples demonstrating the different ways the exceptions file can
|
||||
|
||||
### Exclude control
|
||||
|
||||
Exclude the ["Allowed hostPath" control](https://github.com/armosec/regolibrary/blob/master/controls/allowedhostpath.json#L2) by declaring the control in the `"posturePolicies"` section.
|
||||
Exclude the [C-0060 control](https://github.com/armosec/regolibrary/blob/master/controls/allowedhostpath.json#L2) by declaring the control ID in the `"posturePolicies"` section.
|
||||
|
||||
The resources
|
||||
|
||||
@@ -114,7 +115,7 @@ The resources
|
||||
],
|
||||
"posturePolicies": [
|
||||
{
|
||||
"controlName": "Allowed hostPath"
|
||||
"controlID": "C-0060"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -24,17 +24,6 @@
|
||||
"namespace": "kube-node-lease"
|
||||
}
|
||||
}
|
||||
],
|
||||
"posturePolicies": [
|
||||
{
|
||||
"frameworkName": "NSA"
|
||||
},
|
||||
{
|
||||
"frameworkName": "MITRE"
|
||||
},
|
||||
{
|
||||
"frameworkName": "ArmoBest"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
28
go.mod
28
go.mod
@@ -3,9 +3,9 @@ module github.com/armosec/kubescape
|
||||
go 1.17
|
||||
|
||||
require (
|
||||
github.com/armosec/armoapi-go v0.0.49
|
||||
github.com/armosec/k8s-interface v0.0.60
|
||||
github.com/armosec/opa-utils v0.0.107
|
||||
github.com/armosec/armoapi-go v0.0.57
|
||||
github.com/armosec/k8s-interface v0.0.63
|
||||
github.com/armosec/opa-utils v0.0.110
|
||||
github.com/armosec/rbac-utils v0.0.14
|
||||
github.com/armosec/utils-go v0.0.3
|
||||
github.com/armosec/utils-k8s-go v0.0.1
|
||||
@@ -13,11 +13,11 @@ require (
|
||||
github.com/enescakir/emoji v1.0.0
|
||||
github.com/fatih/color v1.13.0
|
||||
github.com/francoispqt/gojay v1.2.13
|
||||
github.com/gofrs/uuid v4.1.0+incompatible
|
||||
github.com/google/uuid v1.3.0
|
||||
github.com/johnfercher/maroto v0.34.0
|
||||
github.com/mattn/go-isatty v0.0.14
|
||||
github.com/olekukonko/tablewriter v0.0.5
|
||||
github.com/open-policy-agent/opa v0.33.1
|
||||
github.com/satori/go.uuid v1.2.0
|
||||
github.com/spf13/cobra v1.2.1
|
||||
github.com/stretchr/testify v1.7.0
|
||||
go.uber.org/zap v1.19.1
|
||||
@@ -29,7 +29,8 @@ require (
|
||||
)
|
||||
|
||||
require (
|
||||
cloud.google.com/go v0.81.0 // indirect
|
||||
cloud.google.com/go v0.97.0 // indirect
|
||||
cloud.google.com/go/container v1.0.0 // indirect
|
||||
github.com/Azure/go-autorest v14.2.0+incompatible // indirect
|
||||
github.com/Azure/go-autorest/autorest v0.11.18 // indirect
|
||||
github.com/Azure/go-autorest/autorest/adal v0.9.13 // indirect
|
||||
@@ -50,6 +51,7 @@ require (
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.8.0 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.13.0 // indirect
|
||||
github.com/aws/smithy-go v1.9.1 // indirect
|
||||
github.com/boombuler/barcode v1.0.0 // indirect
|
||||
github.com/coreos/go-oidc v2.2.1+incompatible // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/docker/docker v20.10.9+incompatible // indirect
|
||||
@@ -66,12 +68,13 @@ require (
|
||||
github.com/golang/protobuf v1.5.2 // indirect
|
||||
github.com/google/go-cmp v0.5.6 // indirect
|
||||
github.com/google/gofuzz v1.1.0 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.0.5 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.1.1 // indirect
|
||||
github.com/googleapis/gnostic v0.5.5 // indirect
|
||||
github.com/imdario/mergo v0.3.12 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.0.0 // indirect
|
||||
github.com/jmespath/go-jmespath v0.4.0 // indirect
|
||||
github.com/json-iterator/go v1.1.11 // indirect
|
||||
github.com/jung-kurt/gofpdf v1.4.2 // indirect
|
||||
github.com/mattn/go-colorable v0.1.9 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.9 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
@@ -82,6 +85,7 @@ require (
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/pquerna/cachecontrol v0.1.0 // indirect
|
||||
github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0 // indirect
|
||||
github.com/ruudk/golang-pdf417 v0.0.0-20181029194003-1af4ab5afa58 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
|
||||
@@ -92,15 +96,15 @@ require (
|
||||
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 // indirect
|
||||
golang.org/x/net v0.0.0-20210825183410-e898025ed96a // indirect
|
||||
golang.org/x/oauth2 v0.0.0-20211005180243-6b3c2da341f1 // indirect
|
||||
golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf // indirect
|
||||
golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359 // indirect
|
||||
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d // indirect
|
||||
golang.org/x/text v0.3.6 // indirect
|
||||
golang.org/x/text v0.3.7 // indirect
|
||||
golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect
|
||||
gonum.org/v1/gonum v0.9.1 // indirect
|
||||
google.golang.org/api v0.44.0 // indirect
|
||||
google.golang.org/api v0.59.0 // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c // indirect
|
||||
google.golang.org/grpc v1.38.0 // indirect
|
||||
google.golang.org/genproto v0.0.0-20211027162914-98a5263abeca // indirect
|
||||
google.golang.org/grpc v1.40.0 // indirect
|
||||
google.golang.org/protobuf v1.27.1 // indirect
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
gopkg.in/square/go-jose.v2 v2.6.0 // indirect
|
||||
|
||||
102
go.sum
102
go.sum
@@ -19,14 +19,23 @@ cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKP
|
||||
cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk=
|
||||
cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg=
|
||||
cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8=
|
||||
cloud.google.com/go v0.81.0 h1:at8Tk2zUz63cLPR0JPWm5vp77pEZmzxEQBEfRKn1VV8=
|
||||
cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0=
|
||||
cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY=
|
||||
cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM=
|
||||
cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY=
|
||||
cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ=
|
||||
cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI=
|
||||
cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4=
|
||||
cloud.google.com/go v0.97.0 h1:3DXvAyifywvq64LfkKaMOmkWPS1CikIQdMe2lY9vxU8=
|
||||
cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc=
|
||||
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
|
||||
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
|
||||
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
|
||||
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
|
||||
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
|
||||
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
|
||||
cloud.google.com/go/container v1.0.0 h1:k3IMcHEEJR4pQmj4/gNeUuA4azhW7i2RPX8xGhyneZw=
|
||||
cloud.google.com/go/container v1.0.0/go.mod h1:EQLhTDFhzVXTX6TmjfNqa3/ikbCzjGlZcGY67exUnlY=
|
||||
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
|
||||
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
|
||||
cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=
|
||||
@@ -85,16 +94,17 @@ github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmV
|
||||
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
||||
github.com/armosec/armoapi-go v0.0.2/go.mod h1:vIK17yoKbJRQyZXWWLe3AqfqCRITxW8qmSkApyq5xFs=
|
||||
github.com/armosec/armoapi-go v0.0.23/go.mod h1:iaVVGyc23QGGzAdv4n+szGQg3Rbpixn9yQTU3qWRpaw=
|
||||
github.com/armosec/armoapi-go v0.0.49 h1:b3gvZ5YB5DSEfk8pt7x0705b4Pcuahd3wI/ZmGYmB3Y=
|
||||
github.com/armosec/armoapi-go v0.0.49/go.mod h1:iaVVGyc23QGGzAdv4n+szGQg3Rbpixn9yQTU3qWRpaw=
|
||||
github.com/armosec/armoapi-go v0.0.57 h1:uAYohbzo+ZXZ4FR9R0i6181susGllWI4auUXQQhKQdQ=
|
||||
github.com/armosec/armoapi-go v0.0.57/go.mod h1:U/Axd+D5N00x9Ekr7t+5HXqLCMO+98NfJSVAggqJftI=
|
||||
github.com/armosec/k8s-interface v0.0.8/go.mod h1:xxS+V5QT3gVQTwZyAMMDrYLWGrfKOpiJ7Jfhfa0w9sM=
|
||||
github.com/armosec/k8s-interface v0.0.37/go.mod h1:vHxGWqD/uh6+GQb9Sqv7OGMs+Rvc2dsFVc0XtgRh1ZU=
|
||||
github.com/armosec/k8s-interface v0.0.50/go.mod h1:vHxGWqD/uh6+GQb9Sqv7OGMs+Rvc2dsFVc0XtgRh1ZU=
|
||||
github.com/armosec/k8s-interface v0.0.60 h1:jTCiO15QQbHVuxFQ928rp4srf1rQoUzeybfcbv/cuss=
|
||||
github.com/armosec/k8s-interface v0.0.60/go.mod h1:g0jv/fG+VqpT5ivO6D2gJcJ/w68BiffDz+PcU9YFbL4=
|
||||
github.com/armosec/k8s-interface v0.0.63 h1:RuMqsYcneVUD7HJkMVxjc8N6bpWd6rhJ5K85USMq1Sg=
|
||||
github.com/armosec/k8s-interface v0.0.63/go.mod h1:vwprS8qn/iowd5yf0JHpqDsLA5I8W2muqX9AxKhkb0Q=
|
||||
github.com/armosec/opa-utils v0.0.64/go.mod h1:6tQP8UDq2EvEfSqh8vrUdr/9QVSCG4sJfju1SXQOn4c=
|
||||
github.com/armosec/opa-utils v0.0.107 h1:P+SACquUDMbXcOYIbQ+uzwcdJlrguXOTI42PHEJG2NU=
|
||||
github.com/armosec/opa-utils v0.0.107/go.mod h1:Wc1P4gkB6UQeGW8I76zCuitGGl15Omp0bKw7N0tR9dk=
|
||||
github.com/armosec/opa-utils v0.0.110 h1:qncGcbnYjiGULP3yK+4geRNNpRoWqKXQL+Xg+iXc1cM=
|
||||
github.com/armosec/opa-utils v0.0.110/go.mod h1:Wc1P4gkB6UQeGW8I76zCuitGGl15Omp0bKw7N0tR9dk=
|
||||
github.com/armosec/rbac-utils v0.0.1/go.mod h1:pQ8CBiij8kSKV7aeZm9FMvtZN28VgA7LZcYyTWimq40=
|
||||
github.com/armosec/rbac-utils v0.0.14 h1:CKYKcgqJEXWF2Hen/B1pVGtS3nDAG1wp9dDv6oNtq90=
|
||||
github.com/armosec/rbac-utils v0.0.14/go.mod h1:Ex/IdGWhGv9HZq6Hs8N/ApzCKSIvpNe/ETqDfnuyah0=
|
||||
@@ -141,6 +151,7 @@ github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kB
|
||||
github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
|
||||
github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM=
|
||||
github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
|
||||
github.com/boombuler/barcode v1.0.0 h1:s1TvRnXwL2xJRaccrdcBQMZxq6X7DvsMogtmJeHDdrc=
|
||||
github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
|
||||
github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g=
|
||||
github.com/briandowns/spinner v1.18.0 h1:SJs0maNOs4FqhBwiJ3Gr7Z1D39/rukIVGQvpNZVHVcM=
|
||||
@@ -163,6 +174,7 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||
github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/cockroachdb/datadriven v0.0.0-20200714090401-bf6692d28da5/go.mod h1:h6jFvWxBdQXxjopDMZyH2UVceIRfR84bdzbkoKrsWNo=
|
||||
github.com/cockroachdb/errors v1.2.4/go.mod h1:rQD95gz6FARkaKkQXUksEje/d9a6wBJoCr5oaCLELYA=
|
||||
github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f/go.mod h1:i/u985jwjWRlyHXQbwatDASoW0RMlZ/3i9yJHE2xLkI=
|
||||
@@ -214,6 +226,7 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m
|
||||
github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
|
||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
|
||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
|
||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/evanphx/json-patch v0.5.2/go.mod h1:ZWS5hhDbVDyob71nXKNL0+PWn6ToqBHMikGIFbs31qQ=
|
||||
github.com/evanphx/json-patch v4.11.0+incompatible h1:glyUF9yIYtMHzn8xaKw5rMhdWcwsYV8dZHIq5567/xs=
|
||||
@@ -275,7 +288,6 @@ github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
|
||||
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
|
||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||
github.com/gofrs/uuid v4.1.0+incompatible h1:sIa2eCvUTwgjbqXrPLfNwUf9S3i3mpH1O1atV+iL/Wk=
|
||||
github.com/gofrs/uuid v4.1.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
|
||||
@@ -301,6 +313,7 @@ github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt
|
||||
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
|
||||
github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8=
|
||||
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
@@ -349,6 +362,7 @@ github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/
|
||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
||||
github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
||||
github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk=
|
||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
@@ -360,15 +374,22 @@ github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLe
|
||||
github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/googleapis/gax-go v2.0.0+incompatible h1:j0GKcs05QVmm7yesiZq2+9cxHkNK9YM6zKx4D2qucQU=
|
||||
github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY=
|
||||
github.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg=
|
||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||
github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM=
|
||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||
github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0=
|
||||
github.com/googleapis/gax-go/v2 v2.1.1 h1:dp3bWCh+PPO1zjRRiCSczJav13sBvG4UhNyVTa1KqdU=
|
||||
github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM=
|
||||
github.com/googleapis/gnostic v0.5.1/go.mod h1:6U4PtQXGIEt/Z3h5MAT7FNofLnw9vXk2cUuW7uA/OeU=
|
||||
github.com/googleapis/gnostic v0.5.5 h1:9fHAtK0uDfpveeqqo1hkEZJcFvYXAiCN3UutL8F9xHw=
|
||||
github.com/googleapis/gnostic v0.5.5/go.mod h1:7+EbHbldMins07ALC74bsA81Ovc97DwqyJO1AENw9kA=
|
||||
@@ -416,6 +437,8 @@ github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9Y
|
||||
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
|
||||
github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
|
||||
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
|
||||
github.com/johnfercher/maroto v0.34.0 h1:kmqlO280WbjzeaPn8HtqUE3gooauVwqO/3cmrBtCL4A=
|
||||
github.com/johnfercher/maroto v0.34.0/go.mod h1:UeLY7evCe2Au8KwHFzaSGffKGADEZK+u6O8C74mdudM=
|
||||
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
|
||||
github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8=
|
||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||
@@ -431,6 +454,8 @@ github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7V
|
||||
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
|
||||
github.com/jung-kurt/gofpdf v1.0.0/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes=
|
||||
github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes=
|
||||
github.com/jung-kurt/gofpdf v1.4.2 h1:3u2ojTwxPPu3ysIOc5iTwcECpvkFCAe2RJ/tQrvfLi0=
|
||||
github.com/jung-kurt/gofpdf v1.4.2/go.mod h1:rZsO0wEsunjT/L9stF3fJjYbAHgqNYuQB4B8FWvBck0=
|
||||
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
|
||||
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
|
||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||
@@ -573,9 +598,9 @@ github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6L
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/ruudk/golang-pdf417 v0.0.0-20181029194003-1af4ab5afa58 h1:nlG4Wa5+minh3S9LVFtNoY+GVRiudA2e3EVfcCi3RCA=
|
||||
github.com/ruudk/golang-pdf417 v0.0.0-20181029194003-1af4ab5afa58/go.mod h1:6lfFZQK844Gfx8o5WFuvpxWRwnSoipWe/p622j1v06w=
|
||||
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
||||
github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww=
|
||||
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
|
||||
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
|
||||
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
||||
@@ -636,6 +661,8 @@ github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH
|
||||
github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48=
|
||||
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
@@ -742,6 +769,7 @@ golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6 h1:QE6XYQK6naiK1EPAe1g/ILLxN
|
||||
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
|
||||
golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs=
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190507092727-e4e5bf290fec/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/image v0.0.0-20190910094157-69e4b8554b2a/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/image v0.0.0-20200119044424-58c23975cae1/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
@@ -821,6 +849,7 @@ golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLd
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.0.0-20210423184538-5f58ad60dda6/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
|
||||
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
|
||||
golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20210520170846-37e1c6afe023/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
@@ -841,6 +870,9 @@ golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ
|
||||
golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20211005180243-6b3c2da341f1 h1:B333XXssMuKQeBwiNODx4TupZy7bf4sxFZnN2ZOcvUE=
|
||||
golang.org/x/oauth2 v0.0.0-20211005180243-6b3c2da341f1/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw=
|
||||
@@ -919,13 +951,19 @@ golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210817190340-bfb29a6856f2/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf h1:2ucpDCmfkl8Bd/FsLtiD653Wf96cW37s+iGx93zsu4k=
|
||||
golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359 h1:2B5p2L5IfGiD7+b9BOoRMC6DgObAVZV+Fsp050NqXik=
|
||||
golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d h1:SZxvLBoTP5yHO3Frd4z4vrF+DBX9vMVanchswa69toE=
|
||||
@@ -937,8 +975,9 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
@@ -1007,7 +1046,10 @@ golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4f
|
||||
golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
|
||||
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
@@ -1047,8 +1089,17 @@ google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34q
|
||||
google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=
|
||||
google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU=
|
||||
google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94=
|
||||
google.golang.org/api v0.44.0 h1:URs6qR1lAxDsqWITsQXI4ZkGiYJ5dHtRNiCpfs2OeKA=
|
||||
google.golang.org/api v0.44.0/go.mod h1:EBOGZqzyhtvMDoxwS97ctnh0zUmYY6CxqXsc1AvkYD8=
|
||||
google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo=
|
||||
google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4=
|
||||
google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw=
|
||||
google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU=
|
||||
google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k=
|
||||
google.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE=
|
||||
google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE=
|
||||
google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI=
|
||||
google.golang.org/api v0.59.0 h1:fPfFO7gttlXYo2ALuD3HxJzh8vaF++4youI0BkFL6GE=
|
||||
google.golang.org/api v0.59.0/go.mod h1:sT2boj7M9YJxZzgeZqXogmhfmRWDtPzT31xkieUbuZU=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
@@ -1105,8 +1156,26 @@ google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6D
|
||||
google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A=
|
||||
google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c h1:wtujag7C+4D6KMoulW9YauvK2lgdvCMS260jsqqBXr0=
|
||||
google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A=
|
||||
google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
|
||||
google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
|
||||
google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
|
||||
google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24=
|
||||
google.golang.org/genproto v0.0.0-20210713002101-d411969a0d9a/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k=
|
||||
google.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k=
|
||||
google.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48=
|
||||
google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48=
|
||||
google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w=
|
||||
google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
|
||||
google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
|
||||
google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
|
||||
google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
|
||||
google.golang.org/genproto v0.0.0-20210909211513-a8c4777a87af/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
|
||||
google.golang.org/genproto v0.0.0-20210921142501-181ce0d877f6/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
||||
google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
||||
google.golang.org/genproto v0.0.0-20211008145708-270636b82663/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
||||
google.golang.org/genproto v0.0.0-20211027162914-98a5263abeca h1:+e+aQDO4/c9KaG8PXWHTc6/+Du6kz+BKcXCSnV4SSTE=
|
||||
google.golang.org/genproto v0.0.0-20211027162914-98a5263abeca/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
||||
google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
|
||||
google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=
|
||||
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
|
||||
@@ -1130,8 +1199,13 @@ google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAG
|
||||
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
||||
google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
||||
google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
|
||||
google.golang.org/grpc v1.38.0 h1:/9BgsAsa5nWe26HqOlvlgJnqBuktYOLCgjCPqsa56W0=
|
||||
google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
|
||||
google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
|
||||
google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE=
|
||||
google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE=
|
||||
google.golang.org/grpc v1.40.0 h1:AGJ0Ih4mHjSeibYkFGh1dD9KJ/eOtZ93I6hoHhukQ5Q=
|
||||
google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
|
||||
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
|
||||
@@ -2,29 +2,31 @@ apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
labels:
|
||||
app: host-sensor
|
||||
kubernetes.io/metadata.name: armo-kube-host-sensor
|
||||
tier: armo-kube-host-sensor-control-plane
|
||||
name: armo-kube-host-sensor
|
||||
app: kubescape-host-scanner
|
||||
k8s-app: kubescape-host-scanner
|
||||
kubernetes.io/metadata.name: kubescape-host-scanner
|
||||
tier: kubescape-host-scanner-control-plane
|
||||
name: kubescape-host-scanner
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: DaemonSet
|
||||
metadata:
|
||||
name: host-sensor
|
||||
namespace: armo-kube-host-sensor
|
||||
name: host-scanner
|
||||
namespace: kubescape-host-scanner
|
||||
labels:
|
||||
k8s-app: armo-kube-host-sensor
|
||||
app: host-scanner
|
||||
k8s-app: kubescape-host-scanner
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
name: host-sensor
|
||||
name: host-scanner
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
name: host-sensor
|
||||
name: host-scanner
|
||||
spec:
|
||||
tolerations:
|
||||
# this toleration is to have the daemonset runnable on master nodes
|
||||
# this toleration is to have the DaemonDet runnable on master nodes
|
||||
# remove it if your masters can't run pods
|
||||
- key: node-role.kubernetes.io/master
|
||||
operator: Exists
|
||||
@@ -37,12 +39,13 @@ spec:
|
||||
readOnlyRootFilesystem: true
|
||||
procMount: Unmasked
|
||||
ports:
|
||||
- name: http
|
||||
- name: scanner # Do not change port name
|
||||
hostPort: 7888
|
||||
containerPort: 7888
|
||||
protocol: TCP
|
||||
resources:
|
||||
limits:
|
||||
cpu: 1m
|
||||
cpu: 0.1m
|
||||
memory: 200Mi
|
||||
requests:
|
||||
cpu: 1m
|
||||
|
||||
@@ -2,23 +2,21 @@ package hostsensorutils
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/armosec/k8s-interface/k8sinterface"
|
||||
"github.com/armosec/k8s-interface/workloadinterface"
|
||||
"github.com/armosec/kubescape/cautils"
|
||||
"github.com/armosec/kubescape/cautils/logger"
|
||||
"github.com/armosec/kubescape/cautils/logger/helpers"
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/util/yaml"
|
||||
"k8s.io/apimachinery/pkg/watch"
|
||||
appsapplyv1 "k8s.io/client-go/applyconfigurations/apps/v1"
|
||||
coreapplyv1 "k8s.io/client-go/applyconfigurations/core/v1"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -26,25 +24,36 @@ var (
|
||||
hostSensorYAML string
|
||||
)
|
||||
|
||||
const PortName string = "scanner"
|
||||
|
||||
type HostSensorHandler struct {
|
||||
HostSensorPort int32
|
||||
HostSensorPodNames map[string]string //map from pod names to node names
|
||||
IsReady <-chan bool //readonly chan
|
||||
k8sObj *k8sinterface.KubernetesApi
|
||||
DaemonSet *appsv1.DaemonSet
|
||||
podListLock sync.RWMutex
|
||||
gracePeriod int64
|
||||
HostSensorPort int32
|
||||
HostSensorPodNames map[string]string //map from pod names to node names
|
||||
HostSensorUnscheduledPodNames map[string]string //map from pod names to node names
|
||||
IsReady <-chan bool //readonly chan
|
||||
k8sObj *k8sinterface.KubernetesApi
|
||||
DaemonSet *appsv1.DaemonSet
|
||||
podListLock sync.RWMutex
|
||||
gracePeriod int64
|
||||
}
|
||||
|
||||
func NewHostSensorHandler(k8sObj *k8sinterface.KubernetesApi) (*HostSensorHandler, error) {
|
||||
func NewHostSensorHandler(k8sObj *k8sinterface.KubernetesApi, hostSensorYAMLFile string) (*HostSensorHandler, error) {
|
||||
|
||||
if k8sObj == nil {
|
||||
return nil, fmt.Errorf("nil k8s interface received")
|
||||
}
|
||||
if hostSensorYAMLFile != "" {
|
||||
d, err := loadHostSensorFromFile(hostSensorYAMLFile)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to load host-scan yaml file, reason: %s", err.Error())
|
||||
}
|
||||
hostSensorYAML = d
|
||||
}
|
||||
hsh := &HostSensorHandler{
|
||||
k8sObj: k8sObj,
|
||||
HostSensorPodNames: map[string]string{},
|
||||
gracePeriod: int64(15),
|
||||
k8sObj: k8sObj,
|
||||
HostSensorPodNames: map[string]string{},
|
||||
HostSensorUnscheduledPodNames: map[string]string{},
|
||||
gracePeriod: int64(15),
|
||||
}
|
||||
// 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 {
|
||||
@@ -68,7 +77,7 @@ func (hsh *HostSensorHandler) Init() error {
|
||||
defer cautils.StopSpinner()
|
||||
|
||||
if err := hsh.applyYAML(); err != nil {
|
||||
return fmt.Errorf("in HostSensorHandler init failed to apply YAML: %v", err)
|
||||
return fmt.Errorf("failed to apply host sensor YAML, reason: %v", err)
|
||||
}
|
||||
hsh.populatePodNamesToNodeNames()
|
||||
if err := hsh.checkPodForEachNode(); err != nil {
|
||||
@@ -78,55 +87,83 @@ func (hsh *HostSensorHandler) Init() error {
|
||||
}
|
||||
|
||||
func (hsh *HostSensorHandler) applyYAML() error {
|
||||
dec := yaml.NewDocumentDecoder(io.NopCloser(strings.NewReader(hostSensorYAML)))
|
||||
// apply namespace
|
||||
singleYAMLBytes := make([]byte, 4096)
|
||||
if readLen, err := dec.Read(singleYAMLBytes); err != nil {
|
||||
return fmt.Errorf("failed to read YAML of namespace: %v", err)
|
||||
} else {
|
||||
singleYAMLBytes = singleYAMLBytes[:readLen]
|
||||
workloads, err := cautils.ReadFile([]byte(hostSensorYAML), cautils.YAML_FILE_FORMAT)
|
||||
if len(err) != 0 {
|
||||
return fmt.Errorf("failed to read YAML files, reason: %v", err)
|
||||
}
|
||||
namespaceAC := &coreapplyv1.NamespaceApplyConfiguration{}
|
||||
if err := yaml.Unmarshal(singleYAMLBytes, namespaceAC); err != nil {
|
||||
return fmt.Errorf("failed to Unmarshal YAML of namespace: %v", err)
|
||||
}
|
||||
namespaceName := ""
|
||||
|
||||
if ns, err := hsh.k8sObj.KubernetesClient.CoreV1().Namespaces().Apply(hsh.k8sObj.Context, namespaceAC, metav1.ApplyOptions{
|
||||
FieldManager: "kubescape",
|
||||
}); err != nil {
|
||||
return fmt.Errorf("failed to apply YAML of namespace: %v", err)
|
||||
} else {
|
||||
namespaceName = ns.Name
|
||||
}
|
||||
// apply DaemonSet
|
||||
daemonAC := &appsapplyv1.DaemonSetApplyConfiguration{}
|
||||
singleYAMLBytes = make([]byte, 4096)
|
||||
if readLen, err := dec.Read(singleYAMLBytes); err != nil {
|
||||
if erra := hsh.tearDownNamesapce(namespaceName); erra != nil {
|
||||
err = fmt.Errorf("%v; In addidtion %v", err, erra)
|
||||
// Get namespace name
|
||||
namespaceName := ""
|
||||
for i := range workloads {
|
||||
if workloads[i].GetKind() == "Namespace" {
|
||||
namespaceName = workloads[i].GetName()
|
||||
break
|
||||
}
|
||||
return fmt.Errorf("failed to read YAML of DaemonSet: %v", err)
|
||||
} else {
|
||||
singleYAMLBytes = singleYAMLBytes[:readLen]
|
||||
}
|
||||
if err := yaml.Unmarshal(singleYAMLBytes, daemonAC); err != nil {
|
||||
if erra := hsh.tearDownNamesapce(namespaceName); erra != nil {
|
||||
err = fmt.Errorf("%v; In addidtion %v", err, erra)
|
||||
|
||||
// Update workload data before applying
|
||||
for i := range workloads {
|
||||
w := workloadinterface.NewWorkloadObj(workloads[i].GetObject())
|
||||
if w == nil {
|
||||
return fmt.Errorf("invalid workload: %v", workloads[i].GetObject())
|
||||
}
|
||||
return fmt.Errorf("failed to Unmarshal YAML of DaemonSet: %v", err)
|
||||
}
|
||||
daemonAC.Namespace = &namespaceName
|
||||
if ds, err := hsh.k8sObj.KubernetesClient.AppsV1().DaemonSets(namespaceName).Apply(hsh.k8sObj.Context, daemonAC, metav1.ApplyOptions{
|
||||
FieldManager: "kubescape",
|
||||
}); err != nil {
|
||||
if erra := hsh.tearDownNamesapce(namespaceName); erra != nil {
|
||||
err = fmt.Errorf("%v; In addidtion %v", err, erra)
|
||||
// set namespace in all objects
|
||||
if w.GetKind() != "Namespace" {
|
||||
w.SetNamespace(namespaceName)
|
||||
}
|
||||
// Get container port
|
||||
if w.GetKind() == "DaemonSet" {
|
||||
containers, err := w.GetContainers()
|
||||
if err != nil {
|
||||
if erra := hsh.tearDownNamespace(namespaceName); erra != nil {
|
||||
logger.L().Warning("failed to tear down namespace", helpers.Error(erra))
|
||||
}
|
||||
return fmt.Errorf("container not found in DaemonSet: %v", err)
|
||||
}
|
||||
for j := range containers {
|
||||
for k := range containers[j].Ports {
|
||||
if containers[j].Ports[k].Name == PortName {
|
||||
hsh.HostSensorPort = containers[j].Ports[k].ContainerPort
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Apply workload
|
||||
var newWorkload k8sinterface.IWorkload
|
||||
var e error
|
||||
|
||||
if g, err := hsh.k8sObj.GetWorkload(w.GetNamespace(), w.GetKind(), w.GetName()); err == nil && g != nil {
|
||||
newWorkload, e = hsh.k8sObj.UpdateWorkload(w)
|
||||
} else {
|
||||
newWorkload, e = hsh.k8sObj.CreateWorkload(w)
|
||||
}
|
||||
if e != nil {
|
||||
if erra := hsh.tearDownNamespace(namespaceName); erra != nil {
|
||||
logger.L().Warning("failed to tear down namespace", helpers.Error(erra))
|
||||
}
|
||||
return fmt.Errorf("failed to create/update YAML, reason: %v", e)
|
||||
}
|
||||
|
||||
// Save DaemonSet
|
||||
if newWorkload.GetKind() == "DaemonSet" {
|
||||
b, err := json.Marshal(newWorkload.GetObject())
|
||||
if err != nil {
|
||||
if erra := hsh.tearDownNamespace(namespaceName); erra != nil {
|
||||
logger.L().Warning("failed to tear down namespace", helpers.Error(erra))
|
||||
}
|
||||
return fmt.Errorf("failed to Marshal YAML of DaemonSet, reason: %v", err)
|
||||
}
|
||||
var ds appsv1.DaemonSet
|
||||
if err := json.Unmarshal(b, &ds); err != nil {
|
||||
if erra := hsh.tearDownNamespace(namespaceName); erra != nil {
|
||||
logger.L().Warning("failed to tear down namespace", helpers.Error(erra))
|
||||
}
|
||||
return fmt.Errorf("failed to Unmarshal YAML of DaemonSet, reason: %v", err)
|
||||
}
|
||||
hsh.DaemonSet = &ds
|
||||
}
|
||||
return fmt.Errorf("failed to apply YAML of DaemonSet: %v", err)
|
||||
} else {
|
||||
hsh.HostSensorPort = ds.Spec.Template.Spec.Containers[0].Ports[0].ContainerPort
|
||||
hsh.DaemonSet = ds
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -140,12 +177,17 @@ func (hsh *HostSensorHandler) checkPodForEachNode() error {
|
||||
}
|
||||
hsh.podListLock.RLock()
|
||||
podsNum := len(hsh.HostSensorPodNames)
|
||||
unschedPodNum := len(hsh.HostSensorUnscheduledPodNames)
|
||||
hsh.podListLock.RUnlock()
|
||||
if len(nodesList.Items) == podsNum {
|
||||
if len(nodesList.Items) <= podsNum+unschedPodNum {
|
||||
break
|
||||
}
|
||||
if time.Now().After(deadline) {
|
||||
return fmt.Errorf("host-sensor pods number (%d) differ than nodes number (%d) after deadline exceded", podsNum, len(nodesList.Items))
|
||||
hsh.podListLock.RLock()
|
||||
podsMap := hsh.HostSensorPodNames
|
||||
hsh.podListLock.RUnlock()
|
||||
return fmt.Errorf("host-sensor pods number (%d) differ than nodes number (%d) after deadline exceeded. Kubescape will take data only from the pods below: %v",
|
||||
podsNum, len(nodesList.Items), podsMap)
|
||||
}
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
@@ -156,12 +198,17 @@ func (hsh *HostSensorHandler) checkPodForEachNode() error {
|
||||
func (hsh *HostSensorHandler) populatePodNamesToNodeNames() {
|
||||
|
||||
go func() {
|
||||
watchRes, err := hsh.k8sObj.KubernetesClient.CoreV1().Pods(hsh.DaemonSet.Namespace).Watch(hsh.k8sObj.Context, metav1.ListOptions{
|
||||
var watchRes watch.Interface
|
||||
var err error
|
||||
watchRes, err = hsh.k8sObj.KubernetesClient.CoreV1().Pods(hsh.DaemonSet.Namespace).Watch(hsh.k8sObj.Context, metav1.ListOptions{
|
||||
Watch: true,
|
||||
LabelSelector: fmt.Sprintf("name=%s", hsh.DaemonSet.Spec.Template.Labels["name"]),
|
||||
})
|
||||
if err != nil {
|
||||
logger.L().Error("failed to watch over daemonset pods", helpers.Error(err))
|
||||
logger.L().Error("failed to watch over daemonset pods - are we missing watch pods permissions?", helpers.Error(err))
|
||||
}
|
||||
if watchRes == nil {
|
||||
return
|
||||
}
|
||||
for eve := range watchRes.ResultChan() {
|
||||
pod, ok := eve.Object.(*corev1.Pod)
|
||||
@@ -179,17 +226,38 @@ func (hsh *HostSensorHandler) updatePodInListAtomic(eventType watch.EventType, p
|
||||
|
||||
switch eventType {
|
||||
case watch.Added, watch.Modified:
|
||||
if podObj.Status.Phase == corev1.PodRunning && podObj.Status.ContainerStatuses[0].Ready {
|
||||
if podObj.Status.Phase == corev1.PodRunning && len(podObj.Status.ContainerStatuses) > 0 &&
|
||||
podObj.Status.ContainerStatuses[0].Ready {
|
||||
hsh.HostSensorPodNames[podObj.ObjectMeta.Name] = podObj.Spec.NodeName
|
||||
delete(hsh.HostSensorUnscheduledPodNames, podObj.ObjectMeta.Name)
|
||||
} else {
|
||||
delete(hsh.HostSensorPodNames, podObj.ObjectMeta.Name)
|
||||
if podObj.Status.Phase == corev1.PodPending && len(podObj.Status.Conditions) > 0 &&
|
||||
podObj.Status.Conditions[0].Reason == corev1.PodReasonUnschedulable {
|
||||
nodeName := ""
|
||||
if podObj.Spec.Affinity != nil && podObj.Spec.Affinity.NodeAffinity != nil &&
|
||||
podObj.Spec.Affinity.NodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution != nil &&
|
||||
len(podObj.Spec.Affinity.NodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution.NodeSelectorTerms) > 0 &&
|
||||
len(podObj.Spec.Affinity.NodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution.NodeSelectorTerms[0].MatchFields) > 0 &&
|
||||
len(podObj.Spec.Affinity.NodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution.NodeSelectorTerms[0].MatchFields[0].Values) > 0 {
|
||||
nodeName = podObj.Spec.Affinity.NodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution.NodeSelectorTerms[0].MatchFields[0].Values[0]
|
||||
}
|
||||
logger.L().Warning("One host-sensor pod is unable to schedule on node. We will fail to collect the data from this node",
|
||||
helpers.String("message", podObj.Status.Conditions[0].Message),
|
||||
helpers.String("nodeName", nodeName),
|
||||
helpers.String("podName", podObj.ObjectMeta.Name))
|
||||
if nodeName != "" {
|
||||
hsh.HostSensorUnscheduledPodNames[podObj.ObjectMeta.Name] = nodeName
|
||||
}
|
||||
} else {
|
||||
delete(hsh.HostSensorPodNames, podObj.ObjectMeta.Name)
|
||||
}
|
||||
}
|
||||
default:
|
||||
delete(hsh.HostSensorPodNames, podObj.ObjectMeta.Name)
|
||||
}
|
||||
}
|
||||
|
||||
func (hsh *HostSensorHandler) tearDownNamesapce(namespace string) error {
|
||||
func (hsh *HostSensorHandler) tearDownNamespace(namespace string) error {
|
||||
|
||||
if err := hsh.k8sObj.KubernetesClient.CoreV1().Namespaces().Delete(hsh.k8sObj.Context, namespace, metav1.DeleteOptions{GracePeriodSeconds: &hsh.gracePeriod}); err != nil {
|
||||
return fmt.Errorf("failed to delete host-sensor namespace: %v", err)
|
||||
@@ -202,7 +270,7 @@ func (hsh *HostSensorHandler) TearDown() error {
|
||||
if err := hsh.k8sObj.KubernetesClient.AppsV1().DaemonSets(hsh.GetNamespace()).Delete(hsh.k8sObj.Context, hsh.DaemonSet.Name, metav1.DeleteOptions{GracePeriodSeconds: &hsh.gracePeriod}); err != nil {
|
||||
return fmt.Errorf("failed to delete host-sensor daemonset: %v", err)
|
||||
}
|
||||
if err := hsh.tearDownNamesapce(namespace); err != nil {
|
||||
if err := hsh.tearDownNamespace(namespace); err != nil {
|
||||
return fmt.Errorf("failed to delete host-sensor daemonset: %v", err)
|
||||
}
|
||||
// TODO: wait for termination? may take up to 120 seconds!!!
|
||||
@@ -216,3 +284,12 @@ func (hsh *HostSensorHandler) GetNamespace() string {
|
||||
}
|
||||
return hsh.DaemonSet.Namespace
|
||||
}
|
||||
|
||||
func loadHostSensorFromFile(hostSensorYAMLFile string) (string, error) {
|
||||
dat, err := os.ReadFile(hostSensorYAMLFile)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
// TODO - Add file validation
|
||||
return string(dat), err
|
||||
}
|
||||
|
||||
@@ -157,7 +157,7 @@ func (hsh *HostSensorHandler) CollectResources() ([]hostsensor.HostSensorDataEnv
|
||||
return res, nil
|
||||
}
|
||||
|
||||
logger.L().Info("Accessing host sensor")
|
||||
logger.L().Debug("Accessing host sensor")
|
||||
cautils.StartSpinner()
|
||||
defer cautils.StopSpinner()
|
||||
kcData, err := hsh.GetKubeletConfigurations()
|
||||
@@ -197,6 +197,6 @@ func (hsh *HostSensorHandler) CollectResources() ([]hostsensor.HostSensorDataEnv
|
||||
res = append(res, kcData...)
|
||||
// finish
|
||||
|
||||
logger.L().Success("Read host information from host sensor")
|
||||
logger.L().Debug("Done reading information from host sensor")
|
||||
return res, nil
|
||||
}
|
||||
|
||||
@@ -54,6 +54,6 @@ echo -e "\033[0m"
|
||||
$KUBESCAPE_EXEC version
|
||||
echo
|
||||
|
||||
echo -e "\033[35mUsage: $ $KUBESCAPE_EXEC scan --submit"
|
||||
echo -e "\033[35mUsage: $ $KUBESCAPE_EXEC scan --submit --enable-host-scan"
|
||||
|
||||
echo -e "\033[0m"
|
||||
|
||||
@@ -42,7 +42,7 @@ func (eksProviderEnvVar *EKSProviderEnvVar) getRegionForEKS(cluster string) (str
|
||||
}
|
||||
splittedClusterContext := strings.Split(cluster, ".")
|
||||
if len(splittedClusterContext) < 2 {
|
||||
return "", fmt.Errorf("error: failed to get region")
|
||||
return "", fmt.Errorf("failed to get region")
|
||||
}
|
||||
region = splittedClusterContext[1]
|
||||
return region, nil
|
||||
@@ -58,15 +58,18 @@ func NewEKSProviderContext() *EKSProviderContext {
|
||||
}
|
||||
|
||||
func (eksProviderContext *EKSProviderContext) getKubeClusterName() string {
|
||||
cluster := k8sinterface.GetCurrentContext().Cluster
|
||||
var splittedCluster []string
|
||||
context := k8sinterface.GetCurrentContext()
|
||||
if context == nil {
|
||||
return ""
|
||||
}
|
||||
cluster := context.Cluster
|
||||
if cluster != "" {
|
||||
splittedCluster = strings.Split(cluster, ".")
|
||||
splittedCluster := strings.Split(cluster, ".")
|
||||
if len(splittedCluster) > 1 {
|
||||
return splittedCluster[0]
|
||||
}
|
||||
}
|
||||
splittedCluster = strings.Split(k8sinterface.GetClusterName(), ".")
|
||||
splittedCluster := strings.Split(k8sinterface.GetClusterName(), ".")
|
||||
if len(splittedCluster) > 1 {
|
||||
return splittedCluster[0]
|
||||
}
|
||||
@@ -78,9 +81,8 @@ func (eksProviderContext *EKSProviderContext) getKubeCluster() string {
|
||||
if context == nil {
|
||||
return ""
|
||||
}
|
||||
cluster := context.Cluster
|
||||
if cluster != "" {
|
||||
return cluster
|
||||
if context.Cluster != "" {
|
||||
return context.Cluster
|
||||
}
|
||||
return k8sinterface.GetClusterName()
|
||||
}
|
||||
@@ -88,7 +90,7 @@ func (eksProviderContext *EKSProviderContext) getKubeCluster() string {
|
||||
func (eksProviderContext *EKSProviderContext) getRegion(cluster string, provider string) (string, error) {
|
||||
splittedClusterContext := strings.Split(cluster, ".")
|
||||
if len(splittedClusterContext) < 2 {
|
||||
return "", fmt.Errorf("error: failed to get region")
|
||||
return "", fmt.Errorf("failed to get region")
|
||||
}
|
||||
region := splittedClusterContext[1]
|
||||
return region, nil
|
||||
|
||||
@@ -1,12 +1,8 @@
|
||||
package resourcehandler
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/armosec/armoapi-go/armotypes"
|
||||
"github.com/armosec/k8s-interface/workloadinterface"
|
||||
@@ -14,23 +10,7 @@ import (
|
||||
|
||||
"github.com/armosec/k8s-interface/k8sinterface"
|
||||
"github.com/armosec/kubescape/cautils"
|
||||
"github.com/armosec/kubescape/cautils/logger"
|
||||
"github.com/armosec/opa-utils/objectsenvelopes"
|
||||
"github.com/armosec/opa-utils/reporthandling"
|
||||
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
var (
|
||||
YAML_PREFIX = []string{".yaml", ".yml"}
|
||||
JSON_PREFIX = []string{".json"}
|
||||
)
|
||||
|
||||
type FileFormat string
|
||||
|
||||
const (
|
||||
YAML_FILE_FORMAT FileFormat = "yaml"
|
||||
JSON_FILE_FORMAT FileFormat = "json"
|
||||
)
|
||||
|
||||
// FileResourceHandler handle resources from files and URLs
|
||||
@@ -57,7 +37,7 @@ func (fileHandler *FileResourceHandler) GetResources(frameworks []reporthandling
|
||||
workloads := []workloadinterface.IMetadata{}
|
||||
|
||||
// load resource from local file system
|
||||
w, err := loadResourcesFromFiles(fileHandler.inputPatterns)
|
||||
w, err := cautils.LoadResourcesFromFiles(fileHandler.inputPatterns)
|
||||
if err != nil {
|
||||
return nil, allResources, err
|
||||
}
|
||||
@@ -105,22 +85,6 @@ func (fileHandler *FileResourceHandler) GetClusterAPIServerInfo() *version.Info
|
||||
return nil
|
||||
}
|
||||
|
||||
func loadResourcesFromFiles(inputPatterns []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, nil
|
||||
}
|
||||
|
||||
workloads, errs := loadFiles(files)
|
||||
if len(errs) > 0 {
|
||||
logger.L().Error(fmt.Sprintf("%v", errs))
|
||||
}
|
||||
return workloads, nil
|
||||
}
|
||||
|
||||
// build resources map
|
||||
func mapResources(workloads []workloadinterface.IMetadata) map[string][]workloadinterface.IMetadata {
|
||||
|
||||
@@ -149,199 +113,3 @@ func mapResources(workloads []workloadinterface.IMetadata) map[string][]workload
|
||||
return allResources
|
||||
|
||||
}
|
||||
|
||||
func loadFiles(filePaths []string) ([]workloadinterface.IMetadata, []error) {
|
||||
workloads := []workloadinterface.IMetadata{}
|
||||
errs := []error{}
|
||||
for i := range filePaths {
|
||||
f, err := loadFile(filePaths[i])
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
continue
|
||||
}
|
||||
w, e := readFile(f, getFileFormat(filePaths[i]))
|
||||
errs = append(errs, e...)
|
||||
if w != nil {
|
||||
workloads = append(workloads, w...)
|
||||
}
|
||||
}
|
||||
return workloads, errs
|
||||
}
|
||||
|
||||
func loadFile(filePath string) ([]byte, error) {
|
||||
return os.ReadFile(filePath)
|
||||
}
|
||||
func readFile(fileContent []byte, fileFromat FileFormat) ([]workloadinterface.IMetadata, []error) {
|
||||
|
||||
switch fileFromat {
|
||||
case YAML_FILE_FORMAT:
|
||||
return readYamlFile(fileContent)
|
||||
case JSON_FILE_FORMAT:
|
||||
return readJsonFile(fileContent)
|
||||
default:
|
||||
return nil, nil // []error{fmt.Errorf("file extension %s not supported", fileFromat)}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func listFiles(patterns []string) ([]string, []error) {
|
||||
files := []string{}
|
||||
errs := []error{}
|
||||
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...)
|
||||
}
|
||||
}
|
||||
}
|
||||
return files, errs
|
||||
}
|
||||
|
||||
func readYamlFile(yamlFile []byte) ([]workloadinterface.IMetadata, []error) {
|
||||
errs := []error{}
|
||||
|
||||
r := bytes.NewReader(yamlFile)
|
||||
dec := yaml.NewDecoder(r)
|
||||
yamlObjs := []workloadinterface.IMetadata{}
|
||||
|
||||
var t interface{}
|
||||
for dec.Decode(&t) == nil {
|
||||
j := convertYamlToJson(t)
|
||||
if j == nil {
|
||||
continue
|
||||
}
|
||||
if obj, ok := j.(map[string]interface{}); ok {
|
||||
if o := objectsenvelopes.NewObject(obj); o != nil {
|
||||
if o.GetKind() == "List" {
|
||||
yamlObjs = append(yamlObjs, handleListObject(o)...)
|
||||
} else {
|
||||
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, errs
|
||||
}
|
||||
|
||||
func readJsonFile(jsonFile []byte) ([]workloadinterface.IMetadata, []error) {
|
||||
workloads := []workloadinterface.IMetadata{}
|
||||
var jsonObj interface{}
|
||||
if err := json.Unmarshal(jsonFile, &jsonObj); err != nil {
|
||||
return workloads, []error{err}
|
||||
}
|
||||
|
||||
convertJsonToWorkload(jsonObj, &workloads)
|
||||
|
||||
return workloads, nil
|
||||
}
|
||||
func convertJsonToWorkload(jsonObj interface{}, workloads *[]workloadinterface.IMetadata) {
|
||||
|
||||
switch x := jsonObj.(type) {
|
||||
case map[string]interface{}:
|
||||
if o := objectsenvelopes.NewObject(x); o != nil {
|
||||
(*workloads) = append(*workloads, o)
|
||||
}
|
||||
case []interface{}:
|
||||
for i := range x {
|
||||
convertJsonToWorkload(x[i], workloads)
|
||||
}
|
||||
}
|
||||
}
|
||||
func convertYamlToJson(i interface{}) interface{} {
|
||||
switch x := i.(type) {
|
||||
case map[interface{}]interface{}:
|
||||
m2 := map[string]interface{}{}
|
||||
for k, v := range x {
|
||||
if s, ok := k.(string); ok {
|
||||
m2[s] = convertYamlToJson(v)
|
||||
}
|
||||
}
|
||||
return m2
|
||||
case []interface{}:
|
||||
for i, v := range x {
|
||||
x[i] = convertYamlToJson(v)
|
||||
}
|
||||
}
|
||||
return i
|
||||
}
|
||||
|
||||
func isYaml(filePath string) bool {
|
||||
return cautils.StringInSlice(YAML_PREFIX, filepath.Ext(filePath)) != cautils.ValueNotFound
|
||||
}
|
||||
|
||||
func isJson(filePath string) bool {
|
||||
return cautils.StringInSlice(JSON_PREFIX, filepath.Ext(filePath)) != cautils.ValueNotFound
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
if info.IsDir() {
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
return matches, nil
|
||||
}
|
||||
func isFile(name string) bool {
|
||||
if fi, err := os.Stat(name); err == nil {
|
||||
if fi.Mode().IsRegular() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func getFileFormat(filePath string) FileFormat {
|
||||
if isYaml(filePath) {
|
||||
return YAML_FILE_FORMAT
|
||||
} else if isJson(filePath) {
|
||||
return JSON_FILE_FORMAT
|
||||
} else {
|
||||
return FileFormat(filePath)
|
||||
}
|
||||
}
|
||||
|
||||
// handleListObject handle a List manifest
|
||||
func handleListObject(obj workloadinterface.IMetadata) []workloadinterface.IMetadata {
|
||||
yamlObjs := []workloadinterface.IMetadata{}
|
||||
if i, ok := workloadinterface.InspectMap(obj.GetObject(), "items"); ok && i != nil {
|
||||
if items, ok := i.([]interface{}); ok && items != nil {
|
||||
for item := range items {
|
||||
if m, ok := items[item].(map[string]interface{}); ok && m != nil {
|
||||
if o := objectsenvelopes.NewObject(m); o != nil {
|
||||
yamlObjs = append(yamlObjs, o)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return yamlObjs
|
||||
}
|
||||
|
||||
@@ -89,8 +89,7 @@ func (gkeProviderContext *GKEProviderContext) getKubeClusterName() string {
|
||||
if len(parsedName) < 3 {
|
||||
return ""
|
||||
}
|
||||
clusterName = parsedName[3]
|
||||
return clusterName
|
||||
return parsedName[3]
|
||||
}
|
||||
|
||||
func (gkeProviderContext *GKEProviderContext) getKubeCluster() string {
|
||||
@@ -98,9 +97,8 @@ func (gkeProviderContext *GKEProviderContext) getKubeCluster() string {
|
||||
if context == nil {
|
||||
return ""
|
||||
}
|
||||
cluster := context.Cluster
|
||||
if cluster != "" {
|
||||
return cluster
|
||||
if context.Cluster != "" {
|
||||
return context.Cluster
|
||||
}
|
||||
return k8sinterface.GetClusterName()
|
||||
|
||||
|
||||
@@ -80,7 +80,7 @@ func (k8sHandler *K8sResourceHandler) GetResources(frameworks []reporthandling.F
|
||||
}
|
||||
|
||||
cautils.StopSpinner()
|
||||
logger.L().Success("Accessed successfully to Kubernetes objects")
|
||||
logger.L().Success("Accessed to Kubernetes objects")
|
||||
|
||||
return k8sResourcesMap, allResources, nil
|
||||
}
|
||||
@@ -168,6 +168,18 @@ func ConvertMapListToMeta(resourceMap []map[string]interface{}) []workloadinterf
|
||||
return workloads
|
||||
}
|
||||
|
||||
// func (k8sHandler *K8sResourceHandler) collectHostResourcesAPI(allResources map[string]workloadinterface.IMetadata, resourcesMap *cautils.K8SResources) error {
|
||||
|
||||
// HostSensorAPI := map[string]string{
|
||||
// "bla/v1": "",
|
||||
// }
|
||||
// for apiVersion := range allResources {
|
||||
// if HostSensorAPI == apiVersion {
|
||||
// k8sHandler.collectHostResources()
|
||||
// }
|
||||
// }
|
||||
// return nil
|
||||
// }
|
||||
func (k8sHandler *K8sResourceHandler) collectHostResources(allResources map[string]workloadinterface.IMetadata, resourcesMap *cautils.K8SResources) error {
|
||||
logger.L().Debug("Collecting host sensor resources")
|
||||
|
||||
@@ -175,6 +187,7 @@ func (k8sHandler *K8sResourceHandler) collectHostResources(allResources map[stri
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for rscIdx := range hostResources {
|
||||
group, version := getGroupNVersion(hostResources[rscIdx].GetApiVersion())
|
||||
groupResource := k8sinterface.JoinResourceTriplets(group, version, hostResources[rscIdx].GetKind())
|
||||
@@ -220,9 +233,10 @@ func getCloudProviderDescription(allResources map[string]workloadinterface.IMeta
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
logger.L().Debug("cloud", helpers.String("cluster", cluster), helpers.String("clusterName", clusterName), helpers.String("provider", provider), helpers.String("region", region), helpers.String("project", project))
|
||||
|
||||
if provider != "" {
|
||||
logger.L().Debug("cloud", helpers.String("cluster", cluster), helpers.String("clusterName", clusterName), helpers.String("provider", provider), helpers.String("region", region), helpers.String("project", project))
|
||||
|
||||
wl, err := cloudsupport.GetDescriptiveInfoFromCloudProvider(clusterName, provider, region, project)
|
||||
if err != nil {
|
||||
// Return error with useful info on how to configure credentials for getting cloud provider info
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/armosec/k8s-interface/workloadinterface"
|
||||
"github.com/armosec/kubescape/cautils"
|
||||
"github.com/armosec/kubescape/cautils/logger"
|
||||
)
|
||||
|
||||
@@ -28,11 +29,11 @@ func listUrls(patterns []string) []string {
|
||||
urls := []string{}
|
||||
for i := range patterns {
|
||||
if strings.HasPrefix(patterns[i], "http") {
|
||||
if !isYaml(patterns[i]) || !isJson(patterns[i]) { // if url of repo
|
||||
if !cautils.IsYaml(patterns[i]) && !cautils.IsJson(patterns[i]) { // if url of repo
|
||||
if yamls, err := ScanRepository(patterns[i], ""); err == nil { // TODO - support branch
|
||||
urls = append(urls, yamls...)
|
||||
} else {
|
||||
fmt.Print(err) // TODO - handle errors
|
||||
logger.L().Error(err.Error())
|
||||
}
|
||||
} else { // url of single file
|
||||
urls = append(urls, patterns[i])
|
||||
@@ -52,7 +53,7 @@ func downloadFiles(urls []string) ([]workloadinterface.IMetadata, []error) {
|
||||
errs = append(errs, err)
|
||||
continue
|
||||
}
|
||||
w, e := readFile(f, getFileFormat(urls[i]))
|
||||
w, e := cautils.ReadFile(f, cautils.GetFileFormat(urls[i]))
|
||||
errs = append(errs, e...)
|
||||
if w != nil {
|
||||
workloads = append(workloads, w...)
|
||||
|
||||
@@ -15,6 +15,7 @@ const (
|
||||
JsonFormat string = "json"
|
||||
JunitResultFormat string = "junit"
|
||||
PrometheusFormat string = "prometheus"
|
||||
PdfFormat string = "pdf"
|
||||
)
|
||||
|
||||
type IPrinter interface {
|
||||
@@ -29,7 +30,7 @@ func GetWriter(outputFile string) *os.File {
|
||||
f, err := os.OpenFile(outputFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
|
||||
if err != nil {
|
||||
logger.L().Error(fmt.Sprintf("failed to open file for writing, reason: %s", err.Error()))
|
||||
return os.Stderr
|
||||
return os.Stdout
|
||||
}
|
||||
return f
|
||||
}
|
||||
|
||||
@@ -1,125 +0,0 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/armosec/kubescape/cautils"
|
||||
"github.com/armosec/kubescape/cautils/logger"
|
||||
"github.com/armosec/kubescape/resultshandling/printer"
|
||||
"github.com/armosec/opa-utils/reporthandling"
|
||||
)
|
||||
|
||||
type JunitPrinter struct {
|
||||
writer *os.File
|
||||
}
|
||||
|
||||
func NewJunitPrinter() *JunitPrinter {
|
||||
return &JunitPrinter{}
|
||||
}
|
||||
|
||||
func (junitPrinter *JunitPrinter) SetWriter(outputFile string) {
|
||||
junitPrinter.writer = printer.GetWriter(outputFile)
|
||||
}
|
||||
|
||||
func (junitPrinter *JunitPrinter) Score(score float32) {
|
||||
fmt.Fprintf(os.Stderr, "\nOverall risk-score (0- Excellent, 100- All failed): %d\n", int(score))
|
||||
}
|
||||
|
||||
func (junitPrinter *JunitPrinter) ActionPrint(opaSessionObj *cautils.OPASessionObj) {
|
||||
cautils.ReportV2ToV1(opaSessionObj)
|
||||
|
||||
junitResult, err := convertPostureReportToJunitResult(opaSessionObj.PostureReport)
|
||||
if err != nil {
|
||||
logger.L().Fatal("failed to convert posture report object")
|
||||
}
|
||||
postureReportStr, err := xml.Marshal(junitResult)
|
||||
if err != nil {
|
||||
logger.L().Fatal("failed to convert posture report object")
|
||||
}
|
||||
junitPrinter.writer.Write(postureReportStr)
|
||||
}
|
||||
|
||||
type JUnitTestSuites struct {
|
||||
XMLName xml.Name `xml:"testsuites"`
|
||||
Suites []JUnitTestSuite `xml:"testsuite"`
|
||||
}
|
||||
|
||||
// JUnitTestSuite is a single JUnit test suite which may contain many
|
||||
// testcases.
|
||||
type JUnitTestSuite struct {
|
||||
XMLName xml.Name `xml:"testsuite"`
|
||||
Tests int `xml:"tests,attr"`
|
||||
Time string `xml:"time,attr"`
|
||||
Name string `xml:"name,attr"`
|
||||
Resources int `xml:"resources,attr"`
|
||||
Excluded int `xml:"excluded,attr"`
|
||||
Failed int `xml:"filed,attr"`
|
||||
Properties []JUnitProperty `xml:"properties>property,omitempty"`
|
||||
TestCases []JUnitTestCase `xml:"testcase"`
|
||||
}
|
||||
|
||||
// JUnitTestCase is a single test case with its result.
|
||||
type JUnitTestCase struct {
|
||||
XMLName xml.Name `xml:"testcase"`
|
||||
Classname string `xml:"classname,attr"`
|
||||
Name string `xml:"name,attr"`
|
||||
Time string `xml:"time,attr"`
|
||||
Resources int `xml:"resources,attr"`
|
||||
Excluded int `xml:"excluded,attr"`
|
||||
Failed int `xml:"filed,attr"`
|
||||
SkipMessage *JUnitSkipMessage `xml:"skipped,omitempty"`
|
||||
Failure *JUnitFailure `xml:"failure,omitempty"`
|
||||
}
|
||||
|
||||
// JUnitSkipMessage contains the reason why a testcase was skipped.
|
||||
type JUnitSkipMessage struct {
|
||||
Message string `xml:"message,attr"`
|
||||
}
|
||||
|
||||
// JUnitProperty represents a key/value pair used to define properties.
|
||||
type JUnitProperty struct {
|
||||
Name string `xml:"name,attr"`
|
||||
Value string `xml:"value,attr"`
|
||||
}
|
||||
|
||||
// JUnitFailure contains data related to a failed test.
|
||||
type JUnitFailure struct {
|
||||
Message string `xml:"message,attr"`
|
||||
Type string `xml:"type,attr"`
|
||||
Contents string `xml:",chardata"`
|
||||
}
|
||||
|
||||
func convertPostureReportToJunitResult(postureResult *reporthandling.PostureReport) (*JUnitTestSuites, error) {
|
||||
juResult := JUnitTestSuites{XMLName: xml.Name{Local: "Kubescape scan results"}}
|
||||
for _, framework := range postureResult.FrameworkReports {
|
||||
suite := JUnitTestSuite{
|
||||
Name: framework.Name,
|
||||
Resources: framework.GetNumberOfResources(),
|
||||
Excluded: framework.GetNumberOfWarningResources(),
|
||||
Failed: framework.GetNumberOfFailedResources(),
|
||||
}
|
||||
for _, controlReports := range framework.ControlReports {
|
||||
suite.Tests = suite.Tests + 1
|
||||
testCase := JUnitTestCase{}
|
||||
testCase.Name = controlReports.Name
|
||||
testCase.Classname = "Kubescape"
|
||||
testCase.Time = postureResult.ReportGenerationTime.String()
|
||||
if 0 < len(controlReports.RuleReports) && 0 < len(controlReports.RuleReports[0].RuleResponses) {
|
||||
testCase.Resources = controlReports.GetNumberOfResources()
|
||||
testCase.Excluded = controlReports.GetNumberOfWarningResources()
|
||||
testCase.Failed = controlReports.GetNumberOfFailedResources()
|
||||
failure := JUnitFailure{}
|
||||
failure.Message = fmt.Sprintf("%d resources failed", testCase.Failed)
|
||||
for _, ruleResponses := range controlReports.RuleReports[0].RuleResponses {
|
||||
failure.Contents = fmt.Sprintf("%s\n%s", failure.Contents, ruleResponses.AlertMessage)
|
||||
}
|
||||
testCase.Failure = &failure
|
||||
}
|
||||
suite.TestCases = append(suite.TestCases, testCase)
|
||||
}
|
||||
juResult.Suites = append(juResult.Suites, suite)
|
||||
}
|
||||
return &juResult, nil
|
||||
}
|
||||
@@ -1,273 +0,0 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/armosec/k8s-interface/workloadinterface"
|
||||
"github.com/armosec/kubescape/cautils"
|
||||
"github.com/armosec/kubescape/resultshandling/printer"
|
||||
"github.com/armosec/opa-utils/objectsenvelopes"
|
||||
"github.com/armosec/opa-utils/reporthandling"
|
||||
"github.com/enescakir/emoji"
|
||||
"github.com/olekukonko/tablewriter"
|
||||
)
|
||||
|
||||
type PrettyPrinter struct {
|
||||
writer *os.File
|
||||
summary Summary
|
||||
verboseMode bool
|
||||
sortedControlNames []string
|
||||
frameworkSummary ResultSummary
|
||||
}
|
||||
|
||||
func NewPrettyPrinter(verboseMode bool) *PrettyPrinter {
|
||||
return &PrettyPrinter{
|
||||
verboseMode: verboseMode,
|
||||
summary: NewSummary(),
|
||||
}
|
||||
}
|
||||
|
||||
func (prettyPrinter *PrettyPrinter) ActionPrint(opaSessionObj *cautils.OPASessionObj) {
|
||||
cautils.ReportV2ToV1(opaSessionObj)
|
||||
|
||||
// score := calculatePostureScore(opaSessionObj.PostureReport)
|
||||
failedResources := []string{}
|
||||
warningResources := []string{}
|
||||
allResources := []string{}
|
||||
frameworkNames := []string{}
|
||||
frameworkScores := []float32{}
|
||||
|
||||
var overallRiskScore float32 = 0
|
||||
for _, frameworkReport := range opaSessionObj.PostureReport.FrameworkReports {
|
||||
frameworkNames = append(frameworkNames, frameworkReport.Name)
|
||||
frameworkScores = append(frameworkScores, frameworkReport.Score)
|
||||
failedResources = reporthandling.GetUniqueResourcesIDs(append(failedResources, frameworkReport.ListResourcesIDs().GetFailedResources()...))
|
||||
warningResources = reporthandling.GetUniqueResourcesIDs(append(warningResources, frameworkReport.ListResourcesIDs().GetWarningResources()...))
|
||||
allResources = reporthandling.GetUniqueResourcesIDs(append(allResources, frameworkReport.ListResourcesIDs().GetAllResources()...))
|
||||
prettyPrinter.summarySetup(frameworkReport, opaSessionObj.AllResources)
|
||||
overallRiskScore += frameworkReport.Score
|
||||
}
|
||||
|
||||
overallRiskScore /= float32(len(opaSessionObj.PostureReport.FrameworkReports))
|
||||
|
||||
prettyPrinter.frameworkSummary = ResultSummary{
|
||||
RiskScore: overallRiskScore,
|
||||
TotalResources: len(allResources),
|
||||
TotalFailed: len(failedResources),
|
||||
TotalWarning: len(warningResources),
|
||||
}
|
||||
|
||||
prettyPrinter.printResults()
|
||||
prettyPrinter.printSummaryTable(frameworkNames, frameworkScores)
|
||||
|
||||
}
|
||||
|
||||
func (prettyPrinter *PrettyPrinter) SetWriter(outputFile string) {
|
||||
prettyPrinter.writer = printer.GetWriter(outputFile)
|
||||
}
|
||||
|
||||
func (prettyPrinter *PrettyPrinter) Score(score float32) {
|
||||
}
|
||||
|
||||
func (prettyPrinter *PrettyPrinter) summarySetup(fr reporthandling.FrameworkReport, allResources map[string]workloadinterface.IMetadata) {
|
||||
|
||||
for _, cr := range fr.ControlReports {
|
||||
// if len(cr.RuleReports) == 0 {
|
||||
// continue
|
||||
// }
|
||||
workloadsSummary := listResultSummary(cr.RuleReports, allResources)
|
||||
|
||||
var passedWorkloads map[string][]WorkloadSummary
|
||||
if prettyPrinter.verboseMode {
|
||||
passedWorkloads = groupByNamespaceOrKind(workloadsSummary, workloadSummaryPassed)
|
||||
}
|
||||
|
||||
//controlSummary
|
||||
prettyPrinter.summary[cr.Name] = ResultSummary{
|
||||
ID: cr.ControlID,
|
||||
RiskScore: cr.Score,
|
||||
TotalResources: cr.GetNumberOfResources(),
|
||||
TotalFailed: cr.GetNumberOfFailedResources(),
|
||||
TotalWarning: cr.GetNumberOfWarningResources(),
|
||||
FailedWorkloads: groupByNamespaceOrKind(workloadsSummary, workloadSummaryFailed),
|
||||
ExcludedWorkloads: groupByNamespaceOrKind(workloadsSummary, workloadSummaryExclude),
|
||||
PassedWorkloads: passedWorkloads,
|
||||
Description: cr.Description,
|
||||
Remediation: cr.Remediation,
|
||||
ListInputKinds: cr.ListControlsInputKinds(),
|
||||
}
|
||||
|
||||
}
|
||||
prettyPrinter.sortedControlNames = prettyPrinter.getSortedControlsNames()
|
||||
}
|
||||
func (prettyPrinter *PrettyPrinter) printResults() {
|
||||
for i := 0; i < len(prettyPrinter.sortedControlNames); i++ {
|
||||
controlSummary := prettyPrinter.summary[prettyPrinter.sortedControlNames[i]]
|
||||
prettyPrinter.printTitle(prettyPrinter.sortedControlNames[i], &controlSummary)
|
||||
prettyPrinter.printResources(&controlSummary)
|
||||
if prettyPrinter.summary[prettyPrinter.sortedControlNames[i]].TotalResources > 0 {
|
||||
prettyPrinter.printSummary(prettyPrinter.sortedControlNames[i], &controlSummary)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func (prettyPrinter *PrettyPrinter) printSummary(controlName string, controlSummary *ResultSummary) {
|
||||
cautils.SimpleDisplay(prettyPrinter.writer, "Summary - ")
|
||||
cautils.SuccessDisplay(prettyPrinter.writer, "Passed:%v ", controlSummary.TotalResources-controlSummary.TotalFailed-controlSummary.TotalWarning)
|
||||
cautils.WarningDisplay(prettyPrinter.writer, "Excluded:%v ", controlSummary.TotalWarning)
|
||||
cautils.FailureDisplay(prettyPrinter.writer, "Failed:%v ", controlSummary.TotalFailed)
|
||||
cautils.InfoDisplay(prettyPrinter.writer, "Total:%v\n", controlSummary.TotalResources)
|
||||
if controlSummary.TotalFailed > 0 {
|
||||
cautils.DescriptionDisplay(prettyPrinter.writer, "Remediation: %v\n", controlSummary.Remediation)
|
||||
}
|
||||
cautils.DescriptionDisplay(prettyPrinter.writer, "\n")
|
||||
|
||||
}
|
||||
func (prettyPrinter *PrettyPrinter) printTitle(controlName string, controlSummary *ResultSummary) {
|
||||
cautils.InfoDisplay(prettyPrinter.writer, "[control: %s - %s] ", controlName, getControlURL(controlSummary.ID))
|
||||
if controlSummary.TotalResources == 0 {
|
||||
cautils.InfoDisplay(prettyPrinter.writer, "skipped %v\n", emoji.ConfusedFace)
|
||||
} else if controlSummary.TotalFailed != 0 {
|
||||
cautils.FailureDisplay(prettyPrinter.writer, "failed %v\n", emoji.SadButRelievedFace)
|
||||
} else if controlSummary.TotalWarning != 0 {
|
||||
cautils.WarningDisplay(prettyPrinter.writer, "excluded %v\n", emoji.NeutralFace)
|
||||
} else {
|
||||
cautils.SuccessDisplay(prettyPrinter.writer, "passed %v\n", emoji.ThumbsUp)
|
||||
}
|
||||
|
||||
cautils.DescriptionDisplay(prettyPrinter.writer, "Description: %s\n", controlSummary.Description)
|
||||
|
||||
}
|
||||
func (prettyPrinter *PrettyPrinter) printResources(controlSummary *ResultSummary) {
|
||||
|
||||
if len(controlSummary.FailedWorkloads) > 0 {
|
||||
cautils.FailureDisplay(prettyPrinter.writer, "Failed:\n")
|
||||
prettyPrinter.printGroupedResources(controlSummary.FailedWorkloads)
|
||||
}
|
||||
if len(controlSummary.ExcludedWorkloads) > 0 {
|
||||
cautils.WarningDisplay(prettyPrinter.writer, "Excluded:\n")
|
||||
prettyPrinter.printGroupedResources(controlSummary.ExcludedWorkloads)
|
||||
}
|
||||
if len(controlSummary.PassedWorkloads) > 0 {
|
||||
cautils.SuccessDisplay(prettyPrinter.writer, "Passed:\n")
|
||||
prettyPrinter.printGroupedResources(controlSummary.PassedWorkloads)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (prettyPrinter *PrettyPrinter) printGroupedResources(workloads map[string][]WorkloadSummary) {
|
||||
indent := INDENT
|
||||
for title, rsc := range workloads {
|
||||
prettyPrinter.printGroupedResource(indent, title, rsc)
|
||||
}
|
||||
}
|
||||
|
||||
func (prettyPrinter *PrettyPrinter) printGroupedResource(indent string, title string, rsc []WorkloadSummary) {
|
||||
preIndent := indent
|
||||
if title != "" {
|
||||
cautils.SimpleDisplay(prettyPrinter.writer, "%s%s\n", indent, title)
|
||||
indent += indent
|
||||
}
|
||||
|
||||
for r := range rsc {
|
||||
relatedObjectsStr := generateRelatedObjectsStr(rsc[r])
|
||||
cautils.SimpleDisplay(prettyPrinter.writer, fmt.Sprintf("%s%s - %s %s\n", indent, rsc[r].resource.GetKind(), rsc[r].resource.GetName(), relatedObjectsStr))
|
||||
}
|
||||
indent = preIndent
|
||||
}
|
||||
|
||||
func generateRelatedObjectsStr(workload WorkloadSummary) string {
|
||||
relatedStr := ""
|
||||
if workload.resource.GetObjectType() == workloadinterface.TypeWorkloadObject {
|
||||
relatedObjects := objectsenvelopes.NewRegoResponseVectorObject(workload.resource.GetObject()).GetRelatedObjects()
|
||||
for i, related := range relatedObjects {
|
||||
if ns := related.GetNamespace(); i == 0 && ns != "" {
|
||||
relatedStr += fmt.Sprintf("Namespace - %s, ", ns)
|
||||
}
|
||||
relatedStr += fmt.Sprintf("%s - %s, ", related.GetKind(), related.GetName())
|
||||
}
|
||||
}
|
||||
if relatedStr != "" {
|
||||
relatedStr = fmt.Sprintf(" [%s]", relatedStr[:len(relatedStr)-2])
|
||||
}
|
||||
return relatedStr
|
||||
}
|
||||
|
||||
func generateRow(control string, cs ResultSummary) []string {
|
||||
row := []string{control}
|
||||
row = append(row, cs.ToSlice()...)
|
||||
if cs.TotalResources != 0 {
|
||||
row = append(row, fmt.Sprintf("%d", int(cs.RiskScore))+"%")
|
||||
} else {
|
||||
row = append(row, "skipped")
|
||||
}
|
||||
return row
|
||||
}
|
||||
|
||||
func generateHeader() []string {
|
||||
return []string{"Control Name", "Failed Resources", "Excluded Resources", "All Resources", "% risk-score"}
|
||||
}
|
||||
|
||||
func generateFooter(prettyPrinter *PrettyPrinter) []string {
|
||||
// Control name | # failed resources | all resources | % success
|
||||
row := []string{}
|
||||
row = append(row, "Resource Summary") //fmt.Sprintf(""%d", numControlers"))
|
||||
row = append(row, fmt.Sprintf("%d", prettyPrinter.frameworkSummary.TotalFailed))
|
||||
row = append(row, fmt.Sprintf("%d", prettyPrinter.frameworkSummary.TotalWarning))
|
||||
row = append(row, fmt.Sprintf("%d", prettyPrinter.frameworkSummary.TotalResources))
|
||||
row = append(row, fmt.Sprintf("%.2f%s", prettyPrinter.frameworkSummary.RiskScore, "%"))
|
||||
|
||||
return row
|
||||
}
|
||||
func (prettyPrinter *PrettyPrinter) printSummaryTable(frameworksNames []string, frameworkScores []float32) {
|
||||
// For control scan framework will be nil
|
||||
prettyPrinter.printFramework(frameworksNames, frameworkScores)
|
||||
|
||||
summaryTable := tablewriter.NewWriter(prettyPrinter.writer)
|
||||
summaryTable.SetAutoWrapText(false)
|
||||
summaryTable.SetHeader(generateHeader())
|
||||
summaryTable.SetHeaderLine(true)
|
||||
alignments := []int{tablewriter.ALIGN_LEFT, tablewriter.ALIGN_CENTER, tablewriter.ALIGN_CENTER, tablewriter.ALIGN_CENTER, tablewriter.ALIGN_CENTER}
|
||||
summaryTable.SetColumnAlignment(alignments)
|
||||
|
||||
for i := 0; i < len(prettyPrinter.sortedControlNames); i++ {
|
||||
controlSummary := prettyPrinter.summary[prettyPrinter.sortedControlNames[i]]
|
||||
summaryTable.Append(generateRow(prettyPrinter.sortedControlNames[i], controlSummary))
|
||||
}
|
||||
|
||||
summaryTable.SetFooter(generateFooter(prettyPrinter))
|
||||
|
||||
// summaryTable.SetFooter(generateFooter())
|
||||
summaryTable.Render()
|
||||
}
|
||||
|
||||
func (prettyPrinter *PrettyPrinter) printFramework(frameworksNames []string, frameworkScores []float32) {
|
||||
if len(frameworksNames) == 1 {
|
||||
if frameworksNames[0] != "" {
|
||||
cautils.InfoTextDisplay(prettyPrinter.writer, fmt.Sprintf("FRAMEWORK %s\n", frameworksNames[0]))
|
||||
}
|
||||
} else if len(frameworksNames) > 1 {
|
||||
p := "FRAMEWORKS: "
|
||||
for i := 0; i < len(frameworksNames)-1; i++ {
|
||||
p += fmt.Sprintf("%s (risk: %.2f), ", frameworksNames[i], frameworkScores[i])
|
||||
}
|
||||
p += fmt.Sprintf("%s (risk: %.2f)\n", frameworksNames[len(frameworksNames)-1], frameworkScores[len(frameworkScores)-1])
|
||||
cautils.InfoTextDisplay(prettyPrinter.writer, p)
|
||||
}
|
||||
}
|
||||
|
||||
func (prettyPrinter *PrettyPrinter) getSortedControlsNames() []string {
|
||||
controlNames := make([]string, 0, len(prettyPrinter.summary))
|
||||
for k := range prettyPrinter.summary {
|
||||
controlNames = append(controlNames, k)
|
||||
}
|
||||
sort.Strings(controlNames)
|
||||
return controlNames
|
||||
}
|
||||
func getControlURL(controlID string) string {
|
||||
return fmt.Sprintf("https://hub.armo.cloud/docs/%s", strings.ToLower(controlID))
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"github.com/armosec/kubescape/resultshandling/printer"
|
||||
"github.com/armosec/kubescape/resultshandling/printer/v2/controlmapping"
|
||||
)
|
||||
|
||||
var INDENT = " "
|
||||
|
||||
func GetPrinter(printFormat string, verboseMode bool) printer.IPrinter {
|
||||
switch printFormat {
|
||||
case printer.JsonFormat:
|
||||
return NewJsonPrinter()
|
||||
case printer.JunitResultFormat:
|
||||
return NewJunitPrinter()
|
||||
case printer.PrometheusFormat:
|
||||
return NewPrometheusPrinter(verboseMode)
|
||||
default:
|
||||
return controlmapping.NewPrettyPrinter(verboseMode)
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"github.com/armosec/kubescape/cautils"
|
||||
)
|
||||
|
||||
type SilentPrinter struct {
|
||||
}
|
||||
|
||||
func (silentPrinter *SilentPrinter) ActionPrint(opaSessionObj *cautils.OPASessionObj) {
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/armosec/k8s-interface/workloadinterface"
|
||||
"github.com/armosec/opa-utils/reporthandling"
|
||||
)
|
||||
|
||||
type Summary map[string]ResultSummary
|
||||
|
||||
func NewSummary() Summary {
|
||||
return make(map[string]ResultSummary)
|
||||
}
|
||||
|
||||
type ResultSummary struct {
|
||||
ID string
|
||||
RiskScore float32
|
||||
TotalResources int
|
||||
TotalFailed int
|
||||
TotalWarning int
|
||||
Description string
|
||||
Remediation string
|
||||
Framework []string
|
||||
ListInputKinds []string
|
||||
FailedWorkloads map[string][]WorkloadSummary // <namespace>:[<WorkloadSummary>]
|
||||
ExcludedWorkloads map[string][]WorkloadSummary // <namespace>:[<WorkloadSummary>]
|
||||
PassedWorkloads map[string][]WorkloadSummary // <namespace>:[<WorkloadSummary>]
|
||||
}
|
||||
|
||||
type WorkloadSummary struct {
|
||||
resource workloadinterface.IMetadata
|
||||
status string
|
||||
}
|
||||
|
||||
func (controlSummary *ResultSummary) ToSlice() []string {
|
||||
s := []string{}
|
||||
s = append(s, fmt.Sprintf("%d", controlSummary.TotalFailed))
|
||||
s = append(s, fmt.Sprintf("%d", controlSummary.TotalWarning))
|
||||
s = append(s, fmt.Sprintf("%d", controlSummary.TotalResources))
|
||||
return s
|
||||
}
|
||||
|
||||
func workloadSummaryFailed(workloadSummary *WorkloadSummary) bool {
|
||||
return workloadSummary.status == reporthandling.StatusFailed
|
||||
}
|
||||
|
||||
func workloadSummaryExclude(workloadSummary *WorkloadSummary) bool {
|
||||
return workloadSummary.status == reporthandling.StatusWarning
|
||||
}
|
||||
|
||||
func workloadSummaryPassed(workloadSummary *WorkloadSummary) bool {
|
||||
return workloadSummary.status == reporthandling.StatusPassed
|
||||
}
|
||||
@@ -1,85 +0,0 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"github.com/armosec/k8s-interface/k8sinterface"
|
||||
"github.com/armosec/k8s-interface/workloadinterface"
|
||||
"github.com/armosec/opa-utils/objectsenvelopes"
|
||||
"github.com/armosec/opa-utils/reporthandling"
|
||||
)
|
||||
|
||||
// Group workloads by namespace - return {"namespace": <[]WorkloadSummary>}
|
||||
func groupByNamespaceOrKind(resources []WorkloadSummary, status func(workloadSummary *WorkloadSummary) bool) map[string][]WorkloadSummary {
|
||||
mapResources := make(map[string][]WorkloadSummary)
|
||||
for i := range resources {
|
||||
if !status(&resources[i]) {
|
||||
continue
|
||||
}
|
||||
t := resources[i].resource.GetObjectType()
|
||||
if t == objectsenvelopes.TypeRegoResponseVectorObject && !isKindToBeGrouped(resources[i].resource.GetKind()) {
|
||||
t = workloadinterface.TypeWorkloadObject
|
||||
}
|
||||
switch t { // TODO - find a better way to defind the groups
|
||||
case workloadinterface.TypeWorkloadObject:
|
||||
ns := ""
|
||||
if resources[i].resource.GetNamespace() != "" {
|
||||
ns = "Namespace " + resources[i].resource.GetNamespace()
|
||||
}
|
||||
if r, ok := mapResources[ns]; ok {
|
||||
r = append(r, resources[i])
|
||||
mapResources[ns] = r
|
||||
} else {
|
||||
mapResources[ns] = []WorkloadSummary{resources[i]}
|
||||
}
|
||||
case objectsenvelopes.TypeRegoResponseVectorObject:
|
||||
group := resources[i].resource.GetKind() + "s"
|
||||
if r, ok := mapResources[group]; ok {
|
||||
r = append(r, resources[i])
|
||||
mapResources[group] = r
|
||||
} else {
|
||||
mapResources[group] = []WorkloadSummary{resources[i]}
|
||||
}
|
||||
default:
|
||||
group, _ := k8sinterface.SplitApiVersion(resources[i].resource.GetApiVersion())
|
||||
if r, ok := mapResources[group]; ok {
|
||||
r = append(r, resources[i])
|
||||
mapResources[group] = r
|
||||
} else {
|
||||
mapResources[group] = []WorkloadSummary{resources[i]}
|
||||
}
|
||||
}
|
||||
}
|
||||
return mapResources
|
||||
}
|
||||
|
||||
func isKindToBeGrouped(kind string) bool {
|
||||
if kind == "Group" || kind == "User" {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func listResultSummary(ruleReports []reporthandling.RuleReport, allResources map[string]workloadinterface.IMetadata) []WorkloadSummary {
|
||||
workloadsSummary := []WorkloadSummary{}
|
||||
|
||||
for c := range ruleReports {
|
||||
resourcesIDs := ruleReports[c].ListResourcesIDs()
|
||||
workloadsSummary = append(workloadsSummary, newListWorkloadsSummary(allResources, resourcesIDs.GetFailedResources(), reporthandling.StatusFailed)...)
|
||||
workloadsSummary = append(workloadsSummary, newListWorkloadsSummary(allResources, resourcesIDs.GetWarningResources(), reporthandling.StatusWarning)...)
|
||||
workloadsSummary = append(workloadsSummary, newListWorkloadsSummary(allResources, resourcesIDs.GetPassedResources(), reporthandling.StatusPassed)...)
|
||||
}
|
||||
return workloadsSummary
|
||||
}
|
||||
|
||||
func newListWorkloadsSummary(allResources map[string]workloadinterface.IMetadata, resourcesIDs []string, status string) []WorkloadSummary {
|
||||
workloadsSummary := []WorkloadSummary{}
|
||||
|
||||
for _, i := range resourcesIDs {
|
||||
if r, ok := allResources[i]; ok {
|
||||
workloadsSummary = append(workloadsSummary, WorkloadSummary{
|
||||
resource: r,
|
||||
status: status,
|
||||
})
|
||||
}
|
||||
}
|
||||
return workloadsSummary
|
||||
}
|
||||
36
resultshandling/printer/v2/controltable.go
Normal file
36
resultshandling/printer/v2/controltable.go
Normal file
@@ -0,0 +1,36 @@
|
||||
package v2
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
"github.com/armosec/opa-utils/reporthandling/results/v1/reportsummary"
|
||||
)
|
||||
|
||||
func generateRow(controlSummary reportsummary.IControlSummary) []string {
|
||||
row := []string{controlSummary.GetName()}
|
||||
row = append(row, fmt.Sprintf("%d", controlSummary.NumberOfResources().Failed()))
|
||||
row = append(row, fmt.Sprintf("%d", controlSummary.NumberOfResources().Excluded()))
|
||||
row = append(row, fmt.Sprintf("%d", controlSummary.NumberOfResources().All()))
|
||||
|
||||
if !controlSummary.GetStatus().IsSkipped() {
|
||||
row = append(row, fmt.Sprintf("%d", int(controlSummary.GetScore()))+"%")
|
||||
} else {
|
||||
row = append(row, "skipped")
|
||||
}
|
||||
return row
|
||||
}
|
||||
|
||||
func getSortedControlsNames(controls reportsummary.ControlSummaries) []string {
|
||||
controlNames := make([]string, 0, len(controls))
|
||||
for k := range controls {
|
||||
c := controls[k]
|
||||
controlNames = append(controlNames, c.GetName())
|
||||
}
|
||||
sort.Strings(controlNames)
|
||||
return controlNames
|
||||
}
|
||||
|
||||
func getControlTableHeaders() []string {
|
||||
return []string{"CONTROL NAME", "FAILED RESOURCES", "EXCLUDED RESOURCES", "ALL RESOURCES", "% RISK-SCORE"}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package resourcemapping
|
||||
package v2
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
@@ -27,15 +27,10 @@ func (jsonPrinter *JsonPrinter) Score(score float32) {
|
||||
}
|
||||
|
||||
func (jsonPrinter *JsonPrinter) ActionPrint(opaSessionObj *cautils.OPASessionObj) {
|
||||
|
||||
postureReportStr, err := json.Marshal(opaSessionObj.Report)
|
||||
|
||||
finalizeJson(opaSessionObj)
|
||||
r, err := json.Marshal(opaSessionObj.Report)
|
||||
if err != nil {
|
||||
logger.L().Fatal("failed to convert posture report object")
|
||||
logger.L().Fatal("failed to Marshal posture report object")
|
||||
}
|
||||
jsonPrinter.writer.Write(postureReportStr)
|
||||
}
|
||||
|
||||
func (jsonPrinter *JsonPrinter) FinalizeData(opaSessionObj *cautils.OPASessionObj) {
|
||||
// finalizeReport(opaSessionObj)
|
||||
jsonPrinter.writer.Write(r)
|
||||
}
|
||||
@@ -4,73 +4,69 @@ import (
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/armosec/k8s-interface/workloadinterface"
|
||||
"github.com/armosec/kubescape/cautils"
|
||||
"github.com/armosec/kubescape/cautils/logger"
|
||||
"github.com/armosec/kubescape/cautils/logger/helpers"
|
||||
"github.com/armosec/kubescape/resultshandling/printer"
|
||||
reporthandlingv2 "github.com/armosec/opa-utils/reporthandling/v2"
|
||||
"github.com/armosec/opa-utils/reporthandling/results/v1/reportsummary"
|
||||
"github.com/armosec/opa-utils/shared"
|
||||
)
|
||||
|
||||
/*
|
||||
riskScore
|
||||
status
|
||||
|
||||
*/
|
||||
type JunitPrinter struct {
|
||||
writer *os.File
|
||||
writer *os.File
|
||||
verbose bool
|
||||
}
|
||||
|
||||
func NewJunitPrinter() *JunitPrinter {
|
||||
return &JunitPrinter{}
|
||||
}
|
||||
|
||||
func (junitPrinter *JunitPrinter) SetWriter(outputFile string) {
|
||||
junitPrinter.writer = printer.GetWriter(outputFile)
|
||||
}
|
||||
|
||||
func (junitPrinter *JunitPrinter) Score(score float32) {
|
||||
fmt.Fprintf(os.Stderr, "\nOverall risk-score (0- Excellent, 100- All failed): %d\n", int(score))
|
||||
}
|
||||
|
||||
func (junitPrinter *JunitPrinter) FinalizeData(opaSessionObj *cautils.OPASessionObj) {
|
||||
finalizeReport(opaSessionObj)
|
||||
}
|
||||
|
||||
func (junitPrinter *JunitPrinter) ActionPrint(opaSessionObj *cautils.OPASessionObj) {
|
||||
junitResult, err := convertPostureReportToJunitResult(opaSessionObj.Report)
|
||||
if err != nil {
|
||||
logger.L().Fatal("failed to convert posture report object")
|
||||
}
|
||||
postureReportStr, err := xml.Marshal(junitResult)
|
||||
if err != nil {
|
||||
logger.L().Fatal("failed to convert posture report object")
|
||||
}
|
||||
junitPrinter.writer.Write(postureReportStr)
|
||||
// https://llg.cubic.org/docs/junit/
|
||||
|
||||
type JUnitXML struct {
|
||||
TestSuites JUnitTestSuites `xml:"testsuites"`
|
||||
}
|
||||
|
||||
// JUnitTestSuites represents the test summary
|
||||
type JUnitTestSuites struct {
|
||||
XMLName xml.Name `xml:"testsuites"`
|
||||
Suites []JUnitTestSuite `xml:"testsuite"`
|
||||
XMLName xml.Name `xml:"testsuites"`
|
||||
Suites []JUnitTestSuite `xml:"testsuite"` // list of controls
|
||||
Errors int `xml:"errors,attr"` // total number of tests with error result from all testsuites
|
||||
Disabled int `xml:"disabled,attr"` // total number of disabled tests from all testsuites
|
||||
Failures int `xml:"failures,attr"` // total number of failed tests from all testsuites
|
||||
Tests int `xml:"tests,attr"` // total number of tests from all testsuites. Some software may expect to only see the number of successful tests from all testsuites though
|
||||
Time string `xml:"time,attr"` // time in seconds to execute all test suites
|
||||
Name string `xml:"name,attr"` // ? Add framework names ?
|
||||
}
|
||||
|
||||
// JUnitTestSuite is a single JUnit test suite which may contain many
|
||||
// testcases.
|
||||
// JUnitTestSuite represents a single control
|
||||
type JUnitTestSuite struct {
|
||||
XMLName xml.Name `xml:"testsuite"`
|
||||
Tests int `xml:"tests,attr"`
|
||||
Time string `xml:"time,attr"`
|
||||
Name string `xml:"name,attr"`
|
||||
Resources int `xml:"resources,attr"`
|
||||
Excluded int `xml:"excluded,attr"`
|
||||
Failed int `xml:"filed,attr"`
|
||||
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 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
|
||||
Skipped string `xml:"skipped,attr"` // The total number of skipped tests
|
||||
Time string `xml:"time,attr"` // Time taken (in seconds) to execute the tests in the suite
|
||||
Timestamp string `xml:"timestamp,attr"` // when the test was executed in ISO 8601 format (2014-01-21T16:17:18)
|
||||
Properties []JUnitProperty `xml:"properties>property,omitempty"`
|
||||
TestCases []JUnitTestCase `xml:"testcase"`
|
||||
}
|
||||
|
||||
// JUnitTestCase is a single test case with its result.
|
||||
// JUnitTestCase represents a single resource
|
||||
type JUnitTestCase struct {
|
||||
XMLName xml.Name `xml:"testcase"`
|
||||
Classname string `xml:"classname,attr"`
|
||||
Name string `xml:"name,attr"`
|
||||
Time string `xml:"time,attr"`
|
||||
Resources int `xml:"resources,attr"`
|
||||
Excluded int `xml:"excluded,attr"`
|
||||
Failed int `xml:"filed,attr"`
|
||||
Classname string `xml:"classname,attr"` // Full class name for the class the test method is in. required
|
||||
Status string `xml:"status,attr"` // Status
|
||||
Name string `xml:"name,attr"` // Name of the test method, required
|
||||
Time string `xml:"time,attr"` // Time taken (in seconds) to execute the test. optional
|
||||
SkipMessage *JUnitSkipMessage `xml:"skipped,omitempty"`
|
||||
Failure *JUnitFailure `xml:"failure,omitempty"`
|
||||
}
|
||||
@@ -93,36 +89,123 @@ type JUnitFailure struct {
|
||||
Contents string `xml:",chardata"`
|
||||
}
|
||||
|
||||
func convertPostureReportToJunitResult(postureResult *reporthandlingv2.PostureReport) (*JUnitTestSuites, error) {
|
||||
juResult := JUnitTestSuites{XMLName: xml.Name{Local: "Kubescape scan results"}}
|
||||
for _, framework := range postureResult.ListFrameworks().All() {
|
||||
suite := JUnitTestSuite{
|
||||
Name: framework.GetName(),
|
||||
Resources: framework.NumberOfResources().All(),
|
||||
Excluded: framework.NumberOfResources().Excluded(),
|
||||
Failed: framework.NumberOfResources().Failed(),
|
||||
}
|
||||
for _, controlReports := range postureResult.ListControls().All() {
|
||||
suite.Tests = suite.Tests + 1
|
||||
testCase := JUnitTestCase{}
|
||||
testCase.Name = controlReports.GetName()
|
||||
testCase.Classname = "Kubescape"
|
||||
testCase.Time = postureResult.ReportGenerationTime.String()
|
||||
// if 0 < len(controlReports.RuleReports[0].RuleResponses) {
|
||||
|
||||
// testCase.Resources = controlReports.NumberOfResources().All()
|
||||
// testCase.Excluded = controlReports.NumberOfResources().Excluded()
|
||||
// testCase.Failed = controlReports.NumberOfResources().Failed()
|
||||
// failure := JUnitFailure{}
|
||||
// failure.Message = fmt.Sprintf("%d resources failed", testCase.Failed)
|
||||
// for _, ruleResponses := range controlReports.RuleReports[0].RuleResponses {
|
||||
// failure.Contents = fmt.Sprintf("%s\n%s", failure.Contents, ruleResponses.AlertMessage)
|
||||
// }
|
||||
// testCase.Failure = &failure
|
||||
// }
|
||||
suite.TestCases = append(suite.TestCases, testCase)
|
||||
}
|
||||
juResult.Suites = append(juResult.Suites, suite)
|
||||
func NewJunitPrinter(verbose bool) *JunitPrinter {
|
||||
return &JunitPrinter{
|
||||
verbose: verbose,
|
||||
}
|
||||
}
|
||||
|
||||
func (junitPrinter *JunitPrinter) SetWriter(outputFile string) {
|
||||
junitPrinter.writer = printer.GetWriter(outputFile)
|
||||
}
|
||||
|
||||
func (junitPrinter *JunitPrinter) Score(score float32) {
|
||||
fmt.Fprintf(os.Stderr, "\nOverall risk-score (0- Excellent, 100- All failed): %d\n", int(score))
|
||||
}
|
||||
|
||||
func (junitPrinter *JunitPrinter) ActionPrint(opaSessionObj *cautils.OPASessionObj) {
|
||||
junitResult := testsSuites(opaSessionObj)
|
||||
postureReportStr, err := xml.Marshal(junitResult)
|
||||
if err != nil {
|
||||
logger.L().Fatal("failed to Marshal xml result object", helpers.Error(err))
|
||||
}
|
||||
|
||||
logOUtputFile(junitPrinter.writer.Name())
|
||||
|
||||
junitPrinter.writer.Write(postureReportStr)
|
||||
}
|
||||
|
||||
func testsSuites(results *cautils.OPASessionObj) *JUnitTestSuites {
|
||||
return &JUnitTestSuites{
|
||||
Suites: listTestsSuite(results),
|
||||
Tests: results.Report.SummaryDetails.NumberOfControls().All(),
|
||||
Name: "Kubescape Scanning",
|
||||
Failures: results.Report.SummaryDetails.NumberOfControls().Failed(),
|
||||
}
|
||||
}
|
||||
func listTestsSuite(results *cautils.OPASessionObj) []JUnitTestSuite {
|
||||
var testSuites []JUnitTestSuite
|
||||
|
||||
// control scan
|
||||
if len(results.Report.SummaryDetails.ListFrameworks().All()) == 0 {
|
||||
testSuite := JUnitTestSuite{}
|
||||
testSuite.Failures = results.Report.SummaryDetails.NumberOfControls().Failed()
|
||||
testSuite.Timestamp = results.Report.ReportGenerationTime.String()
|
||||
testSuite.ID = 0
|
||||
testSuite.Name = "kubescape"
|
||||
testSuite.Properties = properties(results.Report.SummaryDetails.Score)
|
||||
testSuite.TestCases = testsCases(results, &results.Report.SummaryDetails.Controls, "Kubescape")
|
||||
testSuites = append(testSuites, testSuite)
|
||||
return testSuites
|
||||
}
|
||||
|
||||
for i, f := range results.Report.SummaryDetails.Frameworks {
|
||||
testSuite := JUnitTestSuite{}
|
||||
testSuite.Failures = f.NumberOfControls().Failed()
|
||||
testSuite.Timestamp = results.Report.ReportGenerationTime.String()
|
||||
testSuite.ID = i
|
||||
testSuite.Name = f.Name
|
||||
testSuite.Properties = properties(f.Score)
|
||||
testSuite.TestCases = testsCases(results, f.ListControls(), f.GetName())
|
||||
testSuites = append(testSuites, testSuite)
|
||||
}
|
||||
|
||||
return testSuites
|
||||
}
|
||||
func testsCases(results *cautils.OPASessionObj, controls reportsummary.IControlsSummaries, classname string) []JUnitTestCase {
|
||||
var testCases []JUnitTestCase
|
||||
|
||||
for _, cID := range controls.ListControlsIDs().All() {
|
||||
testCase := JUnitTestCase{}
|
||||
control := results.Report.SummaryDetails.Controls.GetControl(reportsummary.EControlCriteriaID, cID)
|
||||
|
||||
testCase.Name = control.GetName()
|
||||
testCase.Classname = classname
|
||||
testCase.Status = string(control.GetStatus().Status())
|
||||
|
||||
if control.GetStatus().IsFailed() {
|
||||
resources := map[string]interface{}{}
|
||||
resourceIDs := control.ListResourcesIDs().Failed()
|
||||
for j := range resourceIDs {
|
||||
resource := results.AllResources[resourceIDs[j]]
|
||||
resources[resourceToString(resource)] = nil
|
||||
}
|
||||
resourcesStr := shared.MapStringToSlice(resources)
|
||||
sort.Strings(resourcesStr)
|
||||
testCaseFailure := JUnitFailure{}
|
||||
testCaseFailure.Type = "Control"
|
||||
// testCaseFailure.Contents =
|
||||
testCaseFailure.Message = fmt.Sprintf("Remediation: %s\nMore details: %s\n\n%s", control.GetRemediation(), getControlURL(control.GetID()), strings.Join(resourcesStr, "\n"))
|
||||
|
||||
testCase.Failure = &testCaseFailure
|
||||
} else if control.GetStatus().IsSkipped() {
|
||||
testCase.SkipMessage = &JUnitSkipMessage{
|
||||
Message: "", // TODO - fill after statusInfo is supportred
|
||||
}
|
||||
|
||||
}
|
||||
testCases = append(testCases, testCase)
|
||||
}
|
||||
return testCases
|
||||
}
|
||||
|
||||
func resourceToString(resource workloadinterface.IMetadata) string {
|
||||
sep := "; "
|
||||
s := ""
|
||||
s += fmt.Sprintf("apiVersion: %s", resource.GetApiVersion()) + sep
|
||||
s += fmt.Sprintf("kind: %s", resource.GetKind()) + sep
|
||||
if resource.GetNamespace() != "" {
|
||||
s += fmt.Sprintf("namespace: %s", resource.GetNamespace()) + sep
|
||||
}
|
||||
s += fmt.Sprintf("name: %s", resource.GetName())
|
||||
return s
|
||||
}
|
||||
|
||||
func properties(riskScore float32) []JUnitProperty {
|
||||
return []JUnitProperty{
|
||||
{
|
||||
Name: "riskScore",
|
||||
Value: fmt.Sprintf("%.2f", riskScore),
|
||||
},
|
||||
}
|
||||
return &juResult, nil
|
||||
}
|
||||
|
||||
202
resultshandling/printer/v2/pdf.go
Normal file
202
resultshandling/printer/v2/pdf.go
Normal file
@@ -0,0 +1,202 @@
|
||||
package v2
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
b64 "encoding/base64"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/armosec/kubescape/cautils"
|
||||
"github.com/armosec/kubescape/resultshandling/printer"
|
||||
"github.com/armosec/opa-utils/reporthandling/results/v1/reportsummary"
|
||||
"github.com/johnfercher/maroto/pkg/color"
|
||||
"github.com/johnfercher/maroto/pkg/consts"
|
||||
"github.com/johnfercher/maroto/pkg/pdf"
|
||||
"github.com/johnfercher/maroto/pkg/props"
|
||||
)
|
||||
|
||||
const (
|
||||
pdfOutputFile = "report"
|
||||
pdfOutputExt = ".pdf"
|
||||
)
|
||||
|
||||
var (
|
||||
//go:embed pdf/logo.png
|
||||
kubescapeLogo []byte
|
||||
)
|
||||
|
||||
type PdfPrinter struct {
|
||||
writer *os.File
|
||||
sortedControlNames []string
|
||||
}
|
||||
|
||||
func NewPdfPrinter() *PdfPrinter {
|
||||
return &PdfPrinter{}
|
||||
}
|
||||
|
||||
func (pdfPrinter *PdfPrinter) SetWriter(outputFile string) {
|
||||
// Ensure to have an available output file, otherwise create it.
|
||||
if outputFile == "" {
|
||||
outputFile = pdfOutputFile
|
||||
}
|
||||
// Ensure to have the right file extension.
|
||||
if filepath.Ext(strings.TrimSpace(outputFile)) != pdfOutputExt {
|
||||
outputFile = outputFile + pdfOutputExt
|
||||
}
|
||||
pdfPrinter.writer = printer.GetWriter(outputFile)
|
||||
}
|
||||
|
||||
func (pdfPrinter *PdfPrinter) Score(score float32) {
|
||||
fmt.Fprintf(os.Stderr, "\nOverall risk-score (0- Excellent, 100- All failed): %d\n", int(score))
|
||||
}
|
||||
|
||||
func (pdfPrinter *PdfPrinter) ActionPrint(opaSessionObj *cautils.OPASessionObj) {
|
||||
pdfPrinter.sortedControlNames = getSortedControlsNames(opaSessionObj.Report.SummaryDetails.Controls)
|
||||
|
||||
m := pdf.NewMaroto(consts.Portrait, consts.A4)
|
||||
pdfPrinter.printHeader(m)
|
||||
pdfPrinter.printFramework(m, opaSessionObj.Report.SummaryDetails.ListFrameworks().All())
|
||||
pdfPrinter.printTable(m, &opaSessionObj.Report.SummaryDetails)
|
||||
pdfPrinter.printFinalResult(m, &opaSessionObj.Report.SummaryDetails)
|
||||
|
||||
// Extrat output buffer.
|
||||
outBuff, err := m.Output()
|
||||
if err != nil {
|
||||
fmt.Println("Could not save PDF:", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
pdfPrinter.writer.Write(outBuff.Bytes())
|
||||
}
|
||||
|
||||
// Print Kubescape logo and report date.
|
||||
func (pdfPrinter *PdfPrinter) printHeader(m pdf.Maroto) {
|
||||
// Retrieve current time (we need it for the report timestamp).
|
||||
t := time.Now()
|
||||
// Enconde PNG into Base64 to embed it into the pdf.
|
||||
kubescapeLogoEnc := b64.StdEncoding.EncodeToString(kubescapeLogo)
|
||||
|
||||
m.SetPageMargins(10, 15, 10)
|
||||
m.Row(40, func() {
|
||||
//m.Text(fmt.Sprintf("Security Assessment"), props.Text{
|
||||
// Align: consts.Center,
|
||||
// Size: 24,
|
||||
// Family: consts.Arial,
|
||||
// Style: consts.Bold,
|
||||
//})
|
||||
_ = m.Base64Image(kubescapeLogoEnc, consts.Png, props.Rect{
|
||||
Center: true,
|
||||
Percent: 100,
|
||||
})
|
||||
})
|
||||
m.Row(6, func() {
|
||||
m.Text(fmt.Sprintf("Report date: %d-%02d-%02dT%02d:%02d:%02d",
|
||||
t.Year(),
|
||||
t.Month(),
|
||||
t.Day(),
|
||||
t.Hour(),
|
||||
t.Minute(),
|
||||
t.Second()), props.Text{
|
||||
Align: consts.Left,
|
||||
Size: 6.0,
|
||||
Style: consts.Bold,
|
||||
Family: consts.Arial,
|
||||
})
|
||||
})
|
||||
m.Line(1)
|
||||
}
|
||||
|
||||
// Print pdf frameworks after pdf header.
|
||||
func (pdfPrinter *PdfPrinter) printFramework(m pdf.Maroto, frameworks []reportsummary.IPolicies) {
|
||||
m.Row(10, func() {
|
||||
m.Text(frameworksScoresToString(frameworks), props.Text{
|
||||
Align: consts.Center,
|
||||
Size: 8,
|
||||
Family: consts.Arial,
|
||||
Style: consts.Bold,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// Create pdf table
|
||||
func (pdfPrinter *PdfPrinter) printTable(m pdf.Maroto, summaryDetails *reportsummary.SummaryDetails) {
|
||||
headers := getControlTableHeaders()
|
||||
controls := make([][]string, len(pdfPrinter.sortedControlNames))
|
||||
for i := range controls {
|
||||
controls[i] = make([]string, len(headers))
|
||||
}
|
||||
for i := 0; i < len(pdfPrinter.sortedControlNames); i++ {
|
||||
controls[i] = generateRow(summaryDetails.Controls.GetControl(reportsummary.EControlCriteriaName, pdfPrinter.sortedControlNames[i]))
|
||||
}
|
||||
|
||||
m.TableList(headers, controls, props.TableList{
|
||||
HeaderProp: props.TableListContent{
|
||||
Family: consts.Arial,
|
||||
Style: consts.Bold,
|
||||
Size: 8.0,
|
||||
},
|
||||
ContentProp: props.TableListContent{
|
||||
Family: consts.Courier,
|
||||
Style: consts.Normal,
|
||||
Size: 8.0,
|
||||
},
|
||||
Align: consts.Center,
|
||||
AlternatedBackground: &color.Color{
|
||||
Red: 224,
|
||||
Green: 224,
|
||||
Blue: 224,
|
||||
},
|
||||
HeaderContentSpace: 2.0,
|
||||
Line: false,
|
||||
})
|
||||
m.Line(1)
|
||||
m.Row(2, func() {})
|
||||
}
|
||||
|
||||
// Add final results.
|
||||
func (pdfPrinter *PdfPrinter) printFinalResult(m pdf.Maroto, summaryDetails *reportsummary.SummaryDetails) {
|
||||
m.Row(5, func() {
|
||||
m.Col(3, func() {
|
||||
m.Text("Resource summary", props.Text{
|
||||
Align: consts.Left,
|
||||
Size: 8.0,
|
||||
Style: consts.Bold,
|
||||
Family: consts.Arial,
|
||||
})
|
||||
})
|
||||
m.Col(2, func() {
|
||||
m.Text(fmt.Sprintf("%d", summaryDetails.NumberOfResources().Failed()), props.Text{
|
||||
Align: consts.Left,
|
||||
Size: 8.0,
|
||||
Style: consts.Bold,
|
||||
Family: consts.Arial,
|
||||
})
|
||||
})
|
||||
m.Col(2, func() {
|
||||
m.Text(fmt.Sprintf("%d", summaryDetails.NumberOfResources().Excluded()), props.Text{
|
||||
Align: consts.Left,
|
||||
Size: 8.0,
|
||||
Style: consts.Bold,
|
||||
Family: consts.Arial,
|
||||
})
|
||||
})
|
||||
m.Col(2, func() {
|
||||
m.Text(fmt.Sprintf("%d", summaryDetails.NumberOfResources().All()), props.Text{
|
||||
Align: consts.Left,
|
||||
Size: 8.0,
|
||||
Style: consts.Bold,
|
||||
Family: consts.Arial,
|
||||
})
|
||||
})
|
||||
m.Col(2, func() {
|
||||
m.Text(fmt.Sprintf("%.2f%s", summaryDetails.Score, "%"), props.Text{
|
||||
Align: consts.Left,
|
||||
Size: 8.0,
|
||||
Style: consts.Bold,
|
||||
Family: consts.Arial,
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
BIN
resultshandling/printer/v2/pdf/logo.png
Normal file
BIN
resultshandling/printer/v2/pdf/logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 53 KiB |
@@ -1,4 +1,4 @@
|
||||
package controlmapping
|
||||
package v2
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@@ -17,21 +17,27 @@ import (
|
||||
)
|
||||
|
||||
type PrettyPrinter struct {
|
||||
formatVersion string
|
||||
writer *os.File
|
||||
verboseMode bool
|
||||
sortedControlNames []string
|
||||
}
|
||||
|
||||
func NewPrettyPrinter(verboseMode bool) *PrettyPrinter {
|
||||
func NewPrettyPrinter(verboseMode bool, formatVersion string) *PrettyPrinter {
|
||||
return &PrettyPrinter{
|
||||
verboseMode: verboseMode,
|
||||
verboseMode: verboseMode,
|
||||
formatVersion: formatVersion,
|
||||
}
|
||||
}
|
||||
|
||||
func (prettyPrinter *PrettyPrinter) ActionPrint(opaSessionObj *cautils.OPASessionObj) {
|
||||
prettyPrinter.sortedControlNames = getSortedControlsNames(opaSessionObj.Report.SummaryDetails.Controls) // ListControls().All())
|
||||
|
||||
prettyPrinter.printResults(&opaSessionObj.Report.SummaryDetails.Controls, opaSessionObj.AllResources)
|
||||
if prettyPrinter.formatVersion == "v1" {
|
||||
prettyPrinter.printResults(&opaSessionObj.Report.SummaryDetails.Controls, opaSessionObj.AllResources)
|
||||
} else if prettyPrinter.formatVersion == "v2" {
|
||||
prettyPrinter.resourceTable(opaSessionObj.ResourcesResult, opaSessionObj.AllResources)
|
||||
}
|
||||
prettyPrinter.printSummaryTable(&opaSessionObj.Report.SummaryDetails)
|
||||
|
||||
}
|
||||
@@ -152,25 +158,6 @@ func generateRelatedObjectsStr(workload WorkloadSummary) string {
|
||||
}
|
||||
return relatedStr
|
||||
}
|
||||
|
||||
func generateRow(controlSummary reportsummary.IControlSummary) []string {
|
||||
row := []string{controlSummary.GetName()}
|
||||
row = append(row, fmt.Sprintf("%d", controlSummary.NumberOfResources().Failed()))
|
||||
row = append(row, fmt.Sprintf("%d", controlSummary.NumberOfResources().Excluded()))
|
||||
row = append(row, fmt.Sprintf("%d", controlSummary.NumberOfResources().All()))
|
||||
|
||||
if !controlSummary.GetStatus().IsSkipped() {
|
||||
row = append(row, fmt.Sprintf("%d", int(controlSummary.GetScore()))+"%")
|
||||
} else {
|
||||
row = append(row, "skipped")
|
||||
}
|
||||
return row
|
||||
}
|
||||
|
||||
func generateHeader() []string {
|
||||
return []string{"Control Name", "Failed Resources", "Excluded Resources", "All Resources", "% risk-score"}
|
||||
}
|
||||
|
||||
func generateFooter(summaryDetails *reportsummary.SummaryDetails) []string {
|
||||
// Control name | # failed resources | all resources | % success
|
||||
row := []string{}
|
||||
@@ -183,12 +170,10 @@ func generateFooter(summaryDetails *reportsummary.SummaryDetails) []string {
|
||||
return row
|
||||
}
|
||||
func (prettyPrinter *PrettyPrinter) printSummaryTable(summaryDetails *reportsummary.SummaryDetails) {
|
||||
// For control scan framework will be nil
|
||||
prettyPrinter.printFramework(summaryDetails.ListFrameworks().All())
|
||||
|
||||
summaryTable := tablewriter.NewWriter(prettyPrinter.writer)
|
||||
summaryTable.SetAutoWrapText(false)
|
||||
summaryTable.SetHeader(generateHeader())
|
||||
summaryTable.SetHeader(getControlTableHeaders())
|
||||
summaryTable.SetHeaderLine(true)
|
||||
alignments := []int{tablewriter.ALIGN_LEFT, tablewriter.ALIGN_CENTER, tablewriter.ALIGN_CENTER, tablewriter.ALIGN_CENTER, tablewriter.ALIGN_CENTER}
|
||||
summaryTable.SetColumnAlignment(alignments)
|
||||
@@ -201,12 +186,16 @@ func (prettyPrinter *PrettyPrinter) printSummaryTable(summaryDetails *reportsumm
|
||||
|
||||
// summaryTable.SetFooter(generateFooter())
|
||||
summaryTable.Render()
|
||||
|
||||
// For control scan framework will be nil
|
||||
cautils.InfoTextDisplay(prettyPrinter.writer, frameworksScoresToString(summaryDetails.ListFrameworks().All()))
|
||||
}
|
||||
|
||||
func (prettyPrinter *PrettyPrinter) printFramework(frameworks []reportsummary.IPolicies) {
|
||||
func frameworksScoresToString(frameworks []reportsummary.IPolicies) string {
|
||||
if len(frameworks) == 1 {
|
||||
if frameworks[0].GetName() != "" {
|
||||
cautils.InfoTextDisplay(prettyPrinter.writer, fmt.Sprintf("FRAMEWORK %s\n", frameworks[0].GetName()))
|
||||
return fmt.Sprintf("FRAMEWORK %s\n", frameworks[0].GetName())
|
||||
// cautils.InfoTextDisplay(prettyPrinter.writer, ))
|
||||
}
|
||||
} else if len(frameworks) > 1 {
|
||||
p := "FRAMEWORKS: "
|
||||
@@ -215,17 +204,9 @@ func (prettyPrinter *PrettyPrinter) printFramework(frameworks []reportsummary.IP
|
||||
p += fmt.Sprintf("%s (risk: %.2f), ", frameworks[i].GetName(), frameworks[i].GetScore())
|
||||
}
|
||||
p += fmt.Sprintf("%s (risk: %.2f)\n", frameworks[i].GetName(), frameworks[i].GetScore())
|
||||
cautils.InfoTextDisplay(prettyPrinter.writer, p)
|
||||
return p
|
||||
}
|
||||
}
|
||||
func getSortedControlsNames(controls reportsummary.ControlSummaries) []string {
|
||||
controlNames := make([]string, 0, len(controls))
|
||||
for k := range controls {
|
||||
c := controls[k]
|
||||
controlNames = append(controlNames, c.GetName())
|
||||
}
|
||||
sort.Strings(controlNames)
|
||||
return controlNames
|
||||
return ""
|
||||
}
|
||||
|
||||
// func getSortedControlsNames(controls []reportsummary.IPolicies) []string {
|
||||
@@ -1,21 +1,3 @@
|
||||
package v2
|
||||
|
||||
import (
|
||||
"github.com/armosec/kubescape/resultshandling/printer"
|
||||
"github.com/armosec/kubescape/resultshandling/printer/v2/resourcemapping"
|
||||
)
|
||||
|
||||
var INDENT = " "
|
||||
|
||||
func GetPrinter(printFormat string, verboseMode bool) printer.IPrinter {
|
||||
switch printFormat {
|
||||
case printer.JsonFormat:
|
||||
return resourcemapping.NewJsonPrinter()
|
||||
case printer.JunitResultFormat:
|
||||
return NewJunitPrinter()
|
||||
// case printer.PrometheusFormat:
|
||||
// return NewPrometheusPrinter(verboseMode)
|
||||
default:
|
||||
return resourcemapping.NewPrettyPrinter(verboseMode)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,215 +0,0 @@
|
||||
package resourcemapping
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/armosec/kubescape/cautils"
|
||||
"github.com/armosec/kubescape/resultshandling/printer"
|
||||
"github.com/armosec/opa-utils/reporthandling/results/v1/resourcesresults"
|
||||
"github.com/olekukonko/tablewriter"
|
||||
)
|
||||
|
||||
type PrettyPrinter struct {
|
||||
writer *os.File
|
||||
verboseMode bool
|
||||
}
|
||||
|
||||
func NewPrettyPrinter(verboseMode bool) *PrettyPrinter {
|
||||
return &PrettyPrinter{
|
||||
verboseMode: verboseMode,
|
||||
}
|
||||
}
|
||||
|
||||
func (prettyPrinter *PrettyPrinter) printResourceTable(results []resourcesresults.Result) {
|
||||
|
||||
summaryTable := tablewriter.NewWriter(prettyPrinter.writer)
|
||||
summaryTable.SetAutoWrapText(true)
|
||||
summaryTable.SetAutoMergeCells(false)
|
||||
// summaryTable.SetCenterSeparator("=")
|
||||
// summaryTable.SetRowSeparator("*")
|
||||
// summaryTable.
|
||||
summaryTable.SetHeader(generateResourceHeader())
|
||||
summaryTable.SetHeaderLine(true)
|
||||
|
||||
// For control scan framework will be nil
|
||||
for i := range results {
|
||||
// status := result.GetStatus(nil).Status()
|
||||
resourceID := results[i].GetResourceID()
|
||||
control := results[i].ListControls()
|
||||
if raw := generateResourceRow(resourceID, control, prettyPrinter.verboseMode); len(raw) > 0 {
|
||||
summaryTable.Append(raw)
|
||||
}
|
||||
}
|
||||
|
||||
// alignments := []int{tablewriter.ALIGN_LEFT, tablewriter.ALIGN_CENTER, tablewriter.ALIGN_CENTER, tablewriter.ALIGN_CENTER, tablewriter.ALIGN_CENTER}
|
||||
// summaryTable.SetColumnAlignment(alignments)
|
||||
|
||||
// for i := 0; i < len(prettyPrinter.sortedControlNames); i++ {
|
||||
// controlSummary := prettyPrinter.summary[prettyPrinter.sortedControlNames[i]]
|
||||
// summaryTable.Append(generateRow(prettyPrinter.sortedControlNames[i], controlSummary))
|
||||
// }
|
||||
|
||||
// summaryTable.SetFooter(generateFooter(prettyPrinter))
|
||||
|
||||
summaryTable.Render()
|
||||
}
|
||||
|
||||
func (prettyPrinter *PrettyPrinter) ActionPrint(opaSessionObj *cautils.OPASessionObj) {
|
||||
|
||||
prettyPrinter.printResourceTable(opaSessionObj.Report.Results)
|
||||
// var overallRiskScore float32 = 0
|
||||
// for _, frameworkReport := range opaSessionObj.PostureReport.FrameworkReports {
|
||||
// frameworkNames = append(frameworkNames, frameworkReport.Name)
|
||||
// frameworkScores = append(frameworkScores, frameworkReport.Score)
|
||||
// failedResources = reporthandling.GetUniqueResourcesIDs(append(failedResources, frameworkReport.ListResourcesIDs().GetFailedResources()...))
|
||||
// warningResources = reporthandling.GetUniqueResourcesIDs(append(warningResources, frameworkReport.ListResourcesIDs().GetWarningResources()...))
|
||||
// allResources = reporthandling.GetUniqueResourcesIDs(append(allResources, frameworkReport.ListResourcesIDs().GetAllResources()...))
|
||||
// prettyPrinter.summarySetup(frameworkReport, opaSessionObj.AllResources)
|
||||
// overallRiskScore += frameworkReport.Score
|
||||
// }
|
||||
|
||||
// overallRiskScore /= float32(len(opaSessionObj.PostureReport.FrameworkReports))
|
||||
|
||||
// prettyPrinter.frameworkSummary = ResultSummary{
|
||||
// RiskScore: overallRiskScore,
|
||||
// TotalResources: len(allResources),
|
||||
// TotalFailed: len(failedResources),
|
||||
// TotalWarning: len(warningResources),
|
||||
// }
|
||||
|
||||
// prettyPrinter.printResults()
|
||||
// prettyPrinter.printSummaryTable(frameworkNames, frameworkScores)
|
||||
|
||||
}
|
||||
|
||||
func (prettyPrinter *PrettyPrinter) SetWriter(outputFile string) {
|
||||
prettyPrinter.writer = printer.GetWriter(outputFile)
|
||||
}
|
||||
|
||||
func (prettyPrinter *PrettyPrinter) FinalizeData(opaSessionObj *cautils.OPASessionObj) {
|
||||
// finalizeReport(opaSessionObj)
|
||||
}
|
||||
func (prettyPrinter *PrettyPrinter) Score(score float32) {
|
||||
}
|
||||
|
||||
// func (prettyPrinter *PrettyPrinter) printSummary(controlName string, controlSummary *ResultSummary) {
|
||||
// // cautils.SimpleDisplay(prettyPrinter.writer, "Summary - ")
|
||||
// // cautils.SuccessDisplay(prettyPrinter.writer, "Passed:%v ", controlSummary.TotalResources-controlSummary.TotalFailed-controlSummary.TotalWarning)
|
||||
// // cautils.WarningDisplay(prettyPrinter.writer, "Excluded:%v ", controlSummary.TotalWarning)
|
||||
// // cautils.FailureDisplay(prettyPrinter.writer, "Failed:%v ", controlSummary.TotalFailed)
|
||||
// // cautils.InfoDisplay(prettyPrinter.writer, "Total:%v\n", controlSummary.TotalResources)
|
||||
// // if controlSummary.TotalFailed > 0 {
|
||||
// // cautils.DescriptionDisplay(prettyPrinter.writer, "Remediation: %v\n", controlSummary.Remediation)
|
||||
// // }
|
||||
// // cautils.DescriptionDisplay(prettyPrinter.writer, "\n")
|
||||
|
||||
// }
|
||||
|
||||
func generateResourceRow(resourceID string, controls []resourcesresults.ResourceAssociatedControl, verboseMode bool) []string {
|
||||
row := []string{}
|
||||
|
||||
controlsNames := []string{}
|
||||
statuses := []string{}
|
||||
|
||||
for i := range controls {
|
||||
if !verboseMode && controls[i].GetStatus(nil).IsPassed() {
|
||||
continue
|
||||
}
|
||||
if controls[i].GetName() == "" {
|
||||
continue
|
||||
}
|
||||
controlsNames = append(controlsNames, controls[i].GetName())
|
||||
statuses = append(statuses, string(controls[i].GetStatus(nil).Status()))
|
||||
}
|
||||
|
||||
splitted := strings.Split(resourceID, "/")
|
||||
if len(splitted) < 5 || len(controlsNames) == 0 {
|
||||
return row
|
||||
}
|
||||
|
||||
row = append(row, splitted[3])
|
||||
row = append(row, splitted[4])
|
||||
row = append(row, splitted[2])
|
||||
|
||||
row = append(row, strings.Join(controlsNames, "\n"))
|
||||
row = append(row, strings.Join(statuses, "\n"))
|
||||
|
||||
return row
|
||||
}
|
||||
|
||||
// func generateRow(control string, cs ResultSummary) []string {
|
||||
// row := []string{control}
|
||||
// row = append(row, cs.ToSlice()...)
|
||||
// if cs.TotalResources != 0 {
|
||||
// row = append(row, fmt.Sprintf("%d", int(cs.RiskScore))+"%")
|
||||
// } else {
|
||||
// row = append(row, "skipped")
|
||||
// }
|
||||
// return row
|
||||
// }
|
||||
|
||||
func generateResourceHeader() []string {
|
||||
return []string{"Kind", "Name", "Namespace", "Controls", "Statues"}
|
||||
}
|
||||
func generateHeader() []string {
|
||||
return []string{"Control Name", "Failed Resources", "Excluded Resources", "All Resources", "% risk-score"}
|
||||
}
|
||||
|
||||
func generateFooter(prettyPrinter *PrettyPrinter) []string {
|
||||
// Control name | # failed resources | all resources | % success
|
||||
row := []string{}
|
||||
// row = append(row, "Resource Summary") //fmt.Sprintf(""%d", numControlers"))
|
||||
// row = append(row, fmt.Sprintf("%d", prettyPrinter.frameworkSummary.TotalFailed))
|
||||
// row = append(row, fmt.Sprintf("%d", prettyPrinter.frameworkSummary.TotalWarning))
|
||||
// row = append(row, fmt.Sprintf("%d", prettyPrinter.frameworkSummary.TotalResources))
|
||||
// row = append(row, fmt.Sprintf("%.2f%s", prettyPrinter.frameworkSummary.RiskScore, "%"))
|
||||
|
||||
return row
|
||||
}
|
||||
func (prettyPrinter *PrettyPrinter) printSummaryTable(frameworksNames []string, frameworkScores []float32) {
|
||||
// For control scan framework will be nil
|
||||
prettyPrinter.printFramework(frameworksNames, frameworkScores)
|
||||
|
||||
summaryTable := tablewriter.NewWriter(prettyPrinter.writer)
|
||||
// summaryTable.SetAutoWrapText(false)
|
||||
// summaryTable.SetHeader(generateHeader())
|
||||
// summaryTable.SetHeaderLine(true)
|
||||
// alignments := []int{tablewriter.ALIGN_LEFT, tablewriter.ALIGN_CENTER, tablewriter.ALIGN_CENTER, tablewriter.ALIGN_CENTER, tablewriter.ALIGN_CENTER}
|
||||
// summaryTable.SetColumnAlignment(alignments)
|
||||
|
||||
// for i := 0; i < len(prettyPrinter.sortedControlNames); i++ {
|
||||
// controlSummary := prettyPrinter.summary[prettyPrinter.sortedControlNames[i]]
|
||||
// summaryTable.Append(generateRow(prettyPrinter.sortedControlNames[i], controlSummary))
|
||||
// }
|
||||
|
||||
// summaryTable.SetFooter(generateFooter(prettyPrinter))
|
||||
|
||||
summaryTable.Render()
|
||||
}
|
||||
|
||||
func (prettyPrinter *PrettyPrinter) printFramework(frameworksNames []string, frameworkScores []float32) {
|
||||
if len(frameworksNames) == 1 {
|
||||
cautils.InfoTextDisplay(prettyPrinter.writer, fmt.Sprintf("FRAMEWORK %s\n", frameworksNames[0]))
|
||||
} else if len(frameworksNames) > 1 {
|
||||
p := "FRAMEWORKS: "
|
||||
for i := 0; i < len(frameworksNames)-1; i++ {
|
||||
p += fmt.Sprintf("%s (risk: %.2f), ", frameworksNames[i], frameworkScores[i])
|
||||
}
|
||||
p += fmt.Sprintf("%s (risk: %.2f)\n", frameworksNames[len(frameworksNames)-1], frameworkScores[len(frameworkScores)-1])
|
||||
cautils.InfoTextDisplay(prettyPrinter.writer, p)
|
||||
}
|
||||
}
|
||||
|
||||
// func (prettyPrinter *PrettyPrinter) getSortedControlsNames() []string {
|
||||
// controlNames := make([]string, 0, len(prettyPrinter.summary))
|
||||
// for k := range prettyPrinter.summary {
|
||||
// controlNames = append(controlNames, k)
|
||||
// }
|
||||
// sort.Strings(controlNames)
|
||||
// return controlNames
|
||||
// }
|
||||
// func getControlURL(controlID string) string {
|
||||
// return fmt.Sprintf("https://hub.armo.cloud/docs/%s", strings.ToLower(controlID))
|
||||
// }
|
||||
101
resultshandling/printer/v2/resourcetable.go
Normal file
101
resultshandling/printer/v2/resourcetable.go
Normal file
@@ -0,0 +1,101 @@
|
||||
package v2
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/armosec/k8s-interface/workloadinterface"
|
||||
"github.com/armosec/opa-utils/reporthandling/results/v1/resourcesresults"
|
||||
"github.com/olekukonko/tablewriter"
|
||||
)
|
||||
|
||||
func (prettyPrinter *PrettyPrinter) resourceTable(results map[string]resourcesresults.Result, allResources map[string]workloadinterface.IMetadata) {
|
||||
|
||||
summaryTable := tablewriter.NewWriter(prettyPrinter.writer)
|
||||
summaryTable.SetAutoWrapText(true)
|
||||
summaryTable.SetAutoMergeCells(true)
|
||||
summaryTable.SetHeader(generateResourceHeader())
|
||||
summaryTable.SetHeaderLine(true)
|
||||
summaryTable.SetRowLine(true)
|
||||
// summaryTable.SetFooter([]string{"", "", "Total", "", "$146.93"})
|
||||
// For control scan framework will be nil
|
||||
data := Matrix{}
|
||||
for i := range results {
|
||||
resource, ok := allResources[i]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
s := results[i]
|
||||
if raw := generateResourceRows(resource, s.ListControls(), prettyPrinter.verboseMode); len(raw) > 0 {
|
||||
data = append(data, raw...)
|
||||
}
|
||||
}
|
||||
sort.Sort(data)
|
||||
summaryTable.AppendBulk(data)
|
||||
|
||||
summaryTable.Render()
|
||||
}
|
||||
|
||||
func generateResourceRows(resource workloadinterface.IMetadata, controls []resourcesresults.ResourceAssociatedControl, verboseMode bool) [][]string {
|
||||
rows := [][]string{}
|
||||
|
||||
for i := range controls {
|
||||
|
||||
if controls[i].GetName() == "" {
|
||||
continue
|
||||
}
|
||||
row := []string{}
|
||||
|
||||
if !verboseMode && controls[i].GetStatus(nil).IsPassed() {
|
||||
continue
|
||||
}
|
||||
|
||||
row = append(row, fmt.Sprintf("%s\nhttps://hub.armo.cloud/docs/%s", controls[i].GetName(), strings.ToLower(controls[i].GetID())))
|
||||
row = append(row, resource.GetNamespace())
|
||||
paths := failedPathsToString(&controls[i])
|
||||
|
||||
row = append(row, fmt.Sprintf("%s/%s\n%s", resource.GetKind(), resource.GetName(), strings.Join(paths, ";\n")))
|
||||
row = append(row, string(controls[i].GetStatus(nil).Status()))
|
||||
rows = append(rows, row)
|
||||
}
|
||||
|
||||
return rows
|
||||
}
|
||||
|
||||
func generateResourceHeader() []string {
|
||||
return []string{"Control", "Namespace", "Kind/Name", "Statues"}
|
||||
}
|
||||
|
||||
type Matrix [][]string
|
||||
|
||||
func (a Matrix) Len() int { return len(a) }
|
||||
func (a Matrix) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||
func (a Matrix) Less(i, j int) bool {
|
||||
l := len(a[i])
|
||||
for k := 0; k < l; k++ {
|
||||
if a[i][k] < a[j][k] {
|
||||
return true
|
||||
} else if a[i][k] > a[j][k] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func failedPathsToString(control *resourcesresults.ResourceAssociatedControl) []string {
|
||||
var paths []string
|
||||
|
||||
for j := range control.ResourceAssociatedRules {
|
||||
for k := range control.ResourceAssociatedRules[j].Paths {
|
||||
if p := control.ResourceAssociatedRules[j].Paths[k].FailedPath; p != "" {
|
||||
paths = append(paths, p)
|
||||
}
|
||||
if p := control.ResourceAssociatedRules[j].Paths[k].FixPath.Path; p != "" {
|
||||
v := control.ResourceAssociatedRules[j].Paths[k].FixPath.Value
|
||||
paths = append(paths, fmt.Sprintf("%s=%s", p, v))
|
||||
}
|
||||
}
|
||||
}
|
||||
return paths
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package controlmapping
|
||||
package v2
|
||||
|
||||
import (
|
||||
"github.com/armosec/k8s-interface/k8sinterface"
|
||||
@@ -3,22 +3,22 @@ package v2
|
||||
import (
|
||||
"github.com/armosec/k8s-interface/workloadinterface"
|
||||
"github.com/armosec/kubescape/cautils"
|
||||
"github.com/armosec/kubescape/cautils/logger"
|
||||
"github.com/armosec/kubescape/cautils/logger/helpers"
|
||||
"github.com/armosec/opa-utils/reporthandling"
|
||||
"github.com/armosec/opa-utils/reporthandling/results/v1/resourcesresults"
|
||||
)
|
||||
|
||||
// finalizeV2Report finalize the results objects by copying data from map to lists
|
||||
func finalizeReport(opaSessionObj *cautils.OPASessionObj) {
|
||||
func finalizeJson(opaSessionObj *cautils.OPASessionObj) {
|
||||
if len(opaSessionObj.Report.Results) == 0 {
|
||||
opaSessionObj.Report.Results = make([]resourcesresults.Result, len(opaSessionObj.ResourcesResult))
|
||||
finalizeResults(opaSessionObj.Report.Results, opaSessionObj.ResourcesResult)
|
||||
opaSessionObj.ResourcesResult = nil
|
||||
}
|
||||
|
||||
if len(opaSessionObj.Report.Resources) == 0 {
|
||||
opaSessionObj.Report.Resources = make([]reporthandling.Resource, len(opaSessionObj.AllResources))
|
||||
finalizeResources(opaSessionObj.Report.Resources, opaSessionObj.AllResources)
|
||||
opaSessionObj.AllResources = nil
|
||||
finalizeResources(opaSessionObj.Report.Resources, opaSessionObj.Report.Results, opaSessionObj.AllResources)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -30,15 +30,22 @@ func finalizeResults(results []resourcesresults.Result, resourcesResult map[stri
|
||||
}
|
||||
}
|
||||
|
||||
func finalizeResources(resources []reporthandling.Resource, allResources map[string]workloadinterface.IMetadata) {
|
||||
func finalizeResources(resources []reporthandling.Resource, results []resourcesresults.Result, allResources map[string]workloadinterface.IMetadata) {
|
||||
index := 0
|
||||
for resourceID := range allResources {
|
||||
if obj, ok := allResources[resourceID]; ok {
|
||||
for i := range results {
|
||||
if obj, ok := allResources[results[i].ResourceID]; ok {
|
||||
r := *reporthandling.NewResource(obj.GetObject())
|
||||
r.ResourceID = resourceID
|
||||
r.ResourceID = results[i].ResourceID
|
||||
resources[index] = r
|
||||
}
|
||||
|
||||
index++
|
||||
}
|
||||
}
|
||||
|
||||
func logOUtputFile(fileName string) {
|
||||
if fileName != "/dev/stdout" && fileName != "/dev/stderr" {
|
||||
logger.L().Success("Scan results saved", helpers.String("filename", fileName))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/armosec/kubescape/cautils"
|
||||
"github.com/armosec/kubescape/cautils/getter"
|
||||
)
|
||||
|
||||
type ReportMock struct {
|
||||
}
|
||||
|
||||
func NewReportMock() *ReportMock {
|
||||
return &ReportMock{}
|
||||
}
|
||||
func (reportMock *ReportMock) ActionSendReport(opaSessionObj *cautils.OPASessionObj) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (reportMock *ReportMock) SetCustomerGUID(customerGUID string) {
|
||||
}
|
||||
|
||||
func (reportMock *ReportMock) SetClusterName(clusterName string) {
|
||||
}
|
||||
|
||||
func (reportMock *ReportMock) DisplayReportURL() {
|
||||
message := fmt.Sprintf("\nYou 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: https://%s/cli-signup \n", getter.GetArmoAPIConnector().GetFrontendURL())
|
||||
cautils.InfoTextDisplay(os.Stderr, fmt.Sprintf("\n%s\n", message))
|
||||
}
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
"github.com/armosec/kubescape/cautils/logger"
|
||||
"github.com/armosec/kubescape/cautils/logger/helpers"
|
||||
"github.com/armosec/opa-utils/reporthandling"
|
||||
uuid "github.com/satori/go.uuid"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
const MAX_REPORT_SIZE = 2097152 // 2 MB
|
||||
@@ -44,15 +44,14 @@ func (report *ReportEventReceiver) ActionSendReport(opaSessionObj *cautils.OPASe
|
||||
}
|
||||
|
||||
if report.customerGUID == "" {
|
||||
report.message = "WARNING: Failed to publish results. Reason: Unknown accout ID. Run kubescape with the '--account <account ID>' flag. Contact ARMO team for more details"
|
||||
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"
|
||||
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.NewV4().String()
|
||||
opaSessionObj.PostureReport.ReportID = uuid.NewString()
|
||||
opaSessionObj.PostureReport.CustomerGUID = report.customerGUID
|
||||
opaSessionObj.PostureReport.ClusterName = report.clusterName
|
||||
|
||||
@@ -144,7 +143,7 @@ func (report *ReportEventReceiver) generateMessage() {
|
||||
|
||||
if report.customerAdminEMail != "" {
|
||||
logger.L().Debug("", helpers.String("account ID", report.customerGUID))
|
||||
report.message = fmt.Sprintf("%s %s/risk/%s", message, u.String(), report.clusterName)
|
||||
report.message = fmt.Sprintf("%s %s/configuration-scanning/%s", message, u.String(), report.clusterName)
|
||||
return
|
||||
}
|
||||
u.Path = "account/sign-up"
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"github.com/armosec/k8s-interface/workloadinterface"
|
||||
"github.com/armosec/kubescape/cautils/getter"
|
||||
"github.com/armosec/opa-utils/reporthandling"
|
||||
"github.com/gofrs/uuid"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
func (report *ReportEventReceiver) initEventReceiverURL() {
|
||||
@@ -16,7 +16,7 @@ func (report *ReportEventReceiver) initEventReceiverURL() {
|
||||
urlObj.Host = getter.GetArmoAPIConnector().GetReportReceiverURL()
|
||||
urlObj.Path = "/k8s/postureReport"
|
||||
q := urlObj.Query()
|
||||
q.Add("customerGUID", uuid.FromStringOrNil(report.customerGUID).String())
|
||||
q.Add("customerGUID", uuid.MustParse(report.customerGUID).String())
|
||||
q.Add("clusterName", report.clusterName)
|
||||
|
||||
urlObj.RawQuery = q.Encode()
|
||||
|
||||
46
resultshandling/reporter/v2/mockreporter.go
Normal file
46
resultshandling/reporter/v2/mockreporter.go
Normal file
@@ -0,0 +1,46 @@
|
||||
package v2
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/armosec/kubescape/cautils"
|
||||
"github.com/armosec/kubescape/cautils/getter"
|
||||
)
|
||||
|
||||
const NO_SUBMIT_QUERY = "utm_source=GitHub&utm_medium=CLI&utm_campaign=no_submit"
|
||||
|
||||
type ReportMock struct {
|
||||
query string
|
||||
message string
|
||||
}
|
||||
|
||||
func NewReportMock(query, message string) *ReportMock {
|
||||
return &ReportMock{
|
||||
query: query,
|
||||
message: message,
|
||||
}
|
||||
}
|
||||
func (reportMock *ReportMock) ActionSendReport(opaSessionObj *cautils.OPASessionObj) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (reportMock *ReportMock) SetCustomerGUID(customerGUID string) {
|
||||
}
|
||||
|
||||
func (reportMock *ReportMock) SetClusterName(clusterName string) {
|
||||
}
|
||||
|
||||
func (reportMock *ReportMock) DisplayReportURL() {
|
||||
u := fmt.Sprintf("https://%s/account/login", getter.GetArmoAPIConnector().GetFrontendURL())
|
||||
if reportMock.query != "" {
|
||||
u += fmt.Sprintf("?%s", reportMock.query)
|
||||
}
|
||||
sep := "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
|
||||
message := sep + "\n"
|
||||
message += "Scan results have not been submitted: " + reportMock.message + "\n"
|
||||
message += "Sign up for free: "
|
||||
message += u + "\n"
|
||||
message += sep + "\n"
|
||||
cautils.InfoTextDisplay(os.Stderr, fmt.Sprintf("\n%s\n", message))
|
||||
}
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
"github.com/armosec/kubescape/cautils/getter"
|
||||
"github.com/armosec/kubescape/cautils/logger"
|
||||
"github.com/armosec/kubescape/cautils/logger/helpers"
|
||||
uuid "github.com/satori/go.uuid"
|
||||
"github.com/google/uuid"
|
||||
|
||||
"github.com/armosec/opa-utils/reporthandling"
|
||||
"github.com/armosec/opa-utils/reporthandling/results/v1/resourcesresults"
|
||||
@@ -44,22 +44,25 @@ func (report *ReportEventReceiver) ActionSendReport(opaSessionObj *cautils.OPASe
|
||||
finalizeReport(opaSessionObj)
|
||||
|
||||
if report.customerGUID == "" {
|
||||
report.message = "WARNING: Failed to publish results. Reason: Unknown accout ID. Run kubescape with the '--account <account ID>' flag. Contact ARMO team for more details"
|
||||
logger.L().Warning("failed to publish results. Reason: Unknown accout ID. Run kubescape with the '--account <account ID>' flag. 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"
|
||||
logger.L().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")
|
||||
return nil
|
||||
}
|
||||
opaSessionObj.Report.ReportID = uuid.NewV4().String()
|
||||
opaSessionObj.Report.ReportID = uuid.NewString()
|
||||
opaSessionObj.Report.CustomerGUID = report.customerGUID
|
||||
opaSessionObj.Report.ClusterName = report.clusterName
|
||||
|
||||
if err := report.prepareReport(opaSessionObj.Report); err != nil {
|
||||
report.message = err.Error()
|
||||
logger.L().Error("failed to publish results", helpers.Error(err))
|
||||
} else {
|
||||
report.generateMessage()
|
||||
}
|
||||
logger.L().Debug("", helpers.String("account ID", report.customerGUID))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -84,12 +87,6 @@ func (report *ReportEventReceiver) prepareReport(postureReport *reporthandlingv2
|
||||
if err := report.sendResources(host, postureReport, &reportCounter, false); err != nil {
|
||||
return err
|
||||
}
|
||||
// reportCounter++
|
||||
|
||||
// // send results
|
||||
// if err := report.sendResults(host, postureReport, &reportCounter, true); err != nil {
|
||||
// return err
|
||||
// }
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -153,38 +150,6 @@ func (report *ReportEventReceiver) sendResources(host string, postureReport *rep
|
||||
return report.sendReport(host, splittedPostureReport, *reportCounter, true)
|
||||
}
|
||||
|
||||
// func (report *ReportEventReceiver) sendResults(host string, postureReport *reporthandlingv2.PostureReport, reportCounter *int, isLastReport bool) error {
|
||||
// splittedPostureReport := setSubReport(postureReport)
|
||||
// counter := 0
|
||||
|
||||
// for _, v := range postureReport.Results {
|
||||
// r, err := json.Marshal(v)
|
||||
// if err != nil {
|
||||
// return fmt.Errorf("failed to unmarshal resource '%s', reason: %v", v.GetResourceID(), err)
|
||||
// }
|
||||
|
||||
// if counter+len(r) >= MAX_REPORT_SIZE && len(splittedPostureReport.Resources) > 0 {
|
||||
|
||||
// // send report
|
||||
// if err := report.sendReport(host, splittedPostureReport, *reportCounter, false); err != nil {
|
||||
// return err
|
||||
// }
|
||||
// *reportCounter++
|
||||
|
||||
// // delete results
|
||||
// splittedPostureReport.Results = []resourcesresults.Result{}
|
||||
|
||||
// // restart counter
|
||||
// counter = 0
|
||||
// }
|
||||
|
||||
// counter += len(r)
|
||||
// splittedPostureReport.Results = append(splittedPostureReport.Results, v)
|
||||
// }
|
||||
|
||||
// return report.sendReport(host, splittedPostureReport, *reportCounter, isLastReport)
|
||||
// }
|
||||
|
||||
func (report *ReportEventReceiver) sendReport(host string, postureReport *reporthandlingv2.PostureReport, counter int, isLastReport bool) error {
|
||||
postureReport.PaginationInfo = reporthandlingv2.PaginationMarks{
|
||||
ReportNumber: counter,
|
||||
@@ -202,26 +167,32 @@ func (report *ReportEventReceiver) sendReport(host string, postureReport *report
|
||||
}
|
||||
|
||||
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.Scheme = "https"
|
||||
u.Host = getter.GetArmoAPIConnector().GetFrontendURL()
|
||||
|
||||
if report.customerAdminEMail != "" {
|
||||
logger.L().Debug("", helpers.String("account ID", report.customerGUID))
|
||||
report.message = fmt.Sprintf("%s %s/risk/%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)
|
||||
if report.customerAdminEMail != "" { // data has been submitted
|
||||
u.Path = fmt.Sprintf("configuration-scanning/%s", report.clusterName)
|
||||
} else {
|
||||
u.Path = "account/sign-up"
|
||||
q := u.Query()
|
||||
q.Add("invitationToken", report.token)
|
||||
q.Add("customerGUID", report.customerGUID)
|
||||
|
||||
u.RawQuery = q.Encode()
|
||||
}
|
||||
|
||||
sep := "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
|
||||
report.message = sep
|
||||
report.message += " << WOW! Now you can see the scan results on the web >>\n\n"
|
||||
report.message += fmt.Sprintf(" %s\n", u.String())
|
||||
report.message += sep
|
||||
|
||||
u.RawQuery = q.Encode()
|
||||
report.message = fmt.Sprintf("%s %s", message, u.String())
|
||||
}
|
||||
|
||||
func (report *ReportEventReceiver) DisplayReportURL() {
|
||||
cautils.InfoTextDisplay(os.Stderr, fmt.Sprintf("\n\n%s\n\n", report.message))
|
||||
if report.message != "" {
|
||||
cautils.InfoTextDisplay(os.Stderr, fmt.Sprintf("\n\n%s\n\n", report.message))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
"github.com/armosec/kubescape/cautils/getter"
|
||||
"github.com/armosec/opa-utils/reporthandling"
|
||||
reporthandlingv2 "github.com/armosec/opa-utils/reporthandling/v2"
|
||||
"github.com/gofrs/uuid"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
func (report *ReportEventReceiver) initEventReceiverURL() {
|
||||
@@ -18,7 +18,7 @@ func (report *ReportEventReceiver) initEventReceiverURL() {
|
||||
urlObj.Path = "/k8s/v2/postureReport"
|
||||
|
||||
q := urlObj.Query()
|
||||
q.Add("customerGUID", uuid.FromStringOrNil(report.customerGUID).String())
|
||||
q.Add("customerGUID", uuid.MustParse(report.customerGUID).String())
|
||||
q.Add("clusterName", report.clusterName)
|
||||
|
||||
urlObj.RawQuery = q.Encode()
|
||||
|
||||
@@ -3,7 +3,11 @@ package resultshandling
|
||||
import (
|
||||
"github.com/armosec/kubescape/cautils"
|
||||
"github.com/armosec/kubescape/cautils/logger"
|
||||
"github.com/armosec/kubescape/cautils/logger/helpers"
|
||||
"github.com/armosec/kubescape/resultshandling/printer"
|
||||
printerv1 "github.com/armosec/kubescape/resultshandling/printer/v1"
|
||||
printerv2 "github.com/armosec/kubescape/resultshandling/printer/v2"
|
||||
|
||||
"github.com/armosec/kubescape/resultshandling/reporter"
|
||||
"github.com/armosec/opa-utils/reporthandling"
|
||||
)
|
||||
@@ -49,3 +53,25 @@ func CalculatePostureScore(postureReport *reporthandling.PostureReport) float32
|
||||
|
||||
return (float32(len(allResources)) - float32(len(failedResources))) / float32(len(allResources))
|
||||
}
|
||||
|
||||
func NewPrinter(printFormat, formatVersion string, verboseMode bool) printer.IPrinter {
|
||||
|
||||
switch printFormat {
|
||||
case printer.JsonFormat:
|
||||
switch formatVersion {
|
||||
case "v2":
|
||||
return printerv2.NewJsonPrinter()
|
||||
default:
|
||||
logger.L().Warning("Deprecated format version. run with '--format-version' flag", helpers.String("your version", formatVersion), helpers.String("latest version", "v2"))
|
||||
return printerv1.NewJsonPrinter()
|
||||
}
|
||||
case printer.JunitResultFormat:
|
||||
return printerv2.NewJunitPrinter(verboseMode)
|
||||
case printer.PrometheusFormat:
|
||||
return printerv1.NewPrometheusPrinter(verboseMode)
|
||||
case printer.PdfFormat:
|
||||
return printerv2.NewPdfPrinter()
|
||||
default:
|
||||
return printerv2.NewPrettyPrinter(verboseMode, formatVersion)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user