Compare commits

...

106 Commits

Author SHA1 Message Date
dwertent
66196c0d56 pass info in call 2022-03-10 17:13:51 +02:00
dwertent
2d59ba0943 update cmd struct 2022-03-10 17:02:09 +02:00
dwertent
33f92d1a5f update logger support 2022-03-10 11:19:05 +02:00
dwertent
4bd468f03e Merge remote-tracking branch 'armosec/dev' 2022-03-09 20:54:22 +02:00
dwertent
c6eaecd596 export to json 2022-03-09 20:53:55 +02:00
dwertent
a2a5b06024 adding http handler 2022-03-09 20:51:55 +02:00
David Wertenteil
825732f60f Merge pull request #444 from Akasurde/misspell_dev
Misc typo fixes
2022-03-09 20:12:19 +02:00
David Wertenteil
596ec17106 Merge pull request #445 from Akasurde/no_color_dev
cli: added support for no color
2022-03-09 20:01:24 +02:00
Abhijeet Kasurde
fbd0f352c4 cli: added support for no color
Using commandline flag users can now disable colored output
in logging.

Fixes: #434

Signed-off-by: Abhijeet Kasurde <akasurde@redhat.com>
2022-03-09 21:25:48 +05:30
Abhijeet Kasurde
2600052735 Misc typo fixes
Signed-off-by: Abhijeet Kasurde <akasurde@redhat.com>
2022-03-09 21:22:41 +05:30
dwertent
a985b2ce09 Add mock logger 2022-03-08 21:39:09 +02:00
dwertent
829c176644 Merge remote-tracking branch 'armosec/dev' into server-support 2022-03-08 14:52:00 +02:00
dwertent
7d7d247bc2 udpate go mod 2022-03-08 14:51:24 +02:00
dwertent
43ae8e2a81 Merge branch 'master' into server-support 2022-03-08 14:49:41 +02:00
David Wertenteil
b0f37e9465 Merge pull request #441 from xdavidel/update_io_apis
update vulns input / output api
2022-03-08 14:34:45 +02:00
David Wertenteil
396ef55267 Merge pull request #440 from dwertent/master
Use semver.Compare for version check
2022-03-08 10:12:59 +02:00
dwertent
4b07469bb2 use semver.Compare for version check 2022-03-08 10:10:06 +02:00
David Delarosa
260f7b06c1 update vulns input / output api 2022-03-07 17:03:14 +02:00
David Wertenteil
67ba28a3cb Merge pull request #433 from dwertent/master
call `setTenant` when submitting results
2022-03-06 11:29:34 +02:00
dwertent
a768d22a1d call setTenant when submitting results 2022-03-06 11:25:16 +02:00
David Wertenteil
ede88550da Merge pull request #432 from dwertent/master
Update roadmap
2022-03-06 11:03:54 +02:00
David Wertenteil
ab55a0d134 Merge pull request #431 from Bezbran/dev
Read Linux kernel variables from host sensor
2022-03-06 10:59:41 +02:00
Bezalel Brandwine
bfd7060044 read linux kernel variables from host sensor 2022-03-06 10:51:24 +02:00
dwertent
bf215a0f96 update roadmap 2022-03-06 10:50:45 +02:00
dwertent
a2e1fb36df update maintainers 2022-03-06 10:42:30 +02:00
dwertent
4e9c6f34b3 Update maintainers and roadmap 2022-03-06 10:38:03 +02:00
David Wertenteil
b08c0f2ec6 Merge pull request #429 from dwertent/master
Update readme
2022-03-06 09:54:41 +02:00
dwertent
4c0e358afc support submitting v2 2022-03-06 09:51:05 +02:00
dwertent
9ae21b064a update readme 2022-03-06 09:32:26 +02:00
dwertent
2df0c12e10 auto complete examples 2022-03-03 17:45:54 +02:00
David Wertenteil
d37025dc6c json v2 version 2022-03-03 16:17:25 +02:00
dwertent
0b01eb5ee4 swapped print versions 2022-03-03 14:26:34 +02:00
David Wertenteil
d537c56159 Update format output support to v2 2022-03-03 13:24:45 +02:00
dwertent
feb9e3af10 Merge remote-tracking branch 'armosec/dev' 2022-03-03 13:06:36 +02:00
dwertent
ec30ed8439 json v2 support 2022-03-03 13:05:36 +02:00
dwertent
cda9bb0e45 Update junit support 2022-03-03 12:34:03 +02:00
Ben Hirschberg
17f1c6b647 Merge pull request #424 from slashben/dev
added updated road-map
2022-03-01 17:27:39 +02:00
Ben Hirschberg
98079ec1ec added updated roadmap 2022-03-01 17:26:26 +02:00
David Wertenteil
16aaf9b5f8 extend exceptions support 2022-02-28 18:32:49 +02:00
dwertent
ff0264ee15 extend exceptions support 2022-02-28 18:19:17 +02:00
David Wertenteil
bea9bd64a4 Update junit support 2022-02-27 17:33:16 +02:00
dwertent
544a19906e update junit 2022-02-27 17:29:42 +02:00
dwertent
208bb25118 fixed junit support 2022-02-27 16:33:21 +02:00
David Wertenteil
fdb7e278c1 Merge pull request #419 from hayzer/fail-threshold-support-float
Change fail-threshold type to float
2022-02-27 14:56:27 +02:00
David Wertenteil
a132a49d57 extent host-sensor support 2022-02-27 14:45:30 +02:00
dwertent
23e73f5e88 extent host-sensor support 2022-02-27 14:30:32 +02:00
Tomasz Majer
fdcc5e9a66 Support float in fail-threshold 2022-02-27 11:14:05 +01:00
David Wertenteil
77e7b1a2cb Improvments 2022-02-24 18:58:31 +02:00
dwertent
db95da3742 udpate packages 2022-02-24 18:43:29 +02:00
dwertent
dc172a1476 fixed help message 2022-02-24 18:22:29 +02:00
dwertent
8694a929cf support output versions 2022-02-24 13:57:33 +02:00
dwertent
36b3840362 support host sensor from local file 2022-02-24 12:09:47 +02:00
dwertent
d5fcbe842f adding autocompletion 2022-02-24 09:50:12 +02:00
dwertent
155349dac0 adding resource table 2022-02-23 15:54:59 +02:00
David Wertenteil
7956a849d9 Fix argument validation for framework/control 2022-02-23 10:33:49 +02:00
Avinash Upadhyaya
0d1c4cdc02 fix: argument validation for framework/control 2022-02-23 10:24:14 +05:30
David Wertenteil
8c833a5df8 Ignore empty frameworks 2022-02-22 14:48:10 +02:00
dwertent
2c5bb977cb ignore empty framewprks 2022-02-22 14:46:25 +02:00
dwertent
cddf7dd8f6 handle errors 2022-02-22 11:03:44 +02:00
dwertent
306c18147e Merge remote-tracking branch 'upstream/dev' 2022-02-22 10:49:57 +02:00
David Wertenteil
84815eb97d fix uuid high-risk vulnerability 2022-02-22 10:26:56 +02:00
dwertent
890c13a91f stage url 2022-02-22 09:53:40 +02:00
fsl
3887ec8091 fix uuid high-risk vulnerability 2022-02-22 10:29:23 +08:00
fsl
726b06bb70 Merge remote-tracking branch 'origin/dev' into dev 2022-02-22 10:19:30 +08:00
dwertent
c8e07c283e Merge remote-tracking branch 'upstream/dev' 2022-02-21 17:24:32 +02:00
dwertent
88a5128c03 fixed pretty-print v1, expand list support 2022-02-21 17:22:01 +02:00
dwertent
01f2d3b04f Merge branch 'dev' of ssh://github.com/armosec/kubescape into dev 2022-02-21 14:48:53 +02:00
dwertent
fef85a4467 init printer for all pkg 2022-02-21 14:48:48 +02:00
David Wertenteil
e0eadc1f2d support for pdf format output
Resolves #282
2022-02-21 14:47:48 +02:00
dwertent
a881b73e8d Merge remote-tracking branch 'pdf/master' into dev 2022-02-21 10:21:05 +02:00
dwertent
606f5cfb62 fixed discord banner 2022-02-21 09:00:34 +02:00
Rotem Refael
40737d545b Merge pull request #403 from armosec/dev (update logger/cache/ docker build)
Add explicit protocol to solve known K8s 1.16 issue
Post scan message update
Support zap logger (KS_LOGGER_NAME)
Support cache dir location (--cache-dir/KS_CACHE_DIR)
Fixed docker release version
Issues:

Resolved option disable createTenant when using the offline scan option #397
Resolved Mutated release binaries #98
2022-02-20 21:33:15 +02:00
dwertent
990be3afe8 remove cat command 2022-02-20 20:55:22 +02:00
David Wertenteil
7020c2d025 Updating cache handling 2022-02-20 19:06:38 +02:00
dwertent
a6497c1252 fixed python build file 2022-02-20 19:04:16 +02:00
dwertent
9d528a8075 support setting default cach 2022-02-20 13:30:05 +02:00
David Wertenteil
5aec8b6f28 Fix release version in Dockerfile 2022-02-20 09:08:10 +02:00
Avinash Upadhyaya
830ee27169 build: fix release version in Dockerfile 2022-02-20 10:26:40 +05:30
alegrey91
5f2e5c6f4e docs: describe support for pdf format output 2022-02-17 18:17:12 +01:00
alegrey91
cf4317b5f6 feat(resulthandling): add support for pdf format output 2022-02-17 18:16:29 +01:00
David Wertenteil
2453aea6f3 Support zap logger 2022-02-17 17:49:24 +02:00
dwertent
e95b0f840a Support zap logger 2022-02-17 17:47:57 +02:00
David Wertenteil
83680d1207 AccountID handling 2022-02-17 09:30:25 +02:00
dwertent
fd135e9e49 Cache accountID only when submitting data 2022-02-17 09:23:17 +02:00
dwertent
e47eb9cb4e adding logs to python file 2022-02-16 14:02:28 +02:00
dwertent
d288fdc7f2 set tenant only when submiiting data 2022-02-16 13:59:29 +02:00
dwertent
9de73dab29 updalead sha 2022-02-16 13:15:44 +02:00
David Wertenteil
f0afc20ec6 Update post scan message 2022-02-16 10:03:03 +02:00
dwertent
bf75059347 update after scan messgae 2022-02-15 14:25:28 +02:00
David Wertenteil
78835a58c4 add explicit protocol TCP to host-sensor YAML 2022-02-12 19:02:59 +02:00
David Wertenteil
fdccae9a1e [Minor] Typo Correction 2022-02-12 19:01:10 +02:00
avicoder
6d97d42f67 Merge pull request #1 from avicoder/avicoder-typo-fix
[Minor] Fixed a Typo
2022-02-11 03:17:38 +08:00
avicoder
46001e4761 [Minor] Fixed a Typo
Useage to Usage
2022-02-11 03:17:10 +08:00
dwertent
37644e1f57 update readme 2022-02-10 21:05:08 +02:00
dwertent
8a04934fbd Adding readme and yaml 2022-02-10 20:41:28 +02:00
dwertent
31e1b3055f Prometheus support 2022-02-10 20:11:00 +02:00
Bezalel Brandwine
b4d712fcb1 add explicit protocol to solve known K8s issue:
https://github.com/kubernetes/kubernetes/issues/92332
2022-02-10 17:38:13 +02:00
Rotem Refael
7847a4593b Merge pull request #381 from armosec/dev
Minor updates
2022-02-10 16:33:43 +02:00
David Wertenteil
b2036e64f1 Merge pull request #380 from dwertent/master
Hot fix
2022-02-10 14:07:34 +02:00
Bezbran
fd0bbcccfe Merge pull request #16 from armosec/dev
Dev
2022-02-10 13:43:49 +02:00
dwertent
7caa47f949 Merge remote-tracking branch 'upstream/dev' 2022-02-10 13:41:16 +02:00
David Wertenteil
06b171901d Track host sensor pods reports 2022-02-10 13:34:05 +02:00
Bezalel Brandwine
7177e77a8d track host sensor pods tighten 2022-02-10 11:23:55 +02:00
David Wertenteil
3fff1b750a fixed Set image version 2022-02-09 14:38:05 +02:00
Rotem Refael
bd9ade4d15 Merge pull request #370 from armosec/dev
Features:
Adding a logger
Support config command

Minor fixes
2022-02-09 14:02:21 +02:00
David Wertenteil
d630811386 Minor fixes 2022-01-30 11:21:54 +02:00
134 changed files with 3651 additions and 3101 deletions

View File

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

View File

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

140
README.md
View File

@@ -3,6 +3,8 @@
[![build](https://github.com/armosec/kubescape/actions/workflows/build.yaml/badge.svg)](https://github.com/armosec/kubescape/actions/workflows/build.yaml)
[![Go Report Card](https://goreportcard.com/badge/github.com/armosec/kubescape)](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.
@@ -46,14 +48,18 @@ 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?
* Feel free to pick a task from the [roadmap](docs/roadmap.md) or suggest a feature of your own. [Contact us](MAINTAINERS.md) directly for more information :)
* Open a issue, we are trying to respond within 48 hours
* [Join us](https://armosec.github.io/kubescape/) in a discussion on our discord server!
[<img src="docs/discord-banner.png" width="100" alt="logo" align="center">](https://armosec.github.io/kubescape/)
![discord](https://img.shields.io/discord/893048809884643379)
# 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
```
@@ -241,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.

View File

@@ -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")

View File

@@ -3,7 +3,7 @@ FROM golang:1.17-alpine as builder
ARG image_version
ENV RELEASE=image_version
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
View 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 .
```

View File

@@ -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"
)
@@ -85,7 +84,8 @@ type LocalConfig struct {
configObj *ConfigObj
}
func NewLocalConfig(backendAPI getter.IBackend, customerGUID, clusterName string) *LocalConfig {
func NewLocalConfig(
backendAPI getter.IBackend, customerGUID, clusterName string) *LocalConfig {
var configObj *ConfigObj
lc := &LocalConfig{
@@ -113,12 +113,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 +222,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
}
@@ -336,27 +324,6 @@ func GetValueFromConfigJson(key string) (string, error) {
}
func SetKeyValueInConfigJson(key string, value string) error {
data, err := os.ReadFile(ConfigFileFullPath())
if err != nil {
return err
}
var obj map[string]interface{}
err = json.Unmarshal(data, &obj)
if err != nil {
return err
}
obj[key] = value
newData, err := json.Marshal(obj)
if err != nil {
return err
}
return os.WriteFile(ConfigFileFullPath(), newData, 0664)
}
func (c *ClusterConfig) SetKeyValueInConfigmap(key string, value string) error {
configMap, err := c.k8s.KubernetesClient.CoreV1().ConfigMaps(c.configMapNamespace).Get(context.Background(), c.configMapName, metav1.GetOptions{})

View File

@@ -13,11 +13,11 @@ type K8SResources map[string][]string
type OPASessionObj struct {
K8SResources *K8SResources // input k8s objects
Frameworks []reporthandling.Framework // list of frameworks to scan
Policies []reporthandling.Framework // list of frameworks to scan
AllResources map[string]workloadinterface.IMetadata // all scanned resources, map[<rtesource ID>]<resource>
ResourcesResult map[string]resourcesresults.Result // resources scan results, map[<rtesource ID>]<resource result>
PostureReport *reporthandling.PostureReport // scan results v1
Report *reporthandlingv2.PostureReport // scan results v2
PostureReport *reporthandling.PostureReport // scan results v1 - Remove
Report *reporthandlingv2.PostureReport // scan results v2 - Remove
Exceptions []armotypes.PostureExceptionPolicy // list of exceptions to apply on scan results
RegoInputData RegoInputData // input passed to rgo for scanning. map[<control name>][<input arguments>]
}
@@ -25,7 +25,7 @@ type OPASessionObj struct {
func NewOPASessionObj(frameworks []reporthandling.Framework, k8sResources *K8SResources) *OPASessionObj {
return &OPASessionObj{
Report: &reporthandlingv2.PostureReport{},
Frameworks: frameworks,
Policies: frameworks,
K8SResources: k8sResources,
AllResources: make(map[string]workloadinterface.IMetadata),
ResourcesResult: make(map[string]resourcesresults.Result),
@@ -38,7 +38,7 @@ func NewOPASessionObj(frameworks []reporthandling.Framework, k8sResources *K8SRe
func NewOPASessionObjMock() *OPASessionObj {
return &OPASessionObj{
Frameworks: nil,
Policies: nil,
K8SResources: nil,
AllResources: make(map[string]workloadinterface.IMetadata),
ResourcesResult: make(map[string]resourcesresults.Result),

View File

@@ -2,6 +2,7 @@ package cautils
import (
pkgcautils "github.com/armosec/utils-go/utils"
"golang.org/x/mod/semver"
"github.com/armosec/opa-utils/reporthandling"
)
@@ -15,7 +16,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 +31,7 @@ func (policies *Policies) Set(frameworks []reporthandling.Framework, version str
policies.Controls[frameworks[i].Controls[j].ControlID] = frameworks[i].Controls[j]
}
}
}
}
@@ -49,14 +51,15 @@ func ruleWithArmoOpaDependency(attributes map[string]interface{}) bool {
func isRuleKubescapeVersionCompatible(attributes map[string]interface{}, version string) bool {
if from, ok := attributes["useFromKubescapeVersion"]; ok && from != nil {
if version != "" {
if from.(string) > BuildNumber {
if semver.Compare(from.(string), BuildNumber) > 0 {
return false
}
}
}
if until, ok := attributes["useUntilKubescapeVersion"]; ok && until != nil {
if version != "" {
if until.(string) <= BuildNumber {
if semver.Compare(BuildNumber, until.(string)) >= 0 {
return false
}
} else {

View File

@@ -9,16 +9,6 @@ import (
"github.com/mattn/go-isatty"
)
var silent = false
func SetSilentMode(s bool) {
silent = s
}
func IsSilent() bool {
return silent
}
var FailureDisplay = color.New(color.Bold, color.FgHiRed).FprintfFunc()
var WarningDisplay = color.New(color.Bold, color.FgHiYellow).FprintfFunc()
var FailureTextDisplay = color.New(color.Faint, color.FgHiRed).FprintfFunc()
@@ -31,7 +21,7 @@ var DescriptionDisplay = color.New(color.Faint, color.FgWhite).FprintfFunc()
var Spinner *spinner.Spinner
func StartSpinner() {
if !IsSilent() && isatty.IsTerminal(os.Stdout.Fd()) {
if isatty.IsTerminal(os.Stdout.Fd()) {
Spinner = spinner.New(spinner.CharSets[7], 100*time.Millisecond) // Build our new spinner
Spinner.Start()
}

239
cautils/fileutils.go Normal file
View 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
}

View File

@@ -1,4 +1,4 @@
package resourcehandler
package cautils
import (
"os"

View File

@@ -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"
)
@@ -20,12 +21,17 @@ import (
var (
// ATTENTION!!!
// Changes in this URLs variable names, or in the usage is affecting the build process! BE CAREFULL
// Changes in this URLs variable names, or in the usage is affecting the build process! BE CAREFUL
armoERURL = "report.armo.cloud"
armoBEURL = "api.armo.cloud"
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,12 +56,14 @@ 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
}
func GetArmoAPIConnector() *ArmoAPI {
if globalArmoAPIConnector == nil {
logger.L().Error("returning nil API connector")
// logger.L().Error("returning nil API connector")
SetARMOAPIConnector(NewARMOAPIProd())
}
return globalArmoAPIConnector
}
@@ -82,6 +90,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 +127,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 +301,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 +309,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")

View File

@@ -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"
}

View File

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

View File

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

View File

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

View File

@@ -2,9 +2,12 @@ package logger
import (
"os"
"strings"
"github.com/armosec/kubescape/cautils/logger/helpers"
"github.com/armosec/kubescape/cautils/logger/nonelogger"
"github.com/armosec/kubescape/cautils/logger/prettylogger"
"github.com/armosec/kubescape/cautils/logger/zaplogger"
)
type ILogger interface {
@@ -20,22 +23,59 @@ type ILogger interface {
SetWriter(w *os.File)
GetWriter() *os.File
LoggerName() string
}
var l ILogger
// Return initialized logger. If logger not initialized, will call InitializeLogger() with the default value
func L() ILogger {
if l == nil {
InitializeLogger()
InitDefaultLogger()
}
return l
}
func InitializeLogger() {
initializeLogger()
/* InitLogger initialize desired logger
Use:
InitLogger("<logger name>")
Supported logger names (call ListLoggersNames() for listing supported loggers)
- "zap": Logger from package "go.uber.org/zap"
- "pretty", "colorful": Human friendly colorful logger
- "none", "mock", "empty", "ignore": Logger will not print anything
Default:
- "pretty"
e.g.
InitLogger("none") -> will initialize the mock logger
*/
func InitLogger(loggerName string) {
switch strings.ToLower(loggerName) {
case zaplogger.LoggerName:
l = zaplogger.NewZapLogger()
case prettylogger.LoggerName, "colorful":
l = prettylogger.NewPrettyLogger()
case nonelogger.LoggerName, "mock", "empty", "ignore":
l = nonelogger.NewNoneLogger()
default:
InitDefaultLogger()
}
}
func initializeLogger() {
// TODO - support zap logger
func InitDefaultLogger() {
l = prettylogger.NewPrettyLogger()
}
func DisableColor(flag bool) {
prettylogger.DisableColor(flag)
}
func ListLoggersNames() []string {
return []string{prettylogger.LoggerName, zaplogger.LoggerName, nonelogger.LoggerName}
}

View File

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

View File

@@ -29,3 +29,9 @@ func prefix(l helpers.Level) func(w io.Writer, format string, a ...interface{})
}
return message
}
func DisableColor(flag bool) {
if flag {
color.NoColor = true
}
}

View File

@@ -8,6 +8,8 @@ import (
"github.com/armosec/kubescape/cautils/logger/helpers"
)
const LoggerName string = "pretty"
type PrettyLogger struct {
writer *os.File
level helpers.Level
@@ -15,6 +17,7 @@ type PrettyLogger struct {
}
func NewPrettyLogger() *PrettyLogger {
return &PrettyLogger{
writer: os.Stderr, // default to stderr
level: helpers.InfoLevel,
@@ -25,6 +28,7 @@ func NewPrettyLogger() *PrettyLogger {
func (pl *PrettyLogger) GetLevel() string { return pl.level.String() }
func (pl *PrettyLogger) SetWriter(w *os.File) { pl.writer = w }
func (pl *PrettyLogger) GetWriter() *os.File { return pl.writer }
func (pl *PrettyLogger) LoggerName() string { return LoggerName }
func (pl *PrettyLogger) SetLevel(level string) error {
pl.level = helpers.ToLevel(level)
@@ -69,7 +73,7 @@ func (pl *PrettyLogger) print(level helpers.Level, msg string, details ...helper
func detailsToString(details []helpers.IDetails) string {
s := ""
for i := range details {
s += fmt.Sprintf("%s: %s", details[i].Key(), details[i].Value())
s += fmt.Sprintf("%s: %v", details[i].Key(), details[i].Value())
if i < len(details)-1 {
s += "; "
}

View File

@@ -8,22 +8,43 @@ import (
"go.uber.org/zap/zapcore"
)
const LoggerName string = "zap"
type ZapLogger struct {
zapL *zap.Logger
cfg zap.Config
}
func NewZapLogger() *ZapLogger {
ec := zap.NewProductionEncoderConfig()
ec.EncodeTime = zapcore.RFC3339TimeEncoder
cfg := zap.NewProductionConfig()
cfg.DisableCaller = true
cfg.DisableStacktrace = true
cfg.Encoding = "json"
cfg.EncoderConfig = ec
zapLogger, err := cfg.Build()
if err != nil {
panic(err)
}
return &ZapLogger{
zapL: 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 GetWriter() *os.File { return nil }
func (zl *ZapLogger) GetWriter() *os.File { return nil }
func (zl *ZapLogger) LoggerName() string { return LoggerName }
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)...)

View File

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

View File

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

View File

@@ -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,13 @@ func (bpf *BoolPtrFlag) Set(val string) error {
return nil
}
type RootInfo struct {
Logger string // logger level
LoggerName string // logger name ("pretty"/"zap"/"none")
CacheDir string // cached dir
DisableColor bool // Disable Color
}
type ScanInfo struct {
Getters
PolicyIdentifier []reporthandling.PolicyIdentifier
@@ -63,16 +71,18 @@ 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
ReportID string // Report id of the current scan
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 +114,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 +163,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 {

View File

@@ -10,6 +10,7 @@ import (
"github.com/armosec/kubescape/cautils/logger"
"github.com/armosec/kubescape/cautils/logger/helpers"
pkgutils "github.com/armosec/utils-go/utils"
"golang.org/x/mod/semver"
)
const SKIP_VERSION_CHECK = "KUBESCAPE_SKIP_UPDATE_CHECK"
@@ -97,8 +98,8 @@ func (v *VersionCheckHandler) CheckLatestVersion(versionData *VersionCheckReques
}
if latestVersion.ClientUpdate != "" {
if BuildNumber != "" && BuildNumber < latestVersion.ClientUpdate {
logger.L().Warning(warningMessage(latestVersion.Client, latestVersion.ClientUpdate))
if BuildNumber != "" && semver.Compare(BuildNumber, latestVersion.ClientUpdate) >= 0 {
logger.L().Warning(warningMessage(latestVersion.ClientUpdate))
}
}
@@ -133,6 +134,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)
}

View File

@@ -0,0 +1,38 @@
package cautils
import (
"testing"
"github.com/armosec/armoapi-go/armotypes"
"github.com/armosec/opa-utils/reporthandling"
"github.com/stretchr/testify/assert"
)
func TestGetKubernetesObjects(t *testing.T) {
}
var rule_v1_0_131 = &reporthandling.PolicyRule{PortalBase: armotypes.PortalBase{
Attributes: map[string]interface{}{"useUntilKubescapeVersion": "v1.0.132"}}}
var rule_v1_0_132 = &reporthandling.PolicyRule{PortalBase: armotypes.PortalBase{
Attributes: map[string]interface{}{"useFromKubescapeVersion": "v1.0.132", "useUntilKubescapeVersion": "v1.0.133"}}}
var rule_v1_0_133 = &reporthandling.PolicyRule{PortalBase: armotypes.PortalBase{
Attributes: map[string]interface{}{"useFromKubescapeVersion": "v1.0.133", "useUntilKubescapeVersion": "v1.0.134"}}}
var rule_v1_0_134 = &reporthandling.PolicyRule{PortalBase: armotypes.PortalBase{
Attributes: map[string]interface{}{"useFromKubescapeVersion": "v1.0.134"}}}
func TestIsRuleKubescapeVersionCompatible(t *testing.T) {
// local build- no build number
// should use only rules that don't have "until"
buildNumberMock := ""
assert.False(t, isRuleKubescapeVersionCompatible(rule_v1_0_131.Attributes, buildNumberMock))
assert.False(t, isRuleKubescapeVersionCompatible(rule_v1_0_132.Attributes, buildNumberMock))
assert.False(t, isRuleKubescapeVersionCompatible(rule_v1_0_133.Attributes, buildNumberMock))
assert.True(t, isRuleKubescapeVersionCompatible(rule_v1_0_134.Attributes, buildNumberMock))
// should only use rules that version is in range of use
buildNumberMock = "v1.0.133"
assert.True(t, isRuleKubescapeVersionCompatible(rule_v1_0_131.Attributes, buildNumberMock))
assert.False(t, isRuleKubescapeVersionCompatible(rule_v1_0_132.Attributes, buildNumberMock))
assert.False(t, isRuleKubescapeVersionCompatible(rule_v1_0_133.Attributes, buildNumberMock))
assert.False(t, isRuleKubescapeVersionCompatible(rule_v1_0_134.Attributes, buildNumberMock))
}

View File

@@ -0,0 +1,7 @@
package clihandler
func CliDelete() error {
tenant := getTenantConfig("", "", getKubernetesApi()) // change k8sinterface
return tenant.DeleteCachedConfig()
}

View File

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

View File

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

View File

@@ -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)
}

View File

@@ -4,4 +4,5 @@ type ListPolicies struct {
Target string
ListIDs bool
Account string
Format string
}

View File

@@ -3,3 +3,7 @@ package cliobjects
type Submit struct {
Account string
}
type Delete struct {
Account string
}

View File

@@ -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})

View File

@@ -1,19 +0,0 @@
package cmd
import (
"github.com/spf13/cobra"
)
// clusterCmd represents the cluster command
var clusterCmd = &cobra.Command{
Use: "cluster",
Short: "Set configuration for cluster",
Long: ``,
Deprecated: "use the 'set' command instead",
Run: func(cmd *cobra.Command, args []string) {
},
}
func init() {
configCmd.AddCommand(clusterCmd)
}

View File

@@ -1,50 +0,0 @@
package cmd
import (
"fmt"
"strings"
"github.com/armosec/k8s-interface/k8sinterface"
"github.com/armosec/kubescape/cautils"
"github.com/armosec/kubescape/cautils/getter"
"github.com/armosec/kubescape/cautils/logger"
"github.com/spf13/cobra"
)
var getCmd = &cobra.Command{
Use: "get <key>",
Short: "Get configuration in cluster",
Long: ``,
Deprecated: "use the 'view' command instead",
Args: func(cmd *cobra.Command, args []string) error {
if len(args) < 1 || len(args) > 1 {
return fmt.Errorf("requires one argument")
}
keyValue := strings.Split(args[0], "=")
if len(keyValue) != 1 {
return fmt.Errorf("requires one argument")
}
return nil
},
RunE: func(cmd *cobra.Command, args []string) error {
keyValue := strings.Split(args[0], "=")
key := keyValue[0]
k8s := k8sinterface.NewKubernetesApi()
clusterConfig := cautils.NewClusterConfig(k8s, getter.GetArmoAPIConnector(), scanInfo.Account, "")
val, err := clusterConfig.GetValueByKeyFromConfigMap(key)
if err != nil {
if err.Error() == "value does not exist." {
return fmt.Errorf("failed to get value from configmap, reason: %s", err.Error())
}
return err
}
logger.L().Info(key + "=" + val)
return nil
},
}
func init() {
clusterCmd.AddCommand(getCmd)
}

View File

@@ -1,46 +0,0 @@
package cmd
import (
"fmt"
"strings"
"github.com/armosec/k8s-interface/k8sinterface"
"github.com/armosec/kubescape/cautils"
"github.com/armosec/kubescape/cautils/getter"
"github.com/armosec/kubescape/cautils/logger"
"github.com/spf13/cobra"
)
var setClusterCmd = &cobra.Command{
Use: "set <key>=<value>",
Short: "Set configuration in cluster",
Long: ``,
Deprecated: "use the 'set' command instead",
Args: func(cmd *cobra.Command, args []string) error {
if len(args) < 1 || len(args) > 1 {
return fmt.Errorf("requires one argument: <key>=<value>")
}
keyValue := strings.Split(args[0], "=")
if len(keyValue) != 2 {
return fmt.Errorf("requires one argument: <key>=<value>")
}
return nil
},
RunE: func(cmd *cobra.Command, args []string) error {
keyValue := strings.Split(args[0], "=")
key := keyValue[0]
data := keyValue[1]
k8s := k8sinterface.NewKubernetesApi()
clusterConfig := cautils.NewClusterConfig(k8s, getter.GetArmoAPIConnector(), scanInfo.Account, "")
if err := clusterConfig.SetKeyValueInConfigmap(key, data); err != nil {
return err
}
logger.L().Info("value added successfully.")
return nil
},
}
func init() {
clusterCmd.AddCommand(setClusterCmd)
}

View File

@@ -0,0 +1,49 @@
package completion
import (
"os"
"strings"
"github.com/spf13/cobra"
)
var completionCmdExamples = `
# Enable BASH shell autocompletion
$ source <(kubescape completion bash)
$ echo 'source <(kubescape completion bash)' >> ~/.bashrc
# Enable ZSH shell autocompletion
$ source <(kubectl completion zsh)
$ echo 'source <(kubectl completion zsh)' >> "${fpath[1]}/_kubectl"
`
func GetCompletionCmd() *cobra.Command {
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)
}
},
}
return completionCmd
}
// func init() {
// rootCmd.AddCommand(completionCmd)
// }

View File

@@ -1,127 +0,0 @@
package cmd
import (
"fmt"
"os"
"strings"
"github.com/armosec/kubescape/clihandler"
"github.com/armosec/kubescape/clihandler/cliobjects"
"github.com/spf13/cobra"
)
var (
configExample = `
# View cached configurations
kubescape config view
# Delete cached configurations
kubescape config delete
# Set cached configurations
kubescape config set --help
`
setConfigExample = `
# Set account id
kubescape config set accountID <account id>
# Set client id
kubescape config set clientID <client id>
# Set access key
kubescape config set secretKey <access key>
`
)
// configCmd represents the config command
var configCmd = &cobra.Command{
Use: "config",
Short: "handle cached configurations",
Example: configExample,
}
var setConfig = cliobjects.SetConfig{}
// configCmd represents the config command
var configSetCmd = &cobra.Command{
Use: "set",
Short: fmt.Sprintf("Set configurations, supported: %s", strings.Join(stringKeysToSlice(supportConfigSet), "/")),
Example: setConfigExample,
ValidArgs: stringKeysToSlice(supportConfigSet),
RunE: func(cmd *cobra.Command, args []string) error {
if err := parseSetArgs(args); err != nil {
return err
}
if err := clihandler.CliSetConfig(&setConfig); err != nil {
fmt.Fprintf(os.Stderr, "error: %v\n", err)
os.Exit(1)
}
return nil
},
}
var supportConfigSet = map[string]func(*cliobjects.SetConfig, string){
"accountID": func(s *cliobjects.SetConfig, account string) { s.Account = account },
"clientID": func(s *cliobjects.SetConfig, clientID string) { s.ClientID = clientID },
"secretKey": func(s *cliobjects.SetConfig, secretKey string) { s.SecretKey = secretKey },
}
func stringKeysToSlice(m map[string]func(*cliobjects.SetConfig, string)) []string {
l := []string{}
for i := range m {
l = append(l, i)
}
return l
}
func parseSetArgs(args []string) error {
var key string
var value string
if len(args) == 1 {
if keyValue := strings.Split(args[0], "="); len(keyValue) == 2 {
key = keyValue[0]
value = keyValue[1]
}
} else if len(args) == 2 {
key = args[0]
value = args[1]
}
if setConfigFunc, ok := supportConfigSet[key]; ok {
setConfigFunc(&setConfig, value)
} else {
return fmt.Errorf("key '%s' unknown . supported: %s", key, strings.Join(stringKeysToSlice(supportConfigSet), "/"))
}
return nil
}
var configDeleteCmd = &cobra.Command{
Use: "delete",
Short: "Delete cached configurations",
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)
}
},
}
// configCmd represents the config command
var configViewCmd = &cobra.Command{
Use: "view",
Short: "View cached configurations",
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)
}
},
}
func init() {
rootCmd.AddCommand(configCmd)
configCmd.AddCommand(configSetCmd)
configCmd.AddCommand(configDeleteCmd)
configCmd.AddCommand(configViewCmd)
}

View File

@@ -0,0 +1,44 @@
package config
import (
"github.com/spf13/cobra"
)
var (
configExample = `
# View cached configurations
kubescape config view
# Delete cached configurations
kubescape config delete
# Set cached configurations
kubescape config set --help
`
setConfigExample = `
# Set account id
kubescape config set accountID <account id>
# Set client id
kubescape config set clientID <client id>
# Set access key
kubescape config set secretKey <access key>
`
)
func GetConfigCmd() *cobra.Command {
// configCmd represents the config command
configCmd := &cobra.Command{
Use: "config",
Short: "handle cached configurations",
Example: configExample,
}
configCmd.AddCommand(getDeleteCmd())
configCmd.AddCommand(getSetCmd())
configCmd.AddCommand(getViewCmd())
return configCmd
}

View File

@@ -0,0 +1,20 @@
package config
import (
"github.com/armosec/kubescape/cautils/logger"
"github.com/armosec/kubescape/clihandler"
"github.com/spf13/cobra"
)
func getDeleteCmd() *cobra.Command {
return &cobra.Command{
Use: "delete",
Short: "Delete cached configurations",
Long: ``,
Run: func(cmd *cobra.Command, args []string) {
if err := clihandler.CliDelete(); err != nil {
logger.L().Fatal(err.Error())
}
},
}
}

View File

@@ -0,0 +1,69 @@
package config
import (
"fmt"
"strings"
"github.com/armosec/kubescape/cautils/logger"
"github.com/armosec/kubescape/clihandler"
"github.com/armosec/kubescape/clihandler/cliobjects"
"github.com/spf13/cobra"
)
func getSetCmd() *cobra.Command {
// configCmd represents the config command
configSetCmd := &cobra.Command{
Use: "set",
Short: fmt.Sprintf("Set configurations, supported: %s", strings.Join(stringKeysToSlice(supportConfigSet), "/")),
Example: setConfigExample,
ValidArgs: stringKeysToSlice(supportConfigSet),
RunE: func(cmd *cobra.Command, args []string) error {
setConfig, err := parseSetArgs(args)
if err != nil {
return err
}
if err := clihandler.CliSetConfig(setConfig); err != nil {
logger.L().Fatal(err.Error())
}
return nil
},
}
return configSetCmd
}
var supportConfigSet = map[string]func(*cliobjects.SetConfig, string){
"accountID": func(s *cliobjects.SetConfig, account string) { s.Account = account },
"clientID": func(s *cliobjects.SetConfig, clientID string) { s.ClientID = clientID },
"secretKey": func(s *cliobjects.SetConfig, secretKey string) { s.SecretKey = secretKey },
}
func stringKeysToSlice(m map[string]func(*cliobjects.SetConfig, string)) []string {
l := []string{}
for i := range m {
l = append(l, i)
}
return l
}
func parseSetArgs(args []string) (*cliobjects.SetConfig, error) {
var key string
var value string
if len(args) == 1 {
if keyValue := strings.Split(args[0], "="); len(keyValue) == 2 {
key = keyValue[0]
value = keyValue[1]
}
} else if len(args) == 2 {
key = args[0]
value = args[1]
}
setConfig := &cliobjects.SetConfig{}
if setConfigFunc, ok := supportConfigSet[key]; ok {
setConfigFunc(setConfig, value)
} else {
return setConfig, fmt.Errorf("key '%s' unknown . supported: %s", key, strings.Join(stringKeysToSlice(supportConfigSet), "/"))
}
return setConfig, nil
}

View File

@@ -0,0 +1,22 @@
package config
import (
"github.com/armosec/kubescape/cautils/logger"
"github.com/armosec/kubescape/clihandler"
"github.com/spf13/cobra"
)
func getViewCmd() *cobra.Command {
// configCmd represents the config command
return &cobra.Command{
Use: "view",
Short: "View cached configurations",
Long: ``,
Run: func(cmd *cobra.Command, args []string) {
if err := clihandler.CliView(); err != nil {
logger.L().Fatal(err.Error())
}
},
}
}

View File

@@ -1,120 +0,0 @@
package cmd
import (
"fmt"
"io"
"os"
"strings"
"github.com/armosec/kubescape/cautils"
"github.com/armosec/kubescape/cautils/logger"
"github.com/armosec/kubescape/clihandler"
"github.com/armosec/opa-utils/reporthandling"
"github.com/spf13/cobra"
)
var (
controlExample = `
# Scan the 'privileged container' control
kubescape scan control "privileged container"
# Scan list of controls separated with a comma
kubescape scan control "privileged container","allowed hostpath"
# Scan list of controls using the control ID separated with a comma
kubescape scan control C-0058,C-0057
Run 'kubescape list controls' for the list of supported controls
Control documentation:
https://hub.armo.cloud/docs/controls
`
)
// controlCmd represents the control command
var controlCmd = &cobra.Command{
Use: "control <control names list>/<control ids list>",
Short: "The controls you wish to use. Run 'kubescape list controls' for the list of supported controls",
Example: controlExample,
Args: func(cmd *cobra.Command, args []string) error {
if len(args) > 0 {
controls := strings.Split(args[0], ",")
if len(controls) > 1 {
if controls[1] == "" {
return fmt.Errorf("usage: <control-0>,<control-1>")
}
}
} else {
return fmt.Errorf("requires at least one control name")
}
return nil
},
RunE: func(cmd *cobra.Command, args []string) error {
flagValidationControl()
scanInfo.PolicyIdentifier = []reporthandling.PolicyIdentifier{}
if len(args) == 0 {
scanInfo.ScanAll = true
} else { // expected control or list of control sepparated by ","
// Read controls from input args
scanInfo.SetPolicyIdentifiers(strings.Split(args[0], ","), reporthandling.KindControl)
if len(args) > 1 {
if len(args[1:]) == 0 || args[1] != "-" {
scanInfo.InputPatterns = args[1:]
} else { // store stdin to file - do NOT move to separate function !!
tempFile, err := os.CreateTemp(".", "tmp-kubescape*.yaml")
if err != nil {
return err
}
defer os.Remove(tempFile.Name())
if _, err := io.Copy(tempFile, os.Stdin); err != nil {
return err
}
scanInfo.InputPatterns = []string{tempFile.Name()}
}
}
}
scanInfo.FrameworkScan = false
scanInfo.Init()
cautils.SetSilentMode(scanInfo.Silent)
err := clihandler.ScanCliSetup(&scanInfo)
if err != nil {
logger.L().Fatal(err.Error())
}
return nil
},
}
func init() {
scanInfo = cautils.ScanInfo{}
scanCmd.AddCommand(controlCmd)
}
func flagValidationControl() {
if 100 < scanInfo.FailThreshold {
logger.L().Fatal("bad argument: out of range threshold")
}
}
func setScanForFirstControl(controls []string) []reporthandling.PolicyIdentifier {
newPolicy := reporthandling.PolicyIdentifier{}
newPolicy.Kind = reporthandling.KindControl
newPolicy.Name = controls[0]
scanInfo.PolicyIdentifier = append(scanInfo.PolicyIdentifier, newPolicy)
return scanInfo.PolicyIdentifier
}
func SetScanForGivenControls(controls []string) []reporthandling.PolicyIdentifier {
for _, control := range controls {
control := strings.TrimLeft(control, " ")
newPolicy := reporthandling.PolicyIdentifier{}
newPolicy.Kind = reporthandling.KindControl
newPolicy.Name = control
scanInfo.PolicyIdentifier = append(scanInfo.PolicyIdentifier, newPolicy)
}
return scanInfo.PolicyIdentifier
}

View File

@@ -0,0 +1,31 @@
package delete
import (
"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"
`
func GetDeleteCmd() *cobra.Command {
var deleteCmd = &cobra.Command{
Use: "delete <command>",
Short: "Delete configurations in Kubescape SaaS version",
Long: ``,
Run: func(cmd *cobra.Command, args []string) {
},
}
deleteCmd.PersistentFlags().StringVarP(&deleteInfo.Account, "account", "", "", "Armo portal account ID. Default will load account ID from configMap or config file")
deleteCmd.AddCommand(getExceptionsCmd())
return deleteCmd
}

View File

@@ -0,0 +1,33 @@
package delete
import (
"fmt"
"strings"
"github.com/armosec/kubescape/cautils/logger"
"github.com/armosec/kubescape/clihandler"
"github.com/spf13/cobra"
)
func getExceptionsCmd() *cobra.Command {
return &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())
}
},
}
}

View File

@@ -1,4 +1,4 @@
package cmd
package download
import (
"fmt"
@@ -17,7 +17,7 @@ var (
downloadExample = `
# Download all artifacts and save them in the default path (~/.kubescape)
kubescape download artifacts
download
# Download all artifacts and save them in /tmp path
kubescape download artifacts --output /tmp
@@ -38,44 +38,40 @@ var (
`
)
var downloadCmd = &cobra.Command{
Use: "download <policy> <policy name>",
Short: fmt.Sprintf("Download %s", strings.Join(clihandler.DownloadSupportCommands(), ",")),
Long: ``,
Example: downloadExample,
Args: func(cmd *cobra.Command, args []string) error {
supported := strings.Join(clihandler.DownloadSupportCommands(), ",")
if len(args) < 1 {
return fmt.Errorf("policy type required, supported: %v", supported)
}
if cautils.StringInSlice(clihandler.DownloadSupportCommands(), args[0]) == cautils.ValueNotFound {
return fmt.Errorf("invalid parameter '%s'. Supported parameters: %s", args[0], supported)
}
return nil
},
RunE: func(cmd *cobra.Command, args []string) error {
downloadInfo.Target = args[0]
if len(args) >= 2 {
downloadInfo.Name = args[1]
}
if err := clihandler.CliDownload(&downloadInfo); err != nil {
logger.L().Fatal(err.Error())
}
return nil
},
}
func init() {
cobra.OnInitialize(initDownload)
func GeDownloadCmd() *cobra.Command {
downloadCmd := &cobra.Command{
Use: "download <policy> <policy name>",
Short: fmt.Sprintf("Download %s", strings.Join(clihandler.DownloadSupportCommands(), ",")),
Long: ``,
Example: downloadExample,
Args: func(cmd *cobra.Command, args []string) error {
supported := strings.Join(clihandler.DownloadSupportCommands(), ",")
if len(args) < 1 {
return fmt.Errorf("policy type required, supported: %v", supported)
}
if cautils.StringInSlice(clihandler.DownloadSupportCommands(), args[0]) == cautils.ValueNotFound {
return fmt.Errorf("invalid parameter '%s'. Supported parameters: %s", args[0], supported)
}
return nil
},
RunE: func(cmd *cobra.Command, args []string) error {
rootCmd.AddCommand(downloadCmd)
if filepath.Ext(downloadInfo.Path) == ".json" {
downloadInfo.Path, downloadInfo.FileName = filepath.Split(downloadInfo.Path)
}
downloadInfo.Target = args[0]
if len(args) >= 2 {
downloadInfo.Name = args[1]
}
if err := clihandler.CliDownload(&downloadInfo); err != nil {
logger.L().Fatal(err.Error())
}
return nil
},
}
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)
}
return downloadCmd
}

View File

@@ -1,128 +0,0 @@
package cmd
import (
"fmt"
"io"
"os"
"strings"
"github.com/armosec/kubescape/cautils"
"github.com/armosec/kubescape/cautils/logger"
"github.com/armosec/kubescape/clihandler"
"github.com/armosec/opa-utils/reporthandling"
"github.com/spf13/cobra"
)
var (
frameworkExample = `
# Scan all frameworks and submit the results
kubescape scan --submit
# Scan the NSA framework
kubescape scan framework nsa
# Scan the NSA and MITRE framework
kubescape scan framework nsa,mitre
# Scan all frameworks
kubescape scan framework all
# 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
`
)
var frameworkCmd = &cobra.Command{
Use: "framework <framework names list> [`<glob pattern>`/`-`] [flags]",
Short: "The framework you wish to use. Run 'kubescape list frameworks' for the list of supported frameworks",
Example: frameworkExample,
Long: "Execute a scan on a running Kubernetes cluster or `yaml`/`json` files (use glob) or `-` for stdin",
Args: func(cmd *cobra.Command, args []string) error {
if len(args) > 0 {
frameworks := strings.Split(args[0], ",")
if len(frameworks) > 1 {
if frameworks[1] == "" {
return fmt.Errorf("usage: <framework-0>,<framework-1>")
}
}
} else {
return fmt.Errorf("requires at least one framework name")
}
return nil
},
RunE: func(cmd *cobra.Command, args []string) error {
flagValidationFramework()
var frameworks []string
if len(args) == 0 { // scan all frameworks
scanInfo.ScanAll = true
} else {
// Read frameworks from input args
frameworks = strings.Split(args[0], ",")
if cautils.StringInSlice(frameworks, "all") != cautils.ValueNotFound {
scanInfo.ScanAll = true
frameworks = []string{}
}
if len(args) > 1 {
if len(args[1:]) == 0 || args[1] != "-" {
scanInfo.InputPatterns = args[1:]
} else { // store stdin to file - do NOT move to separate function !!
tempFile, err := os.CreateTemp(".", "tmp-kubescape*.yaml")
if err != nil {
return err
}
defer os.Remove(tempFile.Name())
if _, err := io.Copy(tempFile, os.Stdin); err != nil {
return err
}
scanInfo.InputPatterns = []string{tempFile.Name()}
}
}
}
scanInfo.FrameworkScan = true
scanInfo.SetPolicyIdentifiers(frameworks, reporthandling.KindFramework)
scanInfo.Init()
cautils.SetSilentMode(scanInfo.Silent)
err := clihandler.ScanCliSetup(&scanInfo)
if err != nil {
logger.L().Fatal(err.Error())
}
return nil
},
}
func init() {
scanCmd.AddCommand(frameworkCmd)
scanInfo = cautils.ScanInfo{}
scanInfo.FrameworkScan = true
}
// func SetScanForFirstFramework(frameworks []string) []reporthandling.PolicyIdentifier {
// newPolicy := reporthandling.PolicyIdentifier{}
// newPolicy.Kind = reporthandling.KindFramework
// newPolicy.Name = frameworks[0]
// scanInfo.PolicyIdentifier = append(scanInfo.PolicyIdentifier, newPolicy)
// return scanInfo.PolicyIdentifier
// }
func flagValidationFramework() {
if scanInfo.Submit && scanInfo.Local {
logger.L().Fatal("you can use `keep-local` or `submit`, but not both")
}
if 100 < scanInfo.FailThreshold {
logger.L().Fatal("bad argument: out of range threshold")
}
}

View File

@@ -1,67 +0,0 @@
package cmd
import (
"fmt"
"os"
"strings"
"github.com/armosec/kubescape/cautils"
"github.com/armosec/kubescape/clihandler"
"github.com/armosec/kubescape/clihandler/cliobjects"
"github.com/spf13/cobra"
)
var (
listExample = `
# List default supported frameworks names
kubescape list frameworks
# List all supported frameworks names
kubescape list frameworks --account <account id>
# List all supported controls names
kubescape list controls
# List all supported controls ids
kubescape list controls --id
Control documentation:
https://hub.armo.cloud/docs/controls
`
)
var listPolicies = cliobjects.ListPolicies{}
var listCmd = &cobra.Command{
Use: "list <policy> [flags]",
Short: "List frameworks/controls will list the supported frameworks and controls",
Long: ``,
Example: listExample,
Args: func(cmd *cobra.Command, args []string) error {
supported := strings.Join(clihandler.ListSupportCommands(), ",")
if len(args) < 1 {
return fmt.Errorf("policy type requeued, supported: %s", supported)
}
if cautils.StringInSlice(clihandler.ListSupportCommands(), args[0]) == cautils.ValueNotFound {
return fmt.Errorf("invalid parameter '%s'. Supported parameters: %s", args[0], supported)
}
return nil
},
RunE: func(cmd *cobra.Command, args []string) error {
listPolicies.Target = args[0]
if err := clihandler.CliList(&listPolicies); err != nil {
fmt.Fprintf(os.Stderr, "error: %v\n", err)
os.Exit(1)
}
return nil
},
}
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().BoolVarP(&listPolicies.ListIDs, "id", "", false, "List control ID's instead of controls names")
}

View File

@@ -0,0 +1,66 @@
package list
import (
"fmt"
"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"
)
var (
listExample = `
# List default supported frameworks names
kubescape list frameworks
# List all supported frameworks names
kubescape list frameworks --account <account id>
# List all supported controls names
kubescape list controls
# List all supported controls ids
kubescape list controls --id
Control documentation:
https://hub.armo.cloud/docs/controls
`
)
func GetListCmd() *cobra.Command {
var listPolicies = cliobjects.ListPolicies{}
listCmd := &cobra.Command{
Use: "list <policy> [flags]",
Short: "List frameworks/controls will list the supported frameworks and controls",
Long: ``,
Example: listExample,
Args: func(cmd *cobra.Command, args []string) error {
supported := strings.Join(clihandler.ListSupportCommands(), ",")
if len(args) < 1 {
return fmt.Errorf("policy type requeued, supported: %s", supported)
}
if cautils.StringInSlice(clihandler.ListSupportCommands(), args[0]) == cautils.ValueNotFound {
return fmt.Errorf("invalid parameter '%s'. Supported parameters: %s", args[0], supported)
}
return nil
},
RunE: func(cmd *cobra.Command, args []string) error {
listPolicies.Target = args[0]
if err := clihandler.CliList(&listPolicies); err != nil {
logger.L().Fatal(err.Error())
}
return nil
},
}
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")
return listCmd
}

View File

@@ -1,18 +0,0 @@
package cmd
import (
"github.com/spf13/cobra"
)
var localCmd = &cobra.Command{
Use: "local",
Short: "Set configuration locally (for config.json)",
Long: ``,
Deprecated: "use the 'set' command instead",
Run: func(cmd *cobra.Command, args []string) {
},
}
func init() {
configCmd.AddCommand(localCmd)
}

View File

@@ -1,45 +0,0 @@
package cmd
import (
"fmt"
"strings"
"github.com/armosec/kubescape/cautils"
"github.com/spf13/cobra"
)
var localGetCmd = &cobra.Command{
Use: "get <key>",
Short: "Get configuration locally",
Long: ``,
Deprecated: "use the 'view' command instead",
Args: func(cmd *cobra.Command, args []string) error {
if len(args) < 1 || len(args) > 1 {
return fmt.Errorf("requires one argument")
}
keyValue := strings.Split(args[0], "=")
if len(keyValue) != 1 {
return fmt.Errorf("requires one argument")
}
return nil
},
RunE: func(cmd *cobra.Command, args []string) error {
keyValue := strings.Split(args[0], "=")
key := keyValue[0]
val, err := cautils.GetValueFromConfigJson(key)
if err != nil {
if err.Error() == "value does not exist." {
return fmt.Errorf("failed to get value from: %s, reason: %s", cautils.ConfigFileFullPath(), err.Error())
}
return err
}
fmt.Println(key + "=" + val)
return nil
},
}
func init() {
localCmd.AddCommand(localGetCmd)
}

View File

@@ -1,41 +0,0 @@
package cmd
import (
"fmt"
"strings"
"github.com/armosec/kubescape/cautils"
"github.com/spf13/cobra"
)
var localSetCmd = &cobra.Command{
Use: "set <key>=<value>",
Short: "Set configuration locally",
Long: ``,
Deprecated: "use the 'set' command instead",
Args: func(cmd *cobra.Command, args []string) error {
if len(args) < 1 || len(args) > 1 {
return fmt.Errorf("requires one argument: <key>=<value>")
}
keyValue := strings.Split(args[0], "=")
if len(keyValue) != 2 {
return fmt.Errorf("requires one argument: <key>=<value>")
}
return nil
},
RunE: func(cmd *cobra.Command, args []string) error {
keyValue := strings.Split(args[0], "=")
key := keyValue[0]
data := keyValue[1]
if err := cautils.SetKeyValueInConfigJson(key, data); err != nil {
return err
}
fmt.Println("Value added successfully.")
return nil
},
}
func init() {
localCmd.AddCommand(localSetCmd)
}

View File

@@ -1,62 +0,0 @@
package cmd
import (
"github.com/armosec/k8s-interface/k8sinterface"
"github.com/armosec/kubescape/cautils"
"github.com/armosec/kubescape/cautils/getter"
"github.com/armosec/kubescape/cautils/logger"
"github.com/armosec/kubescape/clihandler"
"github.com/armosec/kubescape/clihandler/cliinterfaces"
reporterv1 "github.com/armosec/kubescape/resultshandling/reporter/v1"
"github.com/armosec/rbac-utils/rbacscanner"
"github.com/spf13/cobra"
)
// rabcCmd represents the RBAC command
var rabcCmd = &cobra.Command{
Use: "rbac \nExample:\n$ kubescape submit rbac",
Short: "Submit cluster's Role-Based Access Control(RBAC)",
Long: ``,
RunE: func(cmd *cobra.Command, args []string) error {
k8s := k8sinterface.NewKubernetesApi()
// get config
clusterConfig := getTenantConfig(submitInfo.Account, "", k8s)
// list RBAC
rbacObjects := cautils.NewRBACObjects(rbacscanner.NewRbacScannerFromK8sAPI(k8s, clusterConfig.GetAccountID(), clusterConfig.GetClusterName()))
// submit resources
r := reporterv1.NewReportEventReceiver(clusterConfig.GetConfigObj())
submitInterfaces := cliinterfaces.SubmitInterfaces{
ClusterConfig: clusterConfig,
SubmitObjects: rbacObjects,
Reporter: r,
}
if err := clihandler.Submit(submitInterfaces); err != nil {
logger.L().Fatal(err.Error())
}
return nil
},
}
func init() {
submitCmd.AddCommand(rabcCmd)
}
// getKubernetesApi
func getKubernetesApi() *k8sinterface.KubernetesApi {
if !k8sinterface.IsConnectedToCluster() {
return nil
}
return k8sinterface.NewKubernetesApi()
}
func getTenantConfig(Account, clusterName string, k8s *k8sinterface.KubernetesApi) cautils.ITenantConfig {
if !k8sinterface.IsConnectedToCluster() || k8s == nil {
return cautils.NewLocalConfig(getter.GetArmoAPIConnector(), Account, clusterName)
}
return cautils.NewClusterConfig(k8s, getter.GetArmoAPIConnector(), Account, clusterName)
}

View File

@@ -1,103 +0,0 @@
package cmd
import (
"encoding/json"
"fmt"
"os"
"time"
"github.com/armosec/k8s-interface/workloadinterface"
"github.com/armosec/kubescape/cautils/logger"
"github.com/armosec/kubescape/clihandler"
"github.com/armosec/kubescape/clihandler/cliinterfaces"
reporterv1 "github.com/armosec/kubescape/resultshandling/reporter/v1"
"github.com/armosec/opa-utils/reporthandling"
uuid "github.com/satori/go.uuid"
"github.com/spf13/cobra"
)
type ResultsObject struct {
filePath string
customerGUID string
clusterName string
}
func NewResultsObject(customerGUID, clusterName, filePath string) *ResultsObject {
return &ResultsObject{
filePath: filePath,
customerGUID: customerGUID,
clusterName: clusterName,
}
}
func (resultsObject *ResultsObject) SetResourcesReport() (*reporthandling.PostureReport, error) {
// load framework results from json file
frameworkReports, err := loadResultsFromFile(resultsObject.filePath)
if err != nil {
return nil, err
}
return &reporthandling.PostureReport{
FrameworkReports: frameworkReports,
ReportID: uuid.NewV4().String(),
ReportGenerationTime: time.Now().UTC(),
CustomerGUID: resultsObject.customerGUID,
ClusterName: resultsObject.clusterName,
}, nil
}
func (resultsObject *ResultsObject) ListAllResources() (map[string]workloadinterface.IMetadata, error) {
return map[string]workloadinterface.IMetadata{}, nil
}
var resultsCmd = &cobra.Command{
Use: "results <json file>\nExample:\n$ kubescape submit results path/to/results.json",
Short: "Submit a pre scanned results file. The file must be in json format",
Long: ``,
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) == 0 {
return fmt.Errorf("missing results file")
}
k8s := getKubernetesApi()
// get config
clusterConfig := getTenantConfig(submitInfo.Account, "", k8s)
resultsObjects := NewResultsObject(clusterConfig.GetAccountID(), clusterConfig.GetClusterName(), args[0])
// submit resources
r := reporterv1.NewReportEventReceiver(clusterConfig.GetConfigObj())
submitInterfaces := cliinterfaces.SubmitInterfaces{
ClusterConfig: clusterConfig,
SubmitObjects: resultsObjects,
Reporter: r,
}
if err := clihandler.Submit(submitInterfaces); err != nil {
logger.L().Fatal(err.Error())
}
return nil
},
}
func init() {
submitCmd.AddCommand(resultsCmd)
}
func loadResultsFromFile(filePath string) ([]reporthandling.FrameworkReport, error) {
frameworkReports := []reporthandling.FrameworkReport{}
f, err := os.ReadFile(filePath)
if err != nil {
return nil, err
}
if err = json.Unmarshal(f, &frameworkReports); err != nil {
frameworkReport := reporthandling.FrameworkReport{}
if err = json.Unmarshal(f, &frameworkReport); err != nil {
return frameworkReports, err
}
frameworkReports = append(frameworkReports, frameworkReport)
}
return frameworkReports, nil
}

View File

@@ -1,17 +1,29 @@
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"
"github.com/armosec/kubescape/clihandler/cmd/completion"
"github.com/armosec/kubescape/clihandler/cmd/config"
"github.com/armosec/kubescape/clihandler/cmd/delete"
"github.com/armosec/kubescape/clihandler/cmd/download"
"github.com/armosec/kubescape/clihandler/cmd/list"
"github.com/armosec/kubescape/clihandler/cmd/scan"
"github.com/armosec/kubescape/clihandler/cmd/submit"
"github.com/armosec/kubescape/clihandler/cmd/version"
"github.com/mattn/go-isatty"
"github.com/spf13/cobra"
)
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 +43,86 @@ 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().StringVar(&rootInfo.LoggerName, "logger-name", "", fmt.Sprintf("Logger name. Supported: %s [$KS_LOGGER_NAME]", strings.Join(logger.ListLoggersNames(), "/")))
rootCmd.PersistentFlags().MarkHidden("logger-name")
rootCmd.PersistentFlags().StringVarP(&rootInfo.Logger, "logger", "l", helpers.InfoLevel.String(), fmt.Sprintf("Logger level. Supported: %s [$KS_LOGGER]", strings.Join(helpers.SupportedLevels(), "/")))
rootCmd.PersistentFlags().StringVar(&rootInfo.CacheDir, "cache-dir", getter.DefaultLocalStore, "Cache directory [$KS_CACHE_DIR]")
rootCmd.PersistentFlags().BoolVarP(&rootInfo.DisableColor, "disable-color", "", false, "Disable Color output for logging")
rootCmd.AddCommand(scan.GetScanCommand())
rootCmd.AddCommand(download.GeDownloadCmd())
rootCmd.AddCommand(delete.GetDeleteCmd())
rootCmd.AddCommand(list.GetListCmd())
rootCmd.AddCommand(submit.GetSubmitCmd())
rootCmd.AddCommand(completion.GetCompletionCmd())
rootCmd.AddCommand(version.GetVersionCmd())
rootCmd.AddCommand(config.GetConfigCmd())
}
func initLogger() {
if err := logger.L().SetLevel(scanInfo.Logger); err != nil {
logger.DisableColor(rootInfo.DisableColor)
if rootInfo.LoggerName == "" {
if l := os.Getenv("KS_LOGGER_NAME"); l != "" {
rootInfo.LoggerName = l
} else {
if isatty.IsTerminal(os.Stdout.Fd()) {
rootInfo.LoggerName = "pretty"
} else {
rootInfo.LoggerName = "zap"
}
}
}
logger.InitLogger(rootInfo.LoggerName)
}
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 dir 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 +130,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:

View File

@@ -1,65 +0,0 @@
package cmd
import (
"github.com/armosec/k8s-interface/k8sinterface"
"github.com/armosec/kubescape/cautils"
"github.com/spf13/cobra"
)
var scanInfo cautils.ScanInfo
// 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`,
Args: func(cmd *cobra.Command, args []string) error {
if len(args) > 0 {
if args[0] != "framework" && args[0] != "control" {
scanInfo.ScanAll = true
return frameworkCmd.RunE(cmd, append([]string{"all"}, args...))
}
}
return nil
},
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) == 0 {
scanInfo.ScanAll = true
return frameworkCmd.RunE(cmd, []string{"all"})
}
return nil
},
}
func frameworkInitConfig() {
k8sinterface.SetClusterContextName(scanInfo.KubeContext)
}
func init() {
cobra.OnInitialize(frameworkInitConfig)
rootCmd.AddCommand(scanCmd)
scanCmd.PersistentFlags().StringVarP(&scanInfo.Account, "account", "", "", "Armo portal account ID. Default will load account ID from configMap or config file")
scanCmd.PersistentFlags().StringVarP(&scanInfo.KubeContext, "kube-context", "", "", "Kube context. Default will use the current-context")
scanCmd.PersistentFlags().StringVar(&scanInfo.ControlsInputs, "controls-config", "", "Path to an controls-config obj. If not set will download controls-config from ARMO management portal")
scanCmd.PersistentFlags().StringVar(&scanInfo.UseExceptions, "exceptions", "", "Path to an exceptions obj. If not set will download exceptions from ARMO management portal")
scanCmd.PersistentFlags().StringVar(&scanInfo.UseArtifactsFrom, "use-artifacts-from", "", "Load artifacts from local directory. If not used will download them")
scanCmd.PersistentFlags().StringVarP(&scanInfo.ExcludedNamespaces, "exclude-namespaces", "e", "", "Namespaces to exclude from scanning. Recommended: kube-system,kube-public")
scanCmd.PersistentFlags().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().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")
scanCmd.PersistentFlags().BoolVar(&scanInfo.VerboseMode, "verbose", false, "Display all of the input resources and not only failed resources")
scanCmd.PersistentFlags().BoolVar(&scanInfo.UseDefault, "use-default", false, "Load local policy object from default path. If not used will download latest")
scanCmd.PersistentFlags().StringSliceVar(&scanInfo.UseFrom, "use-from", nil, "Load local policy object from specified path. If not used will download latest")
scanCmd.PersistentFlags().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")
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")
hostF.NoOptDefVal = "true"
hostF.DefValue = "false, for no TTY in stdin"
}

View File

@@ -0,0 +1,123 @@
package scan
import (
"fmt"
"io"
"os"
"strings"
"github.com/armosec/kubescape/cautils"
"github.com/armosec/kubescape/cautils/logger"
"github.com/armosec/kubescape/clihandler"
"github.com/armosec/opa-utils/reporthandling"
"github.com/spf13/cobra"
)
var (
controlExample = `
# Scan the 'privileged container' control
kubescape scan control "privileged container"
# Scan list of controls separated with a comma
kubescape scan control "privileged container","allowed hostpath"
# Scan list of controls using the control ID separated with a comma
kubescape scan control C-0058,C-0057
Run 'kubescape list controls' for the list of supported controls
Control documentation:
https://hub.armo.cloud/docs/controls
`
)
// controlCmd represents the control command
func getControlCmd(scanInfo *cautils.ScanInfo) *cobra.Command {
return &cobra.Command{
Use: "control <control names list>/<control ids list>",
Short: "The controls you wish to use. Run 'kubescape list controls' for the list of supported controls",
Example: controlExample,
Args: func(cmd *cobra.Command, args []string) error {
if len(args) > 0 {
controls := strings.Split(args[0], ",")
if len(controls) > 1 {
for _, control := range controls {
if control == "" {
return fmt.Errorf("usage: <control-0>,<control-1>")
}
}
}
} else {
return fmt.Errorf("requires at least one control name")
}
return nil
},
RunE: func(cmd *cobra.Command, args []string) error {
// flagValidationControl(scanInfo)
scanInfo.PolicyIdentifier = []reporthandling.PolicyIdentifier{}
if len(args) == 0 {
scanInfo.ScanAll = true
} else { // expected control or list of control sepparated by ","
// Read controls from input args
scanInfo.SetPolicyIdentifiers(strings.Split(args[0], ","), reporthandling.KindControl)
if len(args) > 1 {
if len(args[1:]) == 0 || args[1] != "-" {
scanInfo.InputPatterns = args[1:]
} else { // store stdin to file - do NOT move to separate function !!
tempFile, err := os.CreateTemp(".", "tmp-kubescape*.yaml")
if err != nil {
return err
}
defer os.Remove(tempFile.Name())
if _, err := io.Copy(tempFile, os.Stdin); err != nil {
return err
}
scanInfo.InputPatterns = []string{tempFile.Name()}
}
}
}
scanInfo.FrameworkScan = false
results, err := clihandler.Scan(scanInfo)
if err != nil {
logger.L().Fatal(err.Error())
}
results.HandleResults()
if results.GetRiskScore() > float32(scanInfo.FailThreshold) {
return fmt.Errorf("scan risk-score %.2f is above permitted threshold %.2f", results.GetRiskScore(), scanInfo.FailThreshold)
}
return nil
},
}
}
// func flagValidationControl() {
// if 100 < scanInfo.FailThreshold {
// logger.L().Fatal("bad argument: out of range threshold")
// }
// }
// func setScanForFirstControl(scanInfo, controls []string) []reporthandling.PolicyIdentifier {
// newPolicy := reporthandling.PolicyIdentifier{}
// newPolicy.Kind = reporthandling.KindControl
// newPolicy.Name = controls[0]
// scanInfo.PolicyIdentifier = append(scanInfo.PolicyIdentifier, newPolicy)
// return scanInfo.PolicyIdentifier
// }
// func SetScanForGivenControls(scanInfo, controls []string) []reporthandling.PolicyIdentifier {
// for _, control := range controls {
// control := strings.TrimLeft(control, " ")
// newPolicy := reporthandling.PolicyIdentifier{}
// newPolicy.Kind = reporthandling.KindControl
// newPolicy.Name = control
// scanInfo.PolicyIdentifier = append(scanInfo.PolicyIdentifier, newPolicy)
// }
// return scanInfo.PolicyIdentifier
// }

View File

@@ -0,0 +1,130 @@
package scan
import (
"fmt"
"io"
"os"
"strings"
"github.com/armosec/kubescape/cautils"
"github.com/armosec/kubescape/cautils/logger"
"github.com/armosec/kubescape/clihandler"
"github.com/armosec/opa-utils/reporthandling"
"github.com/spf13/cobra"
)
var (
frameworkExample = `
# Scan all frameworks and submit the results
kubescape scan framework all --submit
# Scan the NSA framework
kubescape scan framework nsa
# Scan the NSA and MITRE framework
kubescape scan framework nsa,mitre
# Scan all frameworks
kubescape scan framework all
# Scan kubernetes YAML manifest files
kubescape scan framework nsa *.yaml
Run 'kubescape list frameworks' for the list of supported frameworks
`
)
func getFrameworkCmd(scanInfo *cautils.ScanInfo) *cobra.Command {
return &cobra.Command{
Use: "framework <framework names list> [`<glob pattern>`/`-`] [flags]",
Short: "The framework you wish to use. Run 'kubescape list frameworks' for the list of supported frameworks",
Example: frameworkExample,
Long: "Execute a scan on a running Kubernetes cluster or `yaml`/`json` files (use glob) or `-` for stdin",
Args: func(cmd *cobra.Command, args []string) error {
if len(args) > 0 {
frameworks := strings.Split(args[0], ",")
if len(frameworks) > 1 {
for _, framework := range frameworks {
if framework == "" {
return fmt.Errorf("usage: <framework-0>,<framework-1>")
}
}
}
} else {
return fmt.Errorf("requires at least one framework name")
}
return nil
},
RunE: func(cmd *cobra.Command, args []string) error {
flagValidationFramework(scanInfo)
scanInfo.FrameworkScan = true
var frameworks []string
if len(args) == 0 { // scan all frameworks
scanInfo.ScanAll = true
} else {
// Read frameworks from input args
frameworks = strings.Split(args[0], ",")
if cautils.StringInSlice(frameworks, "all") != cautils.ValueNotFound {
scanInfo.ScanAll = true
frameworks = []string{}
}
if len(args) > 1 {
if len(args[1:]) == 0 || args[1] != "-" {
scanInfo.InputPatterns = args[1:]
} else { // store stdin to file - do NOT move to separate function !!
tempFile, err := os.CreateTemp(".", "tmp-kubescape*.yaml")
if err != nil {
return err
}
defer os.Remove(tempFile.Name())
if _, err := io.Copy(tempFile, os.Stdin); err != nil {
return err
}
scanInfo.InputPatterns = []string{tempFile.Name()}
}
}
}
scanInfo.FrameworkScan = true
scanInfo.SetPolicyIdentifiers(frameworks, reporthandling.KindFramework)
results, err := clihandler.Scan(scanInfo)
if err != nil {
logger.L().Fatal(err.Error())
}
results.HandleResults()
if results.GetRiskScore() > float32(scanInfo.FailThreshold) {
return fmt.Errorf("scan risk-score %.2f is above permitted threshold %.2f", results.GetRiskScore(), scanInfo.FailThreshold)
}
return nil
},
}
}
// func init() {
// scanCmd.AddCommand(frameworkCmd)
// scanInfo = cautils.ScanInfo{}
// }
// func SetScanForFirstFramework(frameworks []string) []reporthandling.PolicyIdentifier {
// newPolicy := reporthandling.PolicyIdentifier{}
// newPolicy.Kind = reporthandling.KindFramework
// newPolicy.Name = frameworks[0]
// scanInfo.PolicyIdentifier = append(scanInfo.PolicyIdentifier, newPolicy)
// return scanInfo.PolicyIdentifier
// }
func flagValidationFramework(scanInfo *cautils.ScanInfo) {
if scanInfo.Submit && scanInfo.Local {
logger.L().Fatal("you can use `keep-local` or `submit`, but not both")
}
if 100 < scanInfo.FailThreshold {
logger.L().Fatal("bad argument: out of range threshold")
}
}

View File

@@ -0,0 +1,98 @@
package scan
import (
"github.com/armosec/k8s-interface/k8sinterface"
"github.com/armosec/kubescape/cautils"
"github.com/spf13/cobra"
)
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>
`
func GetScanCommand() *cobra.Command {
var scanInfo cautils.ScanInfo
// scanCmd represents the scan command
scanCmd := &cobra.Command{
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" {
scanInfo.ScanAll = true
return getFrameworkCmd(&scanInfo).RunE(cmd, append([]string{"all"}, args...))
}
}
return nil
},
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) == 0 {
scanInfo.ScanAll = true
return getFrameworkCmd(&scanInfo).RunE(cmd, []string{"all"})
}
return nil
},
PreRun: func(cmd *cobra.Command, args []string) {
k8sinterface.SetClusterContextName(scanInfo.KubeContext)
},
PostRun: func(cmd *cobra.Command, args []string) {
// TODO - revert context
},
}
scanCmd.PersistentFlags().StringVarP(&scanInfo.Account, "account", "", "", "ARMO portal account ID. Default will load account ID from configMap or config file")
scanCmd.PersistentFlags().StringVarP(&scanInfo.KubeContext, "kube-context", "", "", "Kube context. Default will use the current-context")
scanCmd.PersistentFlags().StringVar(&scanInfo.ControlsInputs, "controls-config", "", "Path to an controls-config obj. If not set will download controls-config from ARMO management portal")
scanCmd.PersistentFlags().StringVar(&scanInfo.UseExceptions, "exceptions", "", "Path to an exceptions obj. If not set will download exceptions from ARMO management portal")
scanCmd.PersistentFlags().StringVar(&scanInfo.UseArtifactsFrom, "use-artifacts-from", "", "Load artifacts from local directory. If not used will download them")
scanCmd.PersistentFlags().StringVarP(&scanInfo.ExcludedNamespaces, "exclude-namespaces", "e", "", "Namespaces to exclude from scanning. Recommended: kube-system,kube-public")
scanCmd.PersistentFlags().Float32VarP(&scanInfo.FailThreshold, "fail-threshold", "t", 100, "Failure threshold is the percent above which the command fails and returns exit code 1")
scanCmd.PersistentFlags().StringVarP(&scanInfo.Format, "format", "f", "pretty-printer", `Output format. Supported formats: "pretty-printer","json","junit","prometheus","pdf"`)
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")
scanCmd.PersistentFlags().BoolVar(&scanInfo.VerboseMode, "verbose", false, "Display all of the input resources and not only failed resources")
scanCmd.PersistentFlags().BoolVar(&scanInfo.UseDefault, "use-default", false, "Load local policy object from default path. If not used will download latest")
scanCmd.PersistentFlags().StringSliceVar(&scanInfo.UseFrom, "use-from", nil, "Load local policy object from specified path. If not used will download latest")
scanCmd.PersistentFlags().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'")
// Deprecated flags - remove 1.May.2022
scanCmd.PersistentFlags().MarkDeprecated("silent", "use '--logger' flag instead. Flag will be removed at 1.May.2022")
// hidden flags
scanCmd.PersistentFlags().MarkHidden("host-scan-yaml") // this flag should be used very cautiously. We prefer users will not use it at all unless the DaemonSet can not run pods on the nodes
scanCmd.PersistentFlags().MarkHidden("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 valuable 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"
scanCmd.AddCommand(getControlCmd(&scanInfo))
scanCmd.AddCommand(getFrameworkCmd(&scanInfo))
return scanCmd
}

View File

@@ -1,46 +0,0 @@
package cmd
import (
"fmt"
"github.com/armosec/kubescape/cautils/logger"
"github.com/armosec/kubescape/clihandler"
"github.com/armosec/kubescape/clihandler/cliobjects"
"github.com/spf13/cobra"
)
var submitInfo cliobjects.Submit
var submitCmdExamples = `
`
var submitCmd = &cobra.Command{
Use: "submit <command>",
Short: "Submit an object to the Kubescape SaaS version",
Long: ``,
Run: func(cmd *cobra.Command, args []string) {
},
}
var submitExceptionsCmd = &cobra.Command{
Use: "exceptions <full path to exceptins file>",
Short: "Submit exceptions to the Kubescape SaaS version",
Args: func(cmd *cobra.Command, args []string) error {
if len(args) != 1 {
return fmt.Errorf("missing full path to exceptions file")
}
return nil
},
Run: func(cmd *cobra.Command, args []string) {
if err := clihandler.SubmitExceptions(submitInfo.Account, args[0]); err != nil {
logger.L().Fatal(err.Error())
}
},
}
func init() {
submitCmd.PersistentFlags().StringVarP(&submitInfo.Account, "account", "", "", "Armo portal account ID. Default will load account ID from configMap or config file")
rootCmd.AddCommand(submitCmd)
submitCmd.AddCommand(submitExceptionsCmd)
}

View File

@@ -0,0 +1,28 @@
package submit
import (
"fmt"
"github.com/armosec/kubescape/cautils/logger"
"github.com/armosec/kubescape/clihandler"
"github.com/armosec/kubescape/clihandler/cliobjects"
"github.com/spf13/cobra"
)
func getExceptionsCmd(submitInfo *cliobjects.Submit) *cobra.Command {
return &cobra.Command{
Use: "exceptions <full path to exceptins file>",
Short: "Submit exceptions to the Kubescape SaaS version",
Args: func(cmd *cobra.Command, args []string) error {
if len(args) != 1 {
return fmt.Errorf("missing full path to exceptions file")
}
return nil
},
Run: func(cmd *cobra.Command, args []string) {
if err := clihandler.SubmitExceptions(submitInfo.Account, args[0]); err != nil {
logger.L().Fatal(err.Error())
}
},
}
}

View File

@@ -0,0 +1,66 @@
package submit
import (
"github.com/armosec/k8s-interface/k8sinterface"
"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"
"github.com/armosec/kubescape/clihandler/cliobjects"
reporterv1 "github.com/armosec/kubescape/resultshandling/reporter/v1"
"github.com/armosec/rbac-utils/rbacscanner"
"github.com/spf13/cobra"
)
// getRBACCmd represents the RBAC command
func getRBACCmd(submitInfo *cliobjects.Submit) *cobra.Command {
return &cobra.Command{
Use: "rbac \nExample:\n$ kubescape submit rbac",
Short: "Submit cluster's Role-Based Access Control(RBAC)",
Long: ``,
RunE: func(cmd *cobra.Command, args []string) error {
k8s := k8sinterface.NewKubernetesApi()
// 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()))
// submit resources
r := reporterv1.NewReportEventReceiver(clusterConfig.GetConfigObj())
submitInterfaces := cliinterfaces.SubmitInterfaces{
ClusterConfig: clusterConfig,
SubmitObjects: rbacObjects,
Reporter: r,
}
if err := clihandler.Submit(submitInterfaces); err != nil {
logger.L().Fatal(err.Error())
}
return nil
},
}
}
// getKubernetesApi
func getKubernetesApi() *k8sinterface.KubernetesApi {
if !k8sinterface.IsConnectedToCluster() {
return nil
}
return k8sinterface.NewKubernetesApi()
}
func getTenantConfig(Account, clusterName string, k8s *k8sinterface.KubernetesApi) cautils.ITenantConfig {
if !k8sinterface.IsConnectedToCluster() || k8s == nil {
return cautils.NewLocalConfig(getter.GetArmoAPIConnector(), Account, clusterName)
}
return cautils.NewClusterConfig(k8s, getter.GetArmoAPIConnector(), Account, clusterName)
}

View File

@@ -0,0 +1,118 @@
package submit
import (
"encoding/json"
"fmt"
"os"
"time"
"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/clihandler/cliobjects"
"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"
"github.com/google/uuid"
"github.com/spf13/cobra"
)
var formatVersion string
type ResultsObject struct {
filePath string
customerGUID string
clusterName string
}
func NewResultsObject(customerGUID, clusterName, filePath string) *ResultsObject {
return &ResultsObject{
filePath: filePath,
customerGUID: customerGUID,
clusterName: clusterName,
}
}
func (resultsObject *ResultsObject) SetResourcesReport() (*reporthandling.PostureReport, error) {
// load framework results from json file
frameworkReports, err := loadResultsFromFile(resultsObject.filePath)
if err != nil {
return nil, err
}
return &reporthandling.PostureReport{
FrameworkReports: frameworkReports,
ReportID: uuid.NewString(),
ReportGenerationTime: time.Now().UTC(),
CustomerGUID: resultsObject.customerGUID,
ClusterName: resultsObject.clusterName,
}, nil
}
func (resultsObject *ResultsObject) ListAllResources() (map[string]workloadinterface.IMetadata, error) {
return map[string]workloadinterface.IMetadata{}, nil
}
func getResultsCmd(submitInfo *cliobjects.Submit) *cobra.Command {
var resultsCmd = &cobra.Command{
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 {
if len(args) == 0 {
return fmt.Errorf("missing results file")
}
k8s := getKubernetesApi()
// 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
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,
SubmitObjects: resultsObjects,
Reporter: r,
}
if err := clihandler.Submit(submitInterfaces); err != nil {
logger.L().Fatal(err.Error())
}
return nil
},
}
resultsCmd.PersistentFlags().StringVar(&formatVersion, "format-version", "v1", "Output object can be differnet between versions, this is for maintaining backward and forward compatibility. Supported:'v1'/'v2'")
return resultsCmd
}
func loadResultsFromFile(filePath string) ([]reporthandling.FrameworkReport, error) {
frameworkReports := []reporthandling.FrameworkReport{}
f, err := os.ReadFile(filePath)
if err != nil {
return nil, err
}
if err = json.Unmarshal(f, &frameworkReports); err != nil {
frameworkReport := reporthandling.FrameworkReport{}
if err = json.Unmarshal(f, &frameworkReport); err != nil {
return frameworkReports, err
}
frameworkReports = append(frameworkReports, frameworkReport)
}
return frameworkReports, nil
}

View File

@@ -0,0 +1,29 @@
package submit
import (
"github.com/armosec/kubescape/clihandler/cliobjects"
"github.com/spf13/cobra"
)
var submitCmdExamples = `
`
func GetSubmitCmd() *cobra.Command {
var submitInfo cliobjects.Submit
submitCmd := &cobra.Command{
Use: "submit <command>",
Short: "Submit an object to the Kubescape SaaS version",
Long: ``,
Run: func(cmd *cobra.Command, args []string) {
},
}
submitCmd.PersistentFlags().StringVarP(&submitInfo.Account, "account", "", "", "Armo portal account ID. Default will load account ID from configMap or config file")
submitCmd.AddCommand(getExceptionsCmd(&submitInfo))
submitCmd.AddCommand(getResultsCmd(&submitInfo))
submitCmd.AddCommand(getRBACCmd(&submitInfo))
return submitCmd
}

View File

@@ -1,25 +0,0 @@
package cmd
import (
"fmt"
"os"
"github.com/armosec/kubescape/cautils"
"github.com/spf13/cobra"
)
var versionCmd = &cobra.Command{
Use: "version",
Short: "Get current version",
Long: ``,
RunE: func(cmd *cobra.Command, args []string) error {
v := cautils.NewIVersionCheckHandler()
v.CheckLatestVersion(cautils.NewVersionCheckRequest(cautils.BuildNumber, "", "", "version"))
fmt.Fprintln(os.Stdout, "Your current version is: "+cautils.BuildNumber)
return nil
},
}
func init() {
rootCmd.AddCommand(versionCmd)
}

View File

@@ -0,0 +1,24 @@
package version
import (
"fmt"
"os"
"github.com/armosec/kubescape/cautils"
"github.com/spf13/cobra"
)
func GetVersionCmd() *cobra.Command {
versionCmd := &cobra.Command{
Use: "version",
Short: "Get current version",
Long: ``,
RunE: func(cmd *cobra.Command, args []string) error {
v := cautils.NewIVersionCheckHandler()
v.CheckLatestVersion(cautils.NewVersionCheckRequest(cautils.BuildNumber, "", "", "version"))
fmt.Fprintln(os.Stdout, "Your current version is: "+cautils.BuildNumber)
return nil
},
}
return versionCmd
}

View File

@@ -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"
@@ -23,6 +20,7 @@ import (
"github.com/armosec/kubescape/resultshandling"
"github.com/armosec/kubescape/resultshandling/reporter"
"github.com/armosec/opa-utils/reporthandling"
"github.com/armosec/opa-utils/resources"
"github.com/mattn/go-isatty"
)
@@ -52,6 +50,13 @@ func getInterfaces(scanInfo *cautils.ScanInfo) componentInterfaces {
// Set submit behavior AFTER loading tenant config
setSubmitBehavior(scanInfo, tenantConfig)
if scanInfo.Submit {
// submit - Create tenant & Submit report
if err := tenantConfig.SetTenant(); err != nil {
logger.L().Error(err.Error())
}
}
// ================== version testing ======================================
v := cautils.NewIVersionCheckHandler()
@@ -83,11 +88,10 @@ func getInterfaces(scanInfo *cautils.ScanInfo) componentInterfaces {
// ================== setup reporter & printer objects ======================================
// reporting behavior - setup reporter
reportHandler := getReporter(tenantConfig, scanInfo.Submit)
reportHandler := getReporter(tenantConfig, scanInfo.ReportID, 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 ======================================
@@ -101,14 +105,13 @@ func getInterfaces(scanInfo *cautils.ScanInfo) componentInterfaces {
}
}
func ScanCliSetup(scanInfo *cautils.ScanInfo) error {
func Scan(scanInfo *cautils.ScanInfo) (*resultshandling.ResultsHandler, error) {
logger.L().Info("ARMO security scanner starting")
interfaces := getInterfaces(scanInfo)
// setPolicyGetter(scanInfo, interfaces.clusterConfig.GetCustomerGUID())
// ===================== Initialization =====================
scanInfo.Init() // initialize scan info
processNotification := make(chan *cautils.OPASessionObj)
reportResults := make(chan *cautils.OPASessionObj)
interfaces := getInterfaces(scanInfo)
cautils.ClusterName = interfaces.tenantConfig.GetClusterName() // TODO - Deprecated
cautils.CustomerGUID = interfaces.tenantConfig.GetAccountID() // TODO - Deprecated
@@ -127,43 +130,41 @@ func ScanCliSetup(scanInfo *cautils.ScanInfo) error {
scanInfo.SetPolicyIdentifiers(listFrameworksNames(scanInfo.Getters.PolicyGetter), reporthandling.KindFramework)
}
//
// remove host scanner components
defer func() {
if err := interfaces.hostSensorHandler.TearDown(); err != nil {
logger.L().Error("failed to tear down host sensor", helpers.Error(err))
}
}()
// cli handler setup
go func() {
// policy handler setup
policyHandler := policyhandler.NewPolicyHandler(&processNotification, interfaces.resourceHandler)
resultsHandling := resultshandling.NewResultsHandler(interfaces.report, interfaces.printerHandler)
if err := Scan(policyHandler, scanInfo); err != nil {
logger.L().Fatal(err.Error())
}
}()
// processor setup - rego run
go func() {
opaprocessorObj := opaprocessor.NewOPAProcessorHandler(&processNotification, &reportResults)
opaprocessorObj.ProcessRulesListenner()
}()
resultsHandling := resultshandling.NewResultsHandler(&reportResults, interfaces.report, interfaces.printerHandler)
score := resultsHandling.HandleResults(scanInfo)
// print report url
interfaces.report.DisplayReportURL()
if score > float32(scanInfo.FailThreshold) {
return fmt.Errorf("scan risk-score %.2f is above permitted threshold %d", score, scanInfo.FailThreshold)
// ===================== policies & resources =====================
policyHandler := policyhandler.NewPolicyHandler(interfaces.resourceHandler)
scanData, err := CollectResources(policyHandler, scanInfo)
if err != nil {
return resultsHandling, err
}
return nil
// ========================= opa testing =====================
deps := resources.NewRegoDependenciesData(k8sinterface.GetK8sConfig(), interfaces.tenantConfig.GetClusterName())
reportResults := opaprocessor.NewOPAProcessor(scanData, deps)
if err := reportResults.ProcessRulesListenner(); err != nil {
// TODO - do something
return resultsHandling, err
}
// ========================= results handling =====================
resultsHandling.SetData(scanData)
// if resultsHandling.GetRiskScore() > float32(scanInfo.FailThreshold) {
// return resultsHandling, fmt.Errorf("scan risk-score %.2f is above permitted threshold %.2f", resultsHandling.GetRiskScore(), scanInfo.FailThreshold)
// }
return resultsHandling, nil
}
func Scan(policyHandler *policyhandler.PolicyHandler, scanInfo *cautils.ScanInfo) error {
func CollectResources(policyHandler *policyhandler.PolicyHandler, scanInfo *cautils.ScanInfo) (*cautils.OPASessionObj, error) {
policyNotification := &reporthandling.PolicyNotification{
Rules: scanInfo.PolicyIdentifier,
KubescapeNotification: reporthandling.KubescapeNotification{
@@ -173,14 +174,16 @@ func Scan(policyHandler *policyhandler.PolicyHandler, scanInfo *cautils.ScanInfo
}
switch policyNotification.KubescapeNotification.NotificationType {
case reporthandling.TypeExecPostureScan:
if err := policyHandler.HandleNotificationRequest(policyNotification, scanInfo); err != nil {
return err
collectedResources, err := policyHandler.CollectResources(policyNotification, scanInfo)
if err != nil {
return nil, err
}
return collectedResources, nil
default:
return fmt.Errorf("notification type '%s' Unknown", policyNotification.KubescapeNotification.NotificationType)
return nil, fmt.Errorf("notification type '%s' Unknown", policyNotification.KubescapeNotification.NotificationType)
}
return nil
return nil, nil
}
func askUserForHostSensor() bool {

View File

@@ -12,7 +12,6 @@ import (
"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"
@@ -49,12 +48,22 @@ 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())
return reporterv2.NewReportEventReceiver(tenantConfig.GetConfigObj())
func getReporter(tenantConfig cautils.ITenantConfig, reportID string, submit, fwScan, clusterScan bool) reporter.IReport {
if submit && clusterScan {
return reporterv2.NewReportEventReceiver(tenantConfig.GetConfigObj(), reportID)
}
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 {
@@ -74,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{}
@@ -113,7 +122,7 @@ func policyIdentifierNames(pi []reporthandling.PolicyIdentifier) string {
return policiesNames
}
// setSubmitBehavior - Setup the desired cluster behavior regarding submittion to the Armo BE
// setSubmitBehavior - Setup the desired cluster behavior regarding submitting to the Armo BE
func setSubmitBehavior(scanInfo *cautils.ScanInfo, tenantConfig cautils.ITenantConfig) {
/*
@@ -139,14 +148,8 @@ func setSubmitBehavior(scanInfo *cautils.ScanInfo, tenantConfig cautils.ITenantC
// Submit report
scanInfo.Submit = true
}
} else { // config not found in cache (not submitted)
if scanInfo.Submit {
// submit - Create tenant & Submit report
if err := tenantConfig.SetTenant(); err != nil {
logger.L().Error(err.Error())
}
}
}
}
// setPolicyGetter set the policy getter - local file/github release/ArmoAPI
@@ -216,8 +219,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
}

7
core/kscore.go Normal file
View File

@@ -0,0 +1,7 @@
package core
// Scan
// List
// Download
// Config
// Submit

View File

@@ -11,7 +11,7 @@ source #287
### Relation to this proposal
There are multiple changes and design decisions needs to be made before Kubescape will support the before outlined controls. However, a focal point the whole picutre is the ability to access vulnerabilty databases of container images. We anticiapte that most container image repositories will support image vulnerabilty scanning, some major players are already do. Since there is no a single API available which all of these data sources support it is important to create an adaption layer within Kubescape so different datasources can serve Kubescape's goals.
There are multiple changes and design decisions needs to be made before Kubescape will support the before outlined controls. However, a focal point the whole picutre is the ability to access vulnerability databases of container images. We anticipate that most container image repositories will support image vulnerability scanning, some major players are already do. Since there is no a single API available which all of these data sources support it is important to create an adaption layer within Kubescape so different datasources can serve Kubescape's goals.
## High level design of Kubescape
@@ -21,7 +21,7 @@ There are multiple changes and design decisions needs to be made before Kubescap
* OPA engine: the [OPA](https://github.com/open-policy-agent/opa) rego interpreter
* Rules processor: Kubescape component, it enumerates and runs the controls while also preparing the all the input data that the controls need for running
* Data sources: set of different modules providing data to the Rules processor so it can run the controls with them. Examples: Kubernetes objects, cloud vendor API objects and adding in this proposal the vulnerability infomration
* Cloud Image Vulnerability adaption interface: the subject of this proposal, it gives a common interface for different registry/vulnerabilty vendors to adapt to.
* Cloud Image Vulnerability adaption interface: the subject of this proposal, it gives a common interface for different registry/vulnerability vendors to adapt to.
* CIV adaptors: specific implementation of the CIV interface, example Harbor adaption
```
-----------------------

View File

@@ -1,91 +0,0 @@
# Kubescape Release
## Input
### Scan a running Kubernetes cluster
* Scan your Kubernetes cluster. Ignore `kube-system` and `kube-public` namespaces
```
kubescape scan framework nsa --exclude-namespaces kube-system,kube-public
```
* Scan your Kubernetes cluster
```
kubescape scan framework nsa
```
### Scan a local Kubernetes manifest
* Scan single Kubernetes manifest file <img src="new-feature.svg">
```
kubescape scan framework nsa <my-workload.yaml>
```
* Scan many Kubernetes manifest files <img src="new-feature.svg">
```
kubescape scan framework nsa <my-workload-1.yaml> <my-workload-2.yaml>
```
* Scan all Kubernetes manifest files in directory <img src="new-feature.svg">
```
kubescape scan framework nsa *.yaml
```
* Scan Kubernetes manifest from stdout <img src="new-feature.svg">
```
cat <my-workload.yaml> | kubescape scan framework nsa -
```
* Scan Kubernetes manifest url <img src="new-feature.svg">
```
kubescape scan framework nsa https://raw.githubusercontent.com/GoogleCloudPlatform/microservices-demo/master/release/kubernetes-manifests.yaml
```
### Scan HELM chart
* Render the helm chart using [`helm template`](https://helm.sh/docs/helm/helm_template/) and pass to stdout <img src="new-feature.svg">
```
helm template [CHART] [flags] --generate-name --dry-run | kubescape scan framework nsa -
```
### Scan on-prem (offline)
* Scan using a framework from the local file system
```
kubescape scan framework --use-from <path>
```
* Scan using the framework from the default location in file system
```
kubescape scan framework --use-default
```
## Output formats
By default, the output is user friendly.
For the sake of automation, it is possible to receive the result in a `json` or `junit xml` format.
* Output in `json` format <img src="new-feature.svg">
```
kubescape scan framework nsa --format json --output results.json
```
* Output in `junit xml` format <img src="new-feature.svg">
```
kubescape scan framework nsa --format junit --output results.xml
```
## Download
* Download and save in file <img src="new-feature.svg">
```
kubescape download framework nsa --output nsa.json
```
* Download and save in default file (`~/.kubescape/<framework name>.json`)
```
kubescape download framework nsa
```

View File

@@ -1,22 +1,66 @@
# 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
* **CD 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
The items in Kubescape roadmap are split to 3 major groups based on the feature planning maturity:
* [Planning](#planning) - we have tickets open for these issues with more or less clear vision of design
* [Backlog](#backlog) - feature which were discussed at a high level but are not ready for development
* [Wishlist](#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
* Installation in the cluster for continuous monitoring
* Host scanner
* Cloud vendor API integration
* Custom exceptions

View File

@@ -1,87 +0,0 @@
<img src="kubescape.png" width="300" alt="logo" align="center">
# More detailed look on command line arguments and options
## Simple run:
```
kubescape scan framework nsa --exclude-namespaces kube-system,kube-public
```
## 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 |
| `-s`/`--silent` | Display progress messages | Silent progress messages |
| `-t`/`--fail-threshold` | `0` (do not fail) | fail command (return exit code 1) if result is below threshold| `0` -> `100` |
| `-f`/`--format` | `pretty-printer` | Output format | `pretty-printer`/`json`/`junit` |
| `-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-default` | `false` | Load local framework object from default path. If not used will download latest | `true`/`false` |
| `--exceptions` | | Path to an [exceptions obj](examples/exceptions.json). If not set will download exceptions from Armo management portal |
| `--results-locally` | `false` | Kubescape sends scan results to Armo management portal to allow users to control exceptions and maintain chronological scan results. Use this flag if you do not wish to use these features | `true`/`false`|
## Usage & Examples
### Examples
* Scan a running Kubernetes cluster with [`nsa`](https://www.nsa.gov/Press-Room/News-Highlights/Article/Article/2716980/nsa-cisa-release-kubernetes-hardening-guidance/) framework
```
kubescape scan framework nsa --exclude-namespaces kube-system,kube-public
```
* Scan local `yaml`/`json` files before deploying
```
kubescape scan framework nsa *.yaml
```
* Scan `yaml`/`json` files from url
```
kubescape scan framework nsa https://raw.githubusercontent.com/GoogleCloudPlatform/microservices-demo/master/release/kubernetes-manifests.yaml
```
* Output in `json` format
```
kubescape scan framework nsa --exclude-namespaces kube-system,kube-public --format json --output results.json
```
* Output in `junit xml` format
```
kubescape scan framework nsa --exclude-namespaces kube-system,kube-public --format junit --output results.xml
```
* Scan with exceptions, objects with exceptions will be presented as `warning` and not `fail`
```
kubescape scan framework nsa --exceptions examples/exceptions.json
```
### Helm Support
* Render the helm chart using [`helm template`](https://helm.sh/docs/helm/helm_template/) and pass to stdout
```
helm template [NAME] [CHART] [flags] --dry-run | kubescape scan framework nsa -
```
for example:
```
helm template bitnami/mysql --generate-name --dry-run | kubescape scan framework nsa -
```
### Offline Support <img src="docs/new-feature.svg">
It is possible to run Kubescape offline!
First download the framework and then scan with `--use-from` flag
* Download and save in file, if file name not specified, will store save to `~/.kubescape/<framework name>.json`
```
kubescape download framework nsa --output nsa.json
```
* Scan using the downloaded framework
```
kubescape scan framework nsa --use-from nsa.json
```
Kubescape is an open source project, we welcome your feedback and ideas for improvement. Were also aiming to collaborate with the Kubernetes community to help make the tests themselves more robust and complete as Kubernetes develops.

View File

@@ -1,5 +0,0 @@
#! /bin/bash
echo "Testing Online Boutique yamls (https://github.com/GoogleCloudPlatform/microservices-demo)"
kubescape scan framework nsa online-boutique/*

View File

@@ -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"
}
]
}

View File

@@ -24,17 +24,6 @@
"namespace": "kube-node-lease"
}
}
],
"posturePolicies": [
{
"frameworkName": "NSA"
},
{
"frameworkName": "MITRE"
},
{
"frameworkName": "ArmoBest"
}
]
}
]

28
go.mod
View File

@@ -3,8 +3,8 @@ 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/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
@@ -13,14 +13,16 @@ 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/gorilla/mux v1.8.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
golang.org/x/mod v0.4.2
gopkg.in/yaml.v2 v2.4.0
k8s.io/api v0.22.2
k8s.io/apimachinery v0.22.2
@@ -29,7 +31,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 +53,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 +70,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 +87,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 +98,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

100
go.sum
View File

@@ -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,13 +94,14 @@ 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.110 h1:qncGcbnYjiGULP3yK+4geRNNpRoWqKXQL+Xg+iXc1cM=
github.com/armosec/opa-utils v0.0.110/go.mod h1:Wc1P4gkB6UQeGW8I76zCuitGGl15Omp0bKw7N0tR9dk=
@@ -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,19 +374,27 @@ 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=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
@@ -416,6 +438,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 +455,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 +599,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 +662,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 +770,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=
@@ -772,6 +801,7 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -821,6 +851,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 +872,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 +953,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 +977,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 +1048,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 +1091,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 +1158,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 +1201,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=

View File

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

View File

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

View File

@@ -91,6 +91,12 @@ func (hsh *HostSensorHandler) sendAllPodsHTTPGETRequest(path, requestKind string
return res, nil
}
// return list of LinuxKernelVariables
func (hsh *HostSensorHandler) GetKernelVariables() ([]hostsensor.HostSensorDataEnvelope, error) {
// loop over pods and port-forward it to each of them
return hsh.sendAllPodsHTTPGETRequest("/LinuxKernelVariables", "LinuxKernelVariables")
}
// return list of OpenPortsList
func (hsh *HostSensorHandler) GetOpenPortsList() ([]hostsensor.HostSensorDataEnvelope, error) {
// loop over pods and port-forward it to each of them
@@ -195,6 +201,12 @@ func (hsh *HostSensorHandler) CollectResources() ([]hostsensor.HostSensorDataEnv
return kcData, err
}
res = append(res, kcData...)
// GetKernelVariables
kcData, err = hsh.GetKernelVariables()
if err != nil {
return kcData, err
}
res = append(res, kcData...)
// finish
logger.L().Debug("Done reading information from host sensor")

41
httphandler/README.md Normal file
View File

@@ -0,0 +1,41 @@
# Run kubescape as a microservice
> This is a beta version, we might make some changes before publishing the official Prometheus support
**Set environment `KS_MICROSERVICE=true`**
Running `kubescape` will start up a webserver on port `8080` which will serve the following paths:
* POST `/v1/scan` - Trigger a kubescape scan. The server will return an ID and will execute the scanning asynchronously
* * `synchronously`: scan synchronously (return results and not ID). Use only in small clusters are with an increased timeout
* GET `/v1/results` - Request kubescape scan results
* * query `id=<string>` -> ID returned when triggering the scan action. If empty will return latest results
* * query `remove` -> Remove results from storage after reading the results
* DELETE `/v1/results` - Delete kubescape scan results from storage If empty will delete latest results
* * query `id=<string>`: Delete ID of specific results
* * query `all`: Delete all cached results
* GET/POST `/metrics` - will trigger cluster scan. will respond with prometheus metrics once they have been scanned. This will respond 503 if the scan failed.
* `/livez` - will respond 200 is server is alive
* `/readyz` - will respond 200 if server can receive requests
## Trigger Kubescape scan
POST /v1/results
body:
```json
```
e.g.:
```bash
curl --header "Content-Type: application/json" \
--request POST \
--data '{"account":"42ec914f-74e6-4bcb-8e69-5edd819d9b15","hostSensor":true}' \
http://127.0.0.1:5000/v1/scan
```
## Installation into kubernetes
The [yaml](ks-prometheus-support.yaml) file will deploy one instance of kubescape (with all relevant dependencies) to run on your cluster
**NOTE** Make sure the configurations suit your cluster (e.g. `serviceType`, namespace, etc.)

View File

@@ -0,0 +1,18 @@
package v1
type PostScanRequest struct {
Format string `json:"format"` // Format results (table, json, junit ...) - default json
ExcludedNamespaces []string `json:"excludedNamespaces"` // used for host sensor namespace
IncludeNamespaces []string `json:"includeNamespaces"` // DEPRECATED?
FailThreshold float32 `json:"failThreshold"` // Failure score threshold
Submit bool `json:"submit"` // Submit results to Armo BE - default will
HostSensor bool `json:"hostSensor"` // Deploy ARMO K8s host sensor to collect data from certain controls
KeepLocal bool `json:"keepLocal"` // Do not submit results
Account string `json:"account"` // account ID
Logger string `json:"-"` // logger level - debug/info/error - default is debug
TargetType string `json:"-"` // framework/control - default is framework
TargetNames []string `json:"-"` // default is all
// UseExceptions string // Load file with exceptions configuration
// ControlsInputs string // Load file with inputs for controls
// VerboseMode bool // Display all of the input resources and not only failed resources
}

View File

@@ -0,0 +1,30 @@
package v1
import (
"strings"
"github.com/armosec/kubescape/cautils"
)
func (scanRequest *PostScanRequest) ToScanInfo() *cautils.ScanInfo {
scanInfo := cautils.ScanInfo{}
scanInfo.Account = scanRequest.Account
scanInfo.ExcludedNamespaces = strings.Join(scanRequest.ExcludedNamespaces, ",")
scanInfo.IncludeNamespaces = strings.Join(scanRequest.IncludeNamespaces, ",")
scanInfo.FailThreshold = scanRequest.FailThreshold // TODO - handle default
scanInfo.Format = scanRequest.Format // TODO - handle default
scanInfo.Local = scanRequest.KeepLocal
scanInfo.Submit = scanRequest.Submit
scanInfo.HostSensorEnabled.SetBool(scanRequest.HostSensor)
return &scanInfo
}
/*
err := clihandler.ScanCliSetup(&scanInfo)
if err != nil {
logger.L().Fatal(err.Error())
}
*/

View File

@@ -0,0 +1,58 @@
package v1
import (
"fmt"
"net/http"
"os"
"github.com/armosec/kubescape/cautils"
"github.com/armosec/kubescape/cautils/logger"
"github.com/armosec/kubescape/cautils/logger/helpers"
"github.com/armosec/kubescape/clihandler"
"github.com/google/uuid"
)
// Metrics http listener for prometheus support
func (handler *HTTPHandler) Metrics(w http.ResponseWriter, r *http.Request) {
if handler.state.isBusy() { // if already scanning the cluster
w.Write([]byte(fmt.Sprintf("scan '%s' in action", handler.state.getID())))
w.WriteHeader(http.StatusServiceUnavailable)
return
}
handler.state.setBusy()
defer handler.state.setNotBusy()
handler.state.setID(uuid.NewString())
// trigger scanning
logger.L().Info(handler.state.getID(), helpers.String("action", "triggering scan"), helpers.Time())
results, err := clihandler.Scan(getPrometheusDefaultScanCommand(handler.state.getID()))
logger.L().Info(handler.state.getID(), helpers.String("action", "done scanning"), helpers.Time())
if err != nil {
w.Write([]byte(fmt.Sprintf("failed to complete scan. reason: %s", err.Error())))
w.WriteHeader(http.StatusInternalServerError)
return
}
res, err := results.ToJson()
if err != nil {
w.Write([]byte(fmt.Sprintf("failed to convert scan scan results to json. reason: %s", err.Error())))
w.WriteHeader(http.StatusInternalServerError)
return
}
w.Write(res)
w.WriteHeader(http.StatusOK)
}
func getPrometheusDefaultScanCommand(scanID string) *cautils.ScanInfo {
scanInfo := cautils.ScanInfo{}
scanInfo.FrameworkScan = true
scanInfo.ScanAll = true // scan all frameworks
scanInfo.ReportID = scanID // scan ID
scanInfo.HostSensorEnabled.Set(os.Getenv("KS_ENABLE_HOST_SENSOR")) // enable host scanner
scanInfo.FailThreshold = 100 // Do not fail scanning
// scanInfo.Format = "prometheus" // results format
return &scanInfo
}

View File

@@ -0,0 +1,178 @@
package v1
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"sync"
"github.com/armosec/kubescape/cautils/logger"
"github.com/armosec/kubescape/cautils/logger/helpers"
"github.com/google/uuid"
)
var OutputDir = "/results"
var FailedOutputDir = "/failed"
type HTTPHandler struct {
state *serverState
}
func NewHTTPHandler() *HTTPHandler {
return &HTTPHandler{
state: newServerState(),
}
}
func (handler *HTTPHandler) Scan(w http.ResponseWriter, r *http.Request) {
defer func() {
if err := recover(); err != nil {
handler.state.setNotBusy()
logger.L().Error("Scan recover", helpers.Error(fmt.Errorf("%v", err)))
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte(fmt.Sprintf("%v", err)))
}
}()
defer r.Body.Close()
switch r.Method {
case http.MethodGet: // return request template
json.NewEncoder(w).Encode(PostScanRequest{})
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
return
case http.MethodPost:
default:
w.WriteHeader(http.StatusMethodNotAllowed)
return
}
if handler.state.isBusy() {
w.Write([]byte(handler.state.getID()))
w.WriteHeader(http.StatusOK)
return
}
handler.state.setBusy()
// generate id
scanID := uuid.NewString()
handler.state.setID(scanID)
readBuffer, err := ioutil.ReadAll(r.Body)
if err != nil {
defer handler.state.setNotBusy()
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte(fmt.Sprintf("failed to read request body, reason: %s", err.Error())))
return
}
scanRequest := PostScanRequest{}
if err := json.Unmarshal(readBuffer, &scanRequest); err != nil {
defer handler.state.setNotBusy()
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte(fmt.Sprintf("failed to parse request payload, reason: %s", err.Error())))
return
}
response := []byte(scanID)
returnResults := r.URL.Query().Has("wait")
var wg sync.WaitGroup
if returnResults {
wg.Add(1)
}
go func() {
// execute scan in the background
logger.L().Info("scan triggered", helpers.String("ID", scanID))
results, err := scan(&scanRequest, scanID)
if err != nil {
logger.L().Error("scanning failed", helpers.String("ID", scanID), helpers.Error(err))
if returnResults {
response = []byte(err.Error())
}
} else {
if returnResults {
w.Header().Set("Content-Type", "application/json")
response = results
}
logger.L().Success("done scanning", helpers.String("ID", scanID))
}
// // saveing the scan status/result in 'response' object only when waiting for results
// if returnResults {
// w.Header().Set("Content-Type", "application/json")
// response = results
// }
wg.Done()
handler.state.setNotBusy()
}()
wg.Wait()
w.Write(response)
w.WriteHeader(http.StatusOK)
}
func (handler *HTTPHandler) Results(w http.ResponseWriter, r *http.Request) {
defer func() {
if err := recover(); err != nil {
handler.state.setNotBusy()
logger.L().Error("Results recover", helpers.Error(fmt.Errorf("%v", err)))
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte(fmt.Sprintf("%v", err)))
}
}()
defer r.Body.Close()
var scanID string
if scanID = r.URL.Query().Get("scanID"); scanID == "" {
scanID = handler.state.getLatestID()
}
logger.L().Info("requesting results", helpers.String("ID", scanID))
if handler.state.isBusy() { // if requested ID is still scanning
if scanID == handler.state.getID() {
logger.L().Info("scan in process", helpers.String("ID", scanID))
w.Write([]byte(handler.state.getID()))
w.WriteHeader(http.StatusOK) // Should we return ok?
return
}
}
switch r.Method {
case http.MethodGet:
if r.URL.Query().Has("remove") {
defer removeResultsFile(scanID)
}
if res, err := readResultsFile(scanID); err != nil {
w.Write([]byte(err.Error()))
w.WriteHeader(http.StatusNoContent)
} else {
w.Write(res)
w.WriteHeader(http.StatusOK)
}
case http.MethodDelete:
if r.URL.Query().Has("all") {
removeResultDirs()
} else {
removeResultsFile(scanID)
}
w.WriteHeader(http.StatusOK)
default:
w.WriteHeader(http.StatusMethodNotAllowed)
}
}
func (handler *HTTPHandler) Live(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
}
func (handler *HTTPHandler) Ready(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
}

View File

@@ -0,0 +1,90 @@
package v1
import (
"fmt"
"os"
"path/filepath"
"github.com/armosec/kubescape/cautils"
"github.com/armosec/kubescape/clihandler"
)
func scan(scanRequest *PostScanRequest, scanID string) ([]byte, error) {
scanInfo := getScanCommand(scanRequest, scanID)
result, err := clihandler.Scan(scanInfo)
if err != nil {
f, e := os.Open(filepath.Join(FailedOutputDir, scanID))
if e != nil {
return []byte{}, fmt.Errorf("failed to scan. reason: '%s'. failed to save error in file. reason: %s", err.Error(), e.Error())
}
defer f.Close()
f.Write([]byte(e.Error()))
}
result.HandleResults()
b, err := result.ToJson()
if err != nil {
err = fmt.Errorf("failed to parse results to json, reason: %s", err.Error())
}
return b, err
}
func readResultsFile(fileID string) ([]byte, error) {
if fileName := searchFile(fileID); fileName != "" {
return os.ReadFile(fileName)
}
return nil, fmt.Errorf("file not found")
}
func removeResultDirs() {
os.ReadDir(OutputDir)
os.ReadDir(FailedOutputDir)
}
func removeResultsFile(fileID string) error {
if fileName := searchFile(fileID); fileName != "" {
return os.Remove(fileName)
}
return nil // no files found to delete
}
func searchFile(fileID string) string {
if fileName, _ := findFile(OutputDir, fileID); fileName != "" {
return fileName
}
if fileName, _ := findFile(FailedOutputDir, fileID); fileName != "" {
return fileName
}
return ""
}
func findFile(targetDir string, fileName string) (string, error) {
matches, err := filepath.Glob(targetDir + fileName)
if err != nil {
return "", err
}
if len(matches) != 0 {
return matches[0], nil
}
return "", nil
}
func getScanCommand(scanRequest *PostScanRequest, scanID string) *cautils.ScanInfo {
scanInfo := scanRequest.ToScanInfo()
scanInfo.ReportID = scanID
// *** start ***
// TODO - support frameworks/controls and support scanning single frameworks/controls
scanInfo.FrameworkScan = true
scanInfo.ScanAll = true
// *** end ***
// *** start ***
// DO NOT CHANGE
scanInfo.Output = filepath.Join(OutputDir, scanID)
// *** end ***
return scanInfo
}

View File

@@ -0,0 +1,59 @@
package v1
import "sync"
type serverState struct {
// response string
busy bool
id string
latestID string
mtx sync.RWMutex
}
func (s *serverState) isBusy() bool {
s.mtx.RLock()
busy := s.busy
s.mtx.RUnlock()
return busy
}
func (s *serverState) setBusy() {
s.mtx.Lock()
s.busy = true
s.mtx.Unlock()
}
func (s *serverState) setNotBusy() {
s.mtx.Lock()
s.busy = false
s.latestID = s.id
s.id = ""
s.mtx.Unlock()
}
func (s *serverState) getID() string {
s.mtx.RLock()
id := s.id
s.mtx.RUnlock()
return id
}
func (s *serverState) setID(id string) {
s.mtx.Lock()
s.id = id
s.mtx.Unlock()
}
func (s *serverState) getLatestID() string {
s.mtx.RLock()
id := s.latestID
s.mtx.RUnlock()
return id
}
func newServerState() *serverState {
return &serverState{
busy: false,
mtx: sync.RWMutex{},
}
}

View File

@@ -0,0 +1,107 @@
---
apiVersion: v1
kind: Namespace
metadata:
labels:
app: kubescape
name: ks-scanner
---
# ------------------- Kubescape Service Account ------------------- #
apiVersion: v1
kind: ServiceAccount
metadata:
labels:
app: kubescape
name: kubescape-discovery
namespace: ks-scanner
---
# ------------------- Kubescape Cluster Role & Cluster Role Binding ------------------- #
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: kubescape-discovery-clusterroles
# "namespace" omitted since ClusterRoles are not namespaced
rules:
- apiGroups: ["*"]
resources: ["*"]
verbs: ["get", "list", "describe"]
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: kubescape-discovery-role-binding
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: kubescape-discovery-clusterroles
subjects:
- kind: ServiceAccount
name: kubescape-discovery
namespace: ks-scanner
---
apiVersion: v1
kind: Service
metadata:
name: kubescape-service
namespace: ks-scanner
labels:
app: kubescape-service
spec:
type: NodePort
ports:
- port: 8080
targetPort: 8080
protocol: TCP
selector:
app: kubescape
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: kubescape
namespace: ks-scanner
labels:
app: kubescape
spec:
replicas: 1
selector:
matchLabels:
app: kubescape
template:
metadata:
labels:
app: kubescape
spec:
serviceAccountName: kubescape-discovery
containers:
- name: kubescape
# livenessProbe:
# httpGet:
# path: /livez
# port: 8080
# initialDelaySeconds: 3
# periodSeconds: 3
# readinessProbe:
# httpGet:
# path: /readyz
# port: 8080
# initialDelaySeconds: 3
# periodSeconds: 3
image: quay.io/armosec/kubescape:prometheus.v1
env:
- name: KS_RUN_PROMETHEUS_SERVER
value: "true"
- name: KS_DEFAULT_CONFIGMAP_NAMESPACE
value: "ks-scanner"
ports:
- containerPort: 8080
command:
- kubescape
resources:
requests:
cpu: 10m
memory: 100Mi
limits:
cpu: 500m
memory: 500Mi

View File

@@ -0,0 +1,20 @@
package listener
import (
"encoding/json"
"fmt"
"net/http"
"github.com/armosec/kubescape/cautils/logger"
"github.com/armosec/kubescape/cautils/logger/helpers"
)
// RecoverFunc recover function for http requests
func RecoverFunc(w http.ResponseWriter) {
if err := recover(); err != nil {
logger.L().Error("", helpers.Error(fmt.Errorf("%v", err)))
w.WriteHeader(http.StatusInternalServerError)
bErr, _ := json.Marshal(err)
w.Write(bErr)
}
}

View File

@@ -0,0 +1,78 @@
package listener
import (
"crypto/tls"
"fmt"
"net/http"
"os"
"github.com/armosec/kubescape/cautils/logger"
"github.com/armosec/kubescape/cautils/logger/helpers"
handlerequestsv1 "github.com/armosec/kubescape/httphandler/handlerequests/v1"
"github.com/gorilla/mux"
)
const (
scanPath = "/v1/scan"
resultsPath = "/v1/results"
prometheusMmeticsPath = "/metrics"
livePath = "/livez"
readyPath = "/readyz"
)
// SetupHTTPListener set up listening http servers
func SetupHTTPListener() error {
keyPair, err := loadTLSKey("", "") // TODO - support key and crt files
if err != nil {
return err
}
server := &http.Server{
Addr: fmt.Sprintf(":%s", getPort()), // TODO - support loading port from config/env
}
if keyPair != nil {
server.TLSConfig = &tls.Config{Certificates: []tls.Certificate{*keyPair}}
}
rtr := mux.NewRouter()
// rtr.HandleFunc(opapolicy.PostureRestAPIPathV1, resthandler.RestAPIReceiveNotification)
// listen
httpHandler := handlerequestsv1.NewHTTPHandler()
rtr.HandleFunc(prometheusMmeticsPath, httpHandler.Metrics)
rtr.HandleFunc(scanPath, httpHandler.Scan)
rtr.HandleFunc(resultsPath, httpHandler.Results)
rtr.HandleFunc(livePath, httpHandler.Live)
rtr.HandleFunc(readyPath, httpHandler.Ready)
server.Handler = rtr
logger.L().Info("Started Kubescape server", helpers.String("port", getPort()))
server.ListenAndServe()
if keyPair != nil {
logger.L().Fatal(server.ListenAndServeTLS("", "").Error())
}
logger.L().Fatal(server.ListenAndServe().Error())
return nil
}
func loadTLSKey(certFile, keyFile string) (*tls.Certificate, error) {
if keyFile == "" || certFile == "" {
return nil, nil
}
pair, err := tls.LoadX509KeyPair(certFile, keyFile)
if err != nil {
return nil, fmt.Errorf("filed to load key pair: %v", err)
}
return &pair, nil
}
func getPort() string {
if p := os.Getenv("KS_PORT"); p != "" {
return p
}
return "8080"
}

View File

@@ -2,8 +2,12 @@ package main
import (
"github.com/armosec/kubescape/clihandler/cmd"
// "github.com/armosec/kubescape/httphandler/listener"
)
func main() {
cmd.Execute()
// // cmd.Execute()
// listener.SetupHTTPListener()
}

View File

@@ -8,7 +8,7 @@ import (
"github.com/armosec/opa-utils/reporthandling"
)
var mockControl_0006 = `{"guid":"","name":"Allowed hostPath","attributes":{"armoBuiltin":true},"id":"C-0006","controlID":"C-0006","creationTime":"","description":"Mounting host directory to the container can be abused to get access to sensitive data and gain persistence on the host machine.","remediation":"Refrain from using host path mount.","rules":[{"guid":"","name":"alert-rw-hostpath","attributes":{"armoBuiltin":true,"m$K8sThreatMatrix":"Persistance::Writable hostPath mount, Lateral Movement::Writable volume mounts on the host"},"creationTime":"","rule":"package armo_builtins\n\n# input: pod\n# apiversion: v1\n# does: returns hostPath volumes\n\ndeny[msga] {\n pod := input[_]\n pod.kind == \"Pod\"\n volumes := pod.spec.volumes\n volume := volumes[_]\n volume.hostPath\n\tcontainer := pod.spec.containers[i]\n\tvolumeMount := container.volumeMounts[k]\n\tvolumeMount.name == volume.name\n\tbegginingOfPath := \"spec.\"\n\tresult := isRWMount(volumeMount, begginingOfPath, i, k)\n\n podname := pod.metadata.name\n\n\tmsga := {\n\t\t\"alertMessage\": sprintf(\"pod: %v has: %v as hostPath volume\", [podname, volume.name]),\n\t\t\"packagename\": \"armo_builtins\",\n\t\t\"alertScore\": 7,\n\t\t\"failedPaths\": [result],\n\t\t\"alertObject\": {\n\t\t\t\"k8sApiObjects\": [pod]\n\t\t}\n\t}\n}\n\n#handles majority of workload resources\ndeny[msga] {\n\twl := input[_]\n\tspec_template_spec_patterns := {\"Deployment\",\"ReplicaSet\",\"DaemonSet\",\"StatefulSet\",\"Job\"}\n\tspec_template_spec_patterns[wl.kind]\n volumes := wl.spec.template.spec.volumes\n volume := volumes[_]\n volume.hostPath\n\tcontainer := wl.spec.template.spec.containers[i]\n\tvolumeMount := container.volumeMounts[k]\n\tvolumeMount.name == volume.name\n\tbegginingOfPath := \"spec.template.spec.\"\n\tresult := isRWMount(volumeMount, begginingOfPath, i, k)\n\n\tmsga := {\n\t\t\"alertMessage\": sprintf(\"%v: %v has: %v as hostPath volume\", [wl.kind, wl.metadata.name, volume.name]),\n\t\t\"packagename\": \"armo_builtins\",\n\t\t\"alertScore\": 7,\n\t\t\"failedPaths\": [result],\n\t\t\"alertObject\": {\n\t\t\t\"k8sApiObjects\": [wl]\n\t\t}\n\t\n\t}\n}\n\n#handles CronJobs\ndeny[msga] {\n\twl := input[_]\n\twl.kind == \"CronJob\"\n volumes := wl.spec.jobTemplate.spec.template.spec.volumes\n volume := volumes[_]\n volume.hostPath\n\n\tcontainer = wl.spec.jobTemplate.spec.template.spec.containers[i]\n\tvolumeMount := container.volumeMounts[k]\n\tvolumeMount.name == volume.name\n\tbegginingOfPath := \"spec.jobTemplate.spec.template.spec.\"\n\tresult := isRWMount(volumeMount, begginingOfPath, i, k)\n\n\tmsga := {\n\t\"alertMessage\": sprintf(\"%v: %v has: %v as hostPath volume\", [wl.kind, wl.metadata.name, volume.name]),\n\t\"packagename\": \"armo_builtins\",\n\t\"alertScore\": 7,\n\t\"failedPaths\": [result],\n\t\"alertObject\": {\n\t\t\t\"k8sApiObjects\": [wl]\n\t\t}\n\t}\n}\n\nisRWMount(mount, begginingOfPath, i, k) = path {\n not mount.readOnly == true\n not mount.readOnly == false\n path = \"\"\n}\nisRWMount(mount, begginingOfPath, i, k) = path {\n mount.readOnly == false\n path = sprintf(\"%vcontainers[%v].volumeMounts[%v].readOnly\", [begginingOfPath, format_int(i, 10), format_int(k, 10)])\n} ","resourceEnumerator":"","ruleLanguage":"Rego","match":[{"apiGroups":["*"],"apiVersions":["*"],"resources":["Deployment","ReplicaSet","DaemonSet","StatefulSet","Job","CronJob","Pod"]}],"ruleDependencies":[{"packageName":"cautils"},{"packageName":"kubernetes.api.client"}],"configInputs":null,"controlConfigInputs":null,"description":"determines if any workload contains a hostPath volume with rw permissions","remediation":"Set the readOnly field of the mount to true","ruleQuery":""}],"rulesIDs":[""],"baseScore":6}`
var mockControl_0006 = `{"guid":"","name":"Allowed hostPath","attributes":{"armoBuiltin":true},"id":"C-0006","controlID":"C-0006","creationTime":"","description":"Mounting host directory to the container can be abused to get access to sensitive data and gain persistence on the host machine.","remediation":"Refrain from using host path mount.","rules":[{"guid":"","name":"alert-rw-hostpath","attributes":{"armoBuiltin":true,"m$K8sThreatMatrix":"Persistence::Writable hostPath mount, Lateral Movement::Writable volume mounts on the host"},"creationTime":"","rule":"package armo_builtins\n\n# input: pod\n# apiversion: v1\n# does: returns hostPath volumes\n\ndeny[msga] {\n pod := input[_]\n pod.kind == \"Pod\"\n volumes := pod.spec.volumes\n volume := volumes[_]\n volume.hostPath\n\tcontainer := pod.spec.containers[i]\n\tvolumeMount := container.volumeMounts[k]\n\tvolumeMount.name == volume.name\n\tbegginingOfPath := \"spec.\"\n\tresult := isRWMount(volumeMount, begginingOfPath, i, k)\n\n podname := pod.metadata.name\n\n\tmsga := {\n\t\t\"alertMessage\": sprintf(\"pod: %v has: %v as hostPath volume\", [podname, volume.name]),\n\t\t\"packagename\": \"armo_builtins\",\n\t\t\"alertScore\": 7,\n\t\t\"failedPaths\": [result],\n\t\t\"alertObject\": {\n\t\t\t\"k8sApiObjects\": [pod]\n\t\t}\n\t}\n}\n\n#handles majority of workload resources\ndeny[msga] {\n\twl := input[_]\n\tspec_template_spec_patterns := {\"Deployment\",\"ReplicaSet\",\"DaemonSet\",\"StatefulSet\",\"Job\"}\n\tspec_template_spec_patterns[wl.kind]\n volumes := wl.spec.template.spec.volumes\n volume := volumes[_]\n volume.hostPath\n\tcontainer := wl.spec.template.spec.containers[i]\n\tvolumeMount := container.volumeMounts[k]\n\tvolumeMount.name == volume.name\n\tbegginingOfPath := \"spec.template.spec.\"\n\tresult := isRWMount(volumeMount, begginingOfPath, i, k)\n\n\tmsga := {\n\t\t\"alertMessage\": sprintf(\"%v: %v has: %v as hostPath volume\", [wl.kind, wl.metadata.name, volume.name]),\n\t\t\"packagename\": \"armo_builtins\",\n\t\t\"alertScore\": 7,\n\t\t\"failedPaths\": [result],\n\t\t\"alertObject\": {\n\t\t\t\"k8sApiObjects\": [wl]\n\t\t}\n\t\n\t}\n}\n\n#handles CronJobs\ndeny[msga] {\n\twl := input[_]\n\twl.kind == \"CronJob\"\n volumes := wl.spec.jobTemplate.spec.template.spec.volumes\n volume := volumes[_]\n volume.hostPath\n\n\tcontainer = wl.spec.jobTemplate.spec.template.spec.containers[i]\n\tvolumeMount := container.volumeMounts[k]\n\tvolumeMount.name == volume.name\n\tbegginingOfPath := \"spec.jobTemplate.spec.template.spec.\"\n\tresult := isRWMount(volumeMount, begginingOfPath, i, k)\n\n\tmsga := {\n\t\"alertMessage\": sprintf(\"%v: %v has: %v as hostPath volume\", [wl.kind, wl.metadata.name, volume.name]),\n\t\"packagename\": \"armo_builtins\",\n\t\"alertScore\": 7,\n\t\"failedPaths\": [result],\n\t\"alertObject\": {\n\t\t\t\"k8sApiObjects\": [wl]\n\t\t}\n\t}\n}\n\nisRWMount(mount, begginingOfPath, i, k) = path {\n not mount.readOnly == true\n not mount.readOnly == false\n path = \"\"\n}\nisRWMount(mount, begginingOfPath, i, k) = path {\n mount.readOnly == false\n path = sprintf(\"%vcontainers[%v].volumeMounts[%v].readOnly\", [begginingOfPath, format_int(i, 10), format_int(k, 10)])\n} ","resourceEnumerator":"","ruleLanguage":"Rego","match":[{"apiGroups":["*"],"apiVersions":["*"],"resources":["Deployment","ReplicaSet","DaemonSet","StatefulSet","Job","CronJob","Pod"]}],"ruleDependencies":[{"packageName":"cautils"},{"packageName":"kubernetes.api.client"}],"configInputs":null,"controlConfigInputs":null,"description":"determines if any workload contains a hostPath volume with rw permissions","remediation":"Set the readOnly field of the mount to true","ruleQuery":""}],"rulesIDs":[""],"baseScore":6}`
var mockControl_0044 = `{"guid":"","name":"Container hostPort","attributes":{"armoBuiltin":true},"id":"C-0044","controlID":"C-0044","creationTime":"","description":"Configuring hostPort limits you to a particular port, and if any two workloads that specify the same HostPort they cannot be deployed to the same node. Therefore, if the number of replica of such workload is higher than the number of nodes, the deployment will fail.","remediation":"Avoid usage of hostPort unless it is absolutely necessary. Use NodePort / ClusterIP instead.","rules":[{"guid":"","name":"container-hostPort","attributes":{"armoBuiltin":true},"creationTime":"","rule":"package armo_builtins\n\n\n# Fails if pod has container with hostPort\ndeny[msga] {\n pod := input[_]\n pod.kind == \"Pod\"\n container := pod.spec.containers[i]\n\tbegginingOfPath := \"spec.\"\n\tpath := isHostPort(container, i, begginingOfPath)\n\tmsga := {\n\t\t\"alertMessage\": sprintf(\"Container: %v has Host-port\", [ container.name]),\n\t\t\"packagename\": \"armo_builtins\",\n\t\t\"alertScore\": 7,\n\t\t\"failedPaths\": path,\n\t\t\"alertObject\": {\n\t\t\t\"k8sApiObjects\": [pod]\n\t\t}\n\t}\n}\n\n# Fails if workload has container with hostPort\ndeny[msga] {\n wl := input[_]\n\tspec_template_spec_patterns := {\"Deployment\",\"ReplicaSet\",\"DaemonSet\",\"StatefulSet\",\"Job\"}\n\tspec_template_spec_patterns[wl.kind]\n container := wl.spec.template.spec.containers[i]\n\tbegginingOfPath := \"spec.template.spec.\"\n path := isHostPort(container, i, begginingOfPath)\n\tmsga := {\n\t\t\"alertMessage\": sprintf(\"Container: %v in %v: %v has Host-port\", [ container.name, wl.kind, wl.metadata.name]),\n\t\t\"packagename\": \"armo_builtins\",\n\t\t\"alertScore\": 7,\n\t\t\"failedPaths\": path,\n\t\t\"alertObject\": {\n\t\t\t\"k8sApiObjects\": [wl]\n\t\t}\n\t}\n}\n\n# Fails if cronjob has container with hostPort\ndeny[msga] {\n \twl := input[_]\n\twl.kind == \"CronJob\"\n\tcontainer = wl.spec.jobTemplate.spec.template.spec.containers[i]\n\tbegginingOfPath := \"spec.jobTemplate.spec.template.spec.\"\n path := isHostPort(container, i, begginingOfPath)\n msga := {\n\t\t\"alertMessage\": sprintf(\"Container: %v in %v: %v has Host-port\", [ container.name, wl.kind, wl.metadata.name]),\n\t\t\"packagename\": \"armo_builtins\",\n\t\t\"alertScore\": 7,\n\t\t\"failedPaths\": path,\n\t\t\"alertObject\": {\n\t\t\t\"k8sApiObjects\": [wl]\n\t\t}\n\t}\n}\n\n\n\nisHostPort(container, i, begginingOfPath) = path {\n\tpath = [sprintf(\"%vcontainers[%v].ports[%v].hostPort\", [begginingOfPath, format_int(i, 10), format_int(j, 10)]) | port = container.ports[j]; port.hostPort]\n\tcount(path) > 0\n}\n","resourceEnumerator":"","ruleLanguage":"Rego","match":[{"apiGroups":["*"],"apiVersions":["*"],"resources":["Deployment","ReplicaSet","DaemonSet","StatefulSet","Job","Pod","CronJob"]}],"ruleDependencies":[],"configInputs":null,"controlConfigInputs":null,"description":"fails if container has hostPort","remediation":"Make sure you do not configure hostPort for the container, if necessary use NodePort / ClusterIP","ruleQuery":"armo_builtins"}],"rulesIDs":[""],"baseScore":4}`

View File

@@ -15,7 +15,6 @@ import (
"github.com/armosec/opa-utils/reporthandling/results/v1/resourcesresults"
"github.com/open-policy-agent/opa/storage"
"github.com/armosec/k8s-interface/k8sinterface"
"github.com/armosec/k8s-interface/workloadinterface"
"github.com/armosec/opa-utils/resources"
@@ -25,59 +24,37 @@ import (
const ScoreConfigPath = "/resources/config"
type OPAProcessorHandler struct {
processedPolicy *chan *cautils.OPASessionObj
reportResults *chan *cautils.OPASessionObj
regoDependenciesData *resources.RegoDependenciesData
}
type OPAProcessor struct {
*cautils.OPASessionObj
regoDependenciesData *resources.RegoDependenciesData
*cautils.OPASessionObj
}
func NewOPAProcessor(sessionObj *cautils.OPASessionObj, regoDependenciesData *resources.RegoDependenciesData) *OPAProcessor {
if regoDependenciesData != nil && sessionObj != nil {
regoDependenciesData.PostureControlInputs = sessionObj.RegoInputData.PostureControlInputs
}
return &OPAProcessor{
OPASessionObj: sessionObj,
regoDependenciesData: regoDependenciesData,
}
}
func (opap *OPAProcessor) ProcessRulesListenner() error {
func NewOPAProcessorHandler(processedPolicy, reportResults *chan *cautils.OPASessionObj) *OPAProcessorHandler {
return &OPAProcessorHandler{
processedPolicy: processedPolicy,
reportResults: reportResults,
regoDependenciesData: resources.NewRegoDependenciesData(k8sinterface.GetK8sConfig(), cautils.ClusterName),
policies := ConvertFrameworksToPolicies(opap.Policies, cautils.BuildNumber)
ConvertFrameworksToSummaryDetails(&opap.Report.SummaryDetails, opap.Policies, policies)
// process
if err := opap.Process(policies); err != nil {
logger.L().Error(err.Error())
// Return error?
}
}
func (opaHandler *OPAProcessorHandler) ProcessRulesListenner() {
// edit results
opap.updateResults()
for {
opaSessionObj := <-*opaHandler.processedPolicy
opap := NewOPAProcessor(opaSessionObj, opaHandler.regoDependenciesData)
//TODO: review this location
scorewrapper := ksscore.NewScoreWrapper(opap.OPASessionObj)
scorewrapper.Calculate(ksscore.EPostureReportV2)
policies := ConvertFrameworksToPolicies(opap.Frameworks, cautils.BuildNumber)
ConvertFrameworksToSummaryDetails(&opap.Report.SummaryDetails, opap.Frameworks, policies)
// process
if err := opap.Process(policies); err != nil {
logger.L().Error(err.Error())
}
// edit results
opap.updateResults()
//TODO: review this location
scorewrapper := ksscore.NewScoreWrapper(opaSessionObj)
scorewrapper.Calculate(ksscore.EPostureReportV2)
// report
*opaHandler.reportResults <- opaSessionObj
}
return nil
}
func (opap *OPAProcessor) Process(policies *cautils.Policies) error {

View File

@@ -32,13 +32,13 @@ func TestProcess(t *testing.T) {
// set opaSessionObj
opaSessionObj := cautils.NewOPASessionObjMock()
opaSessionObj.Frameworks = []reporthandling.Framework{*reporthandling.MockFrameworkA()}
policies := ConvertFrameworksToPolicies(opaSessionObj.Frameworks, "")
opaSessionObj.Policies = []reporthandling.Framework{*reporthandling.MockFrameworkA()}
policies := ConvertFrameworksToPolicies(opaSessionObj.Policies, "")
opaSessionObj.K8SResources = &k8sResources
opaSessionObj.AllResources = allResources
opap := NewOPAProcessor(opaSessionObj, resources.NewRegoDependenciesDataMock())
opap := NewOPAProcessor(opaSessionObj, resources.NewRegoDependenciesDataMock()) // ,
opap.Process(policies)
opap.updateResults()
for _, f := range opap.PostureReport.FrameworkReports {
@@ -68,10 +68,10 @@ func TestProcessResourcesResult(t *testing.T) {
// set opaSessionObj
opaSessionObj := cautils.NewOPASessionObjMock()
opaSessionObj.Frameworks = frameworks
opaSessionObj.Policies = frameworks
policies := ConvertFrameworksToPolicies(opaSessionObj.Frameworks, "")
ConvertFrameworksToSummaryDetails(&opaSessionObj.Report.SummaryDetails, opaSessionObj.Frameworks, policies)
policies := ConvertFrameworksToPolicies(opaSessionObj.Policies, "")
ConvertFrameworksToSummaryDetails(&opaSessionObj.Report.SummaryDetails, opaSessionObj.Policies, policies)
opaSessionObj.K8SResources = &k8sResources
opaSessionObj.AllResources[deployment.GetID()] = deployment

View File

@@ -137,29 +137,6 @@ func ruleWithArmoOpaDependency(annotations map[string]interface{}) bool {
return false
}
// Checks that kubescape version is in range of use for this rule
// In local build (BuildNumber = ""):
// returns true only if rule doesn't have the "until" attribute
func isRuleKubescapeVersionCompatible(rule *reporthandling.PolicyRule) bool {
if from, ok := rule.Attributes["useFromKubescapeVersion"]; ok {
if cautils.BuildNumber != "" {
if from.(string) > cautils.BuildNumber {
return false
}
}
}
if until, ok := rule.Attributes["useUntilKubescapeVersion"]; ok {
if cautils.BuildNumber != "" {
if until.(string) <= cautils.BuildNumber {
return false
}
} else {
return false
}
}
return true
}
func removeData(obj workloadinterface.IMetadata) {
if !k8sinterface.IsTypeWorkload(obj.GetObject()) {
return // remove data only from kubernetes objects

View File

@@ -5,57 +5,9 @@ import (
"github.com/stretchr/testify/assert"
"github.com/armosec/armoapi-go/armotypes"
"github.com/armosec/k8s-interface/workloadinterface"
"github.com/armosec/kubescape/cautils"
"github.com/armosec/opa-utils/reporthandling"
)
func TestGetKubernetesObjects(t *testing.T) {
}
var rule_v1_0_131 = &reporthandling.PolicyRule{PortalBase: armotypes.PortalBase{
Attributes: map[string]interface{}{"useUntilKubescapeVersion": "v1.0.132"}}}
var rule_v1_0_132 = &reporthandling.PolicyRule{PortalBase: armotypes.PortalBase{
Attributes: map[string]interface{}{"useFromKubescapeVersion": "v1.0.132", "useUntilKubescapeVersion": "v1.0.133"}}}
var rule_v1_0_133 = &reporthandling.PolicyRule{PortalBase: armotypes.PortalBase{
Attributes: map[string]interface{}{"useFromKubescapeVersion": "v1.0.133", "useUntilKubescapeVersion": "v1.0.134"}}}
var rule_v1_0_134 = &reporthandling.PolicyRule{PortalBase: armotypes.PortalBase{
Attributes: map[string]interface{}{"useFromKubescapeVersion": "v1.0.134"}}}
func TestIsRuleKubescapeVersionCompatible(t *testing.T) {
// local build- no build number
// should use only rules that don't have "until"
cautils.BuildNumber = ""
if isRuleKubescapeVersionCompatible(rule_v1_0_131) {
t.Error("error in isRuleKubescapeVersionCompatible")
}
if isRuleKubescapeVersionCompatible(rule_v1_0_132) {
t.Error("error in isRuleKubescapeVersionCompatible")
}
if isRuleKubescapeVersionCompatible(rule_v1_0_133) {
t.Error("error in isRuleKubescapeVersionCompatible")
}
if !isRuleKubescapeVersionCompatible(rule_v1_0_134) {
t.Error("error in isRuleKubescapeVersionCompatible")
}
// should only use rules that version is in range of use
cautils.BuildNumber = "v1.0.133"
if isRuleKubescapeVersionCompatible(rule_v1_0_131) {
t.Error("error in isRuleKubescapeVersionCompatible")
}
if isRuleKubescapeVersionCompatible(rule_v1_0_132) {
t.Error("error in isRuleKubescapeVersionCompatible")
}
if !isRuleKubescapeVersionCompatible(rule_v1_0_133) {
t.Error("error in isRuleKubescapeVersionCompatible")
}
if isRuleKubescapeVersionCompatible(rule_v1_0_134) {
t.Error("error in isRuleKubescapeVersionCompatible")
}
}
func TestRemoveData(t *testing.T) {
w := `{"apiVersion":"apps/v1","kind":"Deployment","metadata":{"name":"demoservice-server"},"spec":{"replicas":1,"selector":{"matchLabels":{"app":"demoservice-server"}},"template":{"metadata":{"creationTimestamp":null,"labels":{"app":"demoservice-server"}},"spec":{"containers":[{"env":[{"name":"SERVER_PORT","value":"8089"},{"name":"SLEEP_DURATION","value":"1"},{"name":"DEMO_FOLDERS","value":"/app"},{"name":"ARMO_TEST_NAME","value":"auto_attach_deployment"},{"name":"CAA_ENABLE_CRASH_REPORTER","value":"1"}],"image":"quay.io/armosec/demoservice:v25","imagePullPolicy":"IfNotPresent","name":"demoservice","ports":[{"containerPort":8089,"protocol":"TCP"}],"resources":{},"terminationMessagePath":"/dev/termination-log","terminationMessagePolicy":"File"}],"dnsPolicy":"ClusterFirst","restartPolicy":"Always","schedulerName":"default-scheduler","securityContext":{},"terminationGracePeriodSeconds":30}}}}`

View File

@@ -12,19 +12,17 @@ import (
type PolicyHandler struct {
resourceHandler resourcehandler.IResourceHandler
// we are listening on this chan in opaprocessor/processorhandler.go/ProcessRulesListenner func
processPolicy *chan *cautils.OPASessionObj
getters *cautils.Getters
getters *cautils.Getters
}
// CreatePolicyHandler Create ws-handler obj
func NewPolicyHandler(processPolicy *chan *cautils.OPASessionObj, resourceHandler resourcehandler.IResourceHandler) *PolicyHandler {
func NewPolicyHandler(resourceHandler resourcehandler.IResourceHandler) *PolicyHandler {
return &PolicyHandler{
resourceHandler: resourceHandler,
processPolicy: processPolicy,
}
}
func (policyHandler *PolicyHandler) HandleNotificationRequest(notification *reporthandling.PolicyNotification, scanInfo *cautils.ScanInfo) error {
func (policyHandler *PolicyHandler) CollectResources(notification *reporthandling.PolicyNotification, scanInfo *cautils.ScanInfo) (*cautils.OPASessionObj, error) {
opaSessionObj := cautils.NewOPASessionObj(nil, nil)
// validate notification
// TODO
@@ -32,26 +30,25 @@ func (policyHandler *PolicyHandler) HandleNotificationRequest(notification *repo
// get policies
if err := policyHandler.getPolicies(notification, opaSessionObj); err != nil {
return err
return opaSessionObj, err
}
err := policyHandler.getResources(notification, opaSessionObj, scanInfo)
if err != nil {
return err
return opaSessionObj, err
}
if opaSessionObj.K8SResources == nil || len(*opaSessionObj.K8SResources) == 0 {
return fmt.Errorf("empty list of resources")
return opaSessionObj, fmt.Errorf("empty list of resources")
}
// update channel
*policyHandler.processPolicy <- opaSessionObj
return nil
return opaSessionObj, nil
}
func (policyHandler *PolicyHandler) getResources(notification *reporthandling.PolicyNotification, opaSessionObj *cautils.OPASessionObj, scanInfo *cautils.ScanInfo) error {
opaSessionObj.Report.ClusterAPIServerInfo = policyHandler.resourceHandler.GetClusterAPIServerInfo()
resourcesMap, allResources, err := policyHandler.resourceHandler.GetResources(opaSessionObj.Frameworks, &notification.Designators)
resourcesMap, allResources, err := policyHandler.resourceHandler.GetResources(opaSessionObj.Policies, &notification.Designators)
if err != nil {
return err
}

View File

@@ -12,15 +12,15 @@ import (
func (policyHandler *PolicyHandler) getPolicies(notification *reporthandling.PolicyNotification, policiesAndResources *cautils.OPASessionObj) error {
logger.L().Info("Downloading/Loading policy definitions")
frameworks, err := policyHandler.getScanPolicies(notification)
policies, err := policyHandler.getScanPolicies(notification)
if err != nil {
return err
}
if len(frameworks) == 0 {
if len(policies) == 0 {
return fmt.Errorf("failed to download policies: '%s'. Make sure the policy exist and you spelled it correctly. For more information, please feel free to contact ARMO team", strings.Join(policyIdentifierToSlice(notification.Rules), ", "))
}
policiesAndResources.Frameworks = frameworks
policiesAndResources.Policies = policies
// get exceptions
exceptionPolicies, err := policyHandler.getters.ExceptionsGetter.GetExceptions(cautils.ClusterName)

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