Compare commits

..

151 Commits

Author SHA1 Message Date
Rotem Refael
f94c9496df Merge pull request #223 from armosec/dev
Adding features and fixing bugs
2021-11-11 15:08:38 +02:00
lalafi@cyberarmor.io
1c31281b7b add baseScore to controlReport 2021-11-11 13:57:52 +02:00
dwertent
0e5204ecb4 support custom frameworks 2021-11-11 11:15:33 +02:00
David Wertenteil
f3dc6235d7 Merge pull request #225 from Daniel-GrunbergerCA/master
Supporting custom frameworks
2021-11-11 09:43:40 +02:00
Daniel-GrunbergerCA
37cdf1a19e erase repetitive frameworks 2021-11-10 18:57:05 +02:00
Daniel-GrunbergerCA
1fb642c777 scan with custom framework 2021-11-10 18:30:03 +02:00
dwertent
8f791ceb12 Improve readme 2021-11-10 09:38:34 +02:00
dwertent
f40eaa0f56 Merge branch 'dev' 2021-11-10 08:17:51 +02:00
dwertent
cb34d17ba1 fixed merge 2021-11-10 08:01:33 +02:00
dwertent
328ba82007 Merge branch 'master' of github.com:armosec/kubescape 2021-11-10 07:59:05 +02:00
David Wertenteil
010ed1b047 Merge pull request #222 from dwertent/master
Adding json to http headers
2021-11-10 07:55:38 +02:00
dwertent
5a81a77d92 adding json to http headers 2021-11-10 07:53:35 +02:00
David Wertenteil
c7ea10d206 Merge pull request #221 from dwertent/master
Checking latest version
2021-11-09 17:09:01 +02:00
dwertent
a37d00b40a checking latest version 2021-11-09 17:07:06 +02:00
David Wertenteil
0168b768d2 Merge pull request #214 from mboersma/fix-spelling-fail-threshold
Fix spelling in --fail-threshold description
2021-11-09 15:10:17 +02:00
David Wertenteil
9a85b57ba4 Merge pull request #201 from Joibel/fix/spelling
Minor spelling fixes
2021-11-09 15:10:02 +02:00
dwertent
eafece6497 update helm command in readme 2021-11-09 11:18:30 +02:00
dwertent
8f08271664 udpate cronjob configmap 2021-11-09 11:16:13 +02:00
David Wertenteil
da0271e624 Merge pull request #218 from yonahd/helm_chart
Helm chart for kubescape
2021-11-08 13:17:37 +02:00
Yonah Dissen
94f52fb4ac Documentation running using docker 2021-11-08 11:52:04 +02:00
David Wertenteil
524c2922a4 Merge pull request #219 from Daniel-GrunbergerCA/master
Support scanning multiple controls
2021-11-08 11:29:50 +02:00
Daniel-GrunbergerCA
0891d64654 comment to run workflow 2021-11-08 10:48:39 +02:00
Daniel-GrunbergerCA
d1c23f7442 scan multiple controls 2021-11-08 10:39:31 +02:00
Daniel-GrunbergerCA
8cbbe35f24 Merge remote-tracking branch 'upstream/dev' 2021-11-08 08:53:27 +02:00
Yonah Dissen
a21e9d706e small changes in helm chart 2021-11-07 21:23:57 +02:00
Yonah Dissen
57160c4d04 add helm chart to deploy kubescape in cluster 2021-11-07 21:17:45 +02:00
Yonah Dissen
8b46a49e23 add helm chart to deploy kubescape in cluster 2021-11-07 21:09:30 +02:00
David Wertenteil
c11ebb49f7 Merge pull request #217 from dwertent/master
Fixed include namespaces
2021-11-07 13:56:56 +02:00
dwertent
e4c3935a1b fixed include ns 2021-11-07 13:50:46 +02:00
David Wertenteil
ade062fdd3 Merge pull request #216 from dwertent/master
support armoBest framework name
2021-11-07 09:23:27 +02:00
dwertent
b0f6357482 support armoBest 2021-11-07 09:13:51 +02:00
Matt Boersma
38a9c11286 Fix spelling in --fail-threshold description 2021-11-05 10:33:06 -06:00
David Wertenteil
0d95f02e60 Merge pull request #213 from dwertent/master
support include namespaces
2021-11-04 12:09:27 +02:00
dwertent
1c30528eea support include ns 2021-11-04 12:06:34 +02:00
David Wertenteil
d1b116d314 Merge pull request #210 from dwertent/master
Sbumit support
2021-11-03 17:31:23 +02:00
dwertent
9d20fd41a8 fixed rbac submit 2021-11-03 17:28:37 +02:00
dwertent
54648bb973 update opa pkg 2021-11-03 15:28:42 +02:00
dwertent
fc4edb12f9 adding stdout to smoke tests 2021-11-02 18:59:34 +02:00
dwertent
9a1b8d7ce2 support submit 2021-11-02 16:14:09 +02:00
dwertent
6909975503 controls support yaml inputs 2021-11-02 10:14:39 +02:00
David Wertenteil
5d94bd990a Merge pull request #208 from dwertent/master
Adding smoke testing and support inputs for controls
2021-11-01 17:22:31 +02:00
dwertent
67c8719f34 adding smoke tests to PR 2021-11-01 14:04:20 +02:00
dwertent
d5b60c6ac8 update config api 2021-11-01 13:52:40 +02:00
dwertent
a99d2e9e26 remove scan 2021-11-01 12:41:54 +02:00
dwertent
5c7d89cb9e use command 2021-11-01 11:55:54 +02:00
dwertent
ae7810f0d3 support input from file 2021-11-01 11:44:07 +02:00
Yonah Dissen
5a90dc46f0 fix version for cli in docker image 2021-11-01 09:17:46 +02:00
Yonah Dissen
294f886588 fix version for cli in docker image 2021-10-31 17:50:55 +02:00
dwertent
17aec665cf updated tests 2021-10-31 15:31:23 +02:00
dwertent
959b25e8b7 adding smoke tests 2021-10-31 15:05:22 +02:00
dwertent
9fd2bf3480 Merge remote-tracking branch 'upstream/dev' 2021-10-31 14:38:36 +02:00
dwertent
7b061a4e51 update opa pkg 2021-10-31 14:38:13 +02:00
David Wertenteil
4fcd89390b Merge pull request #206 from Joibel/feature/prometheus
Add a prometheus output format
2021-10-31 10:57:49 +02:00
dwertent
667ffe9cd3 Merge remote-tracking branch 'prometheus/feature/prometheus' 2021-10-31 08:58:57 +02:00
dwertent
6f4086cd8c Merge branch 'master' of github.com:armosec/kubescape 2021-10-31 08:58:44 +02:00
dwertent
2a45a1a400 support controls input 2021-10-28 16:29:28 +03:00
David Wertenteil
eee201de1e Merge pull request #205 from dwertent/master
Adding cronJob support doc
2021-10-28 11:30:17 +03:00
dwertent
6be24bd22a change repeatedly to periodically 2021-10-28 10:21:25 +03:00
dwertent
ca927dec30 update naming convention 2021-10-28 10:05:30 +03:00
dwertent
3a78ef46a3 Merge remote-tracking branch 'upstream/dev' 2021-10-28 09:40:26 +03:00
David Wertenteil
bdb1cd0905 Merge pull request #199 from Daniel-GrunbergerCA/master
Scan with multiple frameworks/control support
2021-10-28 09:35:07 +03:00
dwertent
ffb556a637 update readme 2021-10-28 09:15:22 +03:00
dwertent
40acfb5e9d Adding cronJob doc 2021-10-28 09:10:37 +03:00
Daniel-GrunbergerCA
de8bcfa0d2 enhance help msgs 2021-10-27 14:44:25 +03:00
Daniel-GrunbergerCA
9439f407da add env var to not check latest release 2021-10-27 13:39:03 +03:00
Daniel-GrunbergerCA
5095e62961 support scanning multiple frameworks from multiple files 2021-10-27 13:02:45 +03:00
Daniel-GrunbergerCA
3301907864 print only one table for controls & enhance help msg 2021-10-27 10:25:26 +03:00
Daniel-GrunbergerCA
151175c40f read single control from framework file 2021-10-27 08:49:40 +03:00
Daniel-GrunbergerCA
234d4fa537 Merge remote-tracking branch 'upstream/dev' 2021-10-27 08:27:21 +03:00
Rotem Refael
f384e8a6e3 Merge pull request #203 from armosec/cluster-name-issue
adopt cluster name (HotFix)
2021-10-26 20:51:42 +03:00
dwertent
66068757e1 update cluster name in mock struct 2021-10-26 20:39:18 +03:00
dwertent
8a7cda5dd1 adopt cluster name 2021-10-26 20:27:33 +03:00
Alan Clucas
8e67104ba4 Add prometheus to readme 2021-10-26 16:23:05 +01:00
Alan Clucas
0c9da9ddc8 Add a prometheus metrics style output
Output per control results and also per object counts

This can lead to running this as a service that prometheus can collect from
2021-10-26 16:19:35 +01:00
Alan Clucas
a5ef6aa126 Minor spelling fixes 2021-10-26 15:19:05 +01:00
Rotem Refael
c133b7a2c2 Merge pull request #191 from armosec/dev
Hot fixes relates to submit & account options
2021-10-26 14:10:23 +03:00
Daniel-GrunbergerCA
a0ca68cc41 update json and junit for multiple frameworks 2021-10-26 13:55:12 +03:00
Daniel-GrunbergerCA
41cae0bc93 Merge remote-tracking branch 'upstream/dev' 2021-10-26 13:23:00 +03:00
David Wertenteil
b4198fde8c Merge pull request #198 from dwertent/master
update pkg tag
2021-10-26 12:28:02 +03:00
dwertent
bd24f35738 update tag 2021-10-26 12:26:44 +03:00
Daniel-GrunbergerCA
6fcbb757b5 Merge remote-tracking branch 'upstream/dev' 2021-10-25 17:41:15 +03:00
Daniel-GrunbergerCA
3b8825e5d2 scan multiple frameworks and controls 2021-10-25 17:41:04 +03:00
Rotem Refael
5cf3244918 Merge pull request #192 from dwertent/master
Update multiple score
2021-10-25 17:40:19 +03:00
dwertent
934c9ccc8b fixed lowest 2021-10-25 15:51:23 +03:00
dwertent
41dfdfd1e8 support more than score 2021-10-25 15:14:31 +03:00
David Wertenteil
427fb59c99 Merge pull request #190 from dwertent/master
Fixed submit and url
2021-10-25 12:08:23 +03:00
David Wertenteil
ae825800f6 Merge pull request #189 from Daniel-GrunbergerCA/master
Update tag for newest release of k8s-interface
2021-10-25 12:08:06 +03:00
dwertent
d72700acf6 update submit 2021-10-25 12:05:51 +03:00
dwertent
3310a6a26f Merge remote-tracking branch 'upstream/dev' 2021-10-25 11:55:30 +03:00
dwertent
740b5aa772 add full url 2021-10-25 11:55:08 +03:00
Daniel-GrunbergerCA
04b55e764a fix k8s-interface pkg tag 2021-10-25 10:44:43 +03:00
Daniel-GrunbergerCA
beb4062bb1 update tag 2021-10-25 09:29:43 +03:00
David Wertenteil
5d4cd4acdc Merge pull request #188 from dwertent/master
Use interfaces
2021-10-25 09:21:43 +03:00
dwertent
aec8198131 adding score to interface 2021-10-25 08:41:15 +03:00
dwertent
0a850e47df use interfaces 2021-10-24 17:51:03 +03:00
Rotem Refael
5544820c5e Merge pull request #187 from armosec/dev
Fixed junit counter
2021-10-21 16:23:09 +03:00
dwertent
4f466d517a fixed junit counter 2021-10-21 16:05:51 +03:00
Ben Hirschberg
b3661848dc Merge pull request #186 from armosec/dev
merge readme & install fix
2021-10-21 14:08:57 +03:00
Rotem Refael
548201c256 Merge pull request #185 from dwertent/master
Update readme with new featurs
2021-10-21 13:46:33 +03:00
dwertent
a54e5d9f8b update readme with new featurs 2021-10-21 13:25:41 +03:00
dwertent
536257afa1 fixed version in build.py 2021-10-21 12:48:52 +03:00
dwertent
5a71c3270a Merge branch 'master' of github.com:armosec/kubescape 2021-10-21 11:32:40 +03:00
dwertent
d194dd173f fixed image and entrypoint 2021-10-21 11:32:20 +03:00
Ben Hirschberg
be03a9e984 Merge pull request #173 from armosec/dev
Dev
2021-10-21 10:33:26 +03:00
dwertent
a90177e7c0 update go mod file 2021-10-21 09:56:05 +03:00
Rotem Refael
be9e8ca47d Merge pull request #184 from Daniel-GrunbergerCA/master
Implementing single control scan  (from file and and from regolibrary) & download single control
2021-10-21 08:33:26 +03:00
Daniel-GrunbergerCA
eb9fe85c75 add error handling 2021-10-20 17:45:11 +03:00
Daniel-GrunbergerCA
47183c405f add some comments 2021-10-20 17:29:25 +03:00
Daniel-GrunbergerCA
2725923b9b case insensitive 2021-10-20 16:58:09 +03:00
Daniel-GrunbergerCA
f6c03ed7a2 fix offline support 2021-10-20 16:50:59 +03:00
Daniel-GrunbergerCA
76b5548216 Merge remote-tracking branch 'upstream/dev' 2021-10-20 16:29:56 +03:00
Daniel-GrunbergerCA
cc57a34a32 run control scan form file 2021-10-20 16:18:53 +03:00
Bezbran
f7099b62e6 Merge pull request #182 from Bezbran/dev
Add API version to report structure
2021-10-20 16:05:46 +03:00
Bezalel Brandwine
093ee8916e Add API version to report structure 2021-10-20 16:04:10 +03:00
David Wertenteil
ac0259157b Merge pull request #181 from dwertent/master
Update junit results
2021-10-20 15:57:34 +03:00
dwertent
9cb937798f update readme 2021-10-20 14:53:19 +03:00
dwertent
11d4926c85 updte junit results 2021-10-20 14:49:27 +03:00
Daniel-GrunbergerCA
836211ae2b update go mod 2021-10-20 14:39:33 +03:00
Daniel-GrunbergerCA
fddf3d3f58 Merge remote-tracking branch 'upstream/dev' 2021-10-20 14:15:46 +03:00
Daniel-GrunbergerCA
b036d1079e support control scan with new api 2021-10-20 14:15:27 +03:00
Rotem Refael
137c39e918 Merge pull request #180 from AvnerTzurArmo/dev
add cronjob sample for kubescape
2021-10-20 13:57:26 +03:00
Avner Tzur
8778d022cf add cronjob sample for kubescape 2021-10-20 10:59:30 +03:00
Bezbran
043bdbacec Merge pull request #8 from armosec/dev
Dev
2021-10-20 10:49:28 +03:00
Daniel-GrunbergerCA
e0e19b0258 Merge remote-tracking branch 'upstream/dev' 2021-10-20 09:05:37 +03:00
David Wertenteil
c72e0f790a Merge pull request #179 from dwertent/master
fallback get customer guid from configMap
2021-10-19 16:30:47 +03:00
dwertent
87e79110a2 fallback customer guid from configMap 2021-10-19 16:25:54 +03:00
David Wertenteil
faeae1af60 Merge pull request #178 from dwertent/master
adding summary changes
2021-10-19 15:09:05 +03:00
dwertent
b371fbad01 adding summary 2021-10-19 15:08:00 +03:00
David Wertenteil
90831e153d Merge pull request #177 from dwertent/master
Distinct exclude and failed resources
2021-10-19 14:24:10 +03:00
dwertent
009d8275c1 Distinct exclude and failed resources 2021-10-19 14:22:09 +03:00
Daniel-GrunbergerCA
05c88e0ffc Merge remote-tracking branch 'upstream/dev' 2021-10-18 17:40:11 +03:00
Daniel-GrunbergerCA
7d12552932 add option to run single control 2021-10-18 17:39:35 +03:00
Rotem Refael
b761505bb1 Merge pull request #172 from Moshe-Rappaport-CA/master
Support scanning yamls from gitHub repo
2021-10-18 14:51:42 +03:00
moshep
63367f4f31 support yamls from repo 2021-10-18 14:29:44 +03:00
Rotem Refael
6f9d6b4af3 Update README.md 2021-10-18 14:16:29 +03:00
Rotem Refael
6ed8287b01 Update README.md 2021-10-18 14:15:45 +03:00
Rotem Refael
d948e20682 Change readme text 2021-10-18 14:12:57 +03:00
Rotem Refael
42929dac58 Merge pull request #171 from armosec/dev
Fix workflow for building image
2021-10-18 11:59:29 +03:00
Rotem Refael
74449c64a2 Merge pull request #170 from Daniel-GrunbergerCA/master
Fix env var name
2021-10-18 11:26:18 +03:00
Daniel-GrunbergerCA
0bd164c69e fix workflow for dev 2021-10-18 11:15:55 +03:00
Daniel-GrunbergerCA
d44c082134 fix env var name 2021-10-18 11:11:32 +03:00
Daniel-GrunbergerCA
d756b9bfe4 check repo name 2021-10-18 11:04:57 +03:00
Daniel-GrunbergerCA
6144050212 echo repo 2021-10-18 11:02:54 +03:00
Daniel-GrunbergerCA
b5fe456b0d fix test 2021-10-18 10:59:41 +03:00
Daniel-GrunbergerCA
37791ff391 testing workflow output 2021-10-18 10:57:04 +03:00
Daniel-GrunbergerCA
c2d99163a6 test output for workflow 2021-10-18 10:55:13 +03:00
Daniel-GrunbergerCA
d948353b99 test env var output for build 2021-10-18 10:51:50 +03:00
Daniel-GrunbergerCA
2649cb75f6 Revert "check env var output"
This reverts commit 7c8da4a4b9.
2021-10-18 10:51:28 +03:00
Daniel-GrunbergerCA
7c8da4a4b9 check env var output 2021-10-18 10:49:53 +03:00
dwertent
70a9a7bbbd Update resource count 2021-10-18 10:23:06 +03:00
Bezbran
54a6a8324a Merge pull request #7 from armosec/dev
Dev
2021-10-14 14:54:14 +03:00
98 changed files with 3320 additions and 1035 deletions

View File

@@ -29,7 +29,6 @@ jobs:
os: [ubuntu-latest, macos-latest, windows-latest]
steps:
- uses: actions/checkout@v1
- name: Set up Go
uses: actions/setup-go@v2
with:
@@ -47,6 +46,12 @@ jobs:
CGO_ENABLED: 0
run: python3 --version && python3 build.py
- name: Smoke Testing
env:
RELEASE: v1.0.${{ github.run_number }}
KUBESCAPE_SKIP_UPDATE_CHECK: "true"
run: python3 smoke_testing/init.py ${PWD}/build/${{ matrix.os }}/kubescape
- name: Upload Release binaries
id: upload-release-asset
uses: actions/upload-release-asset@v1
@@ -62,7 +67,7 @@ jobs:
build-docker:
name: Build docker container, tag and upload to registry
needs: build
if: ${{ github.workflow == 'armosec/kubescape' }}
if: ${{ github.repository == 'armosec/kubescape' }}
runs-on: ubuntu-latest
steps:
@@ -72,8 +77,8 @@ jobs:
run: echo quay.io/armosec/kubescape:v1.0.${{ github.run_number }} > build_tag.txt
- name: Build the Docker image
run: docker build . --file build/Dockerfile --tag $(cat build_tag.txt)
run: docker build . --file build/Dockerfile --tag $(cat build_tag.txt) --build-arg run_number=${{ github.run_number }}
- name: Re-Tag Image to latest
run: docker tag $(cat build_tag.txt) quay.io/armosec/kubescape:latest

View File

@@ -30,6 +30,12 @@ jobs:
CGO_ENABLED: 0
run: python3 --version && python3 build.py
- name: Smoke Testing
env:
RELEASE: v1.0.${{ github.run_number }}
KUBESCAPE_SKIP_UPDATE_CHECK: "true"
run: python3 smoke_testing/init.py ${PWD}/build/${{ matrix.os }}/kubescape
- name: Upload build artifacts
uses: actions/upload-artifact@v2
with:
@@ -40,7 +46,7 @@ jobs:
build-docker:
name: Build docker container, tag and upload to registry
needs: build
if: ${{ github.workflow == 'armosec/kubescape' }}
if: ${{ github.repository == 'armosec/kubescape' }}
runs-on: ubuntu-latest
steps:
@@ -50,7 +56,7 @@ jobs:
run: echo quay.io/armosec/kubescape:dev-v1.0.${{ github.run_number }} > build_tag.txt
- name: Build the Docker image
run: docker build . --file build/Dockerfile --tag $(cat build_tag.txt)
run: docker build . --file build/Dockerfile --tag $(cat build_tag.txt) --build-arg run_number=${{ github.run_number }}
- name: Login to Quay.io
env: # Or as an environment variable

View File

@@ -31,8 +31,9 @@ jobs:
CGO_ENABLED: 0
run: python3 --version && python3 build.py
- name: Upload build artifacts
uses: actions/upload-artifact@v2
with:
name: kubescape-${{ matrix.os }}
path: build/${{ matrix.os }}/kubescape
- name: Smoke Testing
env:
RELEASE: v1.0.${{ github.run_number }}
KUBESCAPE_SKIP_UPDATE_CHECK: "true"
run: python3 smoke_testing/init.py ${PWD}/build/${{ matrix.os }}/kubescape

2
.gitignore vendored
View File

@@ -1,4 +1,6 @@
*.vs*
*kubescape*
*debug*
*vender*
*.pyc*
.idea

147
README.md
View File

@@ -3,9 +3,12 @@
[![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 the first tool for testing if Kubernetes is deployed securely as defined in [Kubernetes Hardening Guidance by NSA and CISA](https://www.nsa.gov/Press-Room/News-Highlights/Article/Article/2716980/nsa-cisa-release-kubernetes-hardening-guidance/)
Kubescape is the first open-source tool for testing if Kubernetes is deployed securely according to multiple frameworks:
regulatory, customized company policies and DevSecOps best practices, such as the [NSA-CISA](https://www.armosec.io/blog/kubernetes-hardening-guidance-summary-by-armo) and the [MITRE ATT&CK®](https://www.microsoft.com/security/blog/2021/03/23/secure-containerized-environments-with-updated-threat-matrix-for-kubernetes/) .
Kubescape scans K8s clusters, YAML files, and HELM charts, and detect misconfigurations and software vulnerabilities at early stages of the CI/CD pipeline and provides a risk score instantly and risk trends over time.
Kubescape integrates natively with other DevOps tools, including Jenkins, CircleCI and Github workflows.
Use Kubescape to test clusters or scan single YAML files and integrate it to your processes.
</br>
<img src="docs/demo.gif">
@@ -21,15 +24,22 @@ curl -s https://raw.githubusercontent.com/armosec/kubescape/master/install.sh |
## Run:
```
kubescape scan framework nsa --exclude-namespaces kube-system,kube-public
kubescape scan framework nsa
```
If you wish to scan all namespaces in your cluster, remove the `--exclude-namespaces` flag.
<img src="docs/summary.png">
</br>
> Kubescape is an open source project, we welcome your feedback and ideas for improvement. Were also aiming to collaborate with the Kubernetes community to help make the tests themselves more robust and complete as Kubernetes develops.
</br>
### Click [👍](https://github.com/armosec/kubescape/stargazers) if you want us to continue to develop and improve Kubescape 😀
</br>
# Being part of the team
We invite you to our team! We are excited about this project and want to return the love we get.
@@ -41,8 +51,15 @@ Want to contribute? Want to discuss something? Have an issue?
[<img src="docs/discord-banner.png" width="100" alt="logo" align="center">](https://armosec.github.io/kubescape/)
# Options and examples
## Tutorials
* [Overview](https://youtu.be/wdBkt_0Qhbg)
* [Scanning Kubernetes YAML files](https://youtu.be/Ox6DaR7_4ZI)
* [Managing exceptions in the Kubescape SaaS version](https://youtu.be/OzpvxGmCR80)
## Install on Windows
**Requires powershell v5.0+**
@@ -68,72 +85,94 @@ Set-ExecutionPolicy RemoteSigned -scope CurrentUser
## 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 bellow 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 |
| `--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 | |
| 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` | `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`/`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-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 | |
| `--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 | |
## Usage & Examples
### Examples
* Scan a running Kubernetes cluster with [`nsa`](https://www.nsa.gov/News-Features/Feature-Stories/Article-View/Article/2716980/nsa-cisa-release-kubernetes-hardening-guidance/) framework and submit results to [Armo portal](https://portal.armo.cloud/)
#### Scan a running Kubernetes cluster with [`nsa`](https://www.nsa.gov/News-Features/Feature-Stories/Article-View/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 --exclude-namespaces kube-system,kube-public --submit
kubescape scan framework nsa --submit
```
* Scan a running Kubernetes cluster with [`mitre`](https://www.microsoft.com/security/blog/2020/04/02/attack-matrix-kubernetes/) framework and submit results to [Armo portal](https://portal.armo.cloud/)
#### Scan a running Kubernetes cluster with [`MITRE ATT&CK®`](https://www.microsoft.com/security/blog/2021/03/23/secure-containerized-environments-with-updated-threat-matrix-for-kubernetes/) framework and submit results to the [Kubescape SaaS version](https://portal.armo.cloud/)
```
kubescape scan framework mitre --exclude-namespaces kube-system,kube-public --submit
kubescape scan framework mitre --submit
```
* Scan local `yaml`/`json` files before deploying. [Take a look at the demonstration](https://youtu.be/Ox6DaR7_4ZI)
#### Scan a running Kubernetes cluster with a specific control using the control name or control ID. [List of controls](https://hub.armo.cloud/docs/controls)
```
kubescape scan control "Privileged container"
```
#### Scan specific namespaces
```
kubescape scan framework nsa --include-namespaces development,staging,production
```
#### Scan cluster and exclude some namespaces
```
kubescape scan framework nsa --exclude-namespaces kube-system,kube-public
```
#### Scan local `yaml`/`json` files before deploying. [Take a look at the demonstration](https://youtu.be/Ox6DaR7_4ZI)
```
kubescape scan framework nsa *.yaml
```
* Scan `yaml`/`json` files from url
#### Scan kubernetes manifest files from a public github repository
```
kubescape scan framework nsa https://raw.githubusercontent.com/GoogleCloudPlatform/microservices-demo/master/release/kubernetes-manifests.yaml
kubescape scan framework nsa https://github.com/armosec/kubescape
```
* Output in `json` format
#### Output in `json` format
```
kubescape scan framework nsa --exclude-namespaces kube-system,kube-public --format json --output results.json
kubescape scan framework nsa --format json --output results.json
```
* Output in `junit xml` format
#### Output in `junit xml` format
```
kubescape scan framework nsa --exclude-namespaces kube-system,kube-public --format junit --output results.xml
kubescape scan framework nsa --format junit --output results.xml
```
* Scan with exceptions, objects with exceptions will be presented as `warning` and not `fail` <img src="docs/new-feature.svg">
#### Output in `prometheus` metrics format - Contributed by [@Joibel](https://github.com/Joibel)
```
kubescape scan framework nsa --format prometheus
```
#### Scan with exceptions, objects with exceptions will be presented as `exclude` 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
#### Scan Helm charts - 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:
e.g.
```
helm template bitnami/mysql --generate-name --dry-run | kubescape scan framework nsa -
```
### Offline Support
It is possible to run Kubescape offline!
@@ -150,13 +189,40 @@ kubescape download framework nsa --output nsa.json
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.
## Scan Periodically using Helm - Contributed by [@yonahd](https://github.com/yonahd)
You can scan your cluster periodically by adding a `CronJob` that will repeatedly trigger kubescape
```
helm install kubescape examples/helm_chart/
```
## Scan using docker image
Official Docker image `quay.io/armosec/kubescape`
```
docker run -v "$(pwd)/example.yaml:/app/example.yaml quay.io/armosec/kubescape scan framework nsa /app/example.yaml
```
# 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`.
Now you can submit the results to the Kubaescape SaaS version -
```
kubescape submit results path/to/results.json
```
# How to build
## Build using python (3.7^) script
Kubescpae can be built using:
Kubescape can be built using:
``` sh
python build.py
@@ -187,12 +253,14 @@ go build -o kubescape .
3. Run
```
./kubescape scan framework nsa --exclude-namespaces kube-system,kube-public
./kubescape scan framework nsa
```
4. Enjoy :zany_face:
## How to build in Docker
## Docker Build
### Build your own Docker image
1. Clone Project
```
@@ -204,6 +272,7 @@ git clone https://github.com/armosec/kubescape.git kubescape && cd "$_"
docker build -t kubescape -f build/Dockerfile .
```
# Under the hood
## Tests

View File

@@ -41,7 +41,7 @@ def main():
# Set some variables
packageName = getPackageName()
buildUrl = "github.com/armosec/kubescape/cmd.BuildNumber"
buildUrl = "github.com/armosec/kubescape/cautils.BuildNumber"
releaseVersion = os.getenv("RELEASE")
ArmoBEServer = os.getenv("ArmoBEServer")
ArmoERServer = os.getenv("ArmoERServer")
@@ -60,9 +60,6 @@ def main():
status = subprocess.call(["go", "build", "-o", "%s/%s" % (buildDir, packageName), "-ldflags" ,ldflags])
checkStatus(status, "Failed to build kubescape")
test_cli_prints(buildDir,packageName)
sha1 = hashlib.sha1()
with open(buildDir + "/" + packageName, "rb") as kube:
sha1.update(kube.read())
@@ -70,13 +67,7 @@ def main():
kube_sha.write(sha1.hexdigest())
print("Build Done")
def test_cli_prints(buildDir,packageName):
bin_cli = os.path.abspath(os.path.join(buildDir,packageName))
print(f"testing CLI prints on {bin_cli}")
status = str(subprocess.check_output([bin_cli, "-h"]))
assert "download" in status, "download is missing: " + status
if __name__ == "__main__":
main()

View File

@@ -1,5 +1,10 @@
FROM golang:1.17-alpine as builder
#ENV GOPROXY=https://goproxy.io,direct
ARG run_number
ENV RELEASE=v1.0.${run_number}
ENV GO111MODULE=
ENV CGO_ENABLED=0
@@ -24,4 +29,4 @@ COPY --from=builder /work/build/ubuntu-latest/kubescape /usr/bin/kubescape
# # Download the frameworks. Use the "--use-default" flag when running kubescape
# RUN kubescape download framework nsa && kubescape download framework mitre
CMD ["kubescape"]
ENTRYPOINT ["kubescape"]

View File

@@ -8,10 +8,10 @@ import (
"os"
"strings"
"github.com/armosec/kubescape/cautils/getter"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/armosec/k8s-interface/k8sinterface"
"github.com/armosec/kubescape/cautils/getter"
corev1 "k8s.io/api/core/v1"
)
@@ -30,6 +30,7 @@ type ConfigObj struct {
CustomerGUID string `json:"customerGUID"`
Token string `json:"invitationParam"`
CustomerAdminEMail string `json:"adminMail"`
ClusterName string `json:"clusterName"`
}
func (co *ConfigObj) Json() []byte {
@@ -39,14 +40,30 @@ func (co *ConfigObj) Json() []byte {
return []byte{}
}
// Config - convert ConfigObj to config file
func (co *ConfigObj) Config() []byte {
clusterName := co.ClusterName
co.ClusterName = "" // remove cluster name before saving to file
b, err := json.Marshal(co)
co.ClusterName = clusterName
if err == nil {
return b
}
return []byte{}
}
// ======================================================================================
// =============================== interface ============================================
// ======================================================================================
type IClusterConfig interface {
// setters
SetCustomerGUID(customerGUID string) error
// set
SetConfig(customerGUID string) error
// getters
GetClusterName() string
GetCustomerGUID() string
GetConfigObj() *ConfigObj
GetK8sAPI() *k8sinterface.KubernetesApi
@@ -92,8 +109,10 @@ func ClusterConfigSetup(scanInfo *ScanInfo, k8s *k8sinterface.KubernetesApi, beA
return NewEmptyConfig() // local - Delete local config & Do not send report
}
if scanInfo.Local {
scanInfo.Submit = false
return NewEmptyConfig() // local - Do not send report
}
scanInfo.Submit = true
return clusterConfig // submit/default - Submit report
}
@@ -103,16 +122,17 @@ func ClusterConfigSetup(scanInfo *ScanInfo, k8s *k8sinterface.KubernetesApi, beA
type EmptyConfig struct {
}
func NewEmptyConfig() *EmptyConfig { return &EmptyConfig{} }
func (c *EmptyConfig) GetConfigObj() *ConfigObj { return &ConfigObj{} }
func (c *EmptyConfig) SetCustomerGUID(customerGUID string) error { return nil }
func (c *EmptyConfig) GetCustomerGUID() string { return "" }
func (c *EmptyConfig) GetK8sAPI() *k8sinterface.KubernetesApi { return nil } // TODO: return mock obj
func (c *EmptyConfig) GetDefaultNS() string { return k8sinterface.GetDefaultNamespace() }
func (c *EmptyConfig) GetBackendAPI() getter.IBackend { return nil } // TODO: return mock obj
func NewEmptyConfig() *EmptyConfig { return &EmptyConfig{} }
func (c *EmptyConfig) SetConfig(customerGUID string) error { return nil }
func (c *EmptyConfig) GetConfigObj() *ConfigObj { return &ConfigObj{} }
func (c *EmptyConfig) GetCustomerGUID() string { return "" }
func (c *EmptyConfig) GetK8sAPI() *k8sinterface.KubernetesApi { return nil } // TODO: return mock obj
func (c *EmptyConfig) GetDefaultNS() string { return k8sinterface.GetDefaultNamespace() }
func (c *EmptyConfig) GetBackendAPI() getter.IBackend { return nil } // TODO: return mock obj
func (c *EmptyConfig) GetClusterName() string { return adoptClusterName(k8sinterface.GetClusterName()) }
func (c *EmptyConfig) GenerateURL() {
message := fmt.Sprintf("You can see the results in a user-friendly UI, choose your preferred compliance framework, check risk results history and trends, manage exceptions, get remediation recommendations and much more by registering here: https://%s", getter.GetArmoAPIConnector().GetFrontendURL())
InfoTextDisplay(os.Stdout, message+"\n")
message := fmt.Sprintf("\nCheckout for more cool features: https://%s\n", getter.GetArmoAPIConnector().GetFrontendURL())
InfoTextDisplay(os.Stdout, fmt.Sprintf("\n%s\n", message))
}
// ======================================================================================
@@ -134,12 +154,14 @@ func NewClusterConfig(k8s *k8sinterface.KubernetesApi, backendAPI getter.IBacken
defaultNS: k8sinterface.GetDefaultNamespace(),
}
}
func (c *ClusterConfig) GetConfigObj() *ConfigObj { return c.configObj }
func (c *ClusterConfig) GetK8sAPI() *k8sinterface.KubernetesApi { return c.k8s }
func (c *ClusterConfig) GetDefaultNS() string { return c.defaultNS }
func (c *ClusterConfig) GetBackendAPI() getter.IBackend { return c.backendAPI }
func (c *ClusterConfig) GenerateURL() {
message := "Checkout for more cool features: "
u := url.URL{}
u.Scheme = "https"
@@ -147,9 +169,8 @@ func (c *ClusterConfig) GenerateURL() {
if c.configObj == nil {
return
}
message := fmt.Sprintf("You can see the results in a user-friendly UI, choose your preferred compliance framework, check risk results history and trends, manage exceptions, get remediation recommendations and much more by registering here: %s", u.String())
if c.configObj.CustomerAdminEMail != "" {
InfoTextDisplay(os.Stdout, message+"\n")
InfoTextDisplay(os.Stdout, "\n\n"+message+u.String()+"\n\n")
return
}
u.Path = "account/sign-up"
@@ -158,8 +179,7 @@ func (c *ClusterConfig) GenerateURL() {
q.Add("customerGUID", c.configObj.CustomerGUID)
u.RawQuery = q.Encode()
InfoTextDisplay(os.Stdout, message+"\n")
InfoTextDisplay(os.Stdout, "\n\n"+message+u.String()+"\n\n")
}
func (c *ClusterConfig) GetCustomerGUID() string {
@@ -169,10 +189,19 @@ func (c *ClusterConfig) GetCustomerGUID() string {
return ""
}
func (c *ClusterConfig) SetCustomerGUID(customerGUID string) error {
func (c *ClusterConfig) SetConfig(customerGUID string) error {
if c.configObj == nil {
c.configObj = &ConfigObj{}
}
// cluster name
if c.GetClusterName() == "" {
c.setClusterName(k8sinterface.GetClusterName())
}
// ARMO customer GUID
if customerGUID != "" && c.GetCustomerGUID() != customerGUID {
c.configObj.CustomerGUID = customerGUID // override config customerGUID
c.setCustomerGUID(customerGUID) // override config customerGUID
}
customerGUID = c.GetCustomerGUID()
@@ -181,10 +210,10 @@ func (c *ClusterConfig) SetCustomerGUID(customerGUID string) error {
tenantResponse, err := c.backendAPI.GetCustomerGUID(customerGUID)
if err == nil && tenantResponse != nil {
if tenantResponse.AdminMail != "" { // this customer already belongs to some user
c.configObj.CustomerAdminEMail = tenantResponse.AdminMail
c.setCustomerAdminEMail(tenantResponse.AdminMail)
} else {
c.configObj.Token = tenantResponse.Token
c.configObj.CustomerGUID = tenantResponse.TenantID
c.setToken(tenantResponse.Token)
c.setCustomerGUID(tenantResponse.TenantID)
}
} else {
if err != nil && !strings.Contains(err.Error(), "already exists") {
@@ -198,15 +227,28 @@ func (c *ClusterConfig) SetCustomerGUID(customerGUID string) error {
} else {
c.createConfigMap()
}
if existsConfigFile() {
c.updateConfigFile()
} else {
c.createConfigFile()
}
c.updateConfigFile()
return nil
}
func (c *ClusterConfig) setToken(token string) {
c.configObj.Token = token
}
func (c *ClusterConfig) setCustomerAdminEMail(customerAdminEMail string) {
c.configObj.CustomerAdminEMail = customerAdminEMail
}
func (c *ClusterConfig) setCustomerGUID(customerGUID string) {
c.configObj.CustomerGUID = customerGUID
}
func (c *ClusterConfig) setClusterName(clusterName string) {
c.configObj.ClusterName = adoptClusterName(clusterName)
}
func (c *ClusterConfig) GetClusterName() string {
return c.configObj.ClusterName
}
func (c *ClusterConfig) LoadConfig() {
// get from configMap
if c.existsConfigMap() {
@@ -220,8 +262,9 @@ func (c *ClusterConfig) LoadConfig() {
func (c *ClusterConfig) ToMapString() map[string]interface{} {
m := map[string]interface{}{}
bc, _ := json.Marshal(c.configObj)
json.Unmarshal(bc, &m)
if bc, err := json.Marshal(c.configObj); err == nil {
json.Unmarshal(bc, &m)
}
return m
}
func (c *ClusterConfig) loadConfigFromConfigMap() (*ConfigObj, error) {
@@ -360,14 +403,7 @@ func (c *ClusterConfig) updateConfigMap() error {
}
func (c *ClusterConfig) updateConfigFile() error {
if err := os.WriteFile(ConfigFileFullPath(), c.configObj.Json(), 0664); err != nil {
return err
}
return nil
}
func (c *ClusterConfig) createConfigFile() error {
if err := os.WriteFile(ConfigFileFullPath(), c.configObj.Json(), 0664); err != nil {
if err := os.WriteFile(ConfigFileFullPath(), c.configObj.Config(), 0664); err != nil {
return err
}
return nil
@@ -437,3 +473,7 @@ func DeleteConfigMap(k8s *k8sinterface.KubernetesApi) error {
func DeleteConfigFile() error {
return os.Remove(ConfigFileFullPath())
}
func adoptClusterName(clusterName string) string {
return strings.ReplaceAll(clusterName, "/", "-")
}

View File

@@ -13,6 +13,7 @@ type OPASessionObj struct {
K8SResources *K8SResources
Exceptions []armotypes.PostureExceptionPolicy
PostureReport *reporthandling.PostureReport
RegoInputData RegoInputData // map[<control name>][<input arguments>]
}
func NewOPASessionObj(frameworks []reporthandling.Framework, k8sResources *K8SResources) *OPASessionObj {
@@ -49,3 +50,9 @@ type Exception struct {
Namespaces []string `json:"namespaces"`
Regex string `json:"regex"` // not supported
}
type RegoInputData struct {
PostureControlInputs map[string][]string `json:"postureControlInputs"`
// ClusterName string `json:"clusterName"`
// K8sConfig RegoK8sConfig `json:"k8sconfig"`
}

View File

@@ -0,0 +1,26 @@
package cautils
import (
"encoding/json"
"github.com/open-policy-agent/opa/storage"
"github.com/open-policy-agent/opa/storage/inmem"
"github.com/open-policy-agent/opa/util"
)
func (data *RegoInputData) SetControlsInputs(controlsInputs map[string][]string) {
data.PostureControlInputs = controlsInputs
}
func (data *RegoInputData) TOStorage() (storage.Store, error) {
var jsonObj map[string]interface{}
bytesData, err := json.Marshal(*data)
if err != nil {
return nil, err
}
// glog.Infof("RegoDependenciesData: %s", bytesData)
if err := util.UnmarshalJSON(bytesData, &jsonObj); err != nil {
return nil, err
}
return inmem.NewFromObject(jsonObj), nil
}

View File

@@ -3,4 +3,5 @@ package cautils
type DownloadInfo struct {
Path string
FrameworkName string
ControlName string
}

View File

@@ -1,11 +1,14 @@
package getter
import (
"encoding/json"
"fmt"
"net/http"
"strings"
"time"
"github.com/armosec/armoapi-go/armotypes"
"github.com/armosec/opa-utils/gitregostore"
"github.com/armosec/opa-utils/reporthandling"
"github.com/golang/glog"
)
@@ -28,10 +31,12 @@ var (
// Armo API for downloading policies
type ArmoAPI struct {
httpClient *http.Client
apiURL string
erURL string
feURL string
httpClient *http.Client
apiURL string
erURL string
feURL string
customerGUID string
gs *gitregostore.GitRegoStore
}
var globalArmoAPIConnecctor *ArmoAPI
@@ -80,9 +85,13 @@ func NewARMOAPICustomized(armoERURL, armoBEURL, armoFEURL string) *ArmoAPI {
func newArmoAPI() *ArmoAPI {
return &ArmoAPI{
httpClient: &http.Client{Timeout: time.Duration(61) * time.Second},
gs: gitregostore.InitDefaultGitRegoStore(-1),
}
}
func (armoAPI *ArmoAPI) SetCustomerGUID(customerGUID string) {
armoAPI.customerGUID = customerGUID
}
func (armoAPI *ArmoAPI) GetFrontendURL() string {
return armoAPI.feURL
}
@@ -92,7 +101,7 @@ func (armoAPI *ArmoAPI) GetReportReceiverURL() string {
}
func (armoAPI *ArmoAPI) GetFramework(name string) (*reporthandling.Framework, error) {
respStr, err := HttpGetter(armoAPI.httpClient, armoAPI.getFrameworkURL(name))
respStr, err := HttpGetter(armoAPI.httpClient, armoAPI.getFrameworkURL(name), nil)
if err != nil {
return nil, err
}
@@ -106,12 +115,26 @@ func (armoAPI *ArmoAPI) GetFramework(name string) (*reporthandling.Framework, er
return framework, err
}
func (armoAPI *ArmoAPI) GetControl(policyName string) (*reporthandling.Control, error) {
var control *reporthandling.Control
var err error
if strings.HasPrefix(policyName, "C-") || strings.HasPrefix(policyName, "c-") {
control, err = armoAPI.gs.GetOPAControlByID(policyName)
} else {
control, err = armoAPI.gs.GetOPAControlByName(policyName)
}
if err != nil {
return nil, err
}
return control, nil
}
func (armoAPI *ArmoAPI) GetExceptions(customerGUID, clusterName string) ([]armotypes.PostureExceptionPolicy, error) {
exceptions := []armotypes.PostureExceptionPolicy{}
if customerGUID == "" {
return exceptions, nil
}
respStr, err := HttpGetter(armoAPI.httpClient, armoAPI.getExceptionsURL(customerGUID, clusterName))
respStr, err := HttpGetter(armoAPI.httpClient, armoAPI.getExceptionsURL(customerGUID, clusterName), nil)
if err != nil {
return nil, err
}
@@ -128,7 +151,7 @@ func (armoAPI *ArmoAPI) GetCustomerGUID(customerGUID string) (*TenantResponse, e
if customerGUID != "" {
url = fmt.Sprintf("%s?customerGUID=%s", url, customerGUID)
}
respStr, err := HttpGetter(armoAPI.httpClient, url)
respStr, err := HttpGetter(armoAPI.httpClient, url, nil)
if err != nil {
return nil, err
}
@@ -140,6 +163,75 @@ func (armoAPI *ArmoAPI) GetCustomerGUID(customerGUID string) (*TenantResponse, e
return tenant, nil
}
// ControlsInputs // map[<control name>][<input arguments>]
func (armoAPI *ArmoAPI) GetAccountConfig(customerGUID, clusterName string) (*armotypes.CustomerConfig, error) {
accountConfig := &armotypes.CustomerConfig{}
if customerGUID == "" {
return accountConfig, nil
}
respStr, err := HttpGetter(armoAPI.httpClient, armoAPI.getAccountConfig(customerGUID, clusterName), nil)
if err != nil {
return nil, err
}
if err = JSONDecoder(respStr).Decode(&accountConfig); err != nil {
return nil, err
}
return accountConfig, nil
}
// ControlsInputs // map[<control name>][<input arguments>]
func (armoAPI *ArmoAPI) GetControlsInputs(customerGUID, clusterName string) (map[string][]string, error) {
accountConfig, err := armoAPI.GetAccountConfig(customerGUID, clusterName)
if err == nil {
return accountConfig.Settings.PostureControlInputs, nil
}
return nil, err
}
func (armoAPI *ArmoAPI) ListCustomFrameworks(customerGUID string) ([]string, error) {
respStr, err := HttpGetter(armoAPI.httpClient, armoAPI.getListFrameworkURL(), nil)
if err != nil {
return nil, err
}
frs := []reporthandling.Framework{}
if err = json.Unmarshal([]byte(respStr), &frs); err != nil {
return nil, err
}
frameworkList := []string{}
for _, fr := range frs {
if !isNativeFramework(fr.Name) {
frameworkList = append(frameworkList, fr.Name)
}
}
return frameworkList, nil
}
func (armoAPI *ArmoAPI) ListFrameworks(customerGUID string) ([]string, error) {
respStr, err := HttpGetter(armoAPI.httpClient, armoAPI.getListFrameworkURL(), nil)
if err != nil {
return nil, err
}
frs := []reporthandling.Framework{}
if err = json.Unmarshal([]byte(respStr), &frs); err != nil {
return nil, err
}
frameworkList := []string{}
for _, fr := range frs {
if isNativeFramework(fr.Name) {
frameworkList = append(frameworkList, strings.ToLower(fr.Name))
} else {
frameworkList = append(frameworkList, fr.Name)
}
}
return frameworkList, nil
}
type TenantResponse struct {
TenantID string `json:"tenantId"`
Token string `json:"token"`

View File

@@ -5,20 +5,37 @@ import (
"strings"
)
var NativeFrameworks = []string{"nsa", "mitre", "armobest"}
func (armoAPI *ArmoAPI) getFrameworkURL(frameworkName string) string {
u := url.URL{}
u.Scheme = "https"
u.Host = armoAPI.apiURL
u.Path = "v1/armoFrameworks"
u.Path = "api/v1/armoFrameworks"
q := u.Query()
q.Add("customerGUID", "11111111-1111-1111-1111-111111111111")
q.Add("frameworkName", strings.ToUpper(frameworkName))
q.Add("getRules", "true")
q.Add("customerGUID", armoAPI.customerGUID)
if isNativeFramework(frameworkName) {
q.Add("frameworkName", strings.ToUpper(frameworkName))
} else {
// For customer framework has to be the way it was added
q.Add("frameworkName", frameworkName)
}
u.RawQuery = q.Encode()
return u.String()
}
func (armoAPI *ArmoAPI) getListFrameworkURL() string {
u := url.URL{}
u.Scheme = "https"
u.Host = armoAPI.apiURL
u.Path = "api/v1/armoFrameworks"
q := u.Query()
q.Add("customerGUID", armoAPI.customerGUID)
u.RawQuery = q.Encode()
return u.String()
}
func (armoAPI *ArmoAPI) getExceptionsURL(customerGUID, clusterName string) string {
u := url.URL{}
u.Scheme = "https"
@@ -35,6 +52,22 @@ func (armoAPI *ArmoAPI) getExceptionsURL(customerGUID, clusterName string) strin
return u.String()
}
func (armoAPI *ArmoAPI) getAccountConfig(customerGUID, clusterName string) string {
u := url.URL{}
u.Scheme = "https"
u.Host = armoAPI.apiURL
u.Path = "api/v1/armoCustomerConfiguration"
q := u.Query()
q.Add("customerGUID", customerGUID)
if clusterName != "" { // TODO - fix customer name support in Armo BE
q.Add("clusterName", clusterName)
}
u.RawQuery = q.Encode()
return u.String()
}
func (armoAPI *ArmoAPI) getCustomerURL() string {
u := url.URL{}
u.Scheme = "https"

View File

@@ -1,12 +1,9 @@
package getter
import (
"encoding/json"
"fmt"
"io"
"net/http"
"time"
"strings"
"github.com/armosec/opa-utils/gitregostore"
"github.com/armosec/opa-utils/reporthandling"
)
@@ -14,73 +11,45 @@ import (
// ======================================== DownloadReleasedPolicy =======================================================
// =======================================================================================================================
// Download released version
// Use gitregostore to get policies from github release
type DownloadReleasedPolicy struct {
hostURL string
httpClient *http.Client
gs *gitregostore.GitRegoStore
}
func NewDownloadReleasedPolicy() *DownloadReleasedPolicy {
return &DownloadReleasedPolicy{
hostURL: "",
httpClient: &http.Client{Timeout: 61 * time.Second},
gs: gitregostore.InitDefaultGitRegoStore(-1),
}
}
func (drp *DownloadReleasedPolicy) GetControl(policyName string) (*reporthandling.Control, error) {
var control *reporthandling.Control
var err error
control, err = drp.gs.GetOPAControl(policyName)
if err != nil {
return nil, err
}
return control, nil
}
func (drp *DownloadReleasedPolicy) GetFramework(name string) (*reporthandling.Framework, error) {
if err := drp.setURL(name); err != nil {
return nil, err
}
respStr, err := HttpGetter(drp.httpClient, drp.hostURL)
framework, err := drp.gs.GetOPAFrameworkByName(name)
if err != nil {
return nil, err
}
framework := &reporthandling.Framework{}
if err = JSONDecoder(respStr).Decode(framework); err != nil {
return framework, err
}
SaveFrameworkInFile(framework, GetDefaultPath(name+".json"))
return framework, err
}
func (drp *DownloadReleasedPolicy) setURL(frameworkName string) error {
func isNativeFramework(framework string) bool {
return contains(NativeFrameworks, framework)
}
latestReleases := "https://api.github.com/repos/armosec/regolibrary/releases/latest"
resp, err := http.Get(latestReleases)
if err != nil {
return fmt.Errorf("failed to get latest releases from '%s', reason: %s", latestReleases, err.Error())
}
defer resp.Body.Close()
if resp.StatusCode < 200 || 301 < resp.StatusCode {
return fmt.Errorf("failed to download file, status code: %s", resp.Status)
}
body, err := io.ReadAll(resp.Body)
if err != nil {
return fmt.Errorf("failed to read response body from '%s', reason: %s", latestReleases, err.Error())
}
var data map[string]interface{}
err = json.Unmarshal(body, &data)
if err != nil {
return fmt.Errorf("failed to unmarshal response body from '%s', reason: %s", latestReleases, err.Error())
}
if assets, ok := data["assets"].([]interface{}); ok {
for i := range assets {
if asset, ok := assets[i].(map[string]interface{}); ok {
if name, ok := asset["name"].(string); ok {
if name == frameworkName {
if url, ok := asset["browser_download_url"].(string); ok {
drp.hostURL = url
return nil
}
}
}
}
func contains(s []string, str string) bool {
for _, v := range s {
if strings.EqualFold(v, str) {
return true
}
}
return fmt.Errorf("failed to download '%s' - not found", frameworkName)
return false
}

View File

@@ -7,6 +7,7 @@ import (
type IPolicyGetter interface {
GetFramework(name string) (*reporthandling.Framework, error)
GetControl(name string) (*reporthandling.Control, error)
}
type IExceptionsGetter interface {
@@ -15,3 +16,7 @@ type IExceptionsGetter interface {
type IBackend interface {
GetCustomerGUID(customerGUID string) (*TenantResponse, error)
}
type IControlsInputsGetter interface {
GetControlsInputs(customerGUID, clusterName string) (map[string][]string, error)
}

View File

@@ -1,6 +1,7 @@
package getter
import (
"bytes"
"encoding/json"
"fmt"
"io"
@@ -21,6 +22,31 @@ func GetDefaultPath(name string) string {
return defaultfilePath
}
// Save control as json in file
func SaveControlInFile(control *reporthandling.Control, pathStr string) error {
encodedData, err := json.Marshal(control)
if err != nil {
return err
}
err = os.WriteFile(pathStr, []byte(fmt.Sprintf("%v", string(encodedData))), 0644)
if err != nil {
if os.IsNotExist(err) {
pathDir := path.Dir(pathStr)
if err := os.Mkdir(pathDir, 0744); err != nil {
return err
}
} else {
return err
}
err = os.WriteFile(pathStr, []byte(fmt.Sprintf("%v", string(encodedData))), 0644)
if err != nil {
return err
}
}
return nil
}
func SaveFrameworkInFile(framework *reporthandling.Framework, pathStr string) error {
encodedData, err := json.Marshal(framework)
if err != nil {
@@ -52,12 +78,14 @@ func JSONDecoder(origin string) *json.Decoder {
return dec
}
func HttpGetter(httpClient *http.Client, fullURL string) (string, error) {
func HttpGetter(httpClient *http.Client, fullURL string, headers map[string]string) (string, error) {
req, err := http.NewRequest("GET", fullURL, nil)
if err != nil {
return "", err
}
addHeaders(req, headers)
resp, err := httpClient.Do(req)
if err != nil {
return "", err
@@ -69,6 +97,32 @@ func HttpGetter(httpClient *http.Client, fullURL string) (string, error) {
return respStr, nil
}
func HttpPost(httpClient *http.Client, fullURL string, headers map[string]string, body []byte) (string, error) {
req, err := http.NewRequest("POST", fullURL, bytes.NewReader(body))
if err != nil {
return "", err
}
addHeaders(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 addHeaders(req *http.Request, headers map[string]string) {
if len(headers) >= 0 { // might be nil
for k, v := range headers {
req.Header.Add(k, v)
}
}
}
// HTTPRespToString parses the body as string and checks the HTTP status code, it closes the body reader at the end
func httpRespToString(resp *http.Response) (string, error) {
if resp == nil || resp.Body == nil {

View File

@@ -17,34 +17,71 @@ const DefaultLocalStore = ".kubescape"
// Load policies from a local repository
type LoadPolicy struct {
filePath string
filePaths []string
}
func NewLoadPolicy(filePath string) *LoadPolicy {
func NewLoadPolicy(filePaths []string) *LoadPolicy {
return &LoadPolicy{
filePath: filePath,
filePaths: filePaths,
}
}
func (lp *LoadPolicy) GetFramework(frameworkName string) (*reporthandling.Framework, error) {
// Return control from file
func (lp *LoadPolicy) GetControl(controlName string) (*reporthandling.Control, error) {
framework := &reporthandling.Framework{}
f, err := os.ReadFile(lp.filePath)
control := &reporthandling.Control{}
filePath := lp.filePath()
f, err := os.ReadFile(filePath)
if err != nil {
return nil, err
}
err = json.Unmarshal(f, framework)
if err = json.Unmarshal(f, control); err != nil {
return control, err
}
if controlName != "" && !strings.EqualFold(controlName, control.Name) && !strings.EqualFold(controlName, control.ControlID) {
framework, err := lp.GetFramework(control.Name)
if err != nil {
return nil, fmt.Errorf("control from file not matching")
} else {
for _, ctrl := range framework.Controls {
if strings.EqualFold(ctrl.Name, controlName) || strings.EqualFold(ctrl.ControlID, controlName) {
control = &ctrl
break
}
}
}
}
return control, err
}
func (lp *LoadPolicy) GetFramework(frameworkName string) (*reporthandling.Framework, error) {
framework := &reporthandling.Framework{}
var err error
for _, filePath := range lp.filePaths {
f, err := os.ReadFile(filePath)
if err != nil {
return nil, err
}
if err = json.Unmarshal(f, framework); err != nil {
return framework, err
}
if strings.EqualFold(frameworkName, framework.Name) {
break
}
}
if frameworkName != "" && !strings.EqualFold(frameworkName, framework.Name) {
return nil, fmt.Errorf("framework from file not matching")
}
return framework, err
}
func (lp *LoadPolicy) GetExceptions(customerGUID, clusterName string) ([]armotypes.PostureExceptionPolicy, error) {
filePath := lp.filePath()
exception := []armotypes.PostureExceptionPolicy{}
f, err := os.ReadFile(lp.filePath)
f, err := os.ReadFile(filePath)
if err != nil {
return nil, err
}
@@ -52,3 +89,25 @@ func (lp *LoadPolicy) GetExceptions(customerGUID, clusterName string) ([]armotyp
err = json.Unmarshal(f, &exception)
return exception, err
}
func (lp *LoadPolicy) GetControlsInputs(customerGUID, clusterName string) (map[string][]string, error) {
filePath := lp.filePath()
accountConfig := &armotypes.CustomerConfig{}
f, err := os.ReadFile(filePath)
if err != nil {
return nil, err
}
if err = json.Unmarshal(f, &accountConfig); err == nil {
return accountConfig.Settings.PostureControlInputs, nil
}
return nil, err
}
// temporary support for a list of files
func (lp *LoadPolicy) filePath() string {
if len(lp.filePaths) > 0 {
return lp.filePaths[0]
}
return ""
}

View File

@@ -1,5 +0,0 @@
package cautils
const (
ComponentIdentifier = "Posture"
)

View File

@@ -1,6 +1,8 @@
package cautils
import (
"io"
"os"
"path/filepath"
"github.com/armosec/kubescape/cautils/getter"
@@ -9,59 +11,80 @@ import (
type ScanInfo struct {
Getters
PolicyIdentifier reporthandling.PolicyIdentifier
UseExceptions string // Load exceptions configuration
UseFrom string // Load framework from local file (instead of download). Use when running offline
PolicyIdentifier []reporthandling.PolicyIdentifier
UseExceptions string // Load file with exceptions configuration
ControlsInputs string // Load file with inputs for controls
UseFrom []string // Load framework from local file (instead of download). Use when running offline
UseDefault bool // Load framework from cached file (instead of download). Use when running offline
Format string // Format results (table, json, junit ...)
Output string // Store results in an output file, Output file name
ExcludedNamespaces string // DEPRECATED?
IncludeNamespaces string // DEPRECATED?
InputPatterns []string // Yaml files input patterns
Silent bool // Silent mode - Do not print progress logs
FailThreshold uint16 // Failure score threshold
Submit bool // Submit results to Armo BE
Local bool // Do not submit results
Account string // account ID
FrameworkScan bool // false if scanning control
ScanAll bool // true if scan all frameworks
}
type Getters struct {
ExceptionsGetter getter.IExceptionsGetter
PolicyGetter getter.IPolicyGetter
ExceptionsGetter getter.IExceptionsGetter
ControlsInputsGetter getter.IControlsInputsGetter
PolicyGetter getter.IPolicyGetter
}
func (scanInfo *ScanInfo) Init() {
scanInfo.setUseFrom()
scanInfo.setUseExceptions()
scanInfo.setAccountConfig()
scanInfo.setOutputFile()
scanInfo.setGetter()
}
func (scanInfo *ScanInfo) setUseExceptions() {
if scanInfo.UseExceptions != "" {
// load exceptions from file
scanInfo.ExceptionsGetter = getter.NewLoadPolicy(scanInfo.UseExceptions)
scanInfo.ExceptionsGetter = getter.NewLoadPolicy([]string{scanInfo.UseExceptions})
} else {
scanInfo.ExceptionsGetter = getter.GetArmoAPIConnector()
}
}
func (scanInfo *ScanInfo) setAccountConfig() {
if scanInfo.ControlsInputs != "" {
// load account config from file
scanInfo.ControlsInputsGetter = getter.NewLoadPolicy([]string{scanInfo.ControlsInputs})
} else {
scanInfo.ControlsInputsGetter = getter.GetArmoAPIConnector()
}
}
func (scanInfo *ScanInfo) setUseFrom() {
if scanInfo.UseFrom != "" {
return
}
if scanInfo.UseDefault {
scanInfo.UseFrom = getter.GetDefaultPath(scanInfo.PolicyIdentifier.Name + ".json")
for _, policy := range scanInfo.PolicyIdentifier {
scanInfo.UseFrom = append(scanInfo.UseFrom, getter.GetDefaultPath(policy.Name+".json"))
}
}
}
func (scanInfo *ScanInfo) setGetter() {
if scanInfo.UseFrom != "" {
// load from file
scanInfo.PolicyGetter = getter.NewLoadPolicy(scanInfo.UseFrom)
} else {
scanInfo.PolicyGetter = getter.NewDownloadReleasedPolicy()
func (scanInfo *ScanInfo) SetInputPatterns(args []string) error {
if args[1] != "-" {
scanInfo.InputPatterns = args[1:]
} else { // store stout to file
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()}
}
return nil
}
func (scanInfo *ScanInfo) setOutputFile() {
@@ -84,7 +107,22 @@ func (scanInfo *ScanInfo) ScanRunningCluster() bool {
return len(scanInfo.InputPatterns) == 0
}
// func (scanInfo *ScanInfo) ConnectedToCluster(k8s k8sinterface.) bool {
// _, err := k8s.KubernetesClient.CoreV1().Pods("").List(context.TODO(), metav1.ListOptions{})
// return err == nil
// }
func (scanInfo *ScanInfo) SetPolicyIdentifierForGivenFrameworks(frameworks []string) {
for _, framework := range frameworks {
if !scanInfo.contains(framework) {
newPolicy := reporthandling.PolicyIdentifier{}
newPolicy.Kind = reporthandling.KindFramework
newPolicy.Name = framework
scanInfo.PolicyIdentifier = append(scanInfo.PolicyIdentifier, newPolicy)
}
}
}
func (scanInfo *ScanInfo) contains(framework string) bool {
for _, policy := range scanInfo.PolicyIdentifier {
if policy.Name == framework {
return true
}
}
return false
}

112
cautils/versioncheck.go Normal file
View File

@@ -0,0 +1,112 @@
package cautils
import (
"encoding/json"
"fmt"
"net/http"
"os"
"github.com/armosec/kubescape/cautils/getter"
pkgutils "github.com/armosec/utils-go/utils"
)
const SKIP_VERSION_CHECK = "KUBESCAPE_SKIP_UPDATE_CHECK"
var BuildNumber string
type IVersionCheckHandler interface {
CheckLatestVersion(*VersionCheckRequest) error
}
func NewIVersionCheckHandler() IVersionCheckHandler {
if v, ok := os.LookupEnv(SKIP_VERSION_CHECK); ok && pkgutils.StringToBool(v) {
return NewVersionCheckHandlerMock()
}
return NewVersionCheckHandler()
}
type VersionCheckHandlerMock struct {
}
func NewVersionCheckHandlerMock() *VersionCheckHandlerMock {
return &VersionCheckHandlerMock{}
}
type VersionCheckHandler struct {
versionURL string
}
type VersionCheckRequest struct {
Client string `json:"client"` // kubescape
ClientVersion string `json:"clientVersion"` // kubescape version
Framework string `json:"framework"` // framework name
FrameworkVersion string `json:"frameworkVersion"` // framework version
ScanningTarget string `json:"target"` // scanning target- cluster/yaml
}
type VersionCheckResponse struct {
Client string `json:"client"` // kubescape
ClientUpdate string `json:"clientUpdate"` // kubescape latest version
Framework string `json:"framework"` // framework name
FrameworkUpdate string `json:"frameworkUpdate"` // framework latest version
Message string `json:"message"` // alert message
}
func NewVersionCheckHandler() *VersionCheckHandler {
return &VersionCheckHandler{
versionURL: "https://us-central1-elated-pottery-310110.cloudfunctions.net/ksgf1v1",
}
}
func NewVersionCheckRequest(buildNumber, frameworkName, frameworkVersion, scanningTarget string) *VersionCheckRequest {
return &VersionCheckRequest{
Client: "kubescape",
ClientVersion: buildNumber,
Framework: frameworkName,
FrameworkVersion: frameworkVersion,
ScanningTarget: scanningTarget,
}
}
func (v *VersionCheckHandlerMock) CheckLatestVersion(versionData *VersionCheckRequest) error {
fmt.Println("Skipping version check")
return nil
}
func (v *VersionCheckHandler) CheckLatestVersion(versionData *VersionCheckRequest) error {
latestVersion, err := v.getLatestVersion(versionData)
if err != nil || latestVersion == nil {
return fmt.Errorf("failed to get latest version: %v", err)
}
if latestVersion.ClientUpdate != "" {
fmt.Println(warningMessage(latestVersion.Client, latestVersion.ClientUpdate))
}
if latestVersion.FrameworkUpdate != "" {
fmt.Println(warningMessage(latestVersion.Framework, latestVersion.FrameworkUpdate))
}
return nil
}
func (v *VersionCheckHandler) getLatestVersion(versionData *VersionCheckRequest) (*VersionCheckResponse, error) {
reqBody, err := json.Marshal(versionData)
if err != nil {
return nil, fmt.Errorf("in 'CheckLatestVersion' failed to json.Marshal, reason: %v", err)
}
resp, err := getter.HttpPost(http.DefaultClient, v.versionURL, map[string]string{"Content-Type": "application/json"}, reqBody)
if err != nil {
return nil, err
}
vResp := &VersionCheckResponse{}
if err = getter.JSONDecoder(resp).Decode(vResp); err != nil {
return nil, err
}
return vResp, nil
}
func warningMessage(kind, release string) string {
return fmt.Sprintf("Warning: '%s' is not updated to the latest release: '%s'", kind, release)
}

View File

@@ -0,0 +1,17 @@
package cliinterfaces
import (
"github.com/armosec/kubescape/cautils"
"github.com/armosec/kubescape/resultshandling/reporter"
"github.com/armosec/opa-utils/reporthandling"
)
type ISubmitObjects interface {
SetResourcesReport() (*reporthandling.PostureReport, error)
}
type SubmitInterfaces struct {
SubmitObjects ISubmitObjects
Reporter reporter.IReport
ClusterConfig cautils.IClusterConfig
}

View File

@@ -14,7 +14,7 @@ var getCmd = &cobra.Command{
Use: "get <key>",
Short: "Get configuration in cluster",
Long: ``,
ValidArgs: supportedFrameworks,
ValidArgs: getter.NativeFrameworks,
Args: func(cmd *cobra.Command, args []string) error {
if len(args) < 1 || len(args) > 1 {
return fmt.Errorf("requires one argument")

95
clihandler/cmd/control.go Normal file
View File

@@ -0,0 +1,95 @@
package cmd
import (
"fmt"
"os"
"strings"
"github.com/armosec/kubescape/cautils"
"github.com/armosec/kubescape/cautils/getter"
"github.com/armosec/kubescape/clihandler"
"github.com/armosec/opa-utils/reporthandling"
"github.com/spf13/cobra"
)
// controlCmd represents the control command
var controlCmd = &cobra.Command{
Use: "control <control names list>/<control ids list>.\nExamples:\n$ kubescape scan control C-0058,C-0057 [flags]\n$ kubescape scan contol C-0058 [flags]\n$ kubescape scan control 'privileged container,allowed hostpath' [flags]",
Short: fmt.Sprintf("The control you wish to use for scan. It must be present in at least one of the folloiwng frameworks: %s", getter.NativeFrameworks),
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_one>,<control_two>")
}
}
} 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.SetPolicyIdentifierForGivenFrameworks(getter.NativeFrameworks)
} else {
controls := strings.Split(args[0], ",")
scanInfo.PolicyIdentifier = []reporthandling.PolicyIdentifier{}
scanInfo.PolicyIdentifier = setScanForFirstControl(controls)
if len(controls) > 1 {
scanInfo.PolicyIdentifier = SetScanForGivenControls(controls[1:])
}
if len(args) > 1 {
// Set scan to run on yamls
if err := scanInfo.SetInputPatterns(args); err != nil {
return err
}
}
}
scanInfo.FrameworkScan = false
scanInfo.Init()
cautils.SetSilentMode(scanInfo.Silent)
err := clihandler.ScanCliSetup(&scanInfo)
if err != nil {
fmt.Fprintf(os.Stderr, "error: %v\n", err)
os.Exit(1)
}
return nil
},
}
func init() {
scanInfo = cautils.ScanInfo{}
scanCmd.AddCommand(controlCmd)
}
func flagValidationControl() {
if 100 < scanInfo.FailThreshold {
fmt.Println("bad argument: out of range threshold")
os.Exit(1)
}
}
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,66 @@
package cmd
import (
"fmt"
"strings"
"github.com/armosec/kubescape/cautils"
"github.com/armosec/kubescape/cautils/getter"
"github.com/spf13/cobra"
)
var downloadInfo cautils.DownloadInfo
var downloadCmd = &cobra.Command{
Use: fmt.Sprintf("download framework/control <framework-name>/<control-name> [flags]\nSupported frameworks: %s", getter.NativeFrameworks),
Short: "Download framework/control",
Long: ``,
Args: func(cmd *cobra.Command, args []string) error {
if len(args) != 2 {
return fmt.Errorf("requires two arguments : framework/control <framework-name>/<control-name>")
}
if !strings.EqualFold(args[0], "framework") && !strings.EqualFold(args[0], "control") {
return fmt.Errorf("invalid parameter '%s'. Supported parameters: framework, control", args[0])
}
return nil
},
RunE: func(cmd *cobra.Command, args []string) error {
if strings.EqualFold(args[0], "framework") {
downloadInfo.FrameworkName = strings.ToLower(args[1])
g := getter.NewDownloadReleasedPolicy()
if downloadInfo.Path == "" {
downloadInfo.Path = getter.GetDefaultPath(downloadInfo.FrameworkName + ".json")
}
frameworks, err := g.GetFramework(downloadInfo.FrameworkName)
if err != nil {
return err
}
err = getter.SaveFrameworkInFile(frameworks, downloadInfo.Path)
if err != nil {
return err
}
} else if strings.EqualFold(args[0], "control") {
downloadInfo.ControlName = strings.ToLower(args[1])
g := getter.NewDownloadReleasedPolicy()
if downloadInfo.Path == "" {
downloadInfo.Path = getter.GetDefaultPath(downloadInfo.ControlName + ".json")
}
controls, err := g.GetControl(downloadInfo.ControlName)
if err != nil {
return err
}
err = getter.SaveControlInFile(controls, downloadInfo.Path)
if err != nil {
return err
}
}
return nil
},
}
func init() {
rootCmd.AddCommand(downloadCmd)
downloadInfo = cautils.DownloadInfo{}
downloadCmd.Flags().StringVarP(&downloadInfo.Path, "output", "o", "", "Output file. If specified, will store save to `~/.kubescape/<framework name>.json`")
}

View File

@@ -0,0 +1,82 @@
package cmd
import (
"fmt"
"os"
"strings"
"github.com/armosec/kubescape/cautils"
"github.com/armosec/kubescape/cautils/getter"
"github.com/armosec/kubescape/clihandler"
"github.com/armosec/opa-utils/reporthandling"
"github.com/spf13/cobra"
)
var frameworkCmd = &cobra.Command{
Use: fmt.Sprintf("framework <framework names list> [`<glob pattern>`/`-`] [flags]\nExamples:\n$ kubescape scan framework nsa [flags]\n$ kubescape scan framework mitre,nsa [flags]\n$ kubescape scan framework 'nsa, mitre' [flags]\nSupported frameworks: %s", getter.NativeFrameworks),
Short: fmt.Sprintf("The framework you wish to use. Supported frameworks: %s", strings.Join(getter.NativeFrameworks, ", ")),
Long: "Execute a scan on a running Kubernetes cluster or `yaml`/`json` files (use glob) or `-` for stdin",
ValidArgs: getter.NativeFrameworks,
Args: func(cmd *cobra.Command, args []string) error {
if len(args) == 0 {
return fmt.Errorf("requires at least one framework name")
}
return nil
},
RunE: func(cmd *cobra.Command, args []string) error {
flagValidationFramework()
scanInfo.PolicyIdentifier = []reporthandling.PolicyIdentifier{}
// If no framework provided, use all
if len(args) == 0 {
scanInfo.SetPolicyIdentifierForGivenFrameworks(getter.NativeFrameworks)
scanInfo.ScanAll = true
} else {
// Read frameworks from input args
scanInfo.PolicyIdentifier = []reporthandling.PolicyIdentifier{}
frameworks := strings.Split(strings.Join(strings.Fields(args[0]), ""), ",")
scanInfo.PolicyIdentifier = SetScanForFirstFramework(frameworks)
if len(frameworks) > 1 {
scanInfo.SetPolicyIdentifierForGivenFrameworks(frameworks[1:])
}
if len(args) > 1 {
// expected yaml/url input
if err := scanInfo.SetInputPatterns(args); err != nil {
return err
}
}
}
scanInfo.Init()
cautils.SetSilentMode(scanInfo.Silent)
err := clihandler.ScanCliSetup(&scanInfo)
if err != nil {
return err
}
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 {
fmt.Println("You can use `keep-local` or `submit`, but not both")
os.Exit(1)
}
if 100 < scanInfo.FailThreshold {
fmt.Println("bad argument: out of range threshold")
os.Exit(1)
}
}

80
clihandler/cmd/rbac.go Normal file
View File

@@ -0,0 +1,80 @@
package cmd
import (
"fmt"
"os"
"time"
"github.com/armosec/k8s-interface/k8sinterface"
"github.com/armosec/kubescape/clihandler"
"github.com/armosec/kubescape/clihandler/cliinterfaces"
"github.com/armosec/kubescape/resultshandling/reporter"
"github.com/armosec/opa-utils/reporthandling"
"github.com/armosec/rbac-utils/rbacscanner"
uuid "github.com/satori/go.uuid"
"github.com/spf13/cobra"
)
type RBACObjects struct {
scanner *rbacscanner.RbacScannerFromK8sAPI
}
func NewRBACObjects(scanner *rbacscanner.RbacScannerFromK8sAPI) *RBACObjects {
return &RBACObjects{scanner: scanner}
}
func (rbacObjects *RBACObjects) SetResourcesReport() (*reporthandling.PostureReport, error) {
resources, err := rbacObjects.scanner.ListResources()
if err != nil {
return nil, err
}
return &reporthandling.PostureReport{
ReportID: uuid.NewV4().String(),
ReportGenerationTime: time.Now().UTC(),
CustomerGUID: rbacObjects.scanner.CustomerGUID,
ClusterName: rbacObjects.scanner.ClusterName,
RBACObjects: *resources,
}, nil
}
// 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, err := getSubmittedClusterConfig(k8s)
if err != nil {
return err
}
clusterName := clusterConfig.GetClusterName()
customerGUID := clusterConfig.GetCustomerGUID()
// list RBAC
rbacObjects := NewRBACObjects(rbacscanner.NewRbacScannerFromK8sAPI(k8s, customerGUID, clusterName))
// submit resources
r := reporter.NewReportEventReceiver(customerGUID, clusterName)
submitInterfaces := cliinterfaces.SubmitInterfaces{
ClusterConfig: clusterConfig,
SubmitObjects: rbacObjects,
Reporter: r,
}
if err := clihandler.Submit(submitInterfaces); err != nil {
fmt.Println(err)
os.Exit(1)
}
return nil
},
}
func init() {
submitCmd.AddCommand(rabcCmd)
}

105
clihandler/cmd/results.go Normal file
View File

@@ -0,0 +1,105 @@
package cmd
import (
"encoding/json"
"fmt"
"os"
"time"
"github.com/armosec/k8s-interface/k8sinterface"
"github.com/armosec/kubescape/clihandler"
"github.com/armosec/kubescape/clihandler/cliinterfaces"
"github.com/armosec/kubescape/resultshandling/reporter"
"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
}
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 := k8sinterface.NewKubernetesApi()
// get config
clusterConfig, err := getSubmittedClusterConfig(k8s)
if err != nil {
return err
}
clusterName := clusterConfig.GetClusterName()
customerGUID := clusterConfig.GetCustomerGUID()
resultsObjects := NewResultsObject(customerGUID, clusterName, args[0])
// submit resources
r := reporter.NewReportEventReceiver(customerGUID, clusterName)
submitInterfaces := cliinterfaces.SubmitInterfaces{
ClusterConfig: clusterConfig,
SubmitObjects: resultsObjects,
Reporter: r,
}
if err := clihandler.Submit(submitInterfaces); err != nil {
fmt.Println(err)
os.Exit(1)
}
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

@@ -31,6 +31,7 @@ func Execute() {
}
func init() {
rootCmd.PersistentFlags().StringVarP(&scanInfo.Account, "account", "", "", "Armo portal account ID. Default will load account ID from configMap or config file")
flag.CommandLine.StringVar(&armoBEURLs, "environment", "", envFlagUsage)
rootCmd.PersistentFlags().StringVar(&armoBEURLs, "environment", "", envFlagUsage)
rootCmd.PersistentFlags().MarkHidden("environment")

51
clihandler/cmd/scan.go Normal file
View File

@@ -0,0 +1,51 @@
package cmd
import (
"fmt"
"strings"
"github.com/armosec/kubescape/cautils"
"github.com/armosec/kubescape/cautils/getter"
"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 !strings.EqualFold(args[0], "framework") && !strings.EqualFold(args[0], "control") {
return fmt.Errorf("invalid parameter '%s'. Supported parameters: framework, control", args[0])
}
}
return nil
},
Run: func(cmd *cobra.Command, args []string) {
if len(args) == 0 {
scanInfo.ScanAll = true
frameworks := getter.NativeFrameworks
frameworkArgs := []string{strings.Join(frameworks, ",")}
frameworkCmd.RunE(cmd, frameworkArgs)
}
},
}
func init() {
rootCmd.AddCommand(scanCmd)
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().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.ExcludedNamespaces, "exclude-namespaces", "e", "", "Namespaces to exclude from scanning. Recommended: kube-system,kube-public")
scanCmd.PersistentFlags().StringVar(&scanInfo.IncludeNamespaces, "include-namespaces", "", "scan specific namespaces. e.g: --include-namespaces ns-a,ns-b")
scanCmd.PersistentFlags().StringVarP(&scanInfo.Format, "format", "f", "pretty-printer", `Output format. Supported formats: "pretty-printer"/"json"/"junit"/"prometheus"`)
scanCmd.PersistentFlags().StringVarP(&scanInfo.Output, "output", "o", "", "Output file. Print output to file and not stdout")
scanCmd.PersistentFlags().BoolVarP(&scanInfo.Silent, "silent", "s", false, "Silent progress messages")
scanCmd.PersistentFlags().Uint16VarP(&scanInfo.FailThreshold, "fail-threshold", "t", 0, "Failure threshold is the percent below which the command fails and returns exit code 1")
scanCmd.PersistentFlags().StringSliceVar(&scanInfo.UseFrom, "use-from", nil, "Load local policy object from specified path. If not used will download latest")
scanCmd.PersistentFlags().BoolVar(&scanInfo.UseDefault, "use-default", false, "Load local policy object from default path. If not used will download latest")
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.ControlsInputs, "controls-config", "", "Path to an controls-config obj. If not set will download controls-config from ARMO management portal")
}

27
clihandler/cmd/submit.go Normal file
View File

@@ -0,0 +1,27 @@
package cmd
import (
"github.com/armosec/k8s-interface/k8sinterface"
"github.com/armosec/kubescape/cautils"
"github.com/armosec/kubescape/cautils/getter"
"github.com/spf13/cobra"
)
var submitCmd = &cobra.Command{
Use: "submit <command>",
Short: "Submit an object to the Kubescape SaaS version",
Long: ``,
Run: func(cmd *cobra.Command, args []string) {
},
}
func init() {
rootCmd.AddCommand(submitCmd)
}
func getSubmittedClusterConfig(k8s *k8sinterface.KubernetesApi) (*cautils.ClusterConfig, error) {
clusterConfig := cautils.NewClusterConfig(k8s, getter.GetArmoAPIConnector())
clusterConfig.LoadConfig()
err := clusterConfig.SetConfig(scanInfo.Account)
return clusterConfig, err
}

24
clihandler/cmd/version.go Normal file
View File

@@ -0,0 +1,24 @@
package cmd
import (
"fmt"
"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, "", "", ""))
fmt.Println("Your current version is: " + cautils.BuildNumber)
return nil
},
}
func init() {
rootCmd.AddCommand(versionCmd)
}

195
clihandler/initcli.go Normal file
View File

@@ -0,0 +1,195 @@
package clihandler
import (
"fmt"
"os"
"github.com/armosec/armoapi-go/armotypes"
"github.com/armosec/k8s-interface/k8sinterface"
"github.com/armosec/kubescape/cautils"
"github.com/armosec/kubescape/cautils/getter"
"github.com/armosec/kubescape/clihandler/cliinterfaces"
"github.com/armosec/kubescape/opaprocessor"
"github.com/armosec/kubescape/policyhandler"
"github.com/armosec/kubescape/resourcehandler"
"github.com/armosec/kubescape/resultshandling"
"github.com/armosec/kubescape/resultshandling/printer"
"github.com/armosec/kubescape/resultshandling/reporter"
"github.com/armosec/opa-utils/reporthandling"
"github.com/golang/glog"
)
type componentInterfaces struct {
clusterConfig cautils.IClusterConfig
resourceHandler resourcehandler.IResourceHandler
report reporter.IReport
printerHandler printer.IPrinter
}
func getReporter(scanInfo *cautils.ScanInfo) reporter.IReport {
if !scanInfo.Submit {
return reporter.NewReportMock()
}
if !scanInfo.FrameworkScan {
return reporter.NewReportMock()
}
return reporter.NewReportEventReceiver("", "")
}
func getFieldSelector(scanInfo *cautils.ScanInfo) resourcehandler.IFieldSelector {
if scanInfo.IncludeNamespaces != "" {
return resourcehandler.NewIncludeSelector(scanInfo.IncludeNamespaces)
}
if scanInfo.ExcludedNamespaces != "" {
return resourcehandler.NewExcludeSelector(scanInfo.ExcludedNamespaces)
}
return &resourcehandler.EmptySelector{}
}
func getInterfaces(scanInfo *cautils.ScanInfo) componentInterfaces {
var resourceHandler resourcehandler.IResourceHandler
var clusterConfig cautils.IClusterConfig
var reportHandler reporter.IReport
var scanningTarget string
if !scanInfo.ScanRunningCluster() {
k8sinterface.ConnectedToCluster = false
clusterConfig = cautils.NewEmptyConfig()
// load fom file
resourceHandler = resourcehandler.NewFileResourceHandler(scanInfo.InputPatterns)
// set mock report (do not send report)
reportHandler = reporter.NewReportMock()
scanningTarget = "yaml"
} else {
k8s := k8sinterface.NewKubernetesApi()
resourceHandler = resourcehandler.NewK8sResourceHandler(k8s, getFieldSelector(scanInfo))
clusterConfig = cautils.ClusterConfigSetup(scanInfo, k8s, getter.GetArmoAPIConnector())
// setup reporter
reportHandler = getReporter(scanInfo)
scanningTarget = "cluster"
}
v := cautils.NewIVersionCheckHandler()
v.CheckLatestVersion(cautils.NewVersionCheckRequest(cautils.BuildNumber, "", "", scanningTarget))
// setup printer
printerHandler := printer.GetPrinter(scanInfo.Format)
printerHandler.SetWriter(scanInfo.Output)
return componentInterfaces{
clusterConfig: clusterConfig,
resourceHandler: resourceHandler,
report: reportHandler,
printerHandler: printerHandler,
}
}
func setPolicyGetter(scanInfo *cautils.ScanInfo, customerGUID string) {
if len(scanInfo.UseFrom) > 0 {
//load from file
scanInfo.PolicyGetter = getter.NewLoadPolicy(scanInfo.UseFrom)
} else {
if customerGUID == "" || !scanInfo.FrameworkScan {
scanInfo.PolicyGetter = getter.NewDownloadReleasedPolicy()
} else {
g := getter.GetArmoAPIConnector()
g.SetCustomerGUID(customerGUID)
scanInfo.PolicyGetter = g
if scanInfo.ScanAll {
frameworks, err := g.ListCustomFrameworks(customerGUID)
if err != nil {
glog.Error("could not get custom frameworks")
}
scanInfo.SetPolicyIdentifierForGivenFrameworks(frameworks)
}
}
}
}
func ScanCliSetup(scanInfo *cautils.ScanInfo) error {
interfaces := getInterfaces(scanInfo)
setPolicyGetter(scanInfo, interfaces.clusterConfig.GetCustomerGUID())
processNotification := make(chan *cautils.OPASessionObj)
reportResults := make(chan *cautils.OPASessionObj)
if err := interfaces.clusterConfig.SetConfig(scanInfo.Account); err != nil {
fmt.Println(err)
}
cautils.ClusterName = interfaces.clusterConfig.GetClusterName() // TODO - Deprecated
cautils.CustomerGUID = interfaces.clusterConfig.GetCustomerGUID() // TODO - Deprecated
interfaces.report.SetClusterName(interfaces.clusterConfig.GetClusterName())
interfaces.report.SetCustomerGUID(interfaces.clusterConfig.GetCustomerGUID())
// cli handler setup
go func() {
// policy handler setup
policyHandler := policyhandler.NewPolicyHandler(&processNotification, interfaces.resourceHandler)
if err := Scan(policyHandler, scanInfo); err != nil {
fmt.Println(err)
os.Exit(1)
}
}()
// 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.clusterConfig.GenerateURL()
adjustedFailThreshold := float32(scanInfo.FailThreshold) / 100
if score < adjustedFailThreshold {
return fmt.Errorf("Scan score is below threshold")
}
return nil
}
func Scan(policyHandler *policyhandler.PolicyHandler, scanInfo *cautils.ScanInfo) error {
cautils.ScanStartDisplay()
policyNotification := &reporthandling.PolicyNotification{
NotificationType: reporthandling.TypeExecPostureScan,
Rules: scanInfo.PolicyIdentifier,
Designators: armotypes.PortalDesignator{},
}
switch policyNotification.NotificationType {
case reporthandling.TypeExecPostureScan:
if err := policyHandler.HandleNotificationRequest(policyNotification, scanInfo); err != nil {
return err
}
default:
return fmt.Errorf("notification type '%s' Unknown", policyNotification.NotificationType)
}
return nil
}
func Submit(submitInterfaces cliinterfaces.SubmitInterfaces) error {
// list resources
postureReport, err := submitInterfaces.SubmitObjects.SetResourcesReport()
if err != nil {
return err
}
// report
if err := submitInterfaces.Reporter.ActionSendReport(&cautils.OPASessionObj{PostureReport: postureReport}); err != nil {
return err
}
fmt.Printf("\nData has been submitted successfully")
submitInterfaces.ClusterConfig.GenerateURL()
return nil
}

View File

@@ -1,45 +0,0 @@
package cmd
import (
"fmt"
"github.com/armosec/kubescape/cautils"
"github.com/armosec/kubescape/cautils/getter"
"github.com/spf13/cobra"
)
var downloadInfo cautils.DownloadInfo
var downloadCmd = &cobra.Command{
Use: fmt.Sprintf("download framework <framework-name> [flags]\nSupported frameworks: %s", validFrameworks),
Short: "Download framework controls",
Long: ``,
Args: func(cmd *cobra.Command, args []string) error {
if len(args) != 2 {
return fmt.Errorf("requires two arguments : framework <framework-name>")
}
return nil
},
RunE: func(cmd *cobra.Command, args []string) error {
downloadInfo.FrameworkName = args[1]
g := getter.NewDownloadReleasedPolicy()
if downloadInfo.Path == "" {
downloadInfo.Path = getter.GetDefaultPath(downloadInfo.FrameworkName + ".json")
}
frameworks, err := g.GetFramework(downloadInfo.FrameworkName)
if err != nil {
return err
}
err = getter.SaveFrameworkInFile(frameworks, downloadInfo.Path)
if err != nil {
return err
}
return nil
},
}
func init() {
rootCmd.AddCommand(downloadCmd)
downloadInfo = cautils.DownloadInfo{}
downloadCmd.Flags().StringVarP(&downloadInfo.Path, "output", "o", "", "Output file. If specified, will store save to `~/.kubescape/<framework name>.json`")
}

View File

@@ -1,197 +0,0 @@
package cmd
import (
"fmt"
"io"
"os"
"strings"
"github.com/armosec/armoapi-go/armotypes"
"github.com/armosec/k8s-interface/k8sinterface"
"github.com/armosec/kubescape/cautils"
"github.com/armosec/kubescape/cautils/getter"
"github.com/armosec/kubescape/opaprocessor"
"github.com/armosec/kubescape/policyhandler"
"github.com/armosec/kubescape/resultshandling"
"github.com/armosec/kubescape/resultshandling/printer"
"github.com/armosec/kubescape/resultshandling/reporter"
"github.com/armosec/opa-utils/reporthandling"
"github.com/spf13/cobra"
)
var scanInfo cautils.ScanInfo
var supportedFrameworks = []string{"nsa", "mitre"}
var validFrameworks = strings.Join(supportedFrameworks, ", ")
type CLIHandler struct {
policyHandler *policyhandler.PolicyHandler
scanInfo *cautils.ScanInfo
}
var frameworkCmd = &cobra.Command{
Use: fmt.Sprintf("framework <framework name> [`<glob pattern>`/`-`] [flags]\nSupported frameworks: %s", validFrameworks),
Short: fmt.Sprintf("The framework you wish to use. Supported frameworks: %s", strings.Join(supportedFrameworks, ", ")),
Long: "Execute a scan on a running Kubernetes cluster or `yaml`/`json` files (use glob) or `-` for stdin",
ValidArgs: supportedFrameworks,
Args: func(cmd *cobra.Command, args []string) error {
if len(args) < 1 && !(cmd.Flags().Lookup("use-from").Changed) {
return fmt.Errorf("requires at least one argument")
} else if len(args) > 0 {
if !isValidFramework(strings.ToLower(args[0])) {
return fmt.Errorf(fmt.Sprintf("supported frameworks: %s", strings.Join(supportedFrameworks, ", ")))
}
}
return nil
},
RunE: func(cmd *cobra.Command, args []string) error {
scanInfo.PolicyIdentifier = reporthandling.PolicyIdentifier{}
scanInfo.PolicyIdentifier.Kind = reporthandling.KindFramework
if !(cmd.Flags().Lookup("use-from").Changed) {
scanInfo.PolicyIdentifier.Name = strings.ToLower(args[0])
}
if len(args) > 0 {
if len(args[1:]) == 0 || args[1] != "-" {
scanInfo.InputPatterns = args[1:]
} else { // store stout to file
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.Init()
cautils.SetSilentMode(scanInfo.Silent)
err := CliSetup()
if err != nil {
fmt.Fprintf(os.Stderr, "error: %v\n", err)
os.Exit(1)
}
return nil
},
}
func isValidFramework(framework string) bool {
return cautils.StringInSlice(supportedFrameworks, framework) != cautils.ValueNotFound
}
func init() {
scanCmd.AddCommand(frameworkCmd)
scanInfo = cautils.ScanInfo{}
frameworkCmd.Flags().StringVar(&scanInfo.UseFrom, "use-from", "", "Load local framework object from specified path. If not used will download latest")
frameworkCmd.Flags().BoolVar(&scanInfo.UseDefault, "use-default", false, "Load local framework object from default path. If not used will download latest")
frameworkCmd.Flags().StringVar(&scanInfo.UseExceptions, "exceptions", "", "Path to an exceptions obj. If not set will download exceptions from Armo management portal")
frameworkCmd.Flags().StringVarP(&scanInfo.ExcludedNamespaces, "exclude-namespaces", "e", "", "Namespaces to exclude from scanning. Recommended: kube-system, kube-public")
frameworkCmd.Flags().StringVarP(&scanInfo.Format, "format", "f", "pretty-printer", `Output format. Supported formats: "pretty-printer"/"json"/"junit"`)
frameworkCmd.Flags().StringVarP(&scanInfo.Output, "output", "o", "", "Output file. Print output to file and not stdout")
frameworkCmd.Flags().BoolVarP(&scanInfo.Silent, "silent", "s", false, "Silent progress messages")
frameworkCmd.Flags().Uint16VarP(&scanInfo.FailThreshold, "fail-threshold", "t", 0, "Failure threshold is the percent bellow which the command fails and returns exit code 1")
frameworkCmd.Flags().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")
frameworkCmd.Flags().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")
frameworkCmd.Flags().StringVarP(&scanInfo.Account, "account", "", "", "Armo portal account ID. Default will load account ID from configMap or config file")
}
func CliSetup() error {
flagValidation()
var k8s *k8sinterface.KubernetesApi
var clusterConfig cautils.IClusterConfig
if !scanInfo.ScanRunningCluster() {
k8sinterface.ConnectedToCluster = false
clusterConfig = cautils.NewEmptyConfig()
} else {
k8s = k8sinterface.NewKubernetesApi()
// setup cluster config
clusterConfig = cautils.ClusterConfigSetup(&scanInfo, k8s, getter.GetArmoAPIConnector())
}
processNotification := make(chan *cautils.OPASessionObj)
reportResults := make(chan *cautils.OPASessionObj)
// policy handler setup
policyHandler := policyhandler.NewPolicyHandler(&processNotification, k8s)
if err := clusterConfig.SetCustomerGUID(scanInfo.Account); err != nil {
fmt.Println(err)
}
cautils.CustomerGUID = clusterConfig.GetCustomerGUID()
cautils.ClusterName = k8sinterface.GetClusterName()
// cli handler setup
go func() {
cli := NewCLIHandler(policyHandler)
if err := cli.Scan(); err != nil {
fmt.Println(err)
os.Exit(1)
}
}()
// processor setup - rego run
go func() {
opaprocessorObj := opaprocessor.NewOPAProcessorHandler(&processNotification, &reportResults)
opaprocessorObj.ProcessRulesListenner()
}()
resultsHandling := resultshandling.NewResultsHandler(&reportResults, reporter.NewReportEventReceiver(), printer.NewPrinter(scanInfo.Format, scanInfo.Output))
score := resultsHandling.HandleResults()
// print report url
clusterConfig.GenerateURL()
adjustedFailThreshold := float32(scanInfo.FailThreshold) / 100
if score < adjustedFailThreshold {
return fmt.Errorf("Scan score is bellow threshold")
}
return nil
}
func NewCLIHandler(policyHandler *policyhandler.PolicyHandler) *CLIHandler {
return &CLIHandler{
scanInfo: &scanInfo,
policyHandler: policyHandler,
}
}
func (clihandler *CLIHandler) Scan() error {
cautils.ScanStartDisplay()
policyNotification := &reporthandling.PolicyNotification{
NotificationType: reporthandling.TypeExecPostureScan,
Rules: []reporthandling.PolicyIdentifier{
clihandler.scanInfo.PolicyIdentifier,
},
Designators: armotypes.PortalDesignator{},
}
switch policyNotification.NotificationType {
case reporthandling.TypeExecPostureScan:
//
if err := clihandler.policyHandler.HandleNotificationRequest(policyNotification, clihandler.scanInfo); err != nil {
return err
}
default:
return fmt.Errorf("notification type '%s' Unknown", policyNotification.NotificationType)
}
return nil
}
func flagValidation() {
if scanInfo.Submit && scanInfo.Local {
fmt.Println("You can use `keep-local` or `submit`, but not both")
os.Exit(1)
}
if 100 < scanInfo.FailThreshold {
fmt.Println("bad argument: out of range threshold")
os.Exit(1)
}
}

View File

@@ -1,18 +0,0 @@
package cmd
import (
"github.com/spf13/cobra"
)
// scanCmd represents the scan command
var scanCmd = &cobra.Command{
Use: "scan",
Short: "Scan the current running cluster or yaml files",
Long: `The action you want to perform`,
Run: func(cmd *cobra.Command, args []string) {
},
}
func init() {
rootCmd.AddCommand(scanCmd)
}

View File

@@ -1,49 +0,0 @@
package cmd
import (
"encoding/json"
"fmt"
"io"
"net/http"
"github.com/spf13/cobra"
)
var BuildNumber string
var versionCmd = &cobra.Command{
Use: "version",
Short: "Get current version",
Long: ``,
RunE: func(cmd *cobra.Command, args []string) error {
fmt.Println("Your current version is: " + BuildNumber)
return nil
},
}
func GetLatestVersion() (string, error) {
latestVersion := "https://api.github.com/repos/armosec/kubescape/releases/latest"
resp, err := http.Get(latestVersion)
if err != nil {
return "", fmt.Errorf("failed to get latest releases from '%s', reason: %s", latestVersion, err.Error())
}
defer resp.Body.Close()
if resp.StatusCode < 200 || 301 < resp.StatusCode {
return "", fmt.Errorf("failed to download file, status code: %s", resp.Status)
}
body, err := io.ReadAll(resp.Body)
if err != nil {
return "", fmt.Errorf("failed to read response body from '%s', reason: %s", latestVersion, err.Error())
}
var data map[string]interface{}
err = json.Unmarshal(body, &data)
if err != nil {
return "", fmt.Errorf("failed to unmarshal response body from '%s', reason: %s", latestVersion, err.Error())
}
return fmt.Sprintf("%v", data["tag_name"]), nil
}
func init() {
rootCmd.AddCommand(versionCmd)
}

View File

@@ -13,7 +13,7 @@ kubescape scan framework nsa --exclude-namespaces kube-system,kube-public
| --- | --- | --- | --- |
| `-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 bellow threshold| `0` -> `100` |
| `-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 |
@@ -51,7 +51,7 @@ kubescape scan framework nsa --exclude-namespaces kube-system,kube-public --form
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` <img src="docs/new-feature.svg">
* Scan with exceptions, objects with exceptions will be presented as `warning` and not `fail`
```
kubescape scan framework nsa --exceptions examples/exceptions.json
```
@@ -85,5 +85,3 @@ 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

@@ -0,0 +1,85 @@
# Periodically Kubescape Scanning
You can scan your cluster periodically by adding a `CronJob` that will repeatedly trigger kubescape
* Setup [scanning & submitting](#scanning-and-submitting)
* Setup [scanning without submitting](#scanning-without-submitting)
## Scanning And Submitting
If you wish to periodically scan and submit the result to the [Kubescape SaaS version](https://portal.armo.cloud/) where you can benefit the features the SaaS version provides, please follow this instructions ->
1. Apply kubescape namespace
```
kubectl apply ks-namespace.yaml
```
2. Apply serviceAccount and roles
```
kubectl apply ks-serviceAccount.yaml
```
3. Setup and apply configMap
Before you apply the configMap you need to set the account ID and cluster name in the `ks-configMap.yaml` file.
* Set cluster name:
Run `kubectl config current-context` and set the result in the `data.clusterName` field
* Set account ID:
1. Navigate to the [Kubescape SaaS version](https://portal.armo.cloud/) and login/sign up for free
2. Click the `Add Cluster` button on the top right of the page
</br>
<img src="screenshots/add-cluster.png" alt="add-cluster">
3. Copy the value of `--account` and set it in the `data.customerGUID` field
</br>
<img src="screenshots/account.png" alt="account">
Make sure the configMap looks as following;
```
kind: ConfigMap
apiVersion: v1
metadata:
name: kubescape
labels:
app: kubescape
namespace: kubescape
data:
config.json: |
{
"customerGUID": "XXXXXXXX-XXXX-XXXX-XXXXXXXXXXXX",
"clusterName": "my-awesome-cluster-name"
}
```
Finally, apply the configMap
```
kubectl apply ks-configMap.yaml
```
4. Apply CronJob
Before you apply the cronJob, make sure the scanning frequency suites your needs
```
kubectl apply ks-cronJob-submit.yaml
```
## Scanning Without Submitting
If you wish to periodically scan but not submit the scan results, follow this instructions ->
1. Apply kubescape namespace
```
kubectl apply ks-namespace.yaml
```
2. Apply serviceAccount and roles
```
kubectl apply ks-serviceAccount.yaml
```
3. Apply CronJob
Before you apply the cronJob, make sure the scanning frequency suites your needs
```
kubectl apply ks-cronJob-non-submit.yaml
```

View File

@@ -0,0 +1,14 @@
# ------------------- Kubescape User/Customer ID ------------------- #
kind: ConfigMap
apiVersion: v1
metadata:
name: kubescape
labels:
app: kubescape
namespace: kubescape
data:
config.json: |
{
"customerGUID": "<ID>",
"clusterName": "<cluster name>"
}

View File

@@ -0,0 +1,32 @@
apiVersion: batch/v1
kind: CronJob
metadata:
name: kubescape
labels:
app: kubescape
namespace: kubescape
spec:
# ┌────────────────── timezone (optional)
# | ┌───────────── minute (0 - 59)
# | │ ┌───────────── hour (0 - 23)
# | │ │ ┌───────────── day of the month (1 - 31)
# | │ │ │ ┌───────────── month (1 - 12)
# | │ │ │ │ ┌───────────── day of the week (0 - 6) (Sunday to Saturday;
# | │ │ │ │ │ 7 is also Sunday on some systems)
# | │ │ │ │ │
# | │ │ │ │ │
# CRON_TZ=UTC * * * * *
schedule: "0 0 1 * *"
jobTemplate:
spec:
template:
spec:
containers:
- name: kubescape
image: quay.io/armosec/kubescape:latest
imagePullPolicy: IfNotPresent
command: ["/bin/sh","-c"]
args:
- kubescape scan framework nsa
restartPolicy: OnFailure
serviceAccountName: kubescape-discovery

View File

@@ -0,0 +1,40 @@
apiVersion: batch/v1
kind: CronJob
metadata:
name: kubescape
labels:
app: kubescape
namespace: kubescape
spec:
# ┌────────────────── timezone (optional)
# | ┌───────────── minute (0 - 59)
# | │ ┌───────────── hour (0 - 23)
# | │ │ ┌───────────── day of the month (1 - 31)
# | │ │ │ ┌───────────── month (1 - 12)
# | │ │ │ │ ┌───────────── day of the week (0 - 6) (Sunday to Saturday;
# | │ │ │ │ │ 7 is also Sunday on some systems)
# | │ │ │ │ │
# | │ │ │ │ │
# CRON_TZ=UTC * * * * *
schedule: "0 0 1 * *"
jobTemplate:
spec:
template:
spec:
containers:
- name: kubescape
image: quay.io/armosec/kubescape:latest
imagePullPolicy: IfNotPresent
command: ["/bin/sh","-c"]
args:
- kubescape scan framework nsa --submit
volumeMounts:
- name: kubescape-config-volume
mountPath: /root/.kubescape/config.json
subPath: config.json
restartPolicy: OnFailure
serviceAccountName: kubescape-discovery
volumes:
- name: kubescape-config-volume
configMap:
name: kubescape

View File

@@ -0,0 +1,7 @@
# ------------------- Kubescape User/Customer ID ------------------- #
kind: Namespace
apiVersion: v1
metadata:
name: kubescape
labels:
app: kubescape

View File

@@ -0,0 +1,61 @@
---
# ------------------- Kubescape Service Account ------------------- #
apiVersion: v1
kind: ServiceAccount
metadata:
labels:
app: kubescape
name: kubescape-discovery
namespace: kubescape
---
# ------------------- Kubescape Role & Role Binding ------------------- #
kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: kubescape-discovery-role
namespace: kubescape
rules:
- apiGroups: ["*"]
resources: ["*"]
verbs: ["get", "list", "describe"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: kubescape-discovery-binding
namespace: kubescape
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: kubescape-discovery-role
subjects:
- kind: ServiceAccount
name: kubescape-discovery
---
# ------------------- 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: kubescape

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

View File

@@ -0,0 +1,130 @@
# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
#
# This file is DEPRECATE, please navigate to the official docs ->
# https://github.com/armosec/kubescape/tree/master/examples/cronJob-support/README.md
#
# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
---
# ------------------- Kubescape Service Account ------------------- #
apiVersion: v1
kind: ServiceAccount
metadata:
labels:
app: kubescape
name: kubescape-discovery
namespace: kubescape
---
# ------------------- Kubescape Role & Role Binding ------------------- #
kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: kubescape-discovery-role
namespace: kubescape
rules:
- apiGroups: ["*"]
resources: ["*"]
verbs: ["get", "list", "describe"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: kubescape-discovery-binding
namespace: kubescape
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: kubescape-discovery-role
subjects:
- kind: ServiceAccount
name: kubescape-discovery
---
# ------------------- 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: kubescape
---
# ------------------- Kubescape User/Customer GUID ------------------- #
kind: ConfigMap
apiVersion: v1
metadata:
name: kubescape-configmap
labels:
app: kubescape
namespace: kubescape
data:
config.json: |
{
"customerGUID": <MyGUID>,
"clusterName": <MyK8sClusterName>
}
---
apiVersion: batch/v1
kind: CronJob
metadata:
name: kubescape
labels:
app: kubescape
namespace: kubescape
spec:
# ┌────────────────── timezone (optional)
# | ┌───────────── minute (0 - 59)
# | │ ┌───────────── hour (0 - 23)
# | │ │ ┌───────────── day of the month (1 - 31)
# | │ │ │ ┌───────────── month (1 - 12)
# | │ │ │ │ ┌───────────── day of the week (0 - 6) (Sunday to Saturday;
# | │ │ │ │ │ 7 is also Sunday on some systems)
# | │ │ │ │ │
# | │ │ │ │ │
# CRON_TZ=UTC * * * * *
schedule: "0 0 1 * *"
jobTemplate:
spec:
template:
spec:
containers:
- name: kubescape
image: quay.io/armosec/kubescape:latest
imagePullPolicy: IfNotPresent
command: ["/bin/sh","-c"]
args:
- kubescape scan framework nsa --submit
volumeMounts:
- name: kubescape-config-volume
mountPath: /root/.kubescape/config.json
subPath: config.json
restartPolicy: OnFailure
serviceAccountName: kubescape-discovery
volumes:
- name: kubescape-config-volume
configMap:
name: kubescape-configmap
---

View File

@@ -0,0 +1,29 @@
apiVersion: v2
name: kubescape
description:
Kubescape is the first open-source tool for testing if Kubernetes is deployed securely according to multiple frameworks
regulatory, customized company policies and DevSecOps best practices, such as the [NSA-CISA](https://www.armosec.io/blog/kubernetes-hardening-guidance-summary-by-armo) and the [MITRE ATT&CK®](https://www.microsoft.com/security/blog/2021/03/23/secure-containerized-environments-with-updated-threat-matrix-for-kubernetes/) .
Kubescape scans K8s clusters, YAML files, and HELM charts, and detect misconfigurations and software vulnerabilities at early stages of the CI/CD pipeline and provides a risk score instantly and risk trends over time.
Kubescape integrates natively with other DevOps tools, including Jenkins, CircleCI and Github workflows.
# A chart can be either an 'application' or a 'library' chart.
#
# Application charts are a collection of templates that can be packaged into versioned archives
# to be deployed.
#
# Library charts provide useful utilities or functions for the chart developer. They're included as
# a dependency of application charts to inject those utilities and functions into the rendering
# pipeline. Library charts do not define any templates and therefore cannot be deployed.
type: application
# This is the chart version. This version number should be incremented each time you make changes
# to the chart and its templates, including the app version.
# Versions are expected to follow Semantic Versioning (https://semver.org/)
version: 1.0.0
# This is the version number of the application being deployed. This version number should be
# incremented each time you make changes to the application. Versions are not expected to
# follow Semantic Versioning. They should reflect the version the application is using.
# It is recommended to use it with quotes.
appVersion: "v1.0.128"

View File

@@ -0,0 +1,27 @@
# kubescape
![Version: 1.0.0](https://img.shields.io/badge/Version-1.0.0-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: v1.0.128](https://img.shields.io/badge/AppVersion-v1.0.128-informational?style=flat-square)
Kubescape is the first open-source tool for testing if Kubernetes is deployed securely according to multiple frameworks regulatory, customized company policies and DevSecOps best practices, such as the [NSA-CISA](https://www.armosec.io/blog/kubernetes-hardening-guidance-summary-by-armo) and the [MITRE ATT&CK®](https://www.microsoft.com/security/blog/2021/03/23/secure-containerized-environments-with-updated-threat-matrix-for-kubernetes/) . Kubescape scans K8s clusters, YAML files, and HELM charts, and detect misconfigurations and software vulnerabilities at early stages of the CI/CD pipeline and provides a risk score instantly and risk trends over time. Kubescape integrates natively with other DevOps tools, including Jenkins, CircleCI and Github workflows.
## Values
| Key | Type | Default | Description |
|-----|------|---------|-------------|
| affinity | object | `{}` | |
| configMap | object | `{"create":true,"params":{"clusterName":"<MyK8sClusterName>","customerGUID":"<MyGUID>,"}}` | ARMO customer information |
| fullnameOverride | string | `""` | |
| image | object | `{"imageName":"kubescape","pullPolicy":"IfNotPresent","repository":"quay.io/armosec","tag":"latest"}` | Image and version to deploy |
| imagePullSecrets | list | `[]` | |
| nameOverride | string | `""` | |
| nodeSelector | object | `{}` | |
| podAnnotations | object | `{}` | |
| podSecurityContext | object | `{}` | |
| resources | object | `{"limits":{"cpu":"500m","memory":"512Mi"},"requests":{"cpu":"200m","memory":"256Mi"}}` | Default resources for running the service in cluster |
| schedule | string | `"0 0 * * *"` | Frequency of running the scan |
| securityContext | object | `{}` | |
| serviceAccount | object | `{"annotations":{},"create":true,"name":"kubescape-discovery"}` | Service account that runs the scan and has permissions to view the cluster |
| tolerations | list | `[]` | |
----------------------------------------------
Autogenerated from chart metadata using [helm-docs v1.5.0](https://github.com/norwoodj/helm-docs/releases/v1.5.0)

View File

@@ -0,0 +1,62 @@
{{/*
Expand the name of the chart.
*/}}
{{- define "kubescape.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Create a default fully qualified app name.
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
If release name contains chart name it will be used as a full name.
*/}}
{{- define "kubescape.fullname" -}}
{{- if .Values.fullnameOverride }}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- $name := default .Chart.Name .Values.nameOverride }}
{{- if contains $name .Release.Name }}
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- end }}
{{- end }}
{{/*
Create chart name and version as used by the chart label.
*/}}
{{- define "kubescape.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Common labels
*/}}
{{- define "kubescape.labels" -}}
helm.sh/chart: {{ include "kubescape.chart" . }}
{{ include "kubescape.selectorLabels" . }}
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}
{{/*
Selector labels
*/}}
{{- define "kubescape.selectorLabels" -}}
app.kubernetes.io/name: {{ include "kubescape.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}
{{/*
Create the name of the service account to use
*/}}
{{- define "kubescape.serviceAccountName" -}}
{{- if .Values.serviceAccount.create }}
{{- default (include "kubescape.fullname" .) .Values.serviceAccount.name }}
{{- else }}
{{- default "default" .Values.serviceAccount.name }}
{{- end }}
{{- end }}

View File

@@ -0,0 +1,11 @@
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: {{ include "kubescape.fullname" . }}
labels:
{{- include "kubescape.labels" . | nindent 4 }}
rules:
- apiGroups: ["*"]
resources: ["*"]
verbs: ["get", "list", "describe"]

View File

@@ -0,0 +1,16 @@
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: {{ include "kubescape.fullname" . }}
labels:
{{- include "kubescape.labels" . | nindent 4 }}
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: {{ include "kubescape.fullname" . }}
subjects:
- kind: ServiceAccount
name: {{ include "kubescape.serviceAccountName" . }}
namespace: {{ .Release.Namespace | quote }}

View File

@@ -0,0 +1,14 @@
{{- if .Values.configMap.create -}}
kind: ConfigMap
apiVersion: v1
metadata:
name: {{ include "kubescape.fullname" . }}-configmap
labels:
{{- include "kubescape.labels" . | nindent 4 }}
data:
config.json: |
{
"customerGUID": "{{ .Values.configMap.params.customerGUID }}",
"clusterName": "{{ .Values.configMap.params.clusterName }}"
}
{{- end }}

View File

@@ -0,0 +1,28 @@
apiVersion: batch/v1
kind: CronJob
metadata:
name: {{ include "kubescape.fullname" . }}
labels:
{{- include "kubescape.labels" . | nindent 4 }}
spec:
schedule: "{{ .Values.schedule }}"
jobTemplate:
spec:
template:
spec:
containers:
- name: {{ .Chart.Name }}
image: "{{ .Values.image.repository }}/{{ .Values.image.imageName }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
command: ["/bin/sh", "-c"]
args: ["kubescape scan framework nsa --submit"]
volumeMounts:
- name: kubescape-config-volume
mountPath: /root/.kubescape/config.json
subPath: config.json
restartPolicy: OnFailure
serviceAccountName: {{ include "kubescape.serviceAccountName" . }}
volumes:
- name: kubescape-config-volume
configMap:
name: {{ include "kubescape.fullname" . }}-configmap

View File

@@ -0,0 +1,11 @@
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: {{ include "kubescape.fullname" . }}
labels:
{{- include "kubescape.labels" . | nindent 4 }}
rules:
- apiGroups: ["*"]
resources: ["*"]
verbs: ["get", "list", "describe"]

View File

@@ -0,0 +1,16 @@
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: {{ include "kubescape.fullname" . }}
labels:
{{- include "kubescape.labels" . | nindent 4 }}
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: {{ include "kubescape.fullname" . }}
subjects:
- kind: ServiceAccount
name: {{ include "kubescape.serviceAccountName" . }}
namespace: {{ .Release.Namespace | quote }}

View File

@@ -0,0 +1,12 @@
{{- if .Values.serviceAccount.create -}}
apiVersion: v1
kind: ServiceAccount
metadata:
name: {{ include "kubescape.serviceAccountName" . }}
labels:
{{- include "kubescape.labels" . | nindent 4 }}
{{- with .Values.serviceAccount.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
{{- end }}

View File

@@ -0,0 +1,74 @@
# Default values for kubescape.
# This is a YAML-formatted file.
# Declare variables to be passed into your templates.
# -- Frequency of running the scan
# ┌────────────── timezone (optional)
# | ┌───────────── minute (0 - 59)
# | │ ┌───────────── hour (0 - 23)
# | │ │ ┌───────────── day of the month (1 - 31)
# | │ │ │ ┌───────────── month (1 - 12)
# | │ │ │ │ ┌───────────── day of the week (0 - 6) (Sunday to Saturday;
# | │ │ │ │ │ 7 is also Sunday on some systems)
# | │ │ │ │ │
# | │ │ │ │ │
# UTC * * * * *
schedule: "* * 1 * *"
# -- Image and version to deploy
image:
repository: quay.io/armosec
imageName: kubescape
pullPolicy: Always
# Overrides the image tag whose default is the chart appVersion.
tag: latest
imagePullSecrets: []
nameOverride: ""
fullnameOverride: ""
# -- Service account that runs the scan and has permissions to view the cluster
serviceAccount:
# Specifies whether a service account should be created
create: true
# Annotations to add to the service account
annotations: {}
# The name of the service account to use.
# If not set and create is true, a name is generated using the fullname template
name: "kubescape-discovery"
# -- ARMO customer information
configMap:
create: false
params:
customerGUID: <MyGUID>
clusterName: <MyK8sClusterName>
podAnnotations: {}
podSecurityContext: {}
# fsGroup: 2000
securityContext: {}
# capabilities:
# drop:
# - ALL
# readOnlyRootFilesystem: true
# runAsNonRoot: true
# runAsUser: 1000
# -- Default resources for running the service in cluster
resources:
limits:
cpu: 500m
memory: 512Mi
requests:
cpu: 200m
memory: 256Mi
nodeSelector: {}
tolerations: []
affinity: {}

43
go.mod
View File

@@ -3,36 +3,25 @@ module github.com/armosec/kubescape
go 1.17
require (
github.com/armosec/armoapi-go v0.0.8
github.com/armosec/k8s-interface v0.0.8
github.com/armosec/opa-utils v0.0.39
github.com/armosec/rbac-utils v0.0.1
github.com/armosec/utils-go v0.0.3
github.com/briandowns/spinner v1.16.0
github.com/coreos/go-oidc v2.2.1+incompatible // indirect
github.com/docker/docker v20.10.9+incompatible // indirect
github.com/docker/go-connections v0.4.0 // indirect
github.com/docker/go-units v0.4.0 // indirect
github.com/enescakir/emoji v1.0.0
github.com/fatih/color v1.12.0
github.com/francoispqt/gojay v1.2.13 // indirect
github.com/gofrs/uuid v4.0.0+incompatible
github.com/fatih/color v1.13.0
github.com/gofrs/uuid v4.1.0+incompatible
github.com/golang/glog v1.0.0
github.com/mattn/go-isatty v0.0.13
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/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.0.1 // indirect
github.com/satori/go.uuid v1.2.0
github.com/spf13/cobra v1.2.1
golang.org/x/oauth2 v0.0.0-20211005180243-6b3c2da341f1 // indirect
gopkg.in/yaml.v2 v2.4.0
k8s.io/api v0.22.2
k8s.io/apimachinery v0.22.2
k8s.io/client-go v0.22.2
sigs.k8s.io/controller-runtime v0.10.2 // indirect
)
require (
github.com/armosec/armoapi-go v0.0.7
github.com/armosec/k8s-interface v0.0.5
github.com/armosec/opa-utils v0.0.7
github.com/armosec/utils-go v0.0.3
)
require (
@@ -45,9 +34,15 @@ require (
github.com/Azure/go-autorest/tracing v0.6.0 // indirect
github.com/OneOfOne/xxhash v1.2.8 // indirect
github.com/armosec/utils-k8s-go v0.0.1 // 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
github.com/docker/go-connections v0.4.0 // indirect
github.com/docker/go-units v0.4.0 // indirect
github.com/form3tech-oss/jwt-go v3.2.3+incompatible // indirect
github.com/francoispqt/gojay v1.2.13 // indirect
github.com/ghodss/yaml v1.0.0 // indirect
github.com/go-gota/gota v0.12.0 // indirect
github.com/go-logr/logr v0.4.0 // indirect
github.com/gobwas/glob v0.2.3 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
@@ -58,10 +53,12 @@ require (
github.com/imdario/mergo v0.3.12 // indirect
github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/json-iterator/go v1.1.11 // indirect
github.com/mattn/go-colorable v0.1.8 // 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
github.com/modern-go/reflect2 v1.0.1 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.0.1 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pquerna/cachecontrol v0.1.0 // indirect
github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0 // indirect
@@ -69,12 +66,17 @@ require (
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
github.com/yashtewari/glob-intersection v0.0.0-20180916065949-5c77d914dd0b // indirect
go.uber.org/atomic v1.7.0 // indirect
go.uber.org/multierr v1.6.0 // indirect
go.uber.org/zap v1.19.1 // indirect
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83 // 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/term v0.0.0-20210220032956-6a3ed077a48d // indirect
golang.org/x/text v0.3.6 // indirect
golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect
gonum.org/v1/gonum v0.9.1 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/protobuf v1.27.1 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
@@ -82,6 +84,7 @@ require (
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
k8s.io/klog/v2 v2.9.0 // indirect
k8s.io/utils v0.0.0-20210819203725-bdf08cb9a70a // indirect
sigs.k8s.io/controller-runtime v0.10.2 // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.1.2 // indirect
sigs.k8s.io/yaml v1.2.0 // indirect
)

75
go.sum
View File

@@ -44,6 +44,7 @@ dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7
dmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0/go.mod h1:JLBrvjyP0v+ecvNYvCpyZgu5/xkfAUhi6wJj28eUfSU=
dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1:a1inKt/atXimZ4Mv927x+r7UpyzRUf4emIoiiSC2TN4=
dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU=
gioui.org v0.0.0-20210308172011-57750fc8a0a6/go.mod h1:RSH6KIUZ0p2xy5zHDxgAM4zumjgTw83q2ge/PI+yyw8=
git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=
github.com/Azure/go-ansiterm v0.0.0-20210608223527-2377c96fe795/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
@@ -70,6 +71,7 @@ github.com/OneOfOne/xxhash v1.2.8 h1:31czK/TI9sNkxIKfaUfGlU47BAxQ0ztGgd9vPyqimf8
github.com/OneOfOne/xxhash v1.2.8/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdIIOT9Um7Q=
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
@@ -82,12 +84,15 @@ github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
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.7 h1:SN13+iYrIkxgatU+MwuWnSlhxP1G7rZP7dC8us2I7v0=
github.com/armosec/armoapi-go v0.0.7/go.mod h1:iaVVGyc23QGGzAdv4n+szGQg3Rbpixn9yQTU3qWRpaw=
github.com/armosec/k8s-interface v0.0.5 h1:DWQXZNMSsYQeLQ6xpB21ueFMR9oFnz28iWQTNn31TAk=
github.com/armosec/k8s-interface v0.0.5/go.mod h1:xxS+V5QT3gVQTwZyAMMDrYLWGrfKOpiJ7Jfhfa0w9sM=
github.com/armosec/opa-utils v0.0.7 h1:cafKzdQcCVqaOz6zNdne+wxNqajZFd6Ad2KJpHM3gF8=
github.com/armosec/opa-utils v0.0.7/go.mod h1:fxPGsKEKOf0FDQVciKiCTZv4iibRkbld5lK1hyUyVcA=
github.com/armosec/armoapi-go v0.0.8 h1:JPa9rZynuE2RucamDh6dsy/sjCScmWDsyt1zagJFCDo=
github.com/armosec/armoapi-go v0.0.8/go.mod h1:iaVVGyc23QGGzAdv4n+szGQg3Rbpixn9yQTU3qWRpaw=
github.com/armosec/k8s-interface v0.0.8 h1:Eo3Qen4yFXxzVem49FNeij2ckyzHSAJ0w6PZMaSEIm8=
github.com/armosec/k8s-interface v0.0.8/go.mod h1:xxS+V5QT3gVQTwZyAMMDrYLWGrfKOpiJ7Jfhfa0w9sM=
github.com/armosec/opa-utils v0.0.39 h1:YOPMmwZaseqZT2/io918YycrMoJYu+ggbINnZJR9ZUA=
github.com/armosec/opa-utils v0.0.39/go.mod h1:JaE2a0kB2O22JZCqBBfOS9Pvh9rXs9xXXb9lVo01rTs=
github.com/armosec/rbac-utils v0.0.1 h1:N2MI98F/0zbDjmRZ29CNElU1AXkFLk5csd/qAHOBdXY=
github.com/armosec/rbac-utils v0.0.1/go.mod h1:pQ8CBiij8kSKV7aeZm9FMvtZN28VgA7LZcYyTWimq40=
github.com/armosec/utils-go v0.0.2/go.mod h1:itWmRLzRdsnwjpEOomL0mBWGnVNNIxSjDAdyc+b0iUo=
github.com/armosec/utils-go v0.0.3 h1:uyQI676yRciQM0sSN9uPoqHkbspTxHO0kmzXhBeE/xU=
github.com/armosec/utils-go v0.0.3/go.mod h1:itWmRLzRdsnwjpEOomL0mBWGnVNNIxSjDAdyc+b0iUo=
@@ -95,7 +100,9 @@ github.com/armosec/utils-k8s-go v0.0.1 h1:Ay3y7fW+4+FjVc0+obOWm8YsnEvM31vPAVoKTy
github.com/armosec/utils-k8s-go v0.0.1/go.mod h1:qrU4pmY2iZsOb39Eltpm0sTTNM3E4pmeyWx4dgDUC2U=
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
github.com/aws/aws-sdk-go v1.41.1/go.mod h1:585smgzpB/KqRA+K3y/NL/oYRqQvpNJYvLm+LY1U59Q=
github.com/aws/aws-sdk-go v1.41.11/go.mod h1:585smgzpB/KqRA+K3y/NL/oYRqQvpNJYvLm+LY1U59Q=
github.com/benbjohnson/clock v1.0.3/go.mod h1:bGMdMPoPVvcYyt1gHDf4J2KE153Yf9BuiUKYMaxlTDM=
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
@@ -104,6 +111,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/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.16.0 h1:DFmp6hEaIx2QXXuqSJmtfSBSAjRmpGiKG6ip2Wm/yOs=
github.com/briandowns/spinner v1.16.0/go.mod h1:QOuQk7x+EaDASo80FEXwlwiA+j/PPIcX3FScO+3/ZPQ=
@@ -181,10 +189,12 @@ github.com/evanphx/json-patch v0.5.2/go.mod h1:ZWS5hhDbVDyob71nXKNL0+PWn6ToqBHMi
github.com/evanphx/json-patch v4.11.0+incompatible h1:glyUF9yIYtMHzn8xaKw5rMhdWcwsYV8dZHIq5567/xs=
github.com/evanphx/json-patch v4.11.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fatih/color v1.12.0 h1:mRhaKNwANqRgUBGKmnI5ZxEk7QXmjQeCcuYFMX2bfcc=
github.com/fatih/color v1.12.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
github.com/form3tech-oss/jwt-go v3.2.3+incompatible h1:7ZaBxOI7TMoYBfyA3cQHErNNyAWIKUMIwqxEtgHOs5c=
github.com/form3tech-oss/jwt-go v3.2.3+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
@@ -201,12 +211,19 @@ github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
github.com/go-fonts/dejavu v0.1.0/go.mod h1:4Wt4I4OU2Nq9asgDCteaAaWZOV24E+0/Pwo0gppep4g=
github.com/go-fonts/latin-modern v0.2.0/go.mod h1:rQVLdDMK+mK1xscDwsqM5J8U2jrRa3T0ecnM9pNujks=
github.com/go-fonts/liberation v0.1.1/go.mod h1:K6qoJYypsmfVjWg8KOVDQhLc8UDgIK2HYqyqAO9z7GY=
github.com/go-fonts/stix v0.1.0/go.mod h1:w/c1f0ldAUlJmLBvlbkvVXLAD+tAMqobIIQpmnUIzUY=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gota/gota v0.12.0 h1:T5BDg1hTf5fZ/CO+T/N0E+DDqUhvoKBl+UVckgcAAQg=
github.com/go-gota/gota v0.12.0/go.mod h1:UT+NsWpZC/FhaOyWb9Hui0jXg0Iq8e/YugZHTbyW/34=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
github.com/go-latex/latex v0.0.0-20210118124228-b3d85cf34e07/go.mod h1:CO1AlKB2CSIqUrmQPqA0gdRIlnLEY0gK5JGjh37zN5U=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
@@ -227,13 +244,15 @@ github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg78
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 h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw=
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=
github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/glog v1.0.0 h1:nfP3RFugxnNRyKgeWd4oI1nYvXpxrx8ck8ZrcizshdQ=
github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4=
@@ -375,6 +394,8 @@ github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/X
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
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/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=
@@ -403,13 +424,13 @@ github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN
github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8=
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.9 h1:sqDoxXbdeALODt0DAeJCVp38ps9ZogZEAXjus69YV3U=
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.13 h1:qdl+GuBjcsKKDco5BsxPJlId98mSWNKqYA+Co0SC1yA=
github.com/mattn/go-isatty v0.0.13/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
@@ -472,6 +493,8 @@ github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/9
github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
github.com/peterh/liner v0.0.0-20170211195444-bf27d3ba8e1d/go.mod h1:xIteQHvHuaLYG9IFj6mSxM0fCKrs34IrEQUhOYuGPHc=
github.com/phpdave11/gofpdf v1.4.2/go.mod h1:zpO6xFn9yxo3YLyMvW8HcKWVdbNqgIfOOp2dXMnm1mY=
github.com/phpdave11/gofpdi v1.0.12/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
@@ -514,6 +537,7 @@ 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/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=
@@ -638,6 +662,7 @@ go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/automaxprocs v1.4.0/go.mod h1:/mTEdr7LvHhs0v7mjdxDreTz1OG5zdZGqgOnhWiR/+Q=
go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A=
go.uber.org/goleak v1.1.11-0.20210813005559-691160354723 h1:sHOAIxRGBp443oHZIPB+HsUGaksVCXVQENPxwTfQdH4=
go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4=
@@ -663,18 +688,30 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh
golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83 h1:/ZScEX8SfEmUGRHs0gxpqteO5nfNW6axyZbBdw9A12g=
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
golang.org/x/exp v0.0.0-20191002040644-a1355ae1e2c3/go.mod h1:NOZ3BPKG0ec/BKJQgnvsSFpcKLM5xXVWnvZS97DWHgE=
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6 h1:QE6XYQK6naiK1EPAe1g/ILLxN5RBoH5xkJk3CqlMI/Y=
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-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=
golang.org/x/image v0.0.0-20200430140353-33d19683fad8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.0.0-20200618115811-c13761719519/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.0.0-20201208152932-35266b937fa6/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.0.0-20210216034530-4410531fe030/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
@@ -745,6 +782,7 @@ golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
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-20210520170846-37e1c6afe023/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
@@ -836,6 +874,7 @@ golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210304124612-50617c2ba197/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -870,11 +909,13 @@ golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxb
golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac h1:7zkz7BUtwNFFqcowJ+RIgu2MaV/MapERkDIy+mwPyjs=
golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20181030000716-a0a13e073c7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
@@ -889,6 +930,7 @@ golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgw
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190927191325-030b2cf1153e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
@@ -935,6 +977,14 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gomodules.xyz/jsonpatch/v2 v2.2.0/go.mod h1:WXp+iVDkoLQqPudfQ9GBlwB2eZ5DKOnjQZCYdOS8GPY=
gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo=
gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0=
gonum.org/v1/gonum v0.9.1 h1:HCWmqqNoELL0RAQeKBXWtkp04mGk8koafcB4He6+uhc=
gonum.org/v1/gonum v0.9.1/go.mod h1:TZumC3NeyVQskjXqmyWt4S3bINhy7B4eYwW69EbyX+0=
gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0 h1:OE9mWmgKkjJyEmDAAtGMPjXu+YNeGvK9VTSHY6+Qihc=
gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw=
gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc=
gonum.org/v1/plot v0.9.0/go.mod h1:3Pcqqmp6RHvJI72kgb8fThyUnav364FOsdDo2aGW5lY=
google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
google.golang.org/api v0.1.0/go.mod h1:UGEZY7KEX120AnNLIHFMKIo4obdJhkp2tPbaPlQx13Y=
@@ -1121,6 +1171,7 @@ k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e/go.mod h1:vHXdDvt9+2spS2R
k8s.io/utils v0.0.0-20210819203725-bdf08cb9a70a h1:8dYfu/Fc9Gz2rNJKB9IQRGgQOh2clmRzNIPPY1xLY5g=
k8s.io/utils v0.0.0-20210819203725-bdf08cb9a70a/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.22/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg=

View File

@@ -53,6 +53,6 @@ echo -e "\033[0m"
$KUBESCAPE_EXEC version
echo
echo -e "\033[35mUsage: $ $KUBESCAPE_EXEC scan framework nsa --exclude-namespaces kube-system,kube-public"
echo -e "\033[35mUsage: $ $KUBESCAPE_EXEC scan framework nsa"
echo -e "\033[0m"

16
main.go
View File

@@ -1,23 +1,9 @@
package main
import (
"fmt"
"os"
"github.com/armosec/kubescape/cmd"
"github.com/armosec/kubescape/clihandler/cmd"
)
func main() {
CheckLatestVersion()
cmd.Execute()
}
func CheckLatestVersion() {
latest, err := cmd.GetLatestVersion()
if err != nil {
fmt.Fprintf(os.Stderr, "error: %v\n", err)
} else if latest != cmd.BuildNumber {
fmt.Println("Warning: You are not updated to the latest release: " + latest)
}
}

View File

@@ -8,7 +8,6 @@ import (
"github.com/armosec/kubescape/cautils"
"github.com/armosec/opa-utils/exceptions"
"github.com/armosec/opa-utils/reporthandling"
"github.com/armosec/opa-utils/score"
"github.com/armosec/k8s-interface/k8sinterface"
@@ -16,42 +15,37 @@ import (
"github.com/golang/glog"
"github.com/open-policy-agent/opa/ast"
"github.com/open-policy-agent/opa/rego"
"github.com/open-policy-agent/opa/storage"
uuid "github.com/satori/go.uuid"
)
const ScoreConfigPath = "/resources/config"
var RegoK8sCredentials storage.Store
type OPAProcessorHandler struct {
processedPolicy *chan *cautils.OPASessionObj
reportResults *chan *cautils.OPASessionObj
// componentConfig cautils.ComponentConfig
processedPolicy *chan *cautils.OPASessionObj
reportResults *chan *cautils.OPASessionObj
regoDependenciesData *resources.RegoDependenciesData
}
type OPAProcessor struct {
*cautils.OPASessionObj
regoDependenciesData *resources.RegoDependenciesData
}
func NewOPAProcessor(sessionObj *cautils.OPASessionObj) *OPAProcessor {
func NewOPAProcessor(sessionObj *cautils.OPASessionObj, regoDependenciesData *resources.RegoDependenciesData) *OPAProcessor {
if regoDependenciesData != nil && sessionObj != nil {
regoDependenciesData.PostureControlInputs = sessionObj.RegoInputData.PostureControlInputs
}
return &OPAProcessor{
OPASessionObj: sessionObj,
OPASessionObj: sessionObj,
regoDependenciesData: regoDependenciesData,
}
}
func NewOPAProcessorHandler(processedPolicy, reportResults *chan *cautils.OPASessionObj) *OPAProcessorHandler {
regoDependenciesData := resources.NewRegoDependenciesData(k8sinterface.GetK8sConfig(), cautils.ClusterName)
store, err := regoDependenciesData.TOStorage()
if err != nil {
panic(err)
}
RegoK8sCredentials = store
return &OPAProcessorHandler{
processedPolicy: processedPolicy,
reportResults: reportResults,
processedPolicy: processedPolicy,
reportResults: reportResults,
regoDependenciesData: resources.NewRegoDependenciesData(k8sinterface.GetK8sConfig(), cautils.ClusterName),
}
}
@@ -59,7 +53,7 @@ func (opaHandler *OPAProcessorHandler) ProcessRulesListenner() {
for {
opaSessionObj := <-*opaHandler.processedPolicy
opap := NewOPAProcessor(opaSessionObj)
opap := NewOPAProcessor(opaSessionObj, opaHandler.regoDependenciesData)
// process
if err := opap.Process(); err != nil {
@@ -126,6 +120,8 @@ func (opap *OPAProcessor) processControl(control *reporthandling.Control) (*repo
controlReport := reporthandling.ControlReport{}
controlReport.PortalBase = control.PortalBase
controlReport.ControlID = control.ControlID
controlReport.BaseScore = control.BaseScore
controlReport.Control_ID = control.Control_ID // TODO: delete when 'id' is deprecated
controlReport.Name = control.Name
@@ -204,11 +200,16 @@ func (opap *OPAProcessor) runRegoOnK8s(rule *reporthandling.PolicyRule, k8sObjec
}
func (opap *OPAProcessor) regoEval(inputObj []map[string]interface{}, compiledRego *ast.Compiler) ([]reporthandling.RuleResponse, error) {
store, err := opap.regoDependenciesData.TOStorage() // get store
if err != nil {
return nil, err
}
rego := rego.New(
rego.Query("data.armo_builtins"), // get package name from rule
rego.Compiler(compiledRego),
rego.Input(inputObj),
rego.Store(RegoK8sCredentials),
rego.Store(store),
)
// Run evaluation
@@ -226,17 +227,6 @@ func (opap *OPAProcessor) regoEval(inputObj []map[string]interface{}, compiledRe
return results, nil
}
func (opap *OPAProcessor) updateScore() {
if !k8sinterface.ConnectedToCluster {
return
}
// calculate score
s := score.NewScore(k8sinterface.NewKubernetesApi(), ScoreConfigPath)
s.Calculate(opap.PostureReport.FrameworkReports)
}
func (opap *OPAProcessor) updateResults() {
for f := range opap.PostureReport.FrameworkReports {
// set exceptions

View File

@@ -5,6 +5,7 @@ import (
"github.com/armosec/kubescape/cautils"
"github.com/armosec/opa-utils/reporthandling"
"github.com/armosec/opa-utils/resources"
"github.com/armosec/k8s-interface/k8sinterface"
// _ "k8s.io/client-go/plugin/pkg/client/auth"
@@ -24,7 +25,7 @@ func TestProcess(t *testing.T) {
opaSessionObj.Frameworks = []reporthandling.Framework{*reporthandling.MockFrameworkA()}
opaSessionObj.K8SResources = &k8sResources
opap := NewOPAProcessor(opaSessionObj)
opap := NewOPAProcessor(opaSessionObj, resources.NewRegoDependenciesDataMock())
opap.Process()
opap.updateResults()
for _, f := range opap.PostureReport.FrameworkReports {

View File

@@ -4,25 +4,23 @@ import (
"fmt"
"github.com/armosec/kubescape/cautils"
"github.com/armosec/kubescape/resourcehandler"
"github.com/armosec/opa-utils/reporthandling"
"github.com/armosec/armoapi-go/armotypes"
"github.com/armosec/k8s-interface/k8sinterface"
)
// PolicyHandler -
type PolicyHandler struct {
k8s *k8sinterface.KubernetesApi
resourceHandler resourcehandler.IResourceHandler
// we are listening on this chan in opaprocessor/processorhandler.go/ProcessRulesListenner func
processPolicy *chan *cautils.OPASessionObj
getters *cautils.Getters
}
// CreatePolicyHandler Create ws-handler obj
func NewPolicyHandler(processPolicy *chan *cautils.OPASessionObj, k8s *k8sinterface.KubernetesApi) *PolicyHandler {
func NewPolicyHandler(processPolicy *chan *cautils.OPASessionObj, resourceHandler resourcehandler.IResourceHandler) *PolicyHandler {
return &PolicyHandler{
k8s: k8s,
processPolicy: processPolicy,
resourceHandler: resourceHandler,
processPolicy: processPolicy,
}
}
@@ -33,15 +31,9 @@ func (policyHandler *PolicyHandler) HandleNotificationRequest(notification *repo
policyHandler.getters = &scanInfo.Getters
// get policies
frameworks, exceptions, err := policyHandler.getPolicies(notification)
if err != nil {
if err := policyHandler.getPolicies(notification, opaSessionObj); err != nil {
return err
}
if len(frameworks) == 0 {
return fmt.Errorf("empty list of frameworks")
}
opaSessionObj.Frameworks = frameworks
opaSessionObj.Exceptions = exceptions
k8sResources, err := policyHandler.getResources(notification, opaSessionObj, scanInfo)
if err != nil {
@@ -57,32 +49,8 @@ func (policyHandler *PolicyHandler) HandleNotificationRequest(notification *repo
return nil
}
func (policyHandler *PolicyHandler) getPolicies(notification *reporthandling.PolicyNotification) ([]reporthandling.Framework, []armotypes.PostureExceptionPolicy, error) {
cautils.ProgressTextDisplay("Downloading/Loading framework definitions")
frameworks, exceptions, err := policyHandler.GetPoliciesFromBackend(notification)
if err != nil {
return frameworks, exceptions, err
}
if len(frameworks) == 0 {
err := fmt.Errorf("could not download any policies, please check previous logs")
return frameworks, exceptions, err
}
cautils.SuccessTextDisplay("Downloaded/Loaded framework")
return frameworks, exceptions, nil
}
func (policyHandler *PolicyHandler) getResources(notification *reporthandling.PolicyNotification, opaSessionObj *cautils.OPASessionObj, scanInfo *cautils.ScanInfo) (*cautils.K8SResources, error) {
var k8sResources *cautils.K8SResources
var err error
if k8sinterface.ConnectedToCluster {
k8sResources, err = policyHandler.getK8sResources(opaSessionObj.Frameworks, &notification.Designators, scanInfo.ExcludedNamespaces)
} else {
k8sResources, err = policyHandler.loadResources(opaSessionObj.Frameworks, scanInfo)
}
return k8sResources, err
opaSessionObj.PostureReport.ClusterAPIServerInfo = policyHandler.resourceHandler.GetClusterAPIServerInfo()
return policyHandler.resourceHandler.GetResources(opaSessionObj.Frameworks, &notification.Designators)
}

View File

@@ -2,53 +2,71 @@ package policyhandler
import (
"fmt"
"strings"
"github.com/armosec/armoapi-go/armotypes"
"github.com/armosec/kubescape/cautils"
"github.com/armosec/opa-utils/reporthandling"
)
func (policyHandler *PolicyHandler) GetPoliciesFromBackend(notification *reporthandling.PolicyNotification) ([]reporthandling.Framework, []armotypes.PostureExceptionPolicy, error) {
var errs error
frameworks := []reporthandling.Framework{}
exceptionPolicies := []armotypes.PostureExceptionPolicy{}
func (policyHandler *PolicyHandler) getPolicies(notification *reporthandling.PolicyNotification, policiesAndResources *cautils.OPASessionObj) error {
cautils.ProgressTextDisplay("Downloading/Loading policy definitions")
// Get - cacli opa get
for _, rule := range notification.Rules {
switch rule.Kind {
case reporthandling.KindFramework:
receivedFramework, recExceptionPolicies, err := policyHandler.getFrameworkPolicies(rule.Name)
frameworks, err := policyHandler.getScanPolicies(notification)
if err != nil {
return err
}
if len(frameworks) == 0 {
return fmt.Errorf("failed to download policies, please ARMO team for more information")
}
policiesAndResources.Frameworks = frameworks
// get exceptions
exceptionPolicies, err := policyHandler.getters.ExceptionsGetter.GetExceptions(cautils.CustomerGUID, cautils.ClusterName)
if err == nil {
policiesAndResources.Exceptions = exceptionPolicies
}
// get account configuration
controlsInputs, err := policyHandler.getters.ControlsInputsGetter.GetControlsInputs(cautils.CustomerGUID, cautils.ClusterName)
if err == nil {
policiesAndResources.RegoInputData.PostureControlInputs = controlsInputs
}
cautils.SuccessTextDisplay("Downloaded/Loaded policy")
return nil
}
func (policyHandler *PolicyHandler) getScanPolicies(notification *reporthandling.PolicyNotification) ([]reporthandling.Framework, error) {
frameworks := []reporthandling.Framework{}
switch getScanKind(notification) {
case reporthandling.KindFramework: // Download frameworks
for _, rule := range notification.Rules {
receivedFramework, err := policyHandler.getters.PolicyGetter.GetFramework(rule.Name)
if err != nil {
return frameworks, policyDownloadError(err)
}
if receivedFramework != nil {
frameworks = append(frameworks, *receivedFramework)
if recExceptionPolicies != nil {
exceptionPolicies = append(exceptionPolicies, recExceptionPolicies...)
}
} else if err != nil {
if strings.Contains(err.Error(), "unsupported protocol scheme") {
err = fmt.Errorf("failed to download from GitHub release, try running with `--use-default` flag")
}
return nil, nil, fmt.Errorf("kind: %v, name: %s, error: %s", rule.Kind, rule.Name, err.Error())
}
default:
err := fmt.Errorf("missing rule kind, expected: %s", reporthandling.KindFramework)
errs = fmt.Errorf("%s", err.Error())
}
case reporthandling.KindControl: // Download controls
f := reporthandling.Framework{}
var receivedControl *reporthandling.Control
var err error
for _, rule := range notification.Rules {
receivedControl, err = policyHandler.getters.PolicyGetter.GetControl(rule.Name)
if err != nil {
return frameworks, policyDownloadError(err)
}
if receivedControl != nil {
f.Controls = append(f.Controls, *receivedControl)
}
}
frameworks = append(frameworks, f)
// TODO: add case for control from file
default:
return frameworks, fmt.Errorf("unknown policy kind")
}
return frameworks, exceptionPolicies, errs
}
func (policyHandler *PolicyHandler) getFrameworkPolicies(policyName string) (*reporthandling.Framework, []armotypes.PostureExceptionPolicy, error) {
receivedFramework, err := policyHandler.getters.PolicyGetter.GetFramework(policyName)
if err != nil {
return nil, nil, err
}
receivedException, err := policyHandler.getters.ExceptionsGetter.GetExceptions(cautils.CustomerGUID, cautils.ClusterName)
if err != nil {
return receivedFramework, nil, err
}
return receivedFramework, receivedException, nil
return frameworks, nil
}

View File

@@ -0,0 +1,21 @@
package policyhandler
import (
"fmt"
"strings"
"github.com/armosec/opa-utils/reporthandling"
)
func getScanKind(notification *reporthandling.PolicyNotification) reporthandling.NotificationPolicyKind {
if len(notification.Rules) > 0 {
return notification.Rules[0].Kind
}
return "unknown"
}
func policyDownloadError(err error) error {
if strings.Contains(err.Error(), "unsupported protocol scheme") {
err = fmt.Errorf("failed to download from GitHub release, try running with `--use-default` flag")
}
return err
}

View File

@@ -0,0 +1,61 @@
package resourcehandler
import (
"fmt"
"strings"
"github.com/armosec/k8s-interface/k8sinterface"
"k8s.io/apimachinery/pkg/runtime/schema"
)
type IFieldSelector interface {
GetNamespacesSelector(*schema.GroupVersionResource) string
}
type EmptySelector struct {
}
func (es *EmptySelector) GetNamespacesSelector(resource *schema.GroupVersionResource) string {
return ""
}
type ExcludeSelector struct {
namespace string
}
func NewExcludeSelector(ns string) *ExcludeSelector {
return &ExcludeSelector{namespace: ns}
}
type IncludeSelector struct {
namespace string
}
func NewIncludeSelector(ns string) *IncludeSelector {
return &IncludeSelector{namespace: ns}
}
func (es *ExcludeSelector) GetNamespacesSelector(resource *schema.GroupVersionResource) string {
return getNamespacesSelector(resource, es.namespace, "!=")
}
func (is *IncludeSelector) GetNamespacesSelector(resource *schema.GroupVersionResource) string {
return getNamespacesSelector(resource, is.namespace, "==")
}
func getNamespacesSelector(resource *schema.GroupVersionResource, ns, operator string) string {
fieldSelectors := ""
fieldSelector := "metadata."
if resource.Resource == "namespaces" {
fieldSelector += "name"
} else if k8sinterface.IsNamespaceScope(resource.Group, resource.Resource) {
fieldSelector += "namespace"
} else {
return ""
}
namespacesSlice := strings.Split(ns, ",")
for _, n := range namespacesSlice {
fieldSelectors += fmt.Sprintf("%s%s%s,", fieldSelector, operator, n)
}
return fieldSelectors
}

View File

@@ -1,4 +1,4 @@
package policyhandler
package resourcehandler
import (
"bytes"
@@ -8,7 +8,9 @@ import (
"path/filepath"
"strings"
"github.com/armosec/armoapi-go/armotypes"
"github.com/armosec/k8s-interface/workloadinterface"
"k8s.io/apimachinery/pkg/version"
"github.com/armosec/k8s-interface/k8sinterface"
"github.com/armosec/kubescape/cautils"
@@ -29,11 +31,22 @@ const (
JSON_FILE_FORMAT FileFormat = "json"
)
func (policyHandler *PolicyHandler) loadResources(frameworks []reporthandling.Framework, scanInfo *cautils.ScanInfo) (*cautils.K8SResources, error) {
// FileResourceHandler handle resources from files and URLs
type FileResourceHandler struct {
inputPatterns []string
}
func NewFileResourceHandler(inputPatterns []string) *FileResourceHandler {
return &FileResourceHandler{
inputPatterns: inputPatterns,
}
}
func (fileHandler *FileResourceHandler) GetResources(frameworks []reporthandling.Framework, designator *armotypes.PortalDesignator) (*cautils.K8SResources, error) {
workloads := []k8sinterface.IWorkload{}
// load resource from local file system
w, err := loadResourcesFromFiles(scanInfo.InputPatterns)
w, err := loadResourcesFromFiles(fileHandler.inputPatterns)
if err != nil {
return nil, err
}
@@ -42,7 +55,7 @@ func (policyHandler *PolicyHandler) loadResources(frameworks []reporthandling.Fr
}
// load resources from url
w, err = loadResourcesFromUrl(scanInfo.InputPatterns)
w, err = loadResourcesFromUrl(fileHandler.inputPatterns)
if err != nil {
return nil, err
}
@@ -59,7 +72,7 @@ func (policyHandler *PolicyHandler) loadResources(frameworks []reporthandling.Fr
// build resources map
// map resources based on framework required resources: map["/group/version/kind"][]<k8s workloads>
k8sResources := setResourceMap(frameworks)
k8sResources := setResourceMap(frameworks) // TODO - support designators
// save only relevant resources
for i := range allResources {
@@ -72,6 +85,10 @@ func (policyHandler *PolicyHandler) loadResources(frameworks []reporthandling.Fr
}
func (fileHandler *FileResourceHandler) GetClusterAPIServerInfo() *version.Info {
return nil
}
func loadResourcesFromFiles(inputPatterns []string) ([]k8sinterface.IWorkload, error) {
files, errs := listFiles(inputPatterns)
if len(errs) > 0 {

View File

@@ -1,4 +1,4 @@
package policyhandler
package resourcehandler
import (
"fmt"

View File

@@ -1,8 +1,8 @@
package policyhandler
package resourcehandler
import (
"context"
"fmt"
"strings"
"github.com/armosec/kubescape/cautils"
"github.com/armosec/opa-utils/reporthandling"
@@ -15,12 +15,23 @@ import (
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
k8slabels "k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/version"
"k8s.io/client-go/dynamic"
)
const SelectAllResources = "*"
type K8sResourceHandler struct {
k8s *k8sinterface.KubernetesApi
fieldSelector IFieldSelector
}
func (policyHandler *PolicyHandler) getK8sResources(frameworks []reporthandling.Framework, designator *armotypes.PortalDesignator, excludedNamespaces string) (*cautils.K8SResources, error) {
func NewK8sResourceHandler(k8s *k8sinterface.KubernetesApi, fieldSelector IFieldSelector) *K8sResourceHandler {
return &K8sResourceHandler{
k8s: k8s,
fieldSelector: fieldSelector,
}
}
func (k8sHandler *K8sResourceHandler) GetResources(frameworks []reporthandling.Framework, designator *armotypes.PortalDesignator) (*cautils.K8SResources, error) {
// get k8s resources
cautils.ProgressTextDisplay("Accessing Kubernetes objects")
@@ -31,7 +42,7 @@ func (policyHandler *PolicyHandler) getK8sResources(frameworks []reporthandling.
_, namespace, labels := armotypes.DigestPortalDesignator(designator)
// pull k8s recourses
if err := policyHandler.pullResources(k8sResourcesMap, namespace, labels, excludedNamespaces); err != nil {
if err := k8sHandler.pullResources(k8sResourcesMap, namespace, labels); err != nil {
return k8sResourcesMap, err
}
@@ -39,13 +50,21 @@ func (policyHandler *PolicyHandler) getK8sResources(frameworks []reporthandling.
return k8sResourcesMap, nil
}
func (policyHandler *PolicyHandler) pullResources(k8sResources *cautils.K8SResources, namespace string, labels map[string]string, excludedNamespaces string) error {
func (k8sHandler *K8sResourceHandler) GetClusterAPIServerInfo() *version.Info {
clusterAPIServerInfo, err := k8sHandler.k8s.KubernetesClient.Discovery().ServerVersion()
if err != nil {
cautils.ErrorDisplay(fmt.Sprintf("Failed to discover API server information: %v", err))
return nil
}
return clusterAPIServerInfo
}
func (k8sHandler *K8sResourceHandler) pullResources(k8sResources *cautils.K8SResources, namespace string, labels map[string]string) error {
var errs error
for groupResource := range *k8sResources {
apiGroup, apiVersion, resource := k8sinterface.StringToResourceGroup(groupResource)
gvr := schema.GroupVersionResource{Group: apiGroup, Version: apiVersion, Resource: resource}
result, err := policyHandler.pullSingleResource(&gvr, namespace, labels, excludedNamespaces)
result, err := k8sHandler.pullSingleResource(&gvr, namespace, labels)
if err != nil {
// handle error
if errs == nil {
@@ -61,13 +80,13 @@ func (policyHandler *PolicyHandler) pullResources(k8sResources *cautils.K8SResou
return errs
}
func (policyHandler *PolicyHandler) pullSingleResource(resource *schema.GroupVersionResource, namespace string, labels map[string]string, excludedNamespaces string) ([]unstructured.Unstructured, error) {
func (k8sHandler *K8sResourceHandler) pullSingleResource(resource *schema.GroupVersionResource, namespace string, labels map[string]string) ([]unstructured.Unstructured, error) {
// set labels
listOptions := metav1.ListOptions{}
if excludedNamespaces != "" {
setFieldSelector(&listOptions, resource, excludedNamespaces)
}
listOptions.FieldSelector += k8sHandler.fieldSelector.GetNamespacesSelector(resource)
if len(labels) > 0 {
set := k8slabels.Set(labels)
listOptions.LabelSelector = set.AsSelector().String()
@@ -76,13 +95,13 @@ func (policyHandler *PolicyHandler) pullSingleResource(resource *schema.GroupVer
// set dynamic object
var clientResource dynamic.ResourceInterface
if namespace != "" && k8sinterface.IsNamespaceScope(resource.Group, resource.Resource) {
clientResource = policyHandler.k8s.DynamicClient.Resource(*resource).Namespace(namespace)
clientResource = k8sHandler.k8s.DynamicClient.Resource(*resource).Namespace(namespace)
} else {
clientResource = policyHandler.k8s.DynamicClient.Resource(*resource)
clientResource = k8sHandler.k8s.DynamicClient.Resource(*resource)
}
// list resources
result, err := clientResource.List(policyHandler.k8s.Context, listOptions)
result, err := clientResource.List(context.Background(), listOptions)
if err != nil {
return nil, fmt.Errorf("failed to get resource: %v, namespace: %s, labelSelector: %v, reason: %s", resource, namespace, listOptions.LabelSelector, err.Error())
}
@@ -90,18 +109,3 @@ func (policyHandler *PolicyHandler) pullSingleResource(resource *schema.GroupVer
return result.Items, nil
}
func setFieldSelector(listOptions *metav1.ListOptions, resource *schema.GroupVersionResource, excludedNamespaces string) {
fieldSelector := "metadata."
if resource.Resource == "namespaces" {
fieldSelector += "name"
} else if k8sinterface.IsNamespaceScope(resource.Group, resource.Resource) {
fieldSelector += "namespace"
} else {
return
}
excludedNamespacesSlice := strings.Split(excludedNamespaces, ",")
for _, excludedNamespace := range excludedNamespacesSlice {
listOptions.FieldSelector += fmt.Sprintf("%s!=%s,", fieldSelector, excludedNamespace)
}
}

View File

@@ -1,4 +1,4 @@
package policyhandler
package resourcehandler
import (
"github.com/armosec/kubescape/cautils"

View File

@@ -1,4 +1,4 @@
package policyhandler
package resourcehandler
import (
"github.com/armosec/k8s-interface/k8sinterface"

View File

@@ -0,0 +1,155 @@
package resourcehandler
import (
"encoding/json"
"fmt"
"net/http"
"strings"
"github.com/armosec/kubescape/cautils/getter"
)
type IRepository interface {
setBranch(string) error
setTree() error
getYamlFromTree() []string
}
type innerTree struct {
Path string `json:"path"`
}
type tree struct {
InnerTrees []innerTree `json:"tree"`
}
type GitHubRepository struct {
host string
name string // <org>/<repo>
branch string
tree tree
}
type githubDefaultBranchAPI struct {
DefaultBranch string `json:"default_branch"`
}
func NewGitHubRepository(rep string) *GitHubRepository {
return &GitHubRepository{
host: "github",
name: rep,
}
}
func ScanRepository(command string, branchOptional string) ([]string, error) {
repo, err := getRepository(command)
if err != nil {
return nil, err
}
err = repo.setBranch(branchOptional)
if err != nil {
return nil, err
}
err = repo.setTree()
if err != nil {
return nil, err
}
// get all paths that are of the yaml type, and build them into a valid url
return repo.getYamlFromTree(), nil
}
func getHostAndRepoName(url string) (string, string, error) {
splitUrl := strings.Split(url, "/")
if len(splitUrl) != 5 {
return "", "", fmt.Errorf("failed to pars url: %s", url)
}
hostUrl := splitUrl[2] // github.com, gitlab.com, etc.
repository := splitUrl[3] + "/" + strings.Split(splitUrl[4], ".")[0] // user/reposetory
return hostUrl, repository, nil
}
func getRepository(url string) (IRepository, error) {
hostUrl, repoName, err := getHostAndRepoName(url)
if err != nil {
return nil, err
}
var repo IRepository
switch repoHost := strings.Split(hostUrl, ".")[0]; repoHost {
case "github":
repo = NewGitHubRepository(repoName)
default:
return nil, fmt.Errorf("unknown repository host: %s", repoHost)
}
// Returns the host-url, and the part of the user and repository from the url
return repo, nil
}
func (g *GitHubRepository) setBranch(branchOptional string) error {
// Checks whether the repository type is a master or another type.
// By default it is "master", unless the branchOptional came with a value
if branchOptional == "" {
body, err := getter.HttpGetter(&http.Client{}, g.defaultBranchAPI(), nil)
if err != nil {
return err
}
var data githubDefaultBranchAPI
err = json.Unmarshal([]byte(body), &data)
if err != nil {
return err
}
g.branch = data.DefaultBranch
} else {
g.branch = branchOptional
}
return nil
}
func (g *GitHubRepository) defaultBranchAPI() string {
return fmt.Sprintf("https://api.github.com/repos/%s", g.name)
}
func (g *GitHubRepository) setTree() error {
body, err := getter.HttpGetter(&http.Client{}, g.treeAPI(), nil)
if err != nil {
return err
}
// press all tree to json
var tree tree
err = json.Unmarshal([]byte(body), &tree)
if err != nil {
return fmt.Errorf("failed to unmarshal response body from '%s', reason: %s", g.treeAPI(), err.Error())
// fmt.Printf("failed to unmarshal response body from '%s', reason: %s", urlCommand, err.Error())
// return nil
}
g.tree = tree
return nil
}
func (g *GitHubRepository) treeAPI() string {
return fmt.Sprintf("https://api.github.com/repos/%s/git/trees/%s?recursive=1", g.name, g.branch)
}
// return a list of yaml for a given repository tree
func (g *GitHubRepository) getYamlFromTree() []string {
var urls []string
for _, path := range g.tree.InnerTrees {
if strings.HasSuffix(path.Path, ".yaml") {
urls = append(urls, fmt.Sprintf("%s/%s", g.rowYamlUrl(), path.Path))
}
}
return urls
}
func (g *GitHubRepository) rowYamlUrl() string {
return fmt.Sprintf("https://raw.githubusercontent.com/%s/%s", g.name, g.branch)
}

View File

@@ -0,0 +1,13 @@
package resourcehandler
import (
"github.com/armosec/armoapi-go/armotypes"
"github.com/armosec/kubescape/cautils"
"github.com/armosec/opa-utils/reporthandling"
"k8s.io/apimachinery/pkg/version"
)
type IResourceHandler interface {
GetResources(frameworks []reporthandling.Framework, designator *armotypes.PortalDesignator) (*cautils.K8SResources, error)
GetClusterAPIServerInfo() *version.Info
}

View File

@@ -1,4 +1,4 @@
package policyhandler
package resourcehandler
import (
"bytes"
@@ -28,9 +28,18 @@ func listUrls(patterns []string) []string {
urls := []string{}
for i := range patterns {
if strings.HasPrefix(patterns[i], "http") {
urls = append(urls, patterns[i])
if !isYaml(patterns[i]) || !isJson(patterns[i]) { // if url of repo
if yamls, err := ScanRepository(patterns[i], ""); err == nil { // TODO - support branch
urls = append(urls, yamls...)
} else {
fmt.Print(err) // TODO - handle errors
}
} else { // url of single file
urls = append(urls, patterns[i])
}
}
}
return urls
}

View File

@@ -0,0 +1,42 @@
package printer
import (
"encoding/json"
"fmt"
"os"
"github.com/armosec/kubescape/cautils"
)
type JsonPrinter struct {
writer *os.File
}
func NewJsonPrinter() *JsonPrinter {
return &JsonPrinter{}
}
func (jsonPrinter *JsonPrinter) SetWriter(outputFile string) {
jsonPrinter.writer = getWriter(outputFile)
}
func (jsonPrinter *JsonPrinter) Score(score float32) {
fmt.Printf("\nFinal score: %d", int(score*100))
}
func (jsonPrinter *JsonPrinter) ActionPrint(opaSessionObj *cautils.OPASessionObj) {
var postureReportStr []byte
var err error
if len(opaSessionObj.PostureReport.FrameworkReports) == 1 {
postureReportStr, err = json.Marshal(opaSessionObj.PostureReport.FrameworkReports[0])
} else {
postureReportStr, err = json.Marshal(opaSessionObj.PostureReport.FrameworkReports)
}
if err != nil {
fmt.Println("Failed to convert posture report object!")
os.Exit(1)
}
jsonPrinter.writer.Write(postureReportStr)
}

View File

@@ -3,10 +3,42 @@ package printer
import (
"encoding/xml"
"fmt"
"os"
"github.com/armosec/kubescape/cautils"
"github.com/armosec/opa-utils/reporthandling"
)
type JunitPrinter struct {
writer *os.File
}
func NewJunitPrinter() *JunitPrinter {
return &JunitPrinter{}
}
func (junitPrinter *JunitPrinter) SetWriter(outputFile string) {
junitPrinter.writer = getWriter(outputFile)
}
func (junitPrinter *JunitPrinter) Score(score float32) {
fmt.Printf("\nFinal score: %d", int(score*100))
}
func (junitPrinter *JunitPrinter) ActionPrint(opaSessionObj *cautils.OPASessionObj) {
junitResult, err := convertPostureReportToJunitResult(opaSessionObj.PostureReport)
if err != nil {
fmt.Println("Failed to convert posture report object!")
os.Exit(1)
}
postureReportStr, err := xml.Marshal(junitResult)
if err != nil {
fmt.Println("Failed to convert posture report object!")
os.Exit(1)
}
junitPrinter.writer.Write(postureReportStr)
}
type JUnitTestSuites struct {
XMLName xml.Name `xml:"testsuites"`
Suites []JUnitTestSuite `xml:"testsuite"`
@@ -17,9 +49,11 @@ type JUnitTestSuites struct {
type JUnitTestSuite struct {
XMLName xml.Name `xml:"testsuite"`
Tests int `xml:"tests,attr"`
Failures int `xml:"failures,attr"`
Time string `xml:"time,attr"`
Name string `xml:"name,attr"`
Resources int `xml:"resources,attr"`
Excluded int `xml:"excluded,attr"`
Failed int `xml:"filed,attr"`
Properties []JUnitProperty `xml:"properties>property,omitempty"`
TestCases []JUnitTestCase `xml:"testcase"`
}
@@ -30,6 +64,9 @@ type JUnitTestCase struct {
Classname string `xml:"classname,attr"`
Name string `xml:"name,attr"`
Time string `xml:"time,attr"`
Resources int `xml:"resources,attr"`
Excluded int `xml:"excluded,attr"`
Failed int `xml:"filed,attr"`
SkipMessage *JUnitSkipMessage `xml:"skipped,omitempty"`
Failure *JUnitFailure `xml:"failure,omitempty"`
}
@@ -55,7 +92,12 @@ type JUnitFailure struct {
func convertPostureReportToJunitResult(postureResult *reporthandling.PostureReport) (*JUnitTestSuites, error) {
juResult := JUnitTestSuites{XMLName: xml.Name{Local: "Kubescape scan results"}}
for _, framework := range postureResult.FrameworkReports {
suite := JUnitTestSuite{Name: framework.Name}
suite := JUnitTestSuite{
Name: framework.Name,
Resources: framework.GetNumberOfResources(),
Excluded: framework.GetNumberOfWarningResources(),
Failed: framework.GetNumberOfFailedResources(),
}
for _, controlReports := range framework.ControlReports {
suite.Tests = suite.Tests + 1
testCase := JUnitTestCase{}
@@ -63,9 +105,12 @@ func convertPostureReportToJunitResult(postureResult *reporthandling.PostureRepo
testCase.Classname = "Kubescape"
testCase.Time = "0"
if 0 < len(controlReports.RuleReports[0].RuleResponses) {
suite.Failures = suite.Failures + 1
testCase.Resources = controlReports.GetNumberOfResources()
testCase.Excluded = controlReports.GetNumberOfWarningResources()
testCase.Failed = controlReports.GetNumberOfFailedResources()
failure := JUnitFailure{}
failure.Message = fmt.Sprintf("%d resources failed", len(controlReports.RuleReports[0].RuleResponses))
failure.Message = fmt.Sprintf("%d resources failed", testCase.Failed)
for _, ruleResponses := range controlReports.RuleReports[0].RuleResponses {
failure.Contents = fmt.Sprintf("%s\n%s", failure.Contents, ruleResponses.AlertMessage)
}

View File

@@ -0,0 +1,239 @@
package printer
import (
"fmt"
"os"
"sort"
"github.com/armosec/kubescape/cautils"
"github.com/armosec/opa-utils/reporthandling"
"github.com/enescakir/emoji"
"github.com/olekukonko/tablewriter"
)
type PrettyPrinter struct {
writer *os.File
summary Summary
sortedControlNames []string
frameworkSummary ControlSummary
}
func NewPrettyPrinter() *PrettyPrinter {
return &PrettyPrinter{
summary: NewSummary(),
}
}
// Initializes empty printer for new table
func (printer *PrettyPrinter) init() *PrettyPrinter {
printer.frameworkSummary = ControlSummary{}
printer.summary = Summary{}
printer.sortedControlNames = []string{}
return printer
}
func (printer *PrettyPrinter) ActionPrint(opaSessionObj *cautils.OPASessionObj) {
// score := calculatePostureScore(opaSessionObj.PostureReport)
for _, report := range opaSessionObj.PostureReport.FrameworkReports {
// Print summary table together for control scan
if report.Name != "" {
printer = printer.init()
}
printer.summarySetup(report)
printer.printResults()
printer.printSummaryTable(report.Name)
}
// return score
}
func (printer *PrettyPrinter) SetWriter(outputFile string) {
printer.writer = getWriter(outputFile)
}
func (printer *PrettyPrinter) Score(score float32) {
}
func (printer *PrettyPrinter) summarySetup(fr reporthandling.FrameworkReport) {
printer.frameworkSummary = ControlSummary{
TotalResources: fr.GetNumberOfResources(),
TotalFailed: fr.GetNumberOfFailedResources(),
TotalWarning: fr.GetNumberOfWarningResources(),
}
for _, cr := range fr.ControlReports {
if len(cr.RuleReports) == 0 {
continue
}
workloadsSummary := listResultSummary(cr.RuleReports)
printer.summary[cr.Name] = ControlSummary{
TotalResources: cr.GetNumberOfResources(),
TotalFailed: cr.GetNumberOfFailedResources(),
TotalWarning: cr.GetNumberOfWarningResources(),
FailedWorkloads: groupByNamespace(workloadsSummary, workloadSummaryFailed),
ExcludedWorkloads: groupByNamespace(workloadsSummary, workloadSummaryExclude),
Description: cr.Description,
Remediation: cr.Remediation,
ListInputKinds: cr.ListControlsInputKinds(),
}
}
printer.sortedControlNames = printer.getSortedControlsNames()
}
func (printer *PrettyPrinter) printResults() {
for i := 0; i < len(printer.sortedControlNames); i++ {
controlSummary := printer.summary[printer.sortedControlNames[i]]
printer.printTitle(printer.sortedControlNames[i], &controlSummary)
printer.printResources(&controlSummary)
if printer.summary[printer.sortedControlNames[i]].TotalResources > 0 {
printer.printSummary(printer.sortedControlNames[i], &controlSummary)
}
}
}
func (printer *PrettyPrinter) printSummary(controlName string, controlSummary *ControlSummary) {
cautils.SimpleDisplay(printer.writer, "Summary - ")
cautils.SuccessDisplay(printer.writer, "Passed:%v ", controlSummary.TotalResources-controlSummary.TotalFailed-controlSummary.TotalWarning)
cautils.WarningDisplay(printer.writer, "Excluded:%v ", controlSummary.TotalWarning)
cautils.FailureDisplay(printer.writer, "Failed:%v ", controlSummary.TotalFailed)
cautils.InfoDisplay(printer.writer, "Total:%v\n", controlSummary.TotalResources)
if controlSummary.TotalFailed > 0 {
cautils.DescriptionDisplay(printer.writer, "Remediation: %v\n", controlSummary.Remediation)
}
cautils.DescriptionDisplay(printer.writer, "\n")
}
func (printer *PrettyPrinter) printTitle(controlName string, controlSummary *ControlSummary) {
cautils.InfoDisplay(printer.writer, "[control: %s] ", controlName)
if controlSummary.TotalResources == 0 {
cautils.InfoDisplay(printer.writer, "resources not found %v\n", emoji.ConfusedFace)
} else if controlSummary.TotalFailed != 0 {
cautils.FailureDisplay(printer.writer, "failed %v\n", emoji.SadButRelievedFace)
} else if controlSummary.TotalWarning != 0 {
cautils.WarningDisplay(printer.writer, "excluded %v\n", emoji.NeutralFace)
} else {
cautils.SuccessDisplay(printer.writer, "passed %v\n", emoji.ThumbsUp)
}
cautils.DescriptionDisplay(printer.writer, "Description: %s\n", controlSummary.Description)
}
func (printer *PrettyPrinter) printResources(controlSummary *ControlSummary) {
if len(controlSummary.FailedWorkloads) > 0 {
cautils.FailureDisplay(printer.writer, "Failed:\n")
printer.printGroupedResources(controlSummary.FailedWorkloads)
}
if len(controlSummary.ExcludedWorkloads) > 0 {
cautils.WarningDisplay(printer.writer, "Excluded:\n")
printer.printGroupedResources(controlSummary.ExcludedWorkloads)
}
}
func (printer *PrettyPrinter) printGroupedResources(workloads map[string][]WorkloadSummary) {
indent := INDENT
for ns, rsc := range workloads {
preIndent := indent
if ns != "" {
cautils.SimpleDisplay(printer.writer, "%sNamespace %s\n", indent, ns)
}
preIndent2 := indent
for r := range rsc {
indent += indent
cautils.SimpleDisplay(printer.writer, fmt.Sprintf("%s%s - %s\n", indent, rsc[r].Kind, rsc[r].Name))
indent = preIndent2
}
indent = preIndent
}
}
func generateRow(control string, cs ControlSummary) []string {
row := []string{control}
row = append(row, cs.ToSlice()...)
if cs.TotalResources != 0 {
row = append(row, fmt.Sprintf("%d%s", percentage(cs.TotalResources, cs.TotalFailed), "%"))
} else {
row = append(row, EmptyPercentage)
}
return row
}
func generateHeader() []string {
return []string{"Control Name", "Failed Resources", "Excluded Resources", "All Resources", "% success"}
}
func percentage(big, small int) int {
if big == 0 {
if small == 0 {
return 100
}
return 0
}
return int(float64(float64(big-small)/float64(big)) * 100)
}
func generateFooter(numControlers, sumFailed, sumWarning, sumTotal int) []string {
// Control name | # failed resources | all resources | % success
row := []string{}
row = append(row, "Resource Summary") //fmt.Sprintf(""%d", numControlers"))
row = append(row, fmt.Sprintf("%d", sumFailed))
row = append(row, fmt.Sprintf("%d", sumWarning))
row = append(row, fmt.Sprintf("%d", sumTotal))
if sumTotal != 0 {
row = append(row, fmt.Sprintf("%d%s", percentage(sumTotal, sumFailed), "%"))
} else {
row = append(row, EmptyPercentage)
}
return row
}
func (printer *PrettyPrinter) printSummaryTable(framework string) {
// For control scan framework will be nil
printer.printFramework(framework)
summaryTable := tablewriter.NewWriter(printer.writer)
summaryTable.SetAutoWrapText(false)
summaryTable.SetHeader(generateHeader())
summaryTable.SetHeaderLine(true)
alignments := []int{tablewriter.ALIGN_LEFT, tablewriter.ALIGN_CENTER, tablewriter.ALIGN_CENTER, tablewriter.ALIGN_CENTER, tablewriter.ALIGN_CENTER}
summaryTable.SetColumnAlignment(alignments)
for i := 0; i < len(printer.sortedControlNames); i++ {
controlSummary := printer.summary[printer.sortedControlNames[i]]
summaryTable.Append(generateRow(printer.sortedControlNames[i], controlSummary))
}
summaryTable.SetFooter(generateFooter(len(printer.summary), printer.frameworkSummary.TotalFailed, printer.frameworkSummary.TotalWarning, printer.frameworkSummary.TotalResources))
summaryTable.Render()
}
func (printer *PrettyPrinter) printFramework(framework string) {
if framework != "" {
cautils.InfoTextDisplay(printer.writer, fmt.Sprintf("%s FRAMEWORK\n", framework))
}
}
func (printer *PrettyPrinter) getSortedControlsNames() []string {
controlNames := make([]string, 0, len(printer.summary))
for k := range printer.summary {
controlNames = append(controlNames, k)
}
sort.Strings(controlNames)
return controlNames
}
func getWriter(outputFile string) *os.File {
os.Remove(outputFile)
if outputFile != "" {
f, err := os.OpenFile(outputFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
fmt.Println("Error opening file")
return os.Stdout
}
return f
}
return os.Stdout
}

View File

@@ -1,17 +1,7 @@
package printer
import (
"encoding/json"
"encoding/xml"
"fmt"
"os"
"sort"
"github.com/armosec/kubescape/cautils"
"github.com/armosec/opa-utils/reporthandling"
"github.com/enescakir/emoji"
"github.com/olekukonko/tablewriter"
)
var INDENT = " "
@@ -19,239 +9,27 @@ var INDENT = " "
const EmptyPercentage = "NaN"
const (
PrettyPrinter string = "pretty-printer"
JsonPrinter string = "json"
JunitResultPrinter string = "junit"
PrettyFormat string = "pretty-printer"
JsonFormat string = "json"
JunitResultFormat string = "junit"
PrometheusFormat string = "prometheus"
)
type Printer struct {
writer *os.File
summary Summary
sortedControlNames []string
printerType string
frameworkSummary ControlSummary
type IPrinter interface {
ActionPrint(opaSessionObj *cautils.OPASessionObj)
SetWriter(outputFile string)
Score(score float32)
}
func NewPrinter(printerType, outputFile string) *Printer {
return &Printer{
summary: NewSummary(),
writer: getWriter(outputFile),
printerType: printerType,
func GetPrinter(printFormat string) IPrinter {
switch printFormat {
case JsonFormat:
return NewJsonPrinter()
case JunitResultFormat:
return NewJunitPrinter()
case PrometheusFormat:
return NewPrometheusPrinter()
default:
return NewPrettyPrinter()
}
}
func calculatePostureScore(postureReport *reporthandling.PostureReport) float32 {
totalResources := 0
totalFailed := 0
for _, frameworkReport := range postureReport.FrameworkReports {
totalFailed += frameworkReport.GetNumberOfFailedResources()
totalResources += frameworkReport.GetNumberOfResources()
}
if totalResources == 0 {
return float32(0)
}
return (float32(totalResources) - float32(totalFailed)) / float32(totalResources)
}
func (printer *Printer) ActionPrint(opaSessionObj *cautils.OPASessionObj) float32 {
var score float32
if printer.printerType == PrettyPrinter {
printer.SummarySetup(opaSessionObj.PostureReport)
printer.PrintResults()
printer.PrintSummaryTable()
} else if printer.printerType == JsonPrinter {
postureReportStr, err := json.Marshal(opaSessionObj.PostureReport.FrameworkReports[0])
if err != nil {
fmt.Println("Failed to convert posture report object!")
os.Exit(1)
}
printer.writer.Write(postureReportStr)
} else if printer.printerType == JunitResultPrinter {
junitResult, err := convertPostureReportToJunitResult(opaSessionObj.PostureReport)
if err != nil {
fmt.Println("Failed to convert posture report object!")
os.Exit(1)
}
postureReportStr, err := xml.Marshal(junitResult)
if err != nil {
fmt.Println("Failed to convert posture report object!")
os.Exit(1)
}
printer.writer.Write(postureReportStr)
} else if !cautils.IsSilent() {
fmt.Println("unknown output printer")
os.Exit(1)
}
score = calculatePostureScore(opaSessionObj.PostureReport)
return score
}
func (printer *Printer) SummarySetup(postureReport *reporthandling.PostureReport) {
for _, fr := range postureReport.FrameworkReports {
printer.frameworkSummary = ControlSummary{
TotalResources: fr.GetNumberOfResources(),
TotalFailed: fr.GetNumberOfFailedResources(),
TotalWarnign: fr.GetNumberOfWarningResources(),
}
for _, cr := range fr.ControlReports {
if len(cr.RuleReports) == 0 {
continue
}
workloadsSummary := listResultSummary(cr.RuleReports)
mapResources := groupByNamespace(workloadsSummary)
printer.summary[cr.Name] = ControlSummary{
TotalResources: cr.GetNumberOfResources(),
TotalFailed: cr.GetNumberOfFailedResources(),
TotalWarnign: cr.GetNumberOfWarningResources(),
WorkloadSummary: mapResources,
Description: cr.Description,
Remediation: cr.Remediation,
ListInputKinds: cr.ListControlsInputKinds(),
}
}
}
printer.sortedControlNames = printer.getSortedControlsNames()
}
func (printer *Printer) PrintResults() {
for i := 0; i < len(printer.sortedControlNames); i++ {
controlSummary := printer.summary[printer.sortedControlNames[i]]
printer.printTitle(printer.sortedControlNames[i], &controlSummary)
printer.printResult(printer.sortedControlNames[i], &controlSummary)
if printer.summary[printer.sortedControlNames[i]].TotalResources > 0 {
printer.printSummary(printer.sortedControlNames[i], &controlSummary)
}
}
}
func (printer *Printer) printSummary(controlName string, controlSummary *ControlSummary) {
cautils.SimpleDisplay(printer.writer, "Summary - ")
cautils.SuccessDisplay(printer.writer, "Passed:%v ", controlSummary.TotalResources-controlSummary.TotalFailed-controlSummary.TotalWarnign)
cautils.WarningDisplay(printer.writer, "Excluded:%v ", controlSummary.TotalWarnign)
cautils.FailureDisplay(printer.writer, "Failed:%v ", controlSummary.TotalFailed)
cautils.InfoDisplay(printer.writer, "Total:%v\n", controlSummary.TotalResources)
if controlSummary.TotalFailed > 0 {
cautils.DescriptionDisplay(printer.writer, "Remediation: %v\n", controlSummary.Remediation)
}
cautils.DescriptionDisplay(printer.writer, "\n")
}
func (printer *Printer) printTitle(controlName string, controlSummary *ControlSummary) {
cautils.InfoDisplay(printer.writer, "[control: %s] ", controlName)
if controlSummary.TotalResources == 0 && len(controlSummary.ListInputKinds) > 0 {
cautils.InfoDisplay(printer.writer, "resources not found %v\n", emoji.ConfusedFace)
} else if controlSummary.TotalFailed != 0 {
cautils.FailureDisplay(printer.writer, "failed %v\n", emoji.SadButRelievedFace)
} else if controlSummary.TotalWarnign != 0 {
cautils.WarningDisplay(printer.writer, "excluded %v\n", emoji.NeutralFace)
} else {
cautils.SuccessDisplay(printer.writer, "passed %v\n", emoji.ThumbsUp)
}
cautils.DescriptionDisplay(printer.writer, "Description: %s\n", controlSummary.Description)
}
func (printer *Printer) printResult(controlName string, controlSummary *ControlSummary) {
indent := INDENT
for ns, rsc := range controlSummary.WorkloadSummary {
preIndent := indent
if ns != "" {
cautils.SimpleDisplay(printer.writer, "%sNamespace %s\n", indent, ns)
}
preIndent2 := indent
for r := range rsc {
indent += indent
cautils.SimpleDisplay(printer.writer, fmt.Sprintf("%s%s - %s\n", indent, rsc[r].Kind, rsc[r].Name))
indent = preIndent2
}
indent = preIndent
}
}
func (printer *Printer) PrintUrl(url string) {
cautils.InfoTextDisplay(printer.writer, url)
}
func generateRow(control string, cs ControlSummary) []string {
row := []string{control}
row = append(row, cs.ToSlice()...)
if cs.TotalResources != 0 {
row = append(row, fmt.Sprintf("%d%s", percentage(cs.TotalResources, cs.TotalFailed), "%"))
} else {
row = append(row, EmptyPercentage)
}
return row
}
func generateHeader() []string {
return []string{"Control Name", "Failed Resources", "Excluded Resources", "All Resources", "% success"}
}
func percentage(big, small int) int {
if big == 0 {
if small == 0 {
return 100
}
return 0
}
return int(float64(float64(big-small)/float64(big)) * 100)
}
func generateFooter(numControlers, sumFailed, sumWarning, sumTotal int) []string {
// Control name | # failed resources | all resources | % success
row := []string{}
row = append(row, fmt.Sprintf("%d", numControlers))
row = append(row, fmt.Sprintf("%d", sumFailed))
row = append(row, fmt.Sprintf("%d", sumWarning))
row = append(row, fmt.Sprintf("%d", sumTotal))
if sumTotal != 0 {
row = append(row, fmt.Sprintf("%d%s", percentage(sumTotal, sumFailed), "%"))
} else {
row = append(row, EmptyPercentage)
}
return row
}
func (printer *Printer) PrintSummaryTable() {
summaryTable := tablewriter.NewWriter(printer.writer)
summaryTable.SetAutoWrapText(false)
summaryTable.SetHeader(generateHeader())
summaryTable.SetHeaderLine(true)
summaryTable.SetAlignment(tablewriter.ALIGN_LEFT)
for i := 0; i < len(printer.sortedControlNames); i++ {
controlSummary := printer.summary[printer.sortedControlNames[i]]
summaryTable.Append(generateRow(printer.sortedControlNames[i], controlSummary))
}
summaryTable.SetFooter(generateFooter(len(printer.summary), printer.frameworkSummary.TotalFailed, printer.frameworkSummary.TotalWarnign, printer.frameworkSummary.TotalResources))
summaryTable.Render()
}
func (printer *Printer) getSortedControlsNames() []string {
controlNames := make([]string, 0, len(printer.summary))
for k := range printer.summary {
controlNames = append(controlNames, k)
}
sort.Strings(controlNames)
return controlNames
}
func getWriter(outputFile string) *os.File {
os.Remove(outputFile)
if outputFile != "" {
f, err := os.OpenFile(outputFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
fmt.Println("Error opening file")
return os.Stdout
}
return f
}
return os.Stdout
}

View File

@@ -0,0 +1,100 @@
package printer
import (
"errors"
"fmt"
"os"
"github.com/armosec/kubescape/cautils"
"github.com/armosec/opa-utils/reporthandling"
)
type PrometheusPrinter struct {
writer *os.File
}
func NewPrometheusPrinter() *PrometheusPrinter {
return &PrometheusPrinter{}
}
func (prometheusPrinter *PrometheusPrinter) SetWriter(outputFile string) {
prometheusPrinter.writer = getWriter(outputFile)
}
func (prometheusPrinter *PrometheusPrinter) Score(score float32) {
fmt.Printf("\n# Overall score out of 100\nkubescape_score %f\n", score*100)
}
func (printer *PrometheusPrinter) printDetails(details []reporthandling.RuleResponse, frameworkName string, controlName string) error {
objs := make(map[string]map[string]map[string]int)
for _, ruleResponses := range details {
for _, k8sObj := range ruleResponses.AlertObject.K8SApiObjects {
kind, ok := k8sObj[`kind`].(string)
if (!ok) {
return errors.New("Found object with non string kind")
}
apiVersion,ok := k8sObj[`apiVersion`].(string)
if (!ok) {
return errors.New("Found object with non string apiVersion")
}
gvk := fmt.Sprintf("%s/%s",apiVersion,kind)
metadata,ok := k8sObj[`metadata`].(map[string]interface{})
if (!ok) {
return errors.New("Found object with non convertable metadata")
}
name,ok := metadata[`name`].(string)
if (!ok) {
return errors.New("Found metadata with non string name")
}
namespace,ok := metadata[`namespace`].(string)
if (!ok) {
namespace = ""
}
if (objs[gvk] == nil) {
objs[gvk] = make(map[string]map[string]int)
}
if (objs[gvk][namespace] == nil) {
objs[gvk][namespace] = make(map[string]int)
}
objs[gvk][namespace][name]++
}
}
for gvk, namespaces := range objs {
for namespace, names := range namespaces {
for name, value := range names {
fmt.Fprintf(printer.writer, "# Failed object from %s control %s\n", frameworkName, controlName)
if namespace != "" {
fmt.Fprintf(printer.writer, "kubescape_object_failed_count{framework=\"%s\",control=\"%s\",namespace=\"%s\",name=\"%s\",groupVersionKind=\"%s\"} %d\n", frameworkName, controlName, namespace, name, gvk, value)
} else {
fmt.Fprintf(printer.writer, "kubescape_object_failed_count{framework=\"%s\",control=\"%s\",name=\"%s\",groupVersionKind=\"%s\"} %d\n", frameworkName, controlName, name, gvk, value)
}
}
}
}
return nil
}
func (printer *PrometheusPrinter) printReports(frameworks []reporthandling.FrameworkReport) error {
for _, framework := range frameworks {
for _, controlReports := range framework.ControlReports {
if len(controlReports.RuleReports[0].RuleResponses) > 0 {
fmt.Fprintf(printer.writer, "# Number of resources found as part of %s control %s\nkubescape_resources_found_count{framework=\"%s\",control=\"%s\"} %d\n", framework.Name, controlReports.Name, framework.Name, controlReports.Name, controlReports.GetNumberOfResources())
fmt.Fprintf(printer.writer, "# Number of resources excluded as part of %s control %s\nkubescape_resources_excluded_count{framework=\"%s\",control=\"%s\"} %d\n", framework.Name, controlReports.Name, framework.Name, controlReports.Name, controlReports.GetNumberOfWarningResources())
fmt.Fprintf(printer.writer, "# Number of resources failed as part of %s control %s\nkubescape_resources_failed_count{framework=\"%s\",control=\"%s\"} %d\n", framework.Name, controlReports.Name, framework.Name, controlReports.Name, controlReports.GetNumberOfFailedResources())
err := printer.printDetails(controlReports.RuleReports[0].RuleResponses, framework.Name, controlReports.Name)
if err != nil {
return err
}
}
}
}
return nil
}
func (printer *PrometheusPrinter) ActionPrint(opaSessionObj *cautils.OPASessionObj) {
err := printer.printReports(opaSessionObj.PostureReport.FrameworkReports)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
}

View File

@@ -0,0 +1,11 @@
package printer
import (
"github.com/armosec/kubescape/cautils"
)
type SilentPrinter struct {
}
func (silentPrinter *SilentPrinter) ActionPrint(opaSessionObj *cautils.OPASessionObj) {
}

View File

@@ -13,13 +13,14 @@ func NewSummary() Summary {
}
type ControlSummary struct {
TotalResources int
TotalFailed int
TotalWarnign int
Description string
Remediation string
ListInputKinds []string
WorkloadSummary map[string][]WorkloadSummary // <namespace>:[<WorkloadSummary>]
TotalResources int
TotalFailed int
TotalWarning int
Description string
Remediation string
ListInputKinds []string
FailedWorkloads map[string][]WorkloadSummary // <namespace>:[<WorkloadSummary>]
ExcludedWorkloads map[string][]WorkloadSummary // <namespace>:[<WorkloadSummary>]
}
type WorkloadSummary struct {
@@ -33,7 +34,7 @@ type WorkloadSummary struct {
func (controlSummary *ControlSummary) ToSlice() []string {
s := []string{}
s = append(s, fmt.Sprintf("%d", controlSummary.TotalFailed))
s = append(s, fmt.Sprintf("%d", controlSummary.TotalWarnign))
s = append(s, fmt.Sprintf("%d", controlSummary.TotalWarning))
s = append(s, fmt.Sprintf("%d", controlSummary.TotalResources))
return s
}
@@ -41,3 +42,11 @@ func (controlSummary *ControlSummary) ToSlice() []string {
func (workloadSummary *WorkloadSummary) ToString() string {
return fmt.Sprintf("/%s/%s/%s/%s", workloadSummary.Group, workloadSummary.Namespace, workloadSummary.Kind, workloadSummary.Name)
}
func workloadSummaryFailed(workloadSummary *WorkloadSummary) bool {
return workloadSummary.Exception == nil
}
func workloadSummaryExclude(workloadSummary *WorkloadSummary) bool {
return workloadSummary.Exception != nil && workloadSummary.Exception.IsAlertOnly()
}

View File

@@ -8,14 +8,16 @@ import (
)
// Group workloads by namespace - return {"namespace": <[]WorkloadSummary>}
func groupByNamespace(resources []WorkloadSummary) map[string][]WorkloadSummary {
func groupByNamespace(resources []WorkloadSummary, status func(workloadSummary *WorkloadSummary) bool) map[string][]WorkloadSummary {
mapResources := make(map[string][]WorkloadSummary)
for i := range resources {
if r, ok := mapResources[resources[i].Namespace]; ok {
r = append(r, resources[i])
mapResources[resources[i].Namespace] = r
} else {
mapResources[resources[i].Namespace] = []WorkloadSummary{resources[i]}
if status(&resources[i]) {
if r, ok := mapResources[resources[i].Namespace]; ok {
r = append(r, resources[i])
mapResources[resources[i].Namespace] = r
} else {
mapResources[resources[i].Namespace] = []WorkloadSummary{resources[i]}
}
}
}
return mapResources

View File

@@ -0,0 +1,19 @@
package reporter
import "github.com/armosec/kubescape/cautils"
type ReportMock struct {
}
func NewReportMock() *ReportMock {
return &ReportMock{}
}
func (reportMock *ReportMock) ActionSendReport(opaSessionObj *cautils.OPASessionObj) error {
return nil
}
func (reportMock *ReportMock) SetCustomerGUID(customerGUID string) {
}
func (reportMock *ReportMock) SetClusterName(clusterName string) {
}

View File

@@ -1,61 +1,64 @@
package reporter
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"net/url"
"github.com/armosec/kubescape/cautils"
"github.com/armosec/kubescape/cautils/getter"
"github.com/armosec/opa-utils/reporthandling"
)
type IReport interface {
ActionSendReport(opaSessionObj *cautils.OPASessionObj) error
SetCustomerGUID(customerGUID string)
SetClusterName(clusterName string)
}
type ReportEventReceiver struct {
httpClient http.Client
host url.URL
httpClient *http.Client
clusterName string
customerGUID string
}
func NewReportEventReceiver() *ReportEventReceiver {
hostURL := initEventReceiverURL()
func NewReportEventReceiver(customerGUID, clusterName string) *ReportEventReceiver {
return &ReportEventReceiver{
httpClient: http.Client{},
host: *hostURL,
httpClient: &http.Client{},
clusterName: clusterName,
customerGUID: customerGUID,
}
}
func (report *ReportEventReceiver) ActionSendReportListenner(opaSessionObj *cautils.OPASessionObj) {
if cautils.CustomerGUID == "" {
return
}
//Add score
func (report *ReportEventReceiver) ActionSendReport(opaSessionObj *cautils.OPASessionObj) error {
// Remove data before reporting
keepFields := []string{"kind", "apiVersion", "metadata"}
keepMetadataFields := []string{"name", "namespace", "labels"}
opaSessionObj.PostureReport.RemoveData(keepFields, keepMetadataFields)
if err := report.Send(opaSessionObj.PostureReport); err != nil {
fmt.Println(err)
if err := report.send(opaSessionObj.PostureReport); err != nil {
return err
}
return nil
}
func (report *ReportEventReceiver) Send(postureReport *reporthandling.PostureReport) error {
func (report *ReportEventReceiver) SetCustomerGUID(customerGUID string) {
report.customerGUID = customerGUID
}
func (report *ReportEventReceiver) SetClusterName(clusterName string) {
report.clusterName = clusterName
}
func (report *ReportEventReceiver) send(postureReport *reporthandling.PostureReport) error {
reqBody, err := json.Marshal(*postureReport)
if err != nil {
return fmt.Errorf("in 'Send' failed to json.Marshal, reason: %v", err)
}
host := hostToString(&report.host, postureReport.ReportID)
host := hostToString(report.initEventReceiverURL(), postureReport.ReportID)
req, err := http.NewRequest("POST", host, bytes.NewReader(reqBody))
if err != nil {
return fmt.Errorf("in 'Send', http.NewRequest failed, host: %s, reason: %v", host, err)
}
res, err := report.httpClient.Do(req)
if err != nil {
return fmt.Errorf("httpClient.Do failed: %v", err)
}
msg, err := httpRespToString(res)
msg, err := getter.HttpPost(report.httpClient, host, nil, reqBody)
if err != nil {
return fmt.Errorf("%s, %v:%s", host, err, msg)
}

View File

@@ -1,47 +1,21 @@
package reporter
import (
"fmt"
"io"
"net/http"
"net/url"
"strings"
"github.com/armosec/kubescape/cautils"
"github.com/armosec/kubescape/cautils/getter"
"github.com/gofrs/uuid"
)
// HTTPRespToString parses the body as string and checks the HTTP status code, it closes the body reader at the end
func httpRespToString(resp *http.Response) (string, error) {
if resp == nil || resp.Body == nil {
return "", nil
}
strBuilder := strings.Builder{}
defer resp.Body.Close()
if resp.ContentLength > 0 {
strBuilder.Grow(int(resp.ContentLength))
}
_, err := io.Copy(&strBuilder, resp.Body)
if err != nil {
return strBuilder.String(), err
}
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
err = fmt.Errorf("response status: %d. Content: %s", resp.StatusCode, strBuilder.String())
}
return strBuilder.String(), err
}
func initEventReceiverURL() *url.URL {
func (report *ReportEventReceiver) initEventReceiverURL() *url.URL {
urlObj := url.URL{}
urlObj.Scheme = "https"
urlObj.Host = getter.GetArmoAPIConnector().GetReportReceiverURL()
urlObj.Path = "/k8s/postureReport"
q := urlObj.Query()
q.Add("customerGUID", uuid.FromStringOrNil(cautils.CustomerGUID).String())
q.Add("clusterName", cautils.ClusterName)
q.Add("customerGUID", uuid.FromStringOrNil(report.customerGUID).String())
q.Add("clusterName", report.clusterName)
urlObj.RawQuery = q.Encode()

View File

@@ -1,18 +1,21 @@
package resultshandling
import (
"fmt"
"github.com/armosec/kubescape/cautils"
"github.com/armosec/kubescape/resultshandling/printer"
"github.com/armosec/kubescape/resultshandling/reporter"
"github.com/armosec/opa-utils/reporthandling"
)
type ResultsHandler struct {
opaSessionObj *chan *cautils.OPASessionObj
reporterObj *reporter.ReportEventReceiver
printerObj *printer.Printer
reporterObj reporter.IReport
printerObj printer.IPrinter
}
func NewResultsHandler(opaSessionObj *chan *cautils.OPASessionObj, reporterObj *reporter.ReportEventReceiver, printerObj *printer.Printer) *ResultsHandler {
func NewResultsHandler(opaSessionObj *chan *cautils.OPASessionObj, reporterObj reporter.IReport, printerObj printer.IPrinter) *ResultsHandler {
return &ResultsHandler{
opaSessionObj: opaSessionObj,
reporterObj: reporterObj,
@@ -20,13 +23,38 @@ func NewResultsHandler(opaSessionObj *chan *cautils.OPASessionObj, reporterObj *
}
}
func (resultsHandler *ResultsHandler) HandleResults() float32 {
func (resultsHandler *ResultsHandler) HandleResults(scanInfo *cautils.ScanInfo) float32 {
opaSessionObj := <-*resultsHandler.opaSessionObj
score := resultsHandler.printerObj.ActionPrint(opaSessionObj)
resultsHandler.printerObj.ActionPrint(opaSessionObj)
resultsHandler.reporterObj.ActionSendReportListenner(opaSessionObj)
if err := resultsHandler.reporterObj.ActionSendReport(opaSessionObj); err != nil {
fmt.Println(err)
}
// TODO - get score from table
score := CalculatePostureScore(opaSessionObj.PostureReport)
resultsHandler.printerObj.Score(score)
return score
}
// CalculatePostureScore calculate final score
func CalculatePostureScore(postureReport *reporthandling.PostureReport) float32 {
lowestScore := float32(100)
for _, frameworkReport := range postureReport.FrameworkReports {
totalFailed := frameworkReport.GetNumberOfFailedResources()
totalResources := frameworkReport.GetNumberOfResources()
frameworkScore := float32(0)
if float32(totalResources) > 0 {
frameworkScore = (float32(totalResources) - float32(totalFailed)) / float32(totalResources)
}
if lowestScore > frameworkScore {
lowestScore = frameworkScore
}
}
return lowestScore
}

47
smoke_testing/init.py Normal file
View File

@@ -0,0 +1,47 @@
"""
Kubescape smoke testing
Execute all tests:
python init.py path/the/bin/kubescape
Execute single test:
python test_<name>.py path/the/bin/kubescape
Add a new test:
1. Create a python file with test_ prefix
2. Implement a function named run()
"""
import sys
import smoke_utils
import glob
import os
# get all python files in dir that begin with test_
tests_pkg = list(map(lambda x: os.path.splitext(os.path.basename(x))[0], glob.glob(os.path.join(os.path.dirname(os.path.realpath(__file__)), 'test_*.py'))))
def run(**kwargs):
for i in tests_pkg:
m = __import__(i)
m.run(**kwargs)
if __name__ == "__main__":
# the first argument should be the kubescape binary path
run(kubescape_exec=smoke_utils.get_exec_from_args(sys.argv))
'''
Supported tests:
1. Commands
2. Version number
3. E2E yaml scanning
TODO:
1. Test formats + output
2. Test --fail-threshold
3. Test known supported FW
4. Test FW list
5. Test Controls list
'''

View File

@@ -0,0 +1,13 @@
import subprocess
def get_exec_from_args(args: list):
return args[1]
def run_command(command):
try:
return f"{subprocess.check_output(command, stderr=subprocess.STDOUT)}"
except Exception as e:
return f"{e}"

View File

@@ -0,0 +1,31 @@
import smoke_utils
import sys
def test_command(command: list):
print(f"Testing \"{' '.join(command[1:])}\" command")
msg = smoke_utils.run_command(command)
assert "unknown command" not in msg, f"{command[1:]} is missing: {msg}"
assert "invalid parameter" not in msg, f"{command[1:]} is invalid: {msg}"
print(f"Done testing \"{' '.join(command[1:])}\" command")
def run(kubescape_exec:str):
print("Testing supported commands")
test_command(command=[kubescape_exec, "version"])
test_command(command=[kubescape_exec, "download"])
test_command(command=[kubescape_exec, "config"])
test_command(command=[kubescape_exec, "help"])
test_command(command=[kubescape_exec, "scan", "framework"])
test_command(command=[kubescape_exec, "scan", "control"])
test_command(command=[kubescape_exec, "submit", "results"])
test_command(command=[kubescape_exec, "submit", "rbac"])
print("Done testing commands")
if __name__ == "__main__":
run(kubescape_exec=smoke_utils.get_exec_from_args(sys.argv))

View File

@@ -0,0 +1,19 @@
import os
import smoke_utils
import sys
def full_scan(kubescape_exec: str):
return smoke_utils.run_command(command=[kubescape_exec, "scan", "framework", "nsa", os.path.join("..", "*.yaml")])
def run(kubescape_exec: str):
# return
print("Testing E2E yaml files")
msg = full_scan(kubescape_exec=kubescape_exec)
assert "exit status 1" not in msg, msg
print("Done E2E yaml files")
if __name__ == "__main__":
run(kubescape_exec=smoke_utils.get_exec_from_args(sys.argv))

View File

@@ -0,0 +1,17 @@
import os
import smoke_utils
import sys
def run(kubescape_exec: str):
print("Testing version")
ver = os.getenv("RELEASE")
msg = smoke_utils.run_command(command=[kubescape_exec, "version"])
assert ver in msg, f"expected version: {ver}, found: {msg}"
print("Done testing version")
if __name__ == "__main__":
run(kubescape_exec=smoke_utils.get_exec_from_args(sys.argv))