Compare commits

..

143 Commits

Author SHA1 Message Date
David Wertenteil
fd8dd7ab8a Merge pull request #100 from slashben/master
Adding discord links to readme
2021-10-03 15:26:39 +03:00
Ben Hirschberg
0434f6a935 Merge pull request #99 from taylor/patch-1
Fix link to hardening guide in README
2021-10-03 13:39:05 +03:00
Benyamin Hirschberg
b51a26442c returning examples to the main readme 2021-10-03 09:16:18 +03:00
Benyamin Hirschberg
6462ac0f0e moving options 2021-10-03 09:08:03 +03:00
Benyamin Hirschberg
fc01dbbac9 discord banner 2021-10-03 08:10:29 +03:00
Benyamin Hirschberg
df8576c066 Use a better discord logo 2021-10-03 08:07:42 +03:00
Benyamin Hirschberg
ca8adf28cf Discord invitation 2021-10-03 08:03:17 +03:00
Taylor Carpenter
f70c6c566e Fix link to hardening guide
Signed-off-by: Taylor Carpenter <taylor@vulk.coop>

Updating the link to the Kubernetes Hardening Guidance by NSA and CISA

Ref: https://github.com/armosec/kubescape/issues/97
2021-09-30 10:10:51 -05:00
Benyamin Hirschberg
056d4411b7 Merge pull request #92 from BenHirschbergCa/master
adding CONTRIBUTING.md
2021-09-27 13:55:13 +03:00
Ben Hirschberg
602b83b0e5 adding CONTRIBUTING.md 2021-09-27 13:53:54 +03:00
Benyamin Hirschberg
1cfcc6d930 Merge pull request #90 from BenHirschbergCa/master
installing kubescape in home directory if possible
2021-09-23 21:05:31 +03:00
Ben Hirschberg
8c6f618743 installing kubescape in home directory if possible 2021-09-23 17:54:05 +00:00
Benyamin Hirschberg
6adf1c3162 Merge pull request #88 from BenHirschbergCa/master
Fixing sudo missing issue and returning build without C runtime dependency
2021-09-21 23:59:30 +03:00
Ben Hirschberg
9f5f4f1832 removing useless command in install 2021-09-21 23:48:26 +03:00
Ben Hirschberg
9f49cc83e9 Fixing install.sh to work in environments where there is no sudo && enabling static executable build 2021-09-21 23:45:35 +03:00
Benyamin Hirschberg
67972199ce Merge pull request #86 from armosec/Daniel-GrunbergerCA-patch-1
Update README.md
2021-09-21 23:05:25 +03:00
Daniel Grunberger
9d5db86bf3 Update README.md 2021-09-19 17:42:59 +03:00
Benyamin Hirschberg
39efed5fc1 Merge pull request #85 from armosec/dev
Delivering signup to master
2021-09-19 17:34:31 +03:00
Benyamin Hirschberg
21c2bf22dd Update scaninfo.go 2021-09-19 17:28:46 +03:00
Benyamin Hirschberg
6c94b3a423 Merge branch 'master' into dev 2021-09-19 17:24:14 +03:00
Benyamin Hirschberg
683248db0b Merge pull request #83 from Daniel-GrunbergerCA/master
Finish config local flags
2021-09-19 17:08:56 +03:00
Daniel Grunberger
9032400528 Fix permission denied 2021-09-19 16:19:25 +03:00
Bezbran
1a925b1acf Merge pull request #84 from Bezbran/dev
attributes as lower
2021-09-19 15:09:01 +03:00
Bezalel Brandwine
dadc8c2c60 exception attributes to lower 2021-09-19 15:07:39 +03:00
Daniel-GrunbergerCA
01c1b44bfc finish config local flags 2021-09-19 14:53:04 +03:00
Bezbran
a394a99d8f Merge pull request #2 from armosec/dev
Dev from armosec
2021-09-19 13:39:31 +03:00
Benyamin Hirschberg
4213707b7f Merge pull request #82 from brsolomon-deloitte/bugfix-issue-81-output-ext
Bugfix: correctly compare filepath.Ext() result
2021-09-18 21:26:39 +03:00
Brad Solomon
d53b8272ee Bugfix: correctly compare filepath.Ext() result
Closes #81.

setOutputFile() will incorrectly append .json to an --output
value that already has it. This is because
https://pkg.go.dev/path/filepath#Ext result includes
the ., whereas the current logic only tests against
json, not .json.
2021-09-16 11:57:58 -04:00
Daniel-GrunbergerCA
fdd688ac68 Merge remote-tracking branch 'upstream/dev' 2021-09-14 17:53:35 +03:00
Daniel-GrunbergerCA
5bb961bdc6 start get/set for config.json 2021-09-14 17:53:21 +03:00
Daniel-GrunbergerCA
9e7cc06f97 give higher priority to config.json 2021-09-14 17:14:21 +03:00
Benyamin Hirschberg
1d184d9000 Merge pull request #78 from YiscahLevySilas1/master
remove redundant responses
2021-09-14 16:52:05 +03:00
Benyamin Hirschberg
a5e2ebf647 Update datastructuresmethods.go 2021-09-14 16:51:16 +03:00
Benyamin Hirschberg
29eb573de5 Merge pull request #79 from BenHirschbergCa/dev
Add build for dev branch
2021-09-14 16:49:45 +03:00
Ben Hirschberg
ec6c3da5ec no needs :) 2021-09-14 16:40:12 +03:00
yiscah
e2d4f8961e Merge remote-tracking branch 'upstream/dev' 2021-09-14 16:38:13 +03:00
Ben Hirschberg
a48c680201 build dev branch 2021-09-14 16:36:57 +03:00
yiscah
c869f2c962 append to msg, loop backward on ruleresponses, don't check redundant role/clusterrole k8sresources 2021-09-14 16:36:57 +03:00
Benyamin Hirschberg
f77fc9a06d Merge branch 'armosec:dev' into dev 2021-09-14 16:01:59 +03:00
Daniel Grunberger
e12eae93b9 Merge pull request #77 from Daniel-GrunbergerCA/master
Add  config clsuter & fix github workflow
2021-09-14 15:27:09 +03:00
Daniel-GrunbergerCA
d92fb32574 fix build.yaml 2021-09-14 15:14:26 +03:00
Daniel-GrunbergerCA
541dba3d79 update build.yaml 2021-09-14 15:06:18 +03:00
yiscah
033ed17125 controlReport status is passed only if ALL ruleReports passed 2021-09-14 14:58:10 +03:00
Daniel-GrunbergerCA
aaeb663d15 remove env vars 2021-09-14 14:02:15 +03:00
Daniel-GrunbergerCA
c337005985 update build.yaml 2021-09-14 13:55:53 +03:00
Daniel-GrunbergerCA
192eeee348 update build.yaml for testing 2021-09-14 13:47:34 +03:00
yiscah
27c97684b9 delete redundant rule responses 2021-09-14 13:38:53 +03:00
Daniel-GrunbergerCA
41d5fa70ed Merge remote-tracking branch 'upstream/dev' 2021-09-14 13:21:25 +03:00
Bezalel Brandwine
4206e9c175 add caution for URLs changes 2021-09-14 12:52:35 +03:00
Daniel-GrunbergerCA
8658bb05dd change token to invitation in configmap 2021-09-14 12:23:28 +03:00
Daniel-GrunbergerCA
9b707016a9 Add set/get key-value option 2021-09-14 11:50:53 +03:00
Bezalel Brandwine
4b02826883 invitation token as param in configmap 2021-09-14 10:36:51 +03:00
Bezalel Brandwine
b29774ea71 some outputs refining 2021-09-14 09:58:17 +03:00
Benyamin Hirschberg
bf68e90a8e Merge pull request #8 from armosec/dev
Dev
2021-09-13 17:07:58 +03:00
Benyamin Hirschberg
cc5cdcd831 Merge branch 'dev' into dev 2021-09-13 17:07:51 +03:00
lalafi@cyberarmor.io
07f23ff7d9 Merge branch 'dev' of ssh://github.com/armosec/kubescape into dev 2021-09-13 16:33:32 +03:00
lalafi@cyberarmor.io
2985da6dc9 remove redundant field 2021-09-13 16:33:25 +03:00
Bezalel Brandwine
1523973749 change consts to var so ldflag -s will work 2021-09-13 15:26:29 +03:00
Bezalel Brandwine
ccafd78a14 change URLs to production at build time 2021-09-13 15:10:43 +03:00
Bezalel Brandwine
4f71fe0d55 support rerun on the same cluster flows 2021-09-13 14:25:09 +03:00
David Wertenteil
7bd6b6b4d1 Merge pull request #75 from dwertent/master
Handle download framework error
2021-09-13 11:31:25 +03:00
dwertent
2b976489a2 handle download framework error 2021-09-13 11:21:57 +03:00
dwertent
1440f20f95 merged from master 2021-09-13 11:01:49 +03:00
dwertent
941e7e27c0 Merge branch 'master' into dev 2021-09-13 11:00:30 +03:00
dwertent
5428c6ab2f merged from dev 2021-09-13 10:57:34 +03:00
David Wertenteil
851bb65d17 Merge pull request #74 from dwertent/master
* Update exception support
* Update installation script
2021-09-13 10:53:10 +03:00
dwertent
a3ce04b7e8 update install script 2021-09-13 10:47:29 +03:00
dwertent
4d68ca6aa2 update exceptions support 2021-09-13 10:24:24 +03:00
Ben Hirschberg
29c6767d3c add run locally to readme 2021-09-13 09:41:16 +03:00
Benyamin Hirschberg
d8f5f7975c Merge pull request #73 from BenHirschbergCa/dev
Inverting posture score
2021-09-13 08:34:22 +03:00
Ben Hirschberg
4cd8476837 Inverting posture score 2021-09-13 08:31:13 +03:00
Benyamin Hirschberg
112257449f Merge pull request #72 from BenHirschbergCa/dev
fixing API path
2021-09-13 08:23:22 +03:00
Ben Hirschberg
ff9cf4adf0 fixing API path 2021-09-13 08:22:25 +03:00
Benyamin Hirschberg
f2d387bc9c Merge pull request #71 from Daniel-GrunbergerCA/master
Support score, cluster name and results flag
2021-09-12 20:53:49 +03:00
lalafi@cyberarmor.io
2ceb5150e2 adding control id 2021-09-12 19:28:11 +03:00
Daniel-GrunbergerCA
00006ec721 rm omit empty 2021-09-12 18:49:13 +03:00
Daniel-GrunbergerCA
00aa6948ab Merge remote-tracking branch 'upstream/dev' 2021-09-12 18:47:31 +03:00
lalafi@cyberarmor.io
aad32ec965 added controlID 2021-09-12 18:43:45 +03:00
Daniel-GrunbergerCA
bd24ed3af7 add framework score 2021-09-12 18:37:50 +03:00
Daniel-GrunbergerCA
9fc455bcec Update posture report flag and cluster name 2021-09-12 18:13:24 +03:00
dwertent
0f8ba1e7e8 Merge branches 'master' and 'master' of github.com:armosec/kubescape 2021-09-12 17:45:32 +03:00
David Wertenteil
d3137af3d7 skip score updating 2021-09-12 17:36:23 +03:00
dwertent
775dd037d6 Merge branch 'dev' 2021-09-12 17:34:27 +03:00
dwertent
49cbfe130c do not calculate score 2021-09-12 17:34:00 +03:00
David Wertenteil
9cd61dd996 Merge pull request #70 from Moshe-Rappaport-CA/master
Support control IDs
2021-09-12 17:30:27 +03:00
moshep
292c4aa060 support id 2021-09-12 16:13:33 +03:00
moshep
56a265930d Merge remote-tracking branch 'upstream/dev' 2021-09-12 14:21:19 +03:00
Daniel-GrunbergerCA
4171d110a4 separa interfaces 2021-09-12 11:06:43 +03:00
Benyamin Hirschberg
2568241ef8 Merge pull request #69 from pettersolberg88/master
fix: Fixed Docker build not working
2021-09-12 10:19:18 +03:00
Daniel-GrunbergerCA
d0775565e9 Merge remote-tracking branch 'upstream/dev' 2021-09-12 09:40:17 +03:00
Petter Solberg
3c6b2db919 fix: Fixed Docker build not working
Building the docker image does currently not work because go.mod does not exist.
By running: `docker build -t kubescape -f build/Dockerfile .`
It fails:
```
Step 7/10 : RUN GOOS=linux CGO_ENABLED=0 go build -ldflags="-s -w " -installsuffix cgo  -o kubescape .
 ---> Running in 3e7d4a124446
cautils/k8sinterface/cloudvendorregistrycreds.go:14:2: missing go.sum entry for module providing package github.com/aws/aws-sdk-go/aws (imported by github.com/armosec/kubescape/cautils/k8sinterface); to add:
        go get github.com/armosec/kubescape/cautils/k8sinterface
cautils/k8sinterface/cloudvendorregistrycreds.go:15:2: missing go.sum entry for module providing package github.com/aws/aws-sdk-go/aws/session (imported by github.com/armosec/kubescape/cautils/k8sinterface); to add:
        go get github.com/armosec/kubescape/cautils/k8sinterface
...
```
By changing mod download to go mod tidy, it creates go.sum and the docker build works.
2021-09-10 14:10:09 +02:00
David Wertenteil
ca4d4a096c Support self registration 2021-09-10 01:20:28 +03:00
dwertent
ff27db6b83 Merge remote-tracking branch 'upstream/dev' 2021-09-10 00:58:24 +03:00
Daniel Grunberger
c9ecb6c563 Add version handling (#67)
* start version handling

* add version handling

* update latest version check

* update build.yaml

* erase unused vars

* fix build.yaml

* fix var name

* handle error
2021-09-10 00:57:33 +03:00
dwertent
b9e5782264 support self registeration 2021-09-10 00:56:51 +03:00
Daniel-GrunbergerCA
d852f81cb0 handle error 2021-09-09 19:56:29 +03:00
dwertent
6137aa5d8e support fronegg 2021-09-09 17:15:55 +03:00
Daniel-GrunbergerCA
131b67ee83 fix var name 2021-09-09 16:26:42 +03:00
Daniel-GrunbergerCA
db6f00be08 fix build.yaml 2021-09-09 15:23:34 +03:00
Daniel-GrunbergerCA
2ecc80985a Merge remote-tracking branch 'upstream/dev' 2021-09-09 15:18:56 +03:00
Daniel-GrunbergerCA
93dbfd5110 erase unused vars 2021-09-09 15:15:36 +03:00
Daniel-GrunbergerCA
ae0c384c85 update build.yaml 2021-09-09 15:02:48 +03:00
Benyamin Hirschberg
f60ff1fb26 Merge pull request #63 from zc2638/feat/dockerfile
add docker build
2021-09-09 14:59:46 +03:00
Daniel-GrunbergerCA
08a81696a1 update latest version check 2021-09-09 14:58:52 +03:00
Daniel-GrunbergerCA
8375a8ae63 add version handling 2021-09-09 14:37:25 +03:00
Daniel-GrunbergerCA
31d8cf5118 start version handling 2021-09-09 12:29:42 +03:00
dwertent
597b967e55 Merge remote-tracking branch 'upstream/dev' 2021-09-09 11:43:44 +03:00
zc
77a9956d91 add docker build 2021-09-07 14:21:39 +08:00
dwertent
3f84ee3fcc update summary 2021-09-05 14:42:49 +03:00
dwertent
38103ac90b update screenshot 2021-09-05 14:39:13 +03:00
dwertent
13d27697e1 update readme 2021-09-05 14:33:32 +03:00
dwertent
942f356d19 support exceptions 2021-09-05 14:21:51 +03:00
dwertent
b87b687e2f support exceptions 2021-09-02 17:41:03 +03:00
dwertent
2e313719bb adding scaore and excpetion to code 2021-09-02 14:54:04 +03:00
dwertent
0c5eb48fdb Merge remote-tracking branch 'upstream/master' 2021-09-02 13:21:13 +03:00
dwertent
2ae2c81e0b rm download 2021-09-02 13:18:23 +03:00
dwertent
222b154505 store file localy 2021-08-31 17:08:02 +03:00
dwertent
67c2de74f1 adding download script 2021-08-31 17:05:16 +03:00
dwertent
4a9b36807a remove sudo 2021-08-31 16:43:37 +03:00
dwertent
c6241fab38 remove sudo 2021-08-31 16:42:12 +03:00
dwertent
afbc69c6d2 Merge remote-tracking branch 'upstream/dev' 2021-08-31 16:41:44 +03:00
dwertent
2779cb4e25 update module 2021-08-31 11:47:44 +03:00
dwertent
f46ee93539 update modul name 2021-08-31 11:39:27 +03:00
dwertent
3eb087e5c1 Merge remote-tracking branch 'upstream/dev' 2021-08-31 11:38:14 +03:00
dwertent
59c935e723 update output f 2021-08-31 09:00:52 +03:00
dwertent
bae45d277f Merge remote-tracking branch 'upstream/dev' 2021-08-31 08:47:37 +03:00
dwertent
0b6dfa9cd0 Merge remote-tracking branch 'upstream/dev' 2021-08-30 18:47:07 +03:00
dwertent
1ff3a6c92c support output to file 2021-08-30 18:44:42 +03:00
dwertent
f75cee0d78 support stdin input 2021-08-30 14:54:01 +03:00
dwertent
229f16cb01 Merge remote-tracking branch 'upstream/dev' 2021-08-30 13:52:58 +03:00
dwertent
2c6b1a440f update glob function 2021-08-30 08:53:34 +03:00
dwertent
37afc1352f adding helm support to readme 2021-08-29 13:34:40 +03:00
dwertent
9943119033 recursive glob 2021-08-29 13:15:34 +03:00
dwertent
41457ff551 Merge remote-tracking branch 'upstream/dev' 2021-08-29 10:38:42 +03:00
dwertent
82b64b5828 Merge remote-tracking branch 'origin/dev' 2021-08-29 10:35:59 +03:00
dwertent
229e8acc74 Merge remote-tracking branch 'origin/yamlsupport' 2021-08-29 10:35:32 +03:00
David Wertenteil
30324e1c01 Merge branch 'dev' into yamlsupport 2021-08-29 10:19:09 +03:00
dwertent
8ca356eae7 Merge remote-tracking branch 'upstream/master' 2021-08-29 10:09:54 +03:00
dwertent
29f4ae368d support url input, update readme 2021-08-29 10:08:49 +03:00
dwertent
409080f51b update package name o kubescape 2021-08-29 08:17:09 +03:00
dwertent
0b24c46279 Merge remote-tracking branch 'upstream/dev' 2021-08-26 18:30:50 +03:00
dwertent
49596c5ac1 split to function 2021-08-26 18:29:32 +03:00
dwertent
9bf79db8f8 Merge branch 'Daniel-GrunbergerCA-master' into dev 2021-08-26 12:22:34 +03:00
42 changed files with 1283 additions and 107 deletions

View File

@@ -6,7 +6,6 @@ on:
pull_request:
branches: [ master ]
types: [ closed ]
jobs:
once:
name: Create release
@@ -38,9 +37,14 @@ jobs:
uses: actions/setup-go@v2
with:
go-version: 1.16
- name: Build
run: mkdir -p build/${{ matrix.os }} && go mod tidy && go build -ldflags "-w -s" -o build/${{ matrix.os }}/kubescape # && md5sum build/${{ matrix.os }}/kubescape > build/${{ matrix.os }}/kubescape.md5
env:
RELEASE: v1.0.${{ github.run_number }}
ArmoBEServer: api.armo.cloud
ArmoERServer: report.euprod1.cyberarmorsoft.com
ArmoWebsite: portal.armo.cloud
CGO_ENABLED: 0
run: mkdir -p build/${{ matrix.os }} && go mod tidy && go build -ldflags "-w -s -X github.com/armosec/kubescape/cmd.BuildNumber=$RELEASE -X github.com/armosec/kubescape/cautils/getter.ArmoBEURL=$ArmoBEServer -X github.com/armosec/kubescape/cautils/getter.ArmoERURL=$ArmoERServer -X github.com/armosec/kubescape/cautils/getter.ArmoFEURL=$ArmoWebsite" -o build/${{ matrix.os }}/kubescape # && md5sum build/${{ matrix.os }}/kubescape > build/${{ matrix.os }}/kubescape.md5
- name: Upload Release binaries
id: upload-release-asset

36
.github/workflows/build_dev.yaml vendored Normal file
View File

@@ -0,0 +1,36 @@
name: build-dev
on:
push:
branches: [ dev ]
pull_request:
branches: [ dev ]
types: [ closed ]
jobs:
build:
name: Create cross-platform dev build
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
steps:
- uses: actions/checkout@v1
- name: Set up Go
uses: actions/setup-go@v2
with:
go-version: 1.16
- name: Build
env:
RELEASE: v1.0.${{ github.run_number }}
ArmoBEServer: api.armo.cloud
ArmoERServer: report.euprod1.cyberarmorsoft.com
ArmoWebsite: portal.armo.cloud
CGO_ENABLED: 0
run: mkdir -p build/${{ matrix.os }} && go mod tidy && go build -ldflags "-w -s -X github.com/armosec/kubescape/cmd.BuildNumber=$RELEASE -X github.com/armosec/kubescape/cautils/getter.ArmoBEURL=$ArmoBEServer -X github.com/armosec/kubescape/cautils/getter.ArmoERURL=$ArmoERServer -X github.com/armosec/kubescape/cautils/getter.ArmoFEURL=$ArmoWebsite" -o build/${{ matrix.os }}/kubescape # && md5sum build/${{ matrix.os }}/kubescape > build/${{ matrix.os }}/kubescape.md5
- name: Upload build artifacts
uses: actions/upload-artifact@v2
with:
name: kubescape-${{ matrix.os }}
path: build/${{ matrix.os }}/kubescape

3
.gitignore vendored
View File

@@ -1,4 +1,5 @@
*.vs*
*go.sum*
*kubescape*
*debug*
*debug*
.idea

99
CONTRIBUTING.md Normal file
View File

@@ -0,0 +1,99 @@
# Contributing
First, it is awesome that you are considering contributing to Kubescape! Contributing is important and fun and we welcome your efforts.
When contributing, we categorize contributions into two:
* Small code changes or fixes, whose scope are limited to a single or two files
* Complex features and improvements, whose are not limited
If you have a small change, feel free to fire up a Pull Request.
When planning a bigger change, please first discuss the change you wish to make via issue,
email, or any other method with the owners of this repository before making a change. Most likely your changes or features are great, but sometimes we might already going to this direction (or the exact opposite ;-) ) and we don't want to waste your time.
Please note we have a code of conduct, please follow it in all your interactions with the project.
## Pull Request Process
1. Ensure any install or build dependencies are removed before the end of the layer when doing a
build.
2. Update the README.md with details of changes to the interface, this includes new environment
variables, exposed ports, useful file locations and container parameters.
3. We will merge the Pull Request in once you have the sign-off.
## Code of Conduct
### Our Pledge
In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to making participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, gender identity and expression, level of experience,
nationality, personal appearance, race, religion, or sexual identity and
orientation.
### Our Standards
Examples of behavior that contributes to creating a positive environment
include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or
advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic
address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
We will distance those who are constantly adhere to unacceptable behavior.
### Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.
### Scope
This Code of Conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community. Examples of
representing a project or community include using an official project e-mail
address, posting via an official social media account, or acting as an appointed
representative at an online or offline event. Representation of a project may be
further defined and clarified by project maintainers.
### Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at [INSERT EMAIL ADDRESS]. All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership.
### Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at [http://contributor-covenant.org/version/1/4][version]
[homepage]: http://contributor-covenant.org
[version]: http://contributor-covenant.org/version/1/4/

View File

@@ -3,21 +3,19 @@
[![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/News-Features/Feature-Stories/Article-View/Article/2716980/nsa-cisa-release-kubernetes-hardening-guidance/)
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/)
Use Kubescape to test clusters or scan single YAML files and integrate it to your processes.
<img src="docs/demo.gif">
# TL;DR
## Install & Run
### Install:
## Install:
```
curl -s https://raw.githubusercontent.com/armosec/kubescape/master/install.sh | /bin/bash
```
### Run:
## Run:
```
kubescape scan framework nsa --exclude-namespaces kube-system,kube-public
```
@@ -26,8 +24,20 @@ If you wish to scan all namespaces in your cluster, remove the `--exclude-namesp
<img src="docs/summary.png">
# Being part of the team
### Flags
We invite you to our team! We are excited about this project and want to return the love we get.
Want to contribute? Want to discuss something? Have an issue?
* Open a issue, we are trying to respond within 48 hours
* [Join us](https://discordapp.com/invite/CTcCaBbb) in a discussion on our discord server!
[<img src="docs/discord-banner.png" width="100" alt="logo" align="center">](https://discordapp.com/invite/CTcCaBbb)
# Options and examples
## Flags
| flag | default | description | options |
| --- | --- | --- | --- |
@@ -38,7 +48,8 @@ If you wish to scan all namespaces in your cluster, remove the `--exclude-namesp
| `-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) |
| `--exceptions` | | Path to an [exceptions obj](examples/exceptions.json). If not set will download exceptions from Armo management portal |
| `--results-locally` | `false` | Kubescape sends scan results to Armo management portal to allow users to control exceptions and maintain chronological scan results. Use this flag if you do not wish to use these features | `true`/`false`|
## Usage & Examples
@@ -86,7 +97,6 @@ for example:
```
helm template bitnami/mysql --generate-name --dry-run | kubescape scan framework nsa -
```
### Offline Support <img src="docs/new-feature.svg">
It is possible to run Kubescape offline!
@@ -103,9 +113,12 @@ 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.
# How to build
## For development
Note: development (and the release process) is done with Go `1.16`
1. Clone Project
@@ -125,6 +138,18 @@ go mod tidy && go build -o kubescape .
4. Enjoy :zany_face:
## How to build in Docker
1. Clone Project
```
git clone git@github.com:armosec/kubescape.git kubescape && cd "$_"
```
2. Build
```
docker build -t kubescape -f build/Dockerfile .
```
# Under the hood
## Tests
@@ -149,6 +174,7 @@ Kubescape is running the following tests according to what is defined by [Kubern
* Ingress and Egress blocked
* Container hostPort
* Network policies
* Symlink Exchange Can Allow Host Filesystem Access (CVE-2021-25741)

13
build/Dockerfile Normal file
View File

@@ -0,0 +1,13 @@
FROM golang:1.16-alpine as builder
ENV GOPROXY=https://goproxy.io,direct
ENV GO111MODULE=on
WORKDIR /work
ADD . .
RUN go mod tidy
RUN GOOS=linux CGO_ENABLED=0 go build -ldflags="-s -w " -installsuffix cgo -o kubescape .
FROM alpine
COPY --from=builder /work/kubescape /usr/bin/kubescape
CMD ["kubescape"]

View File

@@ -1,5 +1,7 @@
package armotypes
import "strings"
const (
CostumerGuidQuery = "costumerGUID"
ClusterNameQuery = "cluster"
@@ -42,6 +44,10 @@ const (
DesignatorSid DesignatorType = "Sid" // secret id
)
func (dt DesignatorType) ToLower() DesignatorType {
return DesignatorType(strings.ToLower(string(dt)))
}
// attributes
const (
AttributeCluster = "cluster"

View File

@@ -33,10 +33,10 @@ func (designator *PortalDesignator) GetLabels() map[string]string {
// DigestPortalDesignator - get cluster namespace and labels from designator
func (designator *PortalDesignator) DigestPortalDesignator() (string, string, string, string, map[string]string) {
switch designator.DesignatorType {
case DesignatorAttributes, DesignatorAttribute:
switch designator.DesignatorType.ToLower() {
case DesignatorAttributes.ToLower(), DesignatorAttribute.ToLower():
return designator.DigestAttributesDesignator()
case DesignatorWlid, DesignatorWildWlid:
case DesignatorWlid.ToLower(), DesignatorWildWlid.ToLower():
return cautils.GetClusterFromWlid(designator.WLID), cautils.GetNamespaceFromWlid(designator.WLID), cautils.GetKindFromWlid(designator.WLID), cautils.GetNameFromWlid(designator.WLID), map[string]string{}
// case DesignatorSid: // TODO
default:

325
cautils/customerloader.go Normal file
View File

@@ -0,0 +1,325 @@
package cautils
import (
"context"
"encoding/json"
"fmt"
"io/ioutil"
"net/url"
"os"
"strings"
"github.com/armosec/kubescape/cautils/getter"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/armosec/kubescape/cautils/k8sinterface"
corev1 "k8s.io/api/core/v1"
)
const (
configMapName = "kubescape"
ConfigFileName = "config"
)
type ConfigObj struct {
CustomerGUID string `json:"customerGUID"`
Token string `json:"invitationParam"`
CustomerAdminEMail string `json:"adminMail"`
}
func (co *ConfigObj) Json() []byte {
if b, err := json.Marshal(co); err == nil {
return b
}
return []byte{}
}
type IClusterConfig interface {
SetCustomerGUID() error
GetCustomerGUID() string
GenerateURL()
}
type ClusterConfig struct {
k8s *k8sinterface.KubernetesApi
defaultNS string
armoAPI *getter.ArmoAPI
configObj *ConfigObj
}
type EmptyConfig struct {
}
func (c *EmptyConfig) GenerateURL() {
}
func (c *EmptyConfig) SetCustomerGUID() error {
return nil
}
func (c *EmptyConfig) GetCustomerGUID() string {
return ""
}
func NewEmptyConfig() *EmptyConfig {
return &EmptyConfig{}
}
func NewClusterConfig(k8s *k8sinterface.KubernetesApi, armoAPI *getter.ArmoAPI) *ClusterConfig {
return &ClusterConfig{
k8s: k8s,
armoAPI: armoAPI,
defaultNS: k8sinterface.GetDefaultNamespace(),
}
}
func createConfigJson() {
ioutil.WriteFile(getter.GetDefaultPath(ConfigFileName+".json"), nil, 0664)
}
func update(configObj *ConfigObj) {
ioutil.WriteFile(getter.GetDefaultPath(ConfigFileName+".json"), configObj.Json(), 0664)
}
func (c *ClusterConfig) GenerateURL() {
u := url.URL{}
u.Scheme = "https"
u.Host = getter.ArmoFEURL
if c.configObj == nil {
return
}
if c.configObj.CustomerAdminEMail != "" {
msgStr := fmt.Sprintf("To view all controls and get remediations ask access permissions to %s from %s", u.String(), c.configObj.CustomerAdminEMail)
InfoTextDisplay(os.Stdout, msgStr+"\n")
return
}
u.Path = "account/sign-up"
q := u.Query()
q.Add("invitationToken", c.configObj.Token)
q.Add("customerGUID", c.configObj.CustomerGUID)
u.RawQuery = q.Encode()
fmt.Println("To view all controls and get remediations visit:")
InfoTextDisplay(os.Stdout, u.String()+"\n")
}
func (c *ClusterConfig) GetCustomerGUID() string {
if c.configObj != nil {
return c.configObj.CustomerGUID
}
return ""
}
func (c *ClusterConfig) GetValueByKeyFromConfigMap(key string) (string, error) {
configMap, err := c.k8s.KubernetesClient.CoreV1().ConfigMaps(c.defaultNS).Get(context.Background(), configMapName, metav1.GetOptions{})
if err != nil {
return "", err
}
if val, ok := configMap.Data[key]; ok {
return val, nil
} else {
return "", fmt.Errorf("value does not exist")
}
}
func GetValueFromConfigJson(key string) (string, error) {
data, err := ioutil.ReadFile(getter.GetDefaultPath(ConfigFileName + ".json"))
if err != nil {
return "", err
}
var obj map[string]interface{}
err = json.Unmarshal(data, &obj)
if val, ok := obj[key]; ok {
return fmt.Sprint(val), nil
} else {
return "", fmt.Errorf("value does not exist")
}
}
func SetKeyValueInConfigJson(key string, value string) error {
data, err := ioutil.ReadFile(getter.GetDefaultPath(ConfigFileName + ".json"))
if err != nil {
return err
}
var obj map[string]interface{}
err = json.Unmarshal(data, &obj)
if err != nil {
return err
}
obj[key] = value
newData, err := json.Marshal(obj)
if err != nil {
return err
}
return ioutil.WriteFile(getter.GetDefaultPath(ConfigFileName+".json"), newData, 0664)
}
func (c *ClusterConfig) SetKeyValueInConfigmap(key string, value string) error {
configMap, err := c.k8s.KubernetesClient.CoreV1().ConfigMaps(c.defaultNS).Get(context.Background(), configMapName, metav1.GetOptions{})
if err != nil {
configMap = &corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: configMapName,
},
}
}
if len(configMap.Data) == 0 {
configMap.Data = make(map[string]string)
}
configMap.Data[key] = value
if err != nil {
_, err = c.k8s.KubernetesClient.CoreV1().ConfigMaps(c.defaultNS).Create(context.Background(), configMap, metav1.CreateOptions{})
} else {
_, err = c.k8s.KubernetesClient.CoreV1().ConfigMaps(configMap.Namespace).Update(context.Background(), configMap, metav1.UpdateOptions{})
}
return err
}
func (c *ClusterConfig) SetCustomerGUID() error {
// get from file
if existsConfigJson() {
c.configObj, _ = loadConfigFromFile()
} else if c.existsConfigMap() {
c.configObj, _ = c.loadConfigFromConfigMap()
} else {
c.createConfigMap()
createConfigJson()
}
customerGUID := c.GetCustomerGUID()
// get from armoBE
tenantResponse, err := c.armoAPI.GetCustomerGUID(customerGUID)
if err == nil && tenantResponse != nil {
if tenantResponse.AdminMail != "" { // this customer already belongs to some user
if existsConfigJson() {
update(&ConfigObj{CustomerGUID: customerGUID, CustomerAdminEMail: tenantResponse.AdminMail})
}
if c.existsConfigMap() {
c.configObj.CustomerAdminEMail = tenantResponse.AdminMail
c.updateConfigMap()
}
} else {
if existsConfigJson() {
update(&ConfigObj{CustomerGUID: tenantResponse.TenantID, Token: tenantResponse.Token})
}
if c.existsConfigMap() {
c.configObj = &ConfigObj{CustomerGUID: tenantResponse.TenantID, Token: tenantResponse.Token}
c.updateConfigMap()
}
}
} else {
if err != nil && strings.Contains(err.Error(), "Invitation for tenant already exists") {
return nil
}
return err
}
return nil
}
func (c *ClusterConfig) loadConfigFromConfigMap() (*ConfigObj, error) {
if c.k8s == nil {
return nil, nil
}
configMap, err := c.k8s.KubernetesClient.CoreV1().ConfigMaps(c.defaultNS).Get(context.Background(), configMapName, metav1.GetOptions{})
if err != nil {
return nil, err
}
if bData, err := json.Marshal(configMap.Data); err == nil {
return readConfig(bData)
}
return nil, nil
}
func (c *ClusterConfig) existsConfigMap() bool {
_, err := c.k8s.KubernetesClient.CoreV1().ConfigMaps(c.defaultNS).Get(context.Background(), configMapName, metav1.GetOptions{})
return err == nil
}
func existsConfigJson() bool {
_, err := ioutil.ReadFile(getter.GetDefaultPath(ConfigFileName + ".json"))
return err == nil
}
func (c *ClusterConfig) createConfigMap() error {
if c.k8s == nil {
return nil
}
configMap := &corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: configMapName,
},
}
c.updateConfigData(configMap)
_, err := c.k8s.KubernetesClient.CoreV1().ConfigMaps(c.defaultNS).Create(context.Background(), configMap, metav1.CreateOptions{})
return err
}
func (c *ClusterConfig) updateConfigMap() error {
if c.k8s == nil {
return nil
}
configMap, err := c.k8s.KubernetesClient.CoreV1().ConfigMaps(c.defaultNS).Get(context.Background(), configMapName, metav1.GetOptions{})
if err != nil {
return err
}
c.updateConfigData(configMap)
_, err = c.k8s.KubernetesClient.CoreV1().ConfigMaps(configMap.Namespace).Update(context.Background(), configMap, metav1.UpdateOptions{})
return err
}
func (c *ClusterConfig) updateConfigData(configMap *corev1.ConfigMap) {
if len(configMap.Data) == 0 {
configMap.Data = make(map[string]string)
}
m := c.ToMapString()
for k, v := range m {
if s, ok := v.(string); ok {
configMap.Data[k] = s
}
}
}
func loadConfigFromFile() (*ConfigObj, error) {
dat, err := ioutil.ReadFile(getter.GetDefaultPath(ConfigFileName + ".json"))
if err != nil {
return nil, err
}
return readConfig(dat)
}
func readConfig(dat []byte) (*ConfigObj, error) {
if len(dat) == 0 {
return nil, nil
}
configObj := &ConfigObj{}
err := json.Unmarshal(dat, configObj)
return configObj, err
}
func (c *ClusterConfig) ToMapString() map[string]interface{} {
m := map[string]interface{}{}
bc, _ := json.Marshal(c.configObj)
json.Unmarshal(bc, &m)
return m
}

View File

@@ -3,7 +3,6 @@ package getter
import (
"fmt"
"net/http"
"strings"
"github.com/armosec/kubescape/cautils/armotypes"
"github.com/armosec/kubescape/cautils/opapolicy"
@@ -13,16 +12,23 @@ import (
// =============================================== ArmoAPI ===============================================================
// =======================================================================================================================
var (
// ATTENTION!!!
// Changes in this URLs variable names, or in the usage is affecting the build process! BE CAREFULL
ArmoBEURL = "eggdashbe.eudev3.cyberarmorsoft.com"
ArmoERURL = "report.eudev3.cyberarmorsoft.com"
ArmoFEURL = "armoui.eudev3.cyberarmorsoft.com"
// ArmoURL = "https://dashbe.euprod1.cyberarmorsoft.com"
)
// Armo API for downloading policies
type ArmoAPI struct {
httpClient *http.Client
baseURL string
}
func NewArmoAPI() *ArmoAPI {
return &ArmoAPI{
httpClient: &http.Client{},
baseURL: "https://dashbe.euprod1.cyberarmorsoft.com",
}
}
func (armoAPI *ArmoAPI) GetFramework(name string) (*opapolicy.Framework, error) {
@@ -35,20 +41,11 @@ func (armoAPI *ArmoAPI) GetFramework(name string) (*opapolicy.Framework, error)
if err = JSONDecoder(respStr).Decode(framework); err != nil {
return nil, err
}
SaveFrameworkInFile(framework, GetDefaultPath(name))
SaveFrameworkInFile(framework, GetDefaultPath(name+".json"))
return framework, err
}
func (armoAPI *ArmoAPI) getFrameworkURL(frameworkName string) string {
requestURI := "v1/armoFrameworks"
requestURI += fmt.Sprintf("?customerGUID=%s", "11111111-1111-1111-1111-111111111111")
requestURI += fmt.Sprintf("&frameworkName=%s", strings.ToUpper(frameworkName))
requestURI += "&getRules=true"
return urlEncoder(fmt.Sprintf("%s/%s", armoAPI.baseURL, requestURI))
}
func (armoAPI *ArmoAPI) GetExceptions(customerGUID, clusterName string) ([]armotypes.PostureExceptionPolicy, error) {
exceptions := []armotypes.PostureExceptionPolicy{}
if customerGUID == "" {
@@ -66,11 +63,26 @@ func (armoAPI *ArmoAPI) GetExceptions(customerGUID, clusterName string) ([]armot
return exceptions, nil
}
func (armoAPI *ArmoAPI) getExceptionsURL(customerGUID, clusterName string) string {
requestURI := "api/v1/armoPostureExceptions"
requestURI += fmt.Sprintf("?customerGUID=%s", customerGUID)
if clusterName != "" {
requestURI += fmt.Sprintf("&clusterName=%s", clusterName)
func (armoAPI *ArmoAPI) GetCustomerGUID(customerGUID string) (*TenantResponse, error) {
url := armoAPI.getCustomerURL()
if customerGUID != "" {
url = fmt.Sprintf("%s?customerGUID=%s", url, customerGUID)
}
return urlEncoder(fmt.Sprintf("%s/%s", armoAPI.baseURL, requestURI))
respStr, err := HttpGetter(armoAPI.httpClient, url)
if err != nil {
return nil, err
}
tenant := &TenantResponse{}
if err = JSONDecoder(respStr).Decode(tenant); err != nil {
return nil, err
}
return tenant, nil
}
type TenantResponse struct {
TenantID string `json:"tenantId"`
Token string `json:"token"`
Expires string `json:"expires"`
AdminMail string `json:"adminMail,omitempty"`
}

View File

@@ -0,0 +1,44 @@
package getter
import (
"net/url"
"strings"
)
func (armoAPI *ArmoAPI) getFrameworkURL(frameworkName string) string {
u := url.URL{}
u.Scheme = "https"
u.Host = ArmoBEURL
u.Path = "v1/armoFrameworks"
q := u.Query()
q.Add("customerGUID", "11111111-1111-1111-1111-111111111111")
q.Add("frameworkName", strings.ToUpper(frameworkName))
q.Add("getRules", "true")
u.RawQuery = q.Encode()
return u.String()
}
func (armoAPI *ArmoAPI) getExceptionsURL(customerGUID, clusterName string) string {
u := url.URL{}
u.Scheme = "https"
u.Host = ArmoBEURL
u.Path = "api/v1/armoPostureExceptions"
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"
u.Host = ArmoBEURL
u.Path = "api/v1/createTenant"
return u.String()
}

View File

@@ -6,7 +6,6 @@ import (
"io/ioutil"
"net/http"
"github.com/armosec/kubescape/cautils/armotypes"
"github.com/armosec/kubescape/cautils/opapolicy"
)
@@ -27,12 +26,10 @@ func NewDownloadReleasedPolicy() *DownloadReleasedPolicy {
}
}
func (drp *DownloadReleasedPolicy) GetExceptions(customerGUID, clusterName string) ([]armotypes.PostureExceptionPolicy, error) {
return []armotypes.PostureExceptionPolicy{}, nil
}
func (drp *DownloadReleasedPolicy) GetFramework(name string) (*opapolicy.Framework, error) {
drp.setURL(name)
if err := drp.setURL(name); err != nil {
return nil, err
}
respStr, err := HttpGetter(drp.httpClient, drp.hostURL)
if err != nil {
return nil, err
@@ -43,7 +40,7 @@ func (drp *DownloadReleasedPolicy) GetFramework(name string) (*opapolicy.Framewo
return framework, err
}
SaveFrameworkInFile(framework, GetDefaultPath(name))
SaveFrameworkInFile(framework, GetDefaultPath(name+".json"))
return framework, err
}
@@ -76,12 +73,13 @@ func (drp *DownloadReleasedPolicy) setURL(frameworkName string) error {
if name == frameworkName {
if url, ok := asset["browser_download_url"].(string); ok {
drp.hostURL = url
return nil
}
}
}
}
}
}
return nil
return fmt.Errorf("failed to download '%s' - not found", frameworkName)
}

View File

@@ -7,6 +7,8 @@ import (
type IPolicyGetter interface {
GetFramework(name string) (*opapolicy.Framework, error)
GetExceptions(customerGUID, clusterName string) ([]armotypes.PostureExceptionPolicy, error)
// GetScores(scope, customerName, namespace string) ([]armotypes.PostureExceptionPolicy, error)
}
type IExceptionsGetter interface {
GetExceptions(customerGUID, clusterName string) ([]armotypes.PostureExceptionPolicy, error)
}

View File

@@ -13,8 +13,8 @@ import (
"github.com/armosec/kubescape/cautils/opapolicy"
)
func GetDefaultPath(frameworkName string) string {
defaultfilePath := filepath.Join(DefaultLocalStore, frameworkName+".json")
func GetDefaultPath(name string) string {
defaultfilePath := filepath.Join(DefaultLocalStore, name)
if homeDir, err := os.UserHomeDir(); err == nil {
defaultfilePath = filepath.Join(homeDir, defaultfilePath)
}

View File

@@ -9,12 +9,15 @@ import (
"k8s.io/client-go/dynamic"
"k8s.io/client-go/kubernetes"
restclient "k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
// DO NOT REMOVE - load cloud providers auth
_ "k8s.io/client-go/plugin/pkg/client/auth"
"sigs.k8s.io/controller-runtime/pkg/client/config"
)
var ConnectedToCluster = true
// K8SConfig pointer to k8s config
var K8SConfig *restclient.Config
@@ -27,8 +30,15 @@ type KubernetesApi struct {
// NewKubernetesApi -
func NewKubernetesApi() *KubernetesApi {
var kubernetesClient *kubernetes.Clientset
var err error
kubernetesClient, err := kubernetes.NewForConfig(GetK8sConfig())
if !IsConnectedToCluster() {
fmt.Println(fmt.Errorf("failed to load kubernetes config: no configuration has been provided, try setting KUBECONFIG environment variable"))
os.Exit(1)
}
kubernetesClient, err = kubernetes.NewForConfig(GetK8sConfig())
if err != nil {
fmt.Printf("Failed to load config file, reason: %s", err.Error())
os.Exit(1)
@@ -53,23 +63,54 @@ var RunningIncluster bool
func LoadK8sConfig() error {
kubeconfig, err := config.GetConfig()
if err != nil {
return fmt.Errorf("failed to load kubernetes config: %s\n", strings.ReplaceAll(err.Error(), "KUBERNETES_MASTER", "KUBECONFIG"))
return fmt.Errorf("failed to load kubernetes config: %s", strings.ReplaceAll(err.Error(), "KUBERNETES_MASTER", "KUBECONFIG"))
}
if _, err := restclient.InClusterConfig(); err == nil {
RunningIncluster = true
}
K8SConfig = kubeconfig
return nil
}
// GetK8sConfig get config. load if not loaded yet
func GetK8sConfig() *restclient.Config {
if K8SConfig == nil {
if err := LoadK8sConfig(); err != nil {
// print error
fmt.Printf("%s", err.Error())
os.Exit(1)
}
if !IsConnectedToCluster() {
return nil
}
return K8SConfig
}
func IsConnectedToCluster() bool {
if K8SConfig == nil {
if err := LoadK8sConfig(); err != nil {
ConnectedToCluster = false
}
}
return ConnectedToCluster
}
func GetClusterName() string {
if !ConnectedToCluster {
return ""
}
kubeConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(clientcmd.NewDefaultClientConfigLoadingRules(), &clientcmd.ConfigOverrides{})
config, err := kubeConfig.RawConfig()
if err != nil {
return ""
}
// TODO - Handle if empty
return config.CurrentContext
}
func GetDefaultNamespace() string {
clientCfg, err := clientcmd.NewDefaultClientConfigLoadingRules().Load()
if err != nil {
return "default"
}
namespace := clientCfg.Contexts[clientCfg.CurrentContext].Namespace
if namespace == "" {
namespace = "default"
}
return namespace
}

View File

@@ -41,11 +41,12 @@ type FrameworkReport struct {
}
type ControlReport struct {
armotypes.PortalBase `json:",inline"`
ControlID string `json:"id"`
Name string `json:"name"`
RuleReports []RuleReport `json:"ruleReports"`
Remediation string `json:"remediation"`
Description string `json:"description"`
Score float32 `json:"score,omitempty"`
Score float32 `json:"score"`
BaseScore float32 `json:"baseScore,omitempty"`
ARMOImprovement float32 `json:"ARMOImprovement,omitempty"`
}
@@ -100,6 +101,7 @@ type PolicyRule struct {
// Control represents a collection of rules which are combined together to single purpose
type Control struct {
armotypes.PortalBase `json:",inline"`
ControlID string `json:"id"`
CreationTime string `json:"creationTime"`
Description string `json:"description"`
Remediation string `json:"remediation"`

View File

@@ -33,7 +33,8 @@ func MockFrameworkReportA() *FrameworkReport {
Name: AMockFrameworkName,
ControlReports: []ControlReport{
{
Name: AMockControlName,
ControlID: "C-0010",
Name: AMockControlName,
RuleReports: []RuleReport{
{
Name: AMockRuleName,

View File

@@ -3,6 +3,8 @@ package opapolicy
import (
"bytes"
"encoding/json"
"github.com/armosec/kubescape/cautils/k8sinterface"
)
func (pn *PolicyNotification) ToJSONBytesBuffer() (*bytes.Buffer, error) {
@@ -83,11 +85,11 @@ func (controlReport *ControlReport) ListControlsInputKinds() []string {
func (controlReport *ControlReport) Passed() bool {
for i := range controlReport.RuleReports {
if len(controlReport.RuleReports[i].RuleResponses) == 0 {
return true
if len(controlReport.RuleReports[i].RuleResponses) != 0 {
return false
}
}
return false
return true
}
func (controlReport *ControlReport) Warning() bool {
@@ -120,14 +122,50 @@ func (ruleReport *RuleReport) GetNumberOfResources() int {
func (ruleReport *RuleReport) GetNumberOfFailedResources() int {
sum := 0
for i := range ruleReport.RuleResponses {
for i := len(ruleReport.RuleResponses) - 1; i >= 0; i-- {
if ruleReport.RuleResponses[i].GetSingleResultStatus() == "failed" {
sum += 1
if !ruleReport.DeleteIfRedundantResponse(&ruleReport.RuleResponses[i], i) {
sum++
}
}
}
return sum
}
func (ruleReport *RuleReport) DeleteIfRedundantResponse(RuleResponse *RuleResponse, index int) bool {
if b, rr := ruleReport.IsDuplicateResponseOfResource(RuleResponse, index); b {
rr.AddMessageToResponse(RuleResponse.AlertMessage)
ruleReport.RuleResponses = removeResponse(ruleReport.RuleResponses, index)
return true
}
return false
}
func (ruleResponse *RuleResponse) AddMessageToResponse(message string) {
ruleResponse.AlertMessage += message
}
func (ruleReport *RuleReport) IsDuplicateResponseOfResource(RuleResponse *RuleResponse, index int) (bool, *RuleResponse) {
for i := range ruleReport.RuleResponses {
if i != index {
for j := range ruleReport.RuleResponses[i].AlertObject.K8SApiObjects {
for k := range RuleResponse.AlertObject.K8SApiObjects {
w1 := k8sinterface.NewWorkloadObj(ruleReport.RuleResponses[i].AlertObject.K8SApiObjects[j])
w2 := k8sinterface.NewWorkloadObj(RuleResponse.AlertObject.K8SApiObjects[k])
if w1.GetName() == w2.GetName() && w1.GetNamespace() == w2.GetNamespace() && w1.GetKind() != "Role" && w1.GetKind() != "ClusterRole" {
return true, &ruleReport.RuleResponses[i]
}
}
}
}
}
return false, nil
}
func removeResponse(slice []RuleResponse, index int) []RuleResponse {
return append(slice[:index], slice[index+1:]...)
}
func (ruleReport *RuleReport) GetNumberOfWarningResources() int {
sum := 0
for i := range ruleReport.RuleResponses {
@@ -137,3 +175,62 @@ func (ruleReport *RuleReport) GetNumberOfWarningResources() int {
}
return sum
}
func (postureReport *PostureReport) RemoveData() {
for i := range postureReport.FrameworkReports {
postureReport.FrameworkReports[i].RemoveData()
}
}
func (frameworkReport *FrameworkReport) RemoveData() {
for i := range frameworkReport.ControlReports {
frameworkReport.ControlReports[i].RemoveData()
}
}
func (controlReport *ControlReport) RemoveData() {
for i := range controlReport.RuleReports {
controlReport.RuleReports[i].RemoveData()
}
}
func (ruleReport *RuleReport) RemoveData() {
for i := range ruleReport.RuleResponses {
ruleReport.RuleResponses[i].RemoveData()
}
}
func (r *RuleResponse) RemoveData() {
r.AlertObject.ExternalObjects = nil
keepFields := []string{"kind", "apiVersion", "metadata"}
keepMetadataFields := []string{"name", "namespace", "labels"}
for i := range r.AlertObject.K8SApiObjects {
deleteFromMap(r.AlertObject.K8SApiObjects[i], keepFields)
for k := range r.AlertObject.K8SApiObjects[i] {
if k == "metadata" {
if b, ok := r.AlertObject.K8SApiObjects[i][k].(map[string]interface{}); ok {
deleteFromMap(b, keepMetadataFields)
r.AlertObject.K8SApiObjects[i][k] = b
}
}
}
}
}
func deleteFromMap(m map[string]interface{}, keepFields []string) {
for k := range m {
if StringInSlice(keepFields, k) {
continue
}
delete(m, k)
}
}
func StringInSlice(strSlice []string, str string) bool {
for i := range strSlice {
if strSlice[i] == str {
return true
}
}
return false
}

View File

@@ -19,10 +19,11 @@ type ScanInfo struct {
InputPatterns []string
Silent bool
FailThreshold uint16
DoNotSendResults bool
}
type Getters struct {
ExceptionsGetter getter.IPolicyGetter
ExceptionsGetter getter.IExceptionsGetter
PolicyGetter getter.IPolicyGetter
}
@@ -48,7 +49,7 @@ func (scanInfo *ScanInfo) setUseFrom() {
return
}
if scanInfo.UseDefault {
scanInfo.UseFrom = getter.GetDefaultPath(scanInfo.PolicyIdentifier.Name)
scanInfo.UseFrom = getter.GetDefaultPath(scanInfo.PolicyIdentifier.Name + ".json")
}
}
@@ -66,12 +67,12 @@ func (scanInfo *ScanInfo) setOutputFile() {
return
}
if scanInfo.Format == "json" {
if filepath.Ext(scanInfo.Output) != "json" {
if filepath.Ext(scanInfo.Output) != ".json" {
scanInfo.Output += ".json"
}
}
if scanInfo.Format == "junit" {
if filepath.Ext(scanInfo.Output) != "xml" {
if filepath.Ext(scanInfo.Output) != ".xml" {
scanInfo.Output += ".xml"
}
}
@@ -80,3 +81,8 @@ func (scanInfo *ScanInfo) setOutputFile() {
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
// }

18
cmd/cluster.go Normal file
View File

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

50
cmd/cluster_get.go Normal file
View File

@@ -0,0 +1,50 @@
package cmd
import (
"fmt"
"strings"
"github.com/armosec/kubescape/cautils"
"github.com/armosec/kubescape/cautils/getter"
"github.com/armosec/kubescape/cautils/k8sinterface"
"github.com/spf13/cobra"
)
var getCmd = &cobra.Command{
Use: "get <key>",
Short: "Get configuration in cluster",
Long: ``,
ValidArgs: supportedFrameworks,
Args: func(cmd *cobra.Command, args []string) error {
if len(args) < 1 || len(args) > 1 {
return fmt.Errorf("requires one argument")
}
keyValue := strings.Split(args[0], "=")
if len(keyValue) != 1 {
return fmt.Errorf("requires one argument")
}
return nil
},
RunE: func(cmd *cobra.Command, args []string) error {
keyValue := strings.Split(args[0], "=")
key := keyValue[0]
k8s := k8sinterface.NewKubernetesApi()
clusterConfig := cautils.NewClusterConfig(k8s, getter.NewArmoAPI())
val, err := clusterConfig.GetValueByKeyFromConfigMap(key)
if err != nil {
if err.Error() == "value does not exist." {
fmt.Printf("Could net get value from configmap, reason: %s\n", err)
return nil
}
return err
}
fmt.Println(key + "=" + val)
return nil
},
}
func init() {
clusterCmd.AddCommand(getCmd)
}

44
cmd/cluster_set.go Normal file
View File

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

19
cmd/config.go Normal file
View File

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

View File

@@ -24,7 +24,7 @@ var downloadCmd = &cobra.Command{
downloadInfo.FrameworkName = args[1]
g := getter.NewDownloadReleasedPolicy()
if downloadInfo.Path == "" {
downloadInfo.Path = getter.GetDefaultPath(downloadInfo.FrameworkName)
downloadInfo.Path = getter.GetDefaultPath(downloadInfo.FrameworkName + ".json")
}
frameworks, err := g.GetFramework(downloadInfo.FrameworkName)
if err != nil {

View File

@@ -10,6 +10,7 @@ import (
"github.com/armosec/kubescape/cautils"
"github.com/armosec/kubescape/cautils/armotypes"
"github.com/armosec/kubescape/cautils/getter"
"github.com/armosec/kubescape/cautils/k8sinterface"
"github.com/armosec/kubescape/cautils/opapolicy"
"github.com/armosec/kubescape/opaprocessor"
@@ -93,7 +94,7 @@ func init() {
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.DoNotSendResults, "results-locally", "", false, "Kubescape sends scan results to Armosec backend to allow users to control exceptions and maintain chronological scan results. Use this flag if you do not wish to use these features")
}
func CliSetup() error {
@@ -105,7 +106,9 @@ func CliSetup() error {
}
var k8s *k8sinterface.KubernetesApi
if scanInfo.ScanRunningCluster() {
if !scanInfo.ScanRunningCluster() {
k8sinterface.ConnectedToCluster = false
} else {
k8s = k8sinterface.NewKubernetesApi()
}
@@ -115,12 +118,29 @@ func CliSetup() error {
// policy handler setup
policyHandler := policyhandler.NewPolicyHandler(&processNotification, k8s)
// cli handler setup
cli := NewCLIHandler(policyHandler)
if err := cli.Scan(); err != nil {
panic(err)
// load cluster config
var clusterConfig cautils.IClusterConfig
if !scanInfo.DoNotSendResults && k8sinterface.ConnectedToCluster {
clusterConfig = cautils.NewClusterConfig(k8s, getter.NewArmoAPI())
} else {
clusterConfig = cautils.NewEmptyConfig()
}
if err := clusterConfig.SetCustomerGUID(); 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)
@@ -130,6 +150,9 @@ func CliSetup() error {
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")
@@ -156,12 +179,10 @@ func (clihandler *CLIHandler) Scan() error {
}
switch policyNotification.NotificationType {
case opapolicy.TypeExecPostureScan:
go func() {
if err := clihandler.policyHandler.HandleNotificationRequest(policyNotification, clihandler.scanInfo); err != nil {
fmt.Printf("%v\n", err)
os.Exit(0)
}
}()
//
if err := clihandler.policyHandler.HandleNotificationRequest(policyNotification, clihandler.scanInfo); err != nil {
return err
}
default:
return fmt.Errorf("notification type '%s' Unknown", policyNotification.NotificationType)
}

17
cmd/local.go Normal file
View File

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

46
cmd/local_get.go Normal file
View File

@@ -0,0 +1,46 @@
package cmd
import (
"fmt"
"strings"
"github.com/armosec/kubescape/cautils"
"github.com/armosec/kubescape/cautils/getter"
"github.com/spf13/cobra"
)
var localGetCmd = &cobra.Command{
Use: "get <key>",
Short: "Get configuration locally",
Long: ``,
Args: func(cmd *cobra.Command, args []string) error {
if len(args) < 1 || len(args) > 1 {
return fmt.Errorf("requires one argument")
}
keyValue := strings.Split(args[0], "=")
if len(keyValue) != 1 {
return fmt.Errorf("requires one argument")
}
return nil
},
RunE: func(cmd *cobra.Command, args []string) error {
keyValue := strings.Split(args[0], "=")
key := keyValue[0]
val, err := cautils.GetValueFromConfigJson(key)
if err != nil {
if err.Error() == "value does not exist." {
fmt.Printf("Could net get value from: %s, reason: %s\n", getter.GetDefaultPath(cautils.ConfigFileName+".json"), err)
return nil
}
return err
}
fmt.Println(key + "=" + val)
return nil
},
}
func init() {
localCmd.AddCommand(localGetCmd)
}

40
cmd/local_set.go Normal file
View File

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

49
cmd/version.go Normal file
View File

@@ -0,0 +1,49 @@
package cmd
import (
"encoding/json"
"fmt"
"io/ioutil"
"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 := ioutil.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)
}

BIN
docs/discord-banner.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

89
docs/run-options.md Normal file
View File

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

View File

@@ -1,7 +1,7 @@
#!/bin/bash
set -e
echo "Installing Kubescape..."
echo -e "\033[0;36mInstalling Kubescape..."
echo
BASE_DIR=~/.kubescape
@@ -27,18 +27,38 @@ mkdir -p $BASE_DIR
OUTPUT=$BASE_DIR/$KUBESCAPE_EXEC
curl --progress-bar -L $DOWNLOAD_URL -o $OUTPUT
echo -e "\033[32m[V] Downloaded Kubescape"
# Ping download counter
curl --silent https://us-central1-elated-pottery-310110.cloudfunctions.net/kubescape-download-counter -o /dev/null
chmod +x $OUTPUT || sudo chmod +x $OUTPUT
rm -f /usr/local/bin/$KUBESCAPE_EXEC || sudo rm -f /usr/local/bin/$KUBESCAPE_EXEC
cp $OUTPUT /usr/local/bin || sudo cp $OUTPUT /usr/local/bin
# Checking if SUDO needed/exists
SUDO=
if [ "$(id -u)" -ne 0 ] && [ -n "$(which sudo)" ]; then
SUDO=sudo
fi
# Find install dir
install_dir=/usr/local/bin #default
for pdir in ${PATH//:/ }; do
edir="${pdir/#\~/$HOME}"
if [[ $edir == $HOME/* ]]; then
install_dir=$edir
mkdir -p $install_dir 2>/dev/null || true
SUDO=
break
fi
done
chmod +x $OUTPUT 2>/dev/null
$SUDO rm -f /usr/local/bin/$KUBESCAPE_EXEC 2>/dev/null || true # clearning up old install
$SUDO cp $OUTPUT $install_dir/$KUBESCAPE_EXEC
rm -rf $OUTPUT
echo -e "[V] Finished Installation"
echo
echo -e "\033[32mFinished Installation."
echo -e "\033[0m"
$KUBESCAPE_EXEC version
echo
echo -e "\033[35m Usage: $ $KUBESCAPE_EXEC scan framework nsa --exclude-namespaces kube-system,kube-public"
echo
echo -e "\033[35mUsage: $ $KUBESCAPE_EXEC scan framework nsa --exclude-namespaces kube-system,kube-public"
echo -e "\033[0m"

18
main.go
View File

@@ -1,7 +1,23 @@
package main
import "github.com/armosec/kubescape/cmd"
import (
"fmt"
"os"
"github.com/armosec/kubescape/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

@@ -13,11 +13,11 @@ import (
"github.com/armosec/kubescape/cautils/opapolicy"
"github.com/armosec/kubescape/cautils/opapolicy/resources"
"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"
@@ -42,7 +42,7 @@ func NewOPAProcessor(sessionObj *cautils.OPASessionObj) *OPAProcessor {
func NewOPAProcessorHandler(processedPolicy, reportResults *chan *cautils.OPASessionObj) *OPAProcessorHandler {
regoDependenciesData := resources.NewRegoDependenciesData(k8sinterface.K8SConfig)
regoDependenciesData := resources.NewRegoDependenciesData(k8sinterface.GetK8sConfig())
store, err := regoDependenciesData.TOStorage()
if err != nil {
panic(err)
@@ -70,7 +70,7 @@ func (opaHandler *OPAProcessorHandler) ProcessRulesListenner() {
opap.updateResults()
// update score
opap.updateScore()
// opap.updateScore()
// report
*opaHandler.reportResults <- opaSessionObj
@@ -92,6 +92,7 @@ func (opap *OPAProcessor) Process() error {
}
opap.PostureReport.FrameworkReports = frameworkReports
opap.PostureReport.ReportID = uuid.NewV4().String()
opap.PostureReport.ReportGenerationTime = time.Now().UTC()
// glog.Infof(fmt.Sprintf("Done 'Process'. reportID: %s", opap.PostureReport.ReportID))
cautils.StopSpinner()
@@ -104,6 +105,7 @@ func (opap *OPAProcessor) processFramework(framework *opapolicy.Framework) (*opa
frameworkReport := opapolicy.FrameworkReport{}
frameworkReport.Name = framework.Name
controlReports := []opapolicy.ControlReport{}
for i := range framework.Controls {
controlReport, err := opap.processControl(&framework.Controls[i])
@@ -121,6 +123,7 @@ func (opap *OPAProcessor) processControl(control *opapolicy.Control) (*opapolicy
controlReport := opapolicy.ControlReport{}
controlReport.PortalBase = control.PortalBase
controlReport.ControlID = control.ControlID
controlReport.Name = control.Name
controlReport.Description = control.Description
@@ -219,6 +222,10 @@ func (opap *OPAProcessor) regoEval(inputObj []map[string]interface{}, compiledRe
func (opap *OPAProcessor) updateScore() {
if !k8sinterface.ConnectedToCluster {
return
}
// calculate score
s := score.NewScore(k8sinterface.NewKubernetesApi(), ScoreConfigPath)
s.Calculate(opap.PostureReport.FrameworkReports)
@@ -226,6 +233,8 @@ func (opap *OPAProcessor) updateScore() {
func (opap *OPAProcessor) updateResults() {
for f, frameworkReport := range opap.PostureReport.FrameworkReports {
sumFailed := 0
sumTotal := 0
for c, controlReport := range opap.PostureReport.FrameworkReports[f].ControlReports {
for r, ruleReport := range opap.PostureReport.FrameworkReports[f].ControlReports[c].RuleReports {
// editing the responses -> removing duplications, clearing secret data, etc.
@@ -235,6 +244,10 @@ func (opap *OPAProcessor) updateResults() {
ruleExceptions := exceptions.ListRuleExceptions(opap.Exceptions, frameworkReport.Name, controlReport.Name, ruleReport.Name)
exceptions.AddExceptionsToRuleResponses(opap.PostureReport.FrameworkReports[f].ControlReports[c].RuleReports[r].RuleResponses, ruleExceptions)
}
sumFailed += controlReport.GetNumberOfFailedResources()
sumTotal += controlReport.GetNumberOfResources()
opap.PostureReport.FrameworkReports[f].ControlReports[c].Score = float32(percentage(controlReport.GetNumberOfResources(), controlReport.GetNumberOfFailedResources()))
}
opap.PostureReport.FrameworkReports[f].Score = float32(percentage(sumTotal, sumTotal-sumFailed))
}
}

View File

@@ -139,3 +139,13 @@ func listMatchKinds(match []opapolicy.RuleMatchObjects) []string {
}
return matchKinds
}
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)
}

View File

@@ -79,7 +79,7 @@ func (policyHandler *PolicyHandler) getPolicies(notification *opapolicy.PolicyNo
func (policyHandler *PolicyHandler) getResources(notification *opapolicy.PolicyNotification, opaSessionObj *cautils.OPASessionObj, scanInfo *cautils.ScanInfo) (*cautils.K8SResources, error) {
var k8sResources *cautils.K8SResources
var err error
if scanInfo.ScanRunningCluster() {
if k8sinterface.ConnectedToCluster {
k8sResources, err = policyHandler.getK8sResources(opaSessionObj.Frameworks, &notification.Designators, scanInfo.ExcludedNamespaces)
} else {
k8sResources, err = policyHandler.loadResources(opaSessionObj.Frameworks, scanInfo)

View File

@@ -3,6 +3,7 @@ package policyhandler
import (
"fmt"
"github.com/armosec/kubescape/cautils"
"github.com/armosec/kubescape/cautils/armotypes"
"github.com/armosec/kubescape/cautils/opapolicy"
)
@@ -18,7 +19,7 @@ func (policyHandler *PolicyHandler) GetPoliciesFromBackend(notification *opapoli
case opapolicy.KindFramework:
receivedFramework, recExceptionPolicies, err := policyHandler.getFrameworkPolicies(rule.Name)
if err != nil {
errs = fmt.Errorf("%v\nKind: %v, Name: %s, error: %s", errs, rule.Kind, rule.Name, err.Error())
return nil, nil, fmt.Errorf("kind: %v, name: %s, error: %s", rule.Kind, rule.Name, err.Error())
}
if receivedFramework != nil {
frameworks = append(frameworks, *receivedFramework)
@@ -41,7 +42,7 @@ func (policyHandler *PolicyHandler) getFrameworkPolicies(policyName string) (*op
return nil, nil, err
}
receivedException, err := policyHandler.getters.ExceptionsGetter.GetExceptions("", "")
receivedException, err := policyHandler.getters.ExceptionsGetter.GetExceptions(cautils.CustomerGUID, cautils.ClusterName)
if err != nil {
return receivedFramework, nil, err
}

View File

@@ -178,6 +178,10 @@ func (printer *Printer) printResult(controlName string, controlSummary *ControlS
}
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()...)
@@ -247,7 +251,7 @@ func (printer *Printer) getSortedControlsNames() []string {
}
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 {

View File

@@ -28,6 +28,8 @@ func (report *ReportEventReceiver) ActionSendReportListenner(opaSessionObj *caut
if cautils.CustomerGUID == "" {
return
}
//Add score
opaSessionObj.PostureReport.RemoveData()
if err := report.Send(opaSessionObj.PostureReport); err != nil {
fmt.Println(err)
}

View File

@@ -8,6 +8,7 @@ import (
"strings"
"github.com/armosec/kubescape/cautils"
"github.com/armosec/kubescape/cautils/getter"
"github.com/gofrs/uuid"
)
@@ -36,12 +37,12 @@ func initEventReceiverURL() *url.URL {
urlObj := url.URL{}
urlObj.Scheme = "https"
urlObj.Host = "report.euprod1.cyberarmorsoft.com"
urlObj.Host = getter.ArmoERURL
urlObj.Path = "/k8s/postureReport"
q := urlObj.Query()
q.Add("customerGUID", uuid.FromStringOrNil(cautils.CustomerGUID).String())
q.Add("clusterName", cautils.ClusterName)
urlObj.RawQuery = q.Encode()
return &urlObj
@@ -49,9 +50,7 @@ func initEventReceiverURL() *url.URL {
func hostToString(host *url.URL, reportID string) string {
q := host.Query()
if reportID != "" {
q.Add("reportID", reportID) // TODO - do we add the reportID?
}
q.Add("reportID", reportID) // TODO - do we add the reportID?
host.RawQuery = q.Encode()
return host.String()
}

View File

@@ -24,9 +24,9 @@ func (resultsHandler *ResultsHandler) HandleResults() float32 {
opaSessionObj := <-*resultsHandler.opaSessionObj
resultsHandler.reporterObj.ActionSendReportListenner(opaSessionObj)
score := resultsHandler.printerObj.ActionPrint(opaSessionObj)
resultsHandler.reporterObj.ActionSendReportListenner(opaSessionObj)
return score
}

View File

@@ -1,6 +1,7 @@
package exceptions
import (
"github.com/armosec/kubescape/cautils"
"github.com/armosec/kubescape/cautils/k8sinterface"
"github.com/armosec/kubescape/cautils/armotypes"
@@ -68,6 +69,10 @@ func alertObjectToWorkloads(obj *opapolicy.AlertObject) []k8sinterface.IWorkload
continue
}
resource = append(resource, r)
ns := r.GetNamespace()
if ns != "" {
}
}
return resource
@@ -91,9 +96,9 @@ func hasException(designator *armotypes.PortalDesignator, workload k8sinterface.
return false // if designators are empty
}
// if cluster != "" && cluster != ClusterName { // TODO - where do we receive cluster name from?
// return false // cluster name does not match
// }
if cluster != "" && cautils.ClusterName != "" && cluster != cautils.ClusterName { // TODO - where do we receive cluster name from?
return false // cluster name does not match
}
if namespace != "" && !compareNamespace(workload, namespace) {
return false // namespaces do not match