Compare commits

...

77 Commits

Author SHA1 Message Date
Rotem Refael
033e8f6b44 Merge pull request #346 from armosec/dev
returning rbac submit code + fix crash
2022-01-24 12:40:33 +02:00
Ben Hirschberg
bef40f0e6c Merge pull request #344 from slashben/dev
returning RBAC submit table
2022-01-24 09:13:28 +02:00
Ben Hirschberg
aa2f69125f returning rbac submit code 2022-01-24 09:12:13 +02:00
Ben Hirschberg
d30f3960a7 Merge pull request #341 from slashben/dev
fixing the crash around submit rbac
2022-01-23 21:28:10 +02:00
Ben Hirschberg
5f43da94ba fixing the crash around submit rbac 2022-01-23 20:17:03 +02:00
Rotem Refael
2aa8a0c935 Merge pull request #340 from Anthirian/dev
Fix typo in namespace
2022-01-23 13:43:42 +02:00
Ben Hirschberg
c02f8c6cb5 Merge pull request #338 from vfarcic/dev
Video
2022-01-23 08:18:44 +02:00
Geert Smelt
aa0be474e2 Fix typo in namespace 2022-01-21 15:40:00 +01:00
Viktor Farcic
c0161c9b33 Video 2022-01-20 19:54:57 +01:00
Rotem Refael
514da1e2db Merge pull request #332 from armosec/dev
Emergency fix: checking kube context before use
2022-01-19 16:53:14 +02:00
Ben Hirschberg
75dfceb5da Merge pull request #331 from slashben/dev
checking if local context exists before using
2022-01-19 16:38:22 +02:00
Ben Hirschberg
1ae76b4377 checking if context exists 2022-01-19 16:37:32 +02:00
Rotem Refael
b6f90cba8e Merge pull request #330 from armosec/dev
New kubescape major version
2022-01-19 15:21:52 +02:00
rrefael
62af441a1d Increase release & tag to major 2022-01-19 09:19:16 +02:00
Rotem Refael
228b8957d3 Merge pull request #317 from armosec/report-refactor
- Support report v2 (pagination)
- Scan control only once
- Support download of exceptions,control-configuration,artifacts -> kubescape download
- Support listing frameworks and controls -> kubescape list
- Adding scan framework all for scanning yaml files
- Scan other kubernetes contexts by adding the --kube-context flag
2022-01-18 21:02:56 +02:00
YiscahLevySilas1
b4ce999ab3 Merge pull request #324 from YiscahLevySilas1/dev
added download + load artifacts to readme
2022-01-18 18:02:34 +02:00
yiscah
cc06a414fe added examples of download + load artifacts 2022-01-18 17:50:28 +02:00
YiscahLevySilas1
d3c37c4e5f Merge branch 'armosec:dev' into dev 2022-01-18 17:30:36 +02:00
YiscahLevySilas1
3b448b62b1 Merge pull request #323 from YiscahLevySilas1/dev
support load artifacts from local path
2022-01-18 17:28:08 +02:00
YiscahLevySilas1
6a3f5658b1 Merge branch 'report-refactor' into dev 2022-01-18 17:19:03 +02:00
yiscah
f65e791522 fix parsing local path to artifacts 2022-01-18 17:01:53 +02:00
yiscah
d91304f9ad setUseArtifactsFrom only when flag is set 2022-01-18 16:32:01 +02:00
yiscah
61ce00108e fixed download to local path 2022-01-18 15:29:35 +02:00
Ben Hirschberg
a4eb773eee Merge pull request #322 from slashben/dev
Adding examples for cloud integrations
2022-01-18 15:05:47 +02:00
Ben Hirschberg
cfc69f5a0f adding access to container registry 2022-01-18 14:41:07 +02:00
Ben Hirschberg
a44823c3ed example cloud integration scripts 2022-01-17 13:55:43 +02:00
Ben Hirschberg
8a166e5ba5 typo fix 2022-01-17 11:54:02 +02:00
rrefael
9a7aeff870 update overview text- README 2022-01-16 15:40:34 +02:00
Lior Alafi
cb3bdb9df2 supporting both structures for score calculation 2022-01-14 17:54:39 +02:00
rrefael
0be8d57eaa add --enable-host-scan flag 2022-01-13 15:44:21 +02:00
dwertent
79b9cbf1d6 Merge branch 'report-refactor' 2022-01-13 15:16:15 +02:00
dwertent
500df8737e send small clusters once 2022-01-13 15:03:23 +02:00
David Wertenteil
b8acbd1bee fixed flag and cloud env support 2022-01-13 14:19:26 +02:00
dwertent
0bde8a65ba fixed context flag 2022-01-13 14:16:36 +02:00
dwertent
d2884b8936 update failure message 2022-01-13 13:07:29 +02:00
dwertent
e692359b47 Merge remote-tracking branch 'dwertent/master' into report-refactor 2022-01-13 12:52:34 +02:00
dwertent
473746eab0 merged with Daniels branch 2022-01-13 12:50:50 +02:00
dwertent
050878cbd6 Merge remote-tracking branch 'dwertent/master' into report-refactor 2022-01-13 11:48:13 +02:00
dwertent
e100f18bb0 fixed tests 2022-01-13 11:44:46 +02:00
dwertent
05c82fc166 Merge remote-tracking branch 'dwertent/master' into dev 2022-01-13 11:36:15 +02:00
dwertent
839c3e261f swap result and resource 2022-01-13 11:04:09 +02:00
dwertent
95b579d191 fixed resource list 2022-01-12 14:03:12 +02:00
Bezbran
8656715753 Merge pull request #314 from alegrey91/master
move kube-host-sensor manifest to indipendent yaml file with embed capability
2022-01-12 09:39:26 +02:00
dwertent
05b6394c5c send report to v2 2022-01-11 14:21:16 +02:00
Rotem Refael
d3bdbf31ac Merge pull request #315 from armosec/dev
Typo fixes
Update Kubescape logo
Adding contributors

Issues:
- Closes Spelling error Namescape should be Namespace #296
- Closes Add contributors to Readme #307
2022-01-11 09:58:36 +02:00
yiscah
995f615b10 support load artifacts from local path 2022-01-10 20:00:42 +02:00
alegrey91
392625b774 style(host-sensor): move kube-host-sensor manifest to indipendent yaml file 2022-01-10 18:39:06 +01:00
dwertent
306b9d28ca ignore skipped resources 2022-01-10 15:21:31 +02:00
dwertent
6fe87bba20 Merge branch 'dev' of github.com:dwertent/kubescape into dev 2022-01-10 13:46:19 +02:00
David Wertenteil
c0d534072d print downloaded artifacts 2022-01-10 13:45:43 +02:00
dwertent
009221aa98 report 2022-01-10 10:21:42 +02:00
yiscah
46e5aff5f9 each artifact handle print seprately 2022-01-10 09:07:06 +02:00
YiscahLevySilas1
59498361e7 Merge pull request #3 from dwertent/dev
Dev
2022-01-10 08:35:40 +02:00
dwertent
c652da130d adding download examples 2022-01-10 08:13:44 +02:00
David Wertenteil
9e524ffc34 support download artifacts 2022-01-09 17:37:26 +02:00
David Wertenteil
004cc0c469 Merge branch 'dev' into dev 2022-01-09 17:37:12 +02:00
dwertent
bd089d76af adding cluster flag - support submiting yaml file 2022-01-09 16:13:15 +02:00
yiscah
d5025b54bf handle error for each artifact download seprately 2022-01-09 15:12:03 +02:00
dwertent
740497047d cli print support v2 2022-01-09 10:33:47 +02:00
Ben Hirschberg
3f6cbd57b2 Merge pull request #311 from saiyam1814/patch-1
Adding Kubescape katacoda playground
2022-01-08 18:16:50 +02:00
Saiyam Pathak
2c9524ed45 Adding Kubescape katacoda playground 2022-01-08 19:21:33 +05:30
Ben Hirschberg
384922680a Merge pull request #309 from saiyam1814/master
Adding Contributors
2022-01-07 15:41:53 +02:00
David Wertenteil
d2e9f8f4f8 Fixes #296 Spelling error Namescape should be Namespace 2022-01-07 14:25:24 +02:00
Saiyam Pathak
b4f10f854e Adding contributors 2022-01-07 11:27:32 +05:30
Clint Modien
8ce64d2a7f Fixes #296 Spelling error Namescape should be Namespace 2022-01-06 09:45:11 -08:00
yiscah
d917e21364 support download artifacts 2022-01-06 17:44:50 +02:00
YiscahLevySilas1
32cedaf565 Merge branch 'armosec:dev' into dev 2022-01-06 17:44:23 +02:00
dwertent
4c2a5e9a11 suooirt scan all 2022-01-06 16:21:41 +02:00
dwertent
a41d2a46ff cli support list 2022-01-06 15:28:01 +02:00
rrefael
d021217cf7 add new logo 2022-01-06 14:19:13 +02:00
Ben Hirschberg
2bb612ca3f Merge pull request #303 from armosec/slashben-roadmap-branch
Create roadmap.md
2022-01-06 11:12:48 +02:00
Ben Hirschberg
35534112c6 Create MAINTAINERS.md 2022-01-06 11:10:21 +02:00
Ben Hirschberg
f51e531f3a fixing typos 2022-01-06 11:04:28 +02:00
Ben Hirschberg
2490856ccb Update roadmap.md 2022-01-06 11:02:37 +02:00
Ben Hirschberg
9a5a87b027 Create CODE_OF_CONDUCT.md 2022-01-06 10:44:28 +02:00
Ben Hirschberg
45b8c89865 Create roadmap.md 2022-01-06 10:30:08 +02:00
YiscahLevySilas1
e68e6dcd3d Merge pull request #1 from dwertent/master
Download policies support
2022-01-06 08:51:16 +02:00
65 changed files with 1861 additions and 237 deletions

View File

@@ -16,8 +16,8 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: v1.0.${{ github.run_number }}
release_name: Release v1.0.${{ github.run_number }}
tag_name: v2.0.${{ github.run_number }}
release_name: Release v2.0.${{ github.run_number }}
draft: false
prerelease: false
build:
@@ -39,7 +39,7 @@ jobs:
- name: Build
env:
RELEASE: v1.0.${{ github.run_number }}
RELEASE: v2.0.${{ github.run_number }}
ArmoBEServer: api.armo.cloud
ArmoERServer: report.armo.cloud
ArmoWebsite: portal.armo.cloud
@@ -48,7 +48,7 @@ jobs:
- name: Smoke Testing
env:
RELEASE: v1.0.${{ github.run_number }}
RELEASE: v2.0.${{ github.run_number }}
KUBESCAPE_SKIP_UPDATE_CHECK: "true"
run: python3 smoke_testing/init.py ${PWD}/build/${{ matrix.os }}/kubescape
@@ -74,7 +74,7 @@ jobs:
- uses: actions/checkout@v2
- name: Set name
run: echo quay.io/armosec/kubescape:v1.0.${{ github.run_number }} > build_tag.txt
run: echo quay.io/armosec/kubescape:v2.0.${{ github.run_number }} > build_tag.txt
- name: Build the Docker image
run: docker build . --file build/Dockerfile --tag $(cat build_tag.txt) --build-arg run_number=${{ github.run_number }}

View File

@@ -23,7 +23,7 @@ jobs:
- name: Build
env:
RELEASE: v1.0.${{ github.run_number }}
RELEASE: v2.0.${{ github.run_number }}
ArmoBEServer: api.armo.cloud
ArmoERServer: report.euprod1.cyberarmorsoft.com
ArmoWebsite: portal.armo.cloud
@@ -32,7 +32,7 @@ jobs:
- name: Smoke Testing
env:
RELEASE: v1.0.${{ github.run_number }}
RELEASE: v2.0.${{ github.run_number }}
KUBESCAPE_SKIP_UPDATE_CHECK: "true"
run: python3 smoke_testing/init.py ${PWD}/build/${{ matrix.os }}/kubescape
@@ -53,7 +53,7 @@ jobs:
- uses: actions/checkout@v2
- name: Set name
run: echo quay.io/armosec/kubescape:dev-v1.0.${{ github.run_number }} > build_tag.txt
run: echo quay.io/armosec/kubescape:dev-v2.0.${{ github.run_number }} > build_tag.txt
- name: Build the Docker image
run: docker build . --file build/Dockerfile --tag $(cat build_tag.txt) --build-arg run_number=${{ github.run_number }}

View File

@@ -24,7 +24,7 @@ jobs:
- name: Build
env:
RELEASE: v1.0.${{ github.run_number }}
RELEASE: v2.0.${{ github.run_number }}
ArmoBEServer: api.armo.cloud
ArmoERServer: report.armo.cloud
ArmoWebsite: portal.armo.cloud
@@ -33,7 +33,7 @@ jobs:
- name: Smoke Testing
env:
RELEASE: v1.0.${{ github.run_number }}
RELEASE: v2.0.${{ github.run_number }}
KUBESCAPE_SKIP_UPDATE_CHECK: "true"
run: python3 smoke_testing/init.py ${PWD}/build/${{ matrix.os }}/kubescape

127
CODE_OF_CONDUCT.md Normal file
View File

@@ -0,0 +1,127 @@
# Contributor Covenant Code of Conduct
## Our Pledge
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, religion, or sexual identity
and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our
community include:
* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
* Focusing on what is best not just for us as individuals, but for the
overall community
Examples of unacceptable behavior include:
* The use of sexualized language or imagery, and sexual attention or
advances of any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email
address, without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.
Community leaders 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, and will communicate reasons for moderation
decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement [here](mailto:ben@armosec.io).
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the
reporter of any incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series
of actions.
**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or
permanent ban.
### 3. Temporary Ban
**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within
the community.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.0, available at
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
Community Impact Guidelines were inspired by [Mozilla's code of conduct
enforcement ladder](https://github.com/mozilla/diversity).
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see the FAQ at
https://www.contributor-covenant.org/faq. Translations are available at
https://www.contributor-covenant.org/translations.

9
MAINTAINERS.md Normal file
View File

@@ -0,0 +1,9 @@
# Maintainers
The following table lists Kubescape project maintainers
| Name | GitHub | Email | Organization | Repositories/Area of Expertise | Added/Renewed On |
| --- | --- | --- | --- | --- | --- |
| Ben Hirschberg | @slashben | ben@armosec.io | ARMO | Kubescape CLI | 2021-09-01 |
| Rotem Refael | @rotemamsa | rrefael@armosec.io | ARMO | Kubescape CLI | 2021-10-11 |
| David Wertenteil | @dwertent | dwertent@armosec.io | ARMO | Kubescape CLI | 2021-09-01 |

View File

@@ -3,10 +3,10 @@
[![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 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.
Kubescape is a K8s open-source tool providing a multi-cloud K8s single pane of glass, including risk analysis, security compliance, RBAC visualizer and image vulnerabilities scanning.
Kubescape scans K8s clusters, YAML files, and HELM charts, detecting misconfigurations according to multiple frameworks (such as the [NSA-CISA](https://www.armosec.io/blog/kubernetes-hardening-guidance-summary-by-armo) , [MITRE ATT&CK®](https://www.microsoft.com/security/blog/2021/03/23/secure-containerized-environments-with-updated-threat-matrix-for-kubernetes/)), software vulnerabilities, and RBAC (role-based-access-control) violations at early stages of the CI/CD pipeline, calculates risk score instantly and shows risk trends over time.
It became one of the fastest-growing Kubernetes tools among developers due to its easy-to-use CLI interface, flexible output formats, and automated scanning capabilities, saving Kubernetes users and admins precious time, effort, and resources.
Kubescape integrates natively with other DevOps tools, including Jenkins, CircleCI, Github workflows, Prometheus, and Slack, and supports multi-cloud K8s deployments like EKS, GKE, and AKS.
</br>
@@ -24,7 +24,7 @@ curl -s https://raw.githubusercontent.com/armosec/kubescape/master/install.sh |
## Run:
```
kubescape scan --submit
kubescape scan --submit --enable-host-scan
```
<img src="docs/summary.png">
@@ -54,9 +54,13 @@ Want to contribute? Want to discuss something? Have an issue?
# Options and examples
## Playground
* [Kubescape playground](https://www.katacoda.com/pathaksaiyam/scenarios/kubescape)
## Tutorials
* [Overview](https://youtu.be/wdBkt_0Qhbg)
* [How To Secure Kubernetes Clusters With Kubescape And Armo](https://youtu.be/ZATGiDIDBQk)
* [Scanning Kubernetes YAML files](https://youtu.be/Ox6DaR7_4ZI)
* [Scan Kubescape on an air-gapped environment (offline support)](https://youtu.be/IGXL9s37smM)
* [Managing exceptions in the Kubescape SaaS version](https://youtu.be/OzpvxGmCR80)
@@ -94,12 +98,15 @@ Set-ExecutionPolicy RemoteSigned -scope CurrentUser
| `-t`/`--fail-threshold` | `100` (do not fail) | fail command (return exit code 1) if result is above threshold | `0` -> `100` |
| `-f`/`--format` | `pretty-printer` | Output format | `pretty-printer`/`json`/`junit`/`prometheus` |
| `-o`/`--output` | print to stdout | Save scan result in file | |
| `--use-from` | | Load local framework object from specified path. If not used will download latest | |
| `--use-from` | | Load local framework object from specified path. If not used will download latest |
| `--use-artifacts-from` | | Load artifacts (frameworks, control-config, exceptions) from local directory. If not used will download them | |
| `--use-default` | `false` | Load local framework object from default path. If not used will download latest | `true`/`false` |
| `--exceptions` | | Path to an [exceptions obj](examples/exceptions.json). If not set will download exceptions from Armo management portal | |
| `--exceptions` | | Path to an [exceptions obj](examples/exceptions.json). If not set will download exceptions from Armo management portal |
| `--controls-config` | | Path to a controls-config obj. If not set will download controls-config from ARMO management portal | |
| `--submit` | `false` | If set, Kubescape will send the scan results to Armo management portal where you can see the results in a user-friendly UI, choose your preferred compliance framework, check risk results history and trends, manage exceptions, get remediation recommendations and much more. By default the results are not sent | `true`/`false` |
| `--keep-local` | `false` | Kubescape will not send scan results to Armo management portal. Use this flag if you ran with the `--submit` flag in the past and you do not want to submit your current scan results | `true`/`false` |
| `--account` | | Armo portal account ID. Default will load account ID from configMap or config file | |
| `--kube-context` | current-context | Cluster context to scan | |
| `--verbose` | `false` | Display all of the input resources and not only failed resources | `true`/`false` |
@@ -189,7 +196,7 @@ It is possible to run Kubescape offline!
First download the framework and then scan with `--use-from` flag
1. Download and save in file, if file name not specified, will store save to `~/.kubescape/<framework name>.json`
1. Download and save in file, if file name not specified, will save in `~/.kubescape/<framework name>.json`
```
kubescape download framework nsa --output nsa.json
```
@@ -200,6 +207,19 @@ kubescape scan framework nsa --use-from nsa.json
```
You can also download all artifacts to a local path and then load them using `--use-artifacts-from` flag
1. Download and save in local directory, if path not specified, will save all in `~/.kubescape`
```
kubescape download artifacts --output path/to/local/dir
```
2. Scan using the downloaded artifacts
```
kubescape scan framework nsa --use-artifacts-from path/to/local/dir
```
## 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
@@ -263,7 +283,7 @@ go build -o kubescape .
3. Run
```
./kubescape scan framework nsa
./kubescape scan --submit --enable-host-scan
```
4. Enjoy :zany_face:
@@ -319,3 +339,9 @@ The tools retrieves Kubernetes objects from the API server and runs a set of [re
The results by default printed in a pretty "console friendly" manner, but they can be retrieved in JSON format for further processing.
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.
## Thanks to all the contributors ❤️
<a href = "https://github.com/armosec/kubescape/graphs/contributors">
<img src = "https://contrib.rocks/image?repo=armosec/kubescape"/>
</a>

View File

@@ -76,7 +76,7 @@ type LocalConfig struct {
configObj *ConfigObj
}
func NewLocalConfig(backendAPI getter.IBackend, customerGUID string) *LocalConfig {
func NewLocalConfig(backendAPI getter.IBackend, customerGUID, clusterName string) *LocalConfig {
var configObj *ConfigObj
lc := &LocalConfig{
@@ -95,6 +95,9 @@ func NewLocalConfig(backendAPI getter.IBackend, customerGUID string) *LocalConfi
if customerGUID != "" {
lc.configObj.CustomerGUID = customerGUID // override config customerGUID
}
if clusterName != "" {
lc.configObj.ClusterName = AdoptClusterName(clusterName) // override config clusterName
}
if lc.configObj.CustomerGUID != "" {
if err := lc.SetTenant(); err != nil {
fmt.Println(err)
@@ -107,7 +110,7 @@ func NewLocalConfig(backendAPI getter.IBackend, customerGUID string) *LocalConfi
func (lc *LocalConfig) GetConfigObj() *ConfigObj { return lc.configObj }
func (lc *LocalConfig) GetCustomerGUID() string { return lc.configObj.CustomerGUID }
func (lc *LocalConfig) SetCustomerGUID(customerGUID string) { lc.configObj.CustomerGUID = customerGUID }
func (lc *LocalConfig) GetClusterName() string { return "" }
func (lc *LocalConfig) GetClusterName() string { return lc.configObj.ClusterName }
func (lc *LocalConfig) IsConfigFound() bool { return existsConfigFile() }
func (lc *LocalConfig) SetTenant() error {
// ARMO tenant GUID
@@ -163,7 +166,7 @@ type ClusterConfig struct {
configObj *ConfigObj
}
func NewClusterConfig(k8s *k8sinterface.KubernetesApi, backendAPI getter.IBackend, customerGUID string) *ClusterConfig {
func NewClusterConfig(k8s *k8sinterface.KubernetesApi, backendAPI getter.IBackend, customerGUID, clusterName string) *ClusterConfig {
var configObj *ConfigObj
c := &ClusterConfig{
k8s: k8s,
@@ -186,6 +189,9 @@ func NewClusterConfig(k8s *k8sinterface.KubernetesApi, backendAPI getter.IBacken
if customerGUID != "" {
c.configObj.CustomerGUID = customerGUID // override config customerGUID
}
if clusterName != "" {
c.configObj.ClusterName = AdoptClusterName(clusterName) // override config clusterName
}
if c.configObj.CustomerGUID != "" {
if err := c.SetTenant(); err != nil {
fmt.Println(err)

View File

@@ -1,8 +1,9 @@
package cautils
type DownloadInfo struct {
Path string
Target string
Name string
Account string
Path string // directory to save artifact. Default is "~/.kubescape/"
FileName string // can be empty
Target string // type of artifact to download
Name string // name of artifact to download
Account string // customerGUID
}

View File

@@ -112,6 +112,21 @@ func (armoAPI *ArmoAPI) GetFramework(name string) (*reporthandling.Framework, er
return framework, err
}
func (armoAPI *ArmoAPI) GetFrameworks() ([]reporthandling.Framework, error) {
respStr, err := HttpGetter(armoAPI.httpClient, armoAPI.getListFrameworkURL(), nil)
if err != nil {
return nil, nil
}
frameworks := []reporthandling.Framework{}
if err = JSONDecoder(respStr).Decode(&frameworks); err != nil {
return nil, err
}
// SaveInFile(framework, GetDefaultPath(name+".json"))
return frameworks, err
}
func (armoAPI *ArmoAPI) GetControl(policyName string) (*reporthandling.Control, error) {
return nil, fmt.Errorf("control api is not public")
}

View File

@@ -41,6 +41,14 @@ func (drp *DownloadReleasedPolicy) GetFramework(name string) (*reporthandling.Fr
return framework, err
}
func (drp *DownloadReleasedPolicy) GetFrameworks() ([]reporthandling.Framework, error) {
frameworks, err := drp.gs.GetOPAFrameworks()
if err != nil {
return nil, err
}
return frameworks, err
}
func (drp *DownloadReleasedPolicy) ListFrameworks() ([]string, error) {
return drp.gs.GetOPAFrameworksNamesList()
}

View File

@@ -13,6 +13,7 @@ const ListName ListType = "name"
type IPolicyGetter interface {
GetFramework(name string) (*reporthandling.Framework, error)
GetFrameworks() ([]reporthandling.Framework, error)
GetControl(name string) (*reporthandling.Control, error)
ListFrameworks() ([]string, error)

View File

@@ -78,9 +78,26 @@ func (lp *LoadPolicy) GetFramework(frameworkName string) (*reporthandling.Framew
return framework, err
}
func (lp *LoadPolicy) GetFrameworks() ([]reporthandling.Framework, error) {
frameworks := []reporthandling.Framework{}
var err error
return frameworks, err
}
func (lp *LoadPolicy) ListFrameworks() ([]string, error) {
// TODO - Support
return []string{}, fmt.Errorf("loading frameworks list from file is not supported")
fwNames := []string{}
framework := &reporthandling.Framework{}
for _, f := range lp.filePaths {
file, err := os.ReadFile(f)
if err == nil {
if err := json.Unmarshal(file, framework); err == nil {
if !contains(fwNames, framework.Name) {
fwNames = append(fwNames, framework.Name)
}
}
}
}
return fwNames, nil
}
func (lp *LoadPolicy) ListControls(listType ListType) ([]string, error) {
@@ -108,7 +125,7 @@ func (lp *LoadPolicy) GetControlsInputs(clusterName string) (map[string][]string
return nil, err
}
if err = json.Unmarshal(f, &accountConfig); err == nil {
if err = json.Unmarshal(f, &accountConfig.Settings.PostureControlInputs); err == nil {
return accountConfig.Settings.PostureControlInputs, nil
}
return nil, err

7
cautils/listpolicies.go Normal file
View File

@@ -0,0 +1,7 @@
package cautils
type ListPolicies struct {
Target string
ListIDs bool
Account string
}

View File

@@ -42,6 +42,17 @@ func (rbacObjects *RBACObjects) ListAllResources() (map[string]workloadinterface
func (rbacObjects *RBACObjects) rbacObjectsToResources(resources *rbacutils.RbacObjects) (map[string]workloadinterface.IMetadata, error) {
allresources := map[string]workloadinterface.IMetadata{}
/*
************************************************************************************************************************
This code is adding a non valid ID ->
(github.com/armosec/rbac-utils v0.0.11): "//SA2WLIDmap/SA2WLIDmap"
(github.com/armosec/rbac-utils v0.0.12): "armo.rbac.com/v0beta1//SAID2WLIDmap/SAID2WLIDmap"
Should be investigated
************************************************************************************************************************
*/
// wrap rbac aggregated objects in IMetadata and add to allresources
// TODO - DEPRECATE SA2WLIDmap
SA2WLIDmapIMeta, err := rbacutils.SA2WLIDmapIMetadataWrapper(resources.SA2WLIDmap)
@@ -62,7 +73,7 @@ func (rbacObjects *RBACObjects) rbacObjectsToResources(resources *rbacutils.Rbac
if err != nil {
return nil, err
}
crmap["apiVersion"] = "rbac.authorization.k8s.io/v1"
crmap["apiVersion"] = "rbac.authorization.k8s.io/v1" // TODO - is the the correct apiVersion?
crIMeta := workloadinterface.NewWorkloadObj(crmap)
crIMeta.SetKind("ClusterRole")
allresources[crIMeta.GetID()] = crIMeta
@@ -72,7 +83,7 @@ func (rbacObjects *RBACObjects) rbacObjectsToResources(resources *rbacutils.Rbac
if err != nil {
return nil, err
}
crmap["apiVersion"] = "rbac.authorization.k8s.io/v1"
crmap["apiVersion"] = "rbac.authorization.k8s.io/v1" // TODO - is the the correct apiVersion?
crIMeta := workloadinterface.NewWorkloadObj(crmap)
crIMeta.SetKind("Role")
allresources[crIMeta.GetID()] = crIMeta
@@ -82,7 +93,7 @@ func (rbacObjects *RBACObjects) rbacObjectsToResources(resources *rbacutils.Rbac
if err != nil {
return nil, err
}
crmap["apiVersion"] = "rbac.authorization.k8s.io/v1"
crmap["apiVersion"] = "rbac.authorization.k8s.io/v1" // TODO - is the the correct apiVersion?
crIMeta := workloadinterface.NewWorkloadObj(crmap)
crIMeta.SetKind("ClusterRoleBinding")
allresources[crIMeta.GetID()] = crIMeta
@@ -92,7 +103,7 @@ func (rbacObjects *RBACObjects) rbacObjectsToResources(resources *rbacutils.Rbac
if err != nil {
return nil, err
}
crmap["apiVersion"] = "rbac.authorization.k8s.io/v1"
crmap["apiVersion"] = "rbac.authorization.k8s.io/v1" // TODO - is the the correct apiVersion?
crIMeta := workloadinterface.NewWorkloadObj(crmap)
crIMeta.SetKind("RoleBinding")
allresources[crIMeta.GetID()] = crIMeta

View File

@@ -1,14 +1,17 @@
package v1
package cautils
import (
"github.com/armosec/kubescape/cautils"
"github.com/armosec/k8s-interface/workloadinterface"
"github.com/armosec/opa-utils/reporthandling"
helpersv1 "github.com/armosec/opa-utils/reporthandling/helpers/v1"
"github.com/armosec/opa-utils/reporthandling/results/v1/reportsummary"
"github.com/armosec/opa-utils/score"
)
func reportV2ToV1(opaSessionObj *cautils.OPASessionObj) {
func ReportV2ToV1(opaSessionObj *OPASessionObj) {
if len(opaSessionObj.PostureReport.FrameworkReports) > 0 {
return // report already converted
}
opaSessionObj.PostureReport.ClusterCloudProvider = opaSessionObj.Report.ClusterCloudProvider
@@ -33,9 +36,9 @@ func reportV2ToV1(opaSessionObj *cautils.OPASessionObj) {
frameworks = append(frameworks, fwv1)
}
// remove unused data
opaSessionObj.Report = nil
opaSessionObj.ResourcesResult = nil
// // remove unused data
// opaSessionObj.Report = nil
// opaSessionObj.ResourcesResult = nil
// setup counters and score
for f := range frameworks {
@@ -55,6 +58,7 @@ func reportV2ToV1(opaSessionObj *cautils.OPASessionObj) {
opaSessionObj.PostureReport.FrameworkReports = frameworks
// opaSessionObj.Report.SummaryDetails.Score = 0
// for i := range frameworks {
// for j := range frameworks[i].ControlReports {
// // frameworks[i].ControlReports[j].Score
@@ -72,17 +76,20 @@ func reportV2ToV1(opaSessionObj *cautils.OPASessionObj) {
// opaSessionObj.Report.SummaryDetails.Controls[frameworks[i].ControlReports[j].ControlID] = c
// }
// }
// opaSessionObj.Report.SummaryDetails.Score += opaSessionObj.PostureReport.FrameworkReports[i].Score
// }
// opaSessionObj.Report.SummaryDetails.Score /= float32(len(opaSessionObj.Report.SummaryDetails.Frameworks))
}
func controlReportV2ToV1(opaSessionObj *cautils.OPASessionObj, frameworkName string, controls map[string]reportsummary.ControlSummary) []reporthandling.ControlReport {
func controlReportV2ToV1(opaSessionObj *OPASessionObj, frameworkName string, controls map[string]reportsummary.ControlSummary) []reporthandling.ControlReport {
controlRepors := []reporthandling.ControlReport{}
for controlID, crv2 := range controls {
crv1 := reporthandling.ControlReport{}
crv1.ControlID = controlID
crv1.BaseScore = crv2.ScoreFactor
crv1.Name = crv2.GetName()
crv1.Control_ID = controlID
// crv1.Attributes = crv2.
crv1.Score = crv2.GetScore()
// TODO - add fields
@@ -98,6 +105,9 @@ func controlReportV2ToV1(opaSessionObj *cautils.OPASessionObj, frameworkName str
if _, ok := rulesv1[rulev2.GetName()]; !ok {
rulesv1[rulev2.GetName()] = reporthandling.RuleReport{
Name: rulev2.GetName(),
RuleStatus: reporthandling.RuleStatus{
Status: "success",
},
}
}
@@ -118,10 +128,11 @@ func controlReportV2ToV1(opaSessionObj *cautils.OPASessionObj, frameworkName str
}
if fullRessource, ok := opaSessionObj.AllResources[resourceID]; ok {
ruleResponse.AlertObject.K8SApiObjects = append(ruleResponse.AlertObject.K8SApiObjects, fullRessource.GetObject())
tmp := fullRessource.GetObject()
workloadinterface.RemoveFromMap(tmp, "spec")
ruleResponse.AlertObject.K8SApiObjects = append(ruleResponse.AlertObject.K8SApiObjects, tmp)
}
rulev1.RuleResponses = append(rulev1.RuleResponses, ruleResponse)
}
rulev1.ListInputKinds = append(rulev1.ListInputKinds, resourceID)
@@ -134,6 +145,9 @@ func controlReportV2ToV1(opaSessionObj *cautils.OPASessionObj, frameworkName str
crv1.RuleReports = append(crv1.RuleReports, rulesv1[i])
}
}
if len(crv1.RuleReports) == 0 {
crv1.RuleReports = []reporthandling.RuleReport{}
}
controlRepors = append(controlRepors, crv1)
}
return controlRepors

View File

@@ -1,16 +1,23 @@
package cautils
import (
"encoding/json"
"fmt"
"io/ioutil"
"log"
"os"
"path/filepath"
"strings"
"github.com/armosec/kubescape/cautils/getter"
"github.com/armosec/opa-utils/reporthandling"
)
const (
ScanCluster string = "cluster"
ScanLocalFiles string = "yaml"
ScanCluster string = "cluster"
ScanLocalFiles string = "yaml"
localControlInputsFilename string = "controls-inputs.json"
localExceptionsFilename string = "exceptions.json"
)
type BoolPtrFlag struct {
@@ -52,6 +59,7 @@ type ScanInfo struct {
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
UseArtifactsFrom string // Load artifacts from local path. Use when running offline
VerboseMode bool // Display all of the input resources and not only failed resources
Format string // Format results (table, json, junit ...)
Output string // Store results in an output file, Output file name
@@ -64,6 +72,7 @@ type ScanInfo struct {
HostSensor BoolPtrFlag // Deploy ARMO K8s host sensor to collect data from certain controls
Local bool // Do not submit results
Account string // account ID
KubeContext string // context name
FrameworkScan bool // false if scanning control
ScanAll bool // true if scan all frameworks
}
@@ -77,7 +86,48 @@ type Getters struct {
func (scanInfo *ScanInfo) Init() {
scanInfo.setUseFrom()
scanInfo.setOutputFile()
scanInfo.setUseArtifactsFrom()
}
func (scanInfo *ScanInfo) setUseArtifactsFrom() {
if scanInfo.UseArtifactsFrom == "" {
return
}
// UseArtifactsFrom must be a path without a filename
dir, file := filepath.Split(scanInfo.UseArtifactsFrom)
if dir == "" {
scanInfo.UseArtifactsFrom = file
} else if strings.Contains(file, ".json") {
scanInfo.UseArtifactsFrom = dir
}
// set frameworks files
files, err := ioutil.ReadDir(scanInfo.UseArtifactsFrom)
if err != nil {
log.Fatal(err)
}
framework := &reporthandling.Framework{}
for _, f := range files {
filePath := filepath.Join(scanInfo.UseArtifactsFrom, f.Name())
file, err := os.ReadFile(filePath)
if err == nil {
if err := json.Unmarshal(file, framework); err == nil {
scanInfo.UseFrom = append(scanInfo.UseFrom, filepath.Join(scanInfo.UseArtifactsFrom, f.Name()))
}
}
}
// set config-inputs file
scanInfo.ControlsInputs = filepath.Join(scanInfo.UseArtifactsFrom, localControlInputsFilename)
// set exceptions
scanInfo.UseExceptions = filepath.Join(scanInfo.UseArtifactsFrom, localExceptionsFilename)
}
func (scanInfo *ScanInfo) setUseExceptions() {
if scanInfo.UseExceptions != "" {
// load exceptions from file
scanInfo.ExceptionsGetter = getter.NewLoadPolicy([]string{scanInfo.UseExceptions})
} else {
scanInfo.ExceptionsGetter = getter.GetArmoAPIConnector()
}
}
func (scanInfo *ScanInfo) setUseFrom() {

View File

@@ -2,8 +2,10 @@ package clihandler
import (
"fmt"
"os"
"path/filepath"
"strings"
"github.com/armosec/armoapi-go/armotypes"
"github.com/armosec/kubescape/cautils"
"github.com/armosec/kubescape/cautils/getter"
)
@@ -13,6 +15,7 @@ var downloadFunc = map[string]func(*cautils.DownloadInfo) error{
"exceptions": downloadExceptions,
"control": downloadControl,
"framework": downloadFramework,
"artifacts": downloadArtifacts,
}
func DownloadSupportCommands() []string {
@@ -24,93 +27,150 @@ func DownloadSupportCommands() []string {
}
func CliDownload(downloadInfo *cautils.DownloadInfo) error {
if f, ok := downloadFunc[downloadInfo.Target]; ok {
setPathandFilename(downloadInfo)
if err := downloadArtifact(downloadInfo, downloadFunc); err != nil {
return err
}
return nil
}
func downloadArtifact(downloadInfo *cautils.DownloadInfo, downloadArtifactFunc map[string]func(*cautils.DownloadInfo) error) error {
if f, ok := downloadArtifactFunc[downloadInfo.Target]; ok {
if err := f(downloadInfo); err != nil {
fmt.Println(err)
os.Exit(1)
return err
}
fmt.Println(fmt.Sprintf("'%s' downloaded successfully and saved at: '%s'", downloadInfo.Target, downloadInfo.Path))
return nil
}
return fmt.Errorf("unknown command to download")
}
func setPathandFilename(downloadInfo *cautils.DownloadInfo) {
if downloadInfo.Path == "" {
downloadInfo.Path = getter.GetDefaultPath("")
} else {
dir, file := filepath.Split(downloadInfo.Path)
if dir == "" {
downloadInfo.Path = file
} else if strings.Contains(file, ".json") {
downloadInfo.Path = dir
downloadInfo.FileName = file
}
}
}
func downloadArtifacts(downloadInfo *cautils.DownloadInfo) error {
downloadInfo.FileName = ""
var artifacts = map[string]func(*cautils.DownloadInfo) error{
"controls-inputs": downloadConfigInputs,
"exceptions": downloadExceptions,
"framework": downloadFramework,
}
for artifact := range artifacts {
if err := downloadArtifact(&cautils.DownloadInfo{Target: artifact, Path: downloadInfo.Path, FileName: fmt.Sprintf("%s.json", artifact)}, artifacts); err != nil {
fmt.Printf("error downloading %s, error: %s", artifact, err)
}
}
return nil
}
func downloadConfigInputs(downloadInfo *cautils.DownloadInfo) error {
tenant := getTenantConfig(downloadInfo.Account, getKubernetesApi()) // change k8sinterface
tenant := getTenantConfig(downloadInfo.Account, "", getKubernetesApi())
controlsInputsGetter := getConfigInputsGetter(downloadInfo.Name, tenant.GetCustomerGUID(), nil)
controlInputs, err := controlsInputsGetter.GetControlsInputs(tenant.GetClusterName())
if err != nil {
return err
}
if downloadInfo.Path == "" {
downloadInfo.Path = getter.GetDefaultPath(fmt.Sprintf("%s.json", downloadInfo.Target))
if downloadInfo.FileName == "" {
downloadInfo.FileName = fmt.Sprintf("%s.json", downloadInfo.Target)
}
// save in file
err = getter.SaveInFile(controlInputs, downloadInfo.Path)
err = getter.SaveInFile(controlInputs, filepath.Join(downloadInfo.Path, downloadInfo.FileName))
if err != nil {
return err
}
fmt.Printf("'%s' downloaded successfully and saved at: '%s'\n", downloadInfo.Target, filepath.Join(downloadInfo.Path, downloadInfo.FileName))
return nil
}
func downloadExceptions(downloadInfo *cautils.DownloadInfo) error {
tenant := getTenantConfig(downloadInfo.Account, getKubernetesApi()) // change k8sinterface
var err error
tenant := getTenantConfig(downloadInfo.Account, "", getKubernetesApi())
exceptionsGetter := getExceptionsGetter("")
exceptions, err := exceptionsGetter.GetExceptions(tenant.GetClusterName())
if err != nil {
return err
exceptions := []armotypes.PostureExceptionPolicy{}
if tenant.GetCustomerGUID() != "" {
exceptions, err = exceptionsGetter.GetExceptions(tenant.GetClusterName())
if err != nil {
return err
}
}
if downloadInfo.Path == "" {
downloadInfo.Path = getter.GetDefaultPath(fmt.Sprintf("%s.json", downloadInfo.Target))
if downloadInfo.FileName == "" {
downloadInfo.FileName = fmt.Sprintf("%s.json", downloadInfo.Target)
}
// save in file
err = getter.SaveInFile(exceptions, downloadInfo.Path)
err = getter.SaveInFile(exceptions, filepath.Join(downloadInfo.Path, downloadInfo.FileName))
if err != nil {
return err
}
fmt.Printf("'%s' downloaded successfully and saved at: '%s'\n", downloadInfo.Target, filepath.Join(downloadInfo.Path, downloadInfo.FileName))
return nil
}
func downloadFramework(downloadInfo *cautils.DownloadInfo) error {
tenant := getTenantConfig(downloadInfo.Account, getKubernetesApi()) // change k8sinterface
tenant := getTenantConfig(downloadInfo.Account, "", getKubernetesApi())
g := getPolicyGetter(nil, tenant.GetCustomerGUID(), true, nil)
if downloadInfo.Name == "" {
// TODO - support
return fmt.Errorf("missing framework name")
}
if downloadInfo.Path == "" {
downloadInfo.Path = getter.GetDefaultPath(downloadInfo.Name + ".json")
}
frameworks, err := g.GetFramework(downloadInfo.Name)
if err != nil {
return err
}
err = getter.SaveInFile(frameworks, downloadInfo.Path)
if err != nil {
return err
// if framework name not specified - download all frameworks
frameworks, err := g.GetFrameworks()
if err != nil {
return err
}
for _, fw := range frameworks {
err = getter.SaveInFile(fw, filepath.Join(downloadInfo.Path, (strings.ToLower(fw.Name)+".json")))
if err != nil {
return err
}
fmt.Printf("'%s': '%s' downloaded successfully and saved at: '%s'\n", downloadInfo.Target, fw.Name, filepath.Join(downloadInfo.Path, (strings.ToLower(fw.Name)+".json")))
}
// return fmt.Errorf("missing framework name")
} else {
if downloadInfo.FileName == "" {
downloadInfo.FileName = fmt.Sprintf("%s.json", downloadInfo.Name)
}
framework, err := g.GetFramework(downloadInfo.Name)
if err != nil {
return err
}
err = getter.SaveInFile(framework, filepath.Join(downloadInfo.Path, downloadInfo.FileName))
if err != nil {
return err
}
fmt.Printf("'%s' downloaded successfully and saved at: '%s'\n", downloadInfo.Target, filepath.Join(downloadInfo.Path, downloadInfo.FileName))
}
return nil
}
func downloadControl(downloadInfo *cautils.DownloadInfo) error {
tenant := getTenantConfig(downloadInfo.Account, getKubernetesApi()) // change k8sinterface
g := getPolicyGetter(nil, tenant.GetCustomerGUID(), true, nil)
tenant := getTenantConfig(downloadInfo.Account, "", getKubernetesApi())
g := getPolicyGetter(nil, tenant.GetCustomerGUID(), false, nil)
if downloadInfo.Name == "" {
// TODO - support
return fmt.Errorf("missing control name")
}
if downloadInfo.Path == "" {
downloadInfo.Path = getter.GetDefaultPath(downloadInfo.Name + ".json")
if downloadInfo.FileName == "" {
downloadInfo.FileName = fmt.Sprintf("%s.json", downloadInfo.Name)
}
controls, err := g.GetControl(downloadInfo.Name)
if err != nil {
return err
}
err = getter.SaveInFile(controls, downloadInfo.Path)
err = getter.SaveInFile(controls, filepath.Join(downloadInfo.Path, downloadInfo.FileName))
if err != nil {
return err
}
fmt.Printf("'%s' downloaded successfully and saved at: '%s'\n", downloadInfo.Target, filepath.Join(downloadInfo.Path, downloadInfo.FileName))
return nil
}

58
clihandler/clilist.go Normal file
View File

@@ -0,0 +1,58 @@
package clihandler
import (
"fmt"
"sort"
"strings"
"github.com/armosec/kubescape/cautils"
"github.com/armosec/kubescape/cautils/getter"
)
var listFunc = map[string]func(*cautils.ListPolicies) ([]string, error){
"controls": listControls,
"frameworks": listFrameworks,
}
func ListSupportCommands() []string {
commands := []string{}
for k := range listFunc {
commands = append(commands, k)
}
return commands
}
func CliList(listPolicies *cautils.ListPolicies) error {
if f, ok := listFunc[listPolicies.Target]; ok {
policies, err := f(listPolicies)
if err != nil {
return err
}
sort.Strings(policies)
sep := "\n * "
usageCmd := strings.TrimSuffix(listPolicies.Target, "s")
fmt.Printf("Supported %s:%s%s\n", listPolicies.Target, sep, strings.Join(policies, sep))
fmt.Printf("\nUseage:\n")
fmt.Printf("$ kubescape scan %s \"name\"\n", usageCmd)
fmt.Printf("$ kubescape scan %s \"name-0\",\"name-1\"\n\n", usageCmd)
return nil
}
return fmt.Errorf("unknown command to download")
}
func listFrameworks(listPolicies *cautils.ListPolicies) ([]string, error) {
tenant := getTenantConfig(listPolicies.Account, "", getKubernetesApi()) // change k8sinterface
g := getPolicyGetter(nil, tenant.GetCustomerGUID(), true, nil)
return listFrameworksNames(g), nil
}
func listControls(listPolicies *cautils.ListPolicies) ([]string, error) {
tenant := getTenantConfig(listPolicies.Account, "", getKubernetesApi()) // change k8sinterface
g := getPolicyGetter(nil, tenant.GetCustomerGUID(), false, nil)
l := getter.ListName
if listPolicies.ListIDs {
l = getter.ListID
}
return g.ListControls(l)
}

View File

@@ -11,10 +11,9 @@ import (
)
var getCmd = &cobra.Command{
Use: "get <key>",
Short: "Get configuration in cluster",
Long: ``,
ValidArgs: getter.NativeFrameworks,
Use: "get <key>",
Short: "Get 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")
@@ -31,7 +30,7 @@ var getCmd = &cobra.Command{
key := keyValue[0]
k8s := k8sinterface.NewKubernetesApi()
clusterConfig := cautils.NewClusterConfig(k8s, getter.GetArmoAPIConnector(), scanInfo.Account)
clusterConfig := cautils.NewClusterConfig(k8s, getter.GetArmoAPIConnector(), scanInfo.Account, "")
val, err := clusterConfig.GetValueByKeyFromConfigMap(key)
if err != nil {
if err.Error() == "value does not exist." {

View File

@@ -30,7 +30,7 @@ var setCmd = &cobra.Command{
data := keyValue[1]
k8s := k8sinterface.NewKubernetesApi()
clusterConfig := cautils.NewClusterConfig(k8s, getter.GetArmoAPIConnector(), scanInfo.Account)
clusterConfig := cautils.NewClusterConfig(k8s, getter.GetArmoAPIConnector(), scanInfo.Account, "")
if err := clusterConfig.SetKeyValueInConfigmap(key, data); err != nil {
return err
}

View File

@@ -7,7 +7,6 @@ import (
"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"
@@ -16,14 +15,16 @@ import (
var (
controlExample = `
# Scan the 'privileged container' control
kubescape scan control 'privileged container'
kubescape scan control "privileged container"
# Scan list of controls separated with a comma
kubescape scan control 'privileged container,allowed hostpath'
kubescape scan control "privileged container","allowed hostpath"
# Scan list of controls using the control ID separated with a comma
kubescape scan control C-0058,C-0057
Run 'kubescape list controls' for the list of supported controls
Control documentation:
https://hub.armo.cloud/docs/controls
`
@@ -52,7 +53,7 @@ var controlCmd = &cobra.Command{
scanInfo.PolicyIdentifier = []reporthandling.PolicyIdentifier{}
if len(args) == 0 {
scanInfo.SetPolicyIdentifiers(getter.NativeFrameworks, reporthandling.KindFramework)
// scanInfo.SetPolicyIdentifiers(getter.NativeFrameworks, reporthandling.KindFramework)
scanInfo.ScanAll = true
} else { // expected control or list of control sepparated by ","

View File

@@ -12,14 +12,40 @@ import (
var downloadInfo = cautils.DownloadInfo{}
var (
downloadExample = `
# Download all artifacts and save them in the default path (~/.kubescape)
kubescape download artifacts
# Download all artifacts and save them in /tmp path
kubescape download artifacts --output /tmp
# Download the NSA framework. Run 'kubescape list frameworks' for all frameworks names
kubescape download frameworks nsa
# Download the "Allowed hostPath" control. Run 'kubescape list controls' for all controls names
kubescape download control "Allowed hostPath"
# Download the "C-0001" control. Run 'kubescape list controls --id' for all controls ids
kubescape download control C-0001
# Download the configured exceptions
kubescape download exceptions
# Download the configured controls-inputs
kubescape download controls-inputs
`
)
var downloadCmd = &cobra.Command{
Use: "download <policy> <policy name>",
Short: fmt.Sprintf("Download %s", strings.Join(clihandler.DownloadSupportCommands(), "/")),
Long: ``,
Use: "download <policy> <policy name>",
Short: fmt.Sprintf("Download %s", strings.Join(clihandler.DownloadSupportCommands(), ",")),
Long: ``,
Example: downloadExample,
Args: func(cmd *cobra.Command, args []string) error {
supported := strings.Join(clihandler.DownloadSupportCommands(), ",")
if len(args) < 1 {
return fmt.Errorf("policy type requeued, supported: %v", supported)
return fmt.Errorf("policy type required, supported: %v", supported)
}
if cautils.StringInSlice(clihandler.DownloadSupportCommands(), args[0]) == cautils.ValueNotFound {
return fmt.Errorf("invalid parameter '%s'. Supported parameters: %s", args[0], supported)
@@ -43,7 +69,7 @@ func init() {
// cobra.OnInitialize(initConfig)
rootCmd.AddCommand(downloadCmd)
downloadCmd.Flags().StringVarP(&downloadInfo.Path, "output", "o", "", "Output file. If specified, will store save to `~/.kubescape/<policy name>.json`")
downloadCmd.Flags().StringVarP(&downloadInfo.Path, "output", "o", "", "Output file. If not specified, will save in `~/.kubescape/<policy name>.json`")
downloadCmd.PersistentFlags().StringVarP(&downloadInfo.Account, "account", "", "", "Armo portal account ID. Default will load account ID from configMap or config file")
}

View File

@@ -7,7 +7,6 @@ import (
"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"
@@ -24,6 +23,9 @@ var (
# Scan the NSA and MITRE framework
kubescape scan framework nsa,mitre
# Scan all frameworks
kubescape scan framework all
# Scan kubernetes YAML manifest files
kubescape scan framework nsa *.yaml
@@ -35,14 +37,16 @@ var (
# Display all resources
kubescape scan --verbose
Run 'kubescape list frameworks' for the list of supported frameworks
`
)
var frameworkCmd = &cobra.Command{
Use: "framework <framework names list> [`<glob pattern>`/`-`] [flags]",
Short: "The framework you wish to use. Run 'kubescape list frameworks' for the list of supported frameworks",
Example: frameworkExample,
Long: "Execute a scan on a running Kubernetes cluster or `yaml`/`json` files (use glob) or `-` for stdin",
ValidArgs: getter.NativeFrameworks,
Use: "framework <framework names list> [`<glob pattern>`/`-`] [flags]",
Short: "The framework you wish to use. Run 'kubescape list frameworks' for the list of supported frameworks",
Example: frameworkExample,
Long: "Execute a scan on a running Kubernetes cluster or `yaml`/`json` files (use glob) or `-` for stdin",
// ValidArgs: getter.NativeFrameworks,
Args: func(cmd *cobra.Command, args []string) error {
if len(args) > 0 {
frameworks := strings.Split(args[0], ",")
@@ -61,12 +65,15 @@ var frameworkCmd = &cobra.Command{
var frameworks []string
if len(args) == 0 { // scan all frameworks
frameworks = getter.NativeFrameworks
// frameworks = getter.NativeFrameworks
scanInfo.ScanAll = true
} else {
// Read frameworks from input args
frameworks = strings.Split(args[0], ",")
if cautils.StringInSlice(frameworks, "all") != cautils.ValueNotFound {
scanInfo.ScanAll = true
frameworks = []string{}
}
if len(args) > 1 {
if len(args[1:]) == 0 || args[1] != "-" {
scanInfo.InputPatterns = args[1:]
@@ -84,6 +91,8 @@ var frameworkCmd = &cobra.Command{
}
}
}
scanInfo.FrameworkScan = true
scanInfo.SetPolicyIdentifiers(frameworks, reporthandling.KindFramework)
scanInfo.Init()

66
clihandler/cmd/list.go Normal file
View File

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

View File

@@ -4,8 +4,8 @@ import (
"fmt"
"strings"
"github.com/armosec/k8s-interface/k8sinterface"
"github.com/armosec/kubescape/cautils"
"github.com/armosec/kubescape/cautils/getter"
"github.com/spf13/cobra"
)
@@ -27,17 +27,25 @@ var scanCmd = &cobra.Command{
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)
// frameworks := getter.NativeFrameworks
// frameworkArgs := []string{strings.Join(frameworks, ",")}
frameworkCmd.RunE(cmd, []string{"all"})
}
},
}
func frameworkInitConfig() {
k8sinterface.SetClusterContextName(scanInfo.KubeContext)
}
func init() {
cobra.OnInitialize(frameworkInitConfig)
rootCmd.AddCommand(scanCmd)
rootCmd.PersistentFlags().StringVarP(&scanInfo.KubeContext, "kube-context", "", "", "Kube context. Default will use the current-context")
scanCmd.PersistentFlags().StringVar(&scanInfo.ControlsInputs, "controls-config", "", "Path to an controls-config obj. If not set will download controls-config from ARMO management portal")
scanCmd.PersistentFlags().StringVar(&scanInfo.UseExceptions, "exceptions", "", "Path to an exceptions obj. If not set will download exceptions from ARMO management portal")
scanCmd.PersistentFlags().StringVar(&scanInfo.UseArtifactsFrom, "use-artifacts-from", "", "Load artifacts from local directory. If not used will download them")
scanCmd.PersistentFlags().StringVarP(&scanInfo.ExcludedNamespaces, "exclude-namespaces", "e", "", "Namespaces to exclude from scanning. Recommended: kube-system,kube-public")
scanCmd.PersistentFlags().Uint16VarP(&scanInfo.FailThreshold, "fail-threshold", "t", 100, "Failure threshold is the percent above which the command fails and returns exit code 1")
scanCmd.PersistentFlags().StringVarP(&scanInfo.Format, "format", "f", "pretty-printer", `Output format. Supported formats: "pretty-printer"/"json"/"junit"/"prometheus"`)

View File

@@ -20,7 +20,7 @@ func init() {
}
func getSubmittedClusterConfig(k8s *k8sinterface.KubernetesApi) (*cautils.ClusterConfig, error) {
clusterConfig := cautils.NewClusterConfig(k8s, getter.GetArmoAPIConnector(), scanInfo.Account) // TODO - support none cluster env submit
clusterConfig := cautils.NewClusterConfig(k8s, getter.GetArmoAPIConnector(), scanInfo.Account, scanInfo.KubeContext) // TODO - support none cluster env submit
if clusterConfig.GetCustomerGUID() != "" {
if err := clusterConfig.SetTenant(); err != nil {
return clusterConfig, err

View File

@@ -5,6 +5,7 @@ import (
"io/fs"
"os"
"github.com/armosec/k8s-interface/k8sinterface"
"github.com/armosec/kubescape/resultshandling/printer"
printerv1 "github.com/armosec/kubescape/resultshandling/printer/v1"
@@ -34,9 +35,16 @@ type componentInterfaces struct {
func getInterfaces(scanInfo *cautils.ScanInfo) componentInterfaces {
k8s := getKubernetesApi()
var k8s *k8sinterface.KubernetesApi
if scanInfo.GetScanningEnvironment() == cautils.ScanCluster {
k8s = getKubernetesApi()
if k8s == nil {
fmt.Println("Failed connecting to Kubernetes cluster")
os.Exit(1)
}
}
tenantConfig := getTenantConfig(scanInfo.Account, k8s)
tenantConfig := getTenantConfig(scanInfo.Account, scanInfo.KubeContext, k8s)
// Set submit behavior AFTER loading tenant config
setSubmitBehavior(scanInfo, tenantConfig)
@@ -99,6 +107,9 @@ func ScanCliSetup(scanInfo *cautils.ScanInfo) error {
scanInfo.Getters.ExceptionsGetter = getExceptionsGetter(scanInfo.UseExceptions)
// TODO - list supported frameworks/controls
if scanInfo.ScanAll {
scanInfo.SetPolicyIdentifiers(listFrameworksNames(scanInfo.Getters.PolicyGetter), reporthandling.KindFramework)
}
//
defer func() {

View File

@@ -11,22 +11,24 @@ import (
"github.com/armosec/kubescape/resourcehandler"
"github.com/armosec/kubescape/resultshandling/reporter"
reporterv1 "github.com/armosec/kubescape/resultshandling/reporter/v1"
reporterv2 "github.com/armosec/kubescape/resultshandling/reporter/v2"
"github.com/armosec/opa-utils/reporthandling"
"github.com/armosec/rbac-utils/rbacscanner"
// reporterv2 "github.com/armosec/kubescape/resultshandling/reporter/v2"
)
// getKubernetesApi
func getKubernetesApi() *k8sinterface.KubernetesApi {
if !k8sinterface.IsConnectedToCluster() {
return nil
}
return k8sinterface.NewKubernetesApi()
}
func getTenantConfig(Account string, k8s *k8sinterface.KubernetesApi) cautils.ITenantConfig {
if !k8sinterface.IsConnectedToCluster() {
return cautils.NewLocalConfig(getter.GetArmoAPIConnector(), Account)
func getTenantConfig(Account, clusterName string, k8s *k8sinterface.KubernetesApi) cautils.ITenantConfig {
if !k8sinterface.IsConnectedToCluster() || k8s == nil {
return cautils.NewLocalConfig(getter.GetArmoAPIConnector(), Account, clusterName)
}
return cautils.NewClusterConfig(k8s, getter.GetArmoAPIConnector(), Account)
return cautils.NewClusterConfig(k8s, getter.GetArmoAPIConnector(), Account, clusterName)
}
func getExceptionsGetter(useExceptions string) getter.IExceptionsGetter {
@@ -47,12 +49,14 @@ func getRBACHandler(tenantConfig cautils.ITenantConfig, k8s *k8sinterface.Kubern
func getReporter(tenantConfig cautils.ITenantConfig, submit bool) reporter.IReport {
if submit {
return reporterv1.NewReportEventReceiver(tenantConfig.GetConfigObj())
// return reporterv1.NewReportEventReceiver(tenantConfig.GetConfigObj())
return reporterv2.NewReportEventReceiver(tenantConfig.GetConfigObj())
}
return reporterv1.NewReportMock()
}
func getResourceHandler(scanInfo *cautils.ScanInfo, tenantConfig cautils.ITenantConfig, k8s *k8sinterface.KubernetesApi, hostSensorHandler hostsensorutils.IHostSensor) resourcehandler.IResourceHandler {
if scanInfo.GetScanningEnvironment() == cautils.ScanLocalFiles {
if len(scanInfo.InputPatterns) > 0 || k8s == nil {
return resourcehandler.NewFileResourceHandler(scanInfo.InputPatterns)
}
rbacObjects := getRBACHandler(tenantConfig, k8s, scanInfo.Submit)
@@ -60,9 +64,10 @@ func getResourceHandler(scanInfo *cautils.ScanInfo, tenantConfig cautils.ITenant
}
func getHostSensorHandler(scanInfo *cautils.ScanInfo, k8s *k8sinterface.KubernetesApi) hostsensorutils.IHostSensor {
if scanInfo.GetScanningEnvironment() == cautils.ScanLocalFiles {
if !k8sinterface.IsConnectedToCluster() || k8s == nil {
return &hostsensorutils.HostSensorHandlerMock{}
}
hasHostSensorControls := true
// we need to determined which controls needs host sensor
if scanInfo.HostSensor.Get() == nil && hasHostSensorControls {
@@ -125,12 +130,6 @@ func setSubmitBehavior(scanInfo *cautils.ScanInfo, tenantConfig cautils.ITenantC
return
}
// do not submit yaml/url scanning
if scanInfo.GetScanningEnvironment() == cautils.ScanLocalFiles {
scanInfo.Submit = false
return
}
if tenantConfig.IsConfigFound() { // config found in cache (submitted)
if !scanInfo.Local {
// Submit report
@@ -212,3 +211,11 @@ func getDefaultFrameworksPaths() []string {
}
return fwPaths
}
func listFrameworksNames(policyGetter getter.IPolicyGetter) []string {
fw, err := policyGetter.ListFrameworks()
if err != nil {
fw = getDefaultFrameworksPaths()
}
return fw
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 53 KiB

View File

@@ -1,4 +1,4 @@
# Container image vulnerabilty adaptor interface proposal
# Container image vulnerability adaptor interface proposal
## Rationale
@@ -6,7 +6,7 @@ source #287
### Big picture
* Kubescape team planning to create controls which take into account image vulnerabilities, example: looking for public internet facing workloads with critical vulnerabilities. These are seriously effecting the security health of a cluster and therefore we think it is important to cover it. We think that most container registries are/will support image scanning like Harbor and therefore the ability to get information from them is important.
* Kubescape team is planning to create controls which take into account image vulnerabilities, example: looking for public internet facing workloads with critical vulnerabilities. These are seriously effecting the security health of a cluster and therefore we think it is important to cover it. We think that most container registries are/will support image scanning like Harbor and therefore the ability to get information from them is important.
* There are information in the image repository which is important for existing controls as well. They are incomplete without it, example see this issue: Non-root containers check is broken #19 . These are not necessarily image vulnerability related. Can be information in the image manifest (like the issue before), but it can be the image BOM related.
### Relation to this proposal
@@ -114,4 +114,63 @@ type IContainerImageVulnerabilityAdaptor interface {
GetImagesInformation(imageIDs []ContainerImageIdentifier) ([]ContainerImageInformation, error)
}
```
# Integration
# Input
The objects received from the interface will be converted to an Imetadata compatible objects as following
```
{
"apiVersion": "image.vulnscan.com/v1",
"kind": "VulnScan",
"metadata": {
"name": "nginx:latest"
},
"data": {
// returned by the adaptor API (structure like our backend gives for an image
}
}
```
# Output
The rego results will be a combination of the k8s artifact and the list of relevant CVEs for the control
```
{
"apiVersion": "result.vulnscan.com/v1",
"kind": "Pod",
"metadata": {
"name": "nginx"
},
"relatedObjects": [
{
"apiVersion": "v1",
"kind": "Pod",
"metadata": {
"name": "nginx"
},
"spec": {
// podSpec
},
},
{
"apiVersion": "container.vulnscan.com/v1",
"kind": "VulnScan",
"metadata": {
"name": "nginx:latest",
},
"data": {
// returned by the adaptor API (structure like our backend gives for an image
}
}
]
}
```

23
docs/roadmap.md Normal file
View File

@@ -0,0 +1,23 @@
# Kubescape project roadmap
## Proposals
* [Container registry integration](/docs/proposals/container-image-vulnerability-adaptor.md)
## Planed features
* Image vulnerablity scanning based controls
* Assited remidiation (telling where/what to fix)
* Git integration for pull requests
* Integration with container registries
* Custom controls and regos
* API server configuration validation
* Kubelet configuration validation
## Completed features
* Integration with Prometheus
* Confiugration of controls (customizing rules for a given environment)
* Installation in the cluster for continous monitoring
* Host scanner
* Cloud vendor API integration
* Custom exceptions
* Custom frameworks

View File

@@ -0,0 +1,68 @@
#!/bin/bash
# AWS
# Attach the Kubescape service account to an AWS IAM role with the described cluster permission
# Prerequisites:
# eksctl, awscli v2
# Set environment variables
echo 'Set environment variables'
export kubescape_namespace=armo-system
export kubescape_serviceaccount=armo-kubescape-service-account
# Get current context
echo 'Get current context'
export context=$(kubectl config current-context)
# Get cluster arn
echo 'Get cluster arn'
export cluster_arn=$(kubectl config view -o jsonpath="{.contexts[?(@.name == \"$context\")].context.cluster}")
# Get cluster name
echo 'Get cluster name'
export cluster_name=$(echo "$cluster_arn" | awk -F'/' '{print $NF}')
# Get cluster region
echo 'Get cluster region'
export cluster_region=$(echo "$cluster_arn" | awk -F':' '{print $4}')
# First step, Create IAM OIDC provider for the cluster (Not required if the third step runs as is):
echo 'Create IAM OIDC provider for the cluster'
eksctl utils associate-iam-oidc-provider --cluster $cluster_name --approve
# Second step, Create a policy and service account role:
# Create a kubescape policy
echo 'Create a kubescape policy'
export kubescape_policy_arn=$(aws iam create-policy \
--output yaml \
--query 'Policy.Arn' \
--policy-name kubescape \
--policy-document \
"$(cat <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "eks:DescribeCluster",
"Resource": "$cluster_arn"
}
]
}
EOF
)")
# Create Kubernetes Kubescape service account, and AWS IAM attachment role
echo 'Create Kubernetes Kubescape service account, and AWS IAM attachment role'
eksctl create iamserviceaccount \
--name $kubescape_serviceaccount \
--namespace $kubescape_namespace \
--cluster $cluster_name \
--attach-policy-arn $kubescape_policy_arn \
--approve \
--override-existing-serviceaccounts
# Install/Upgrade Kubescape chart
echo 'Install/Upgrade Kubescape chart'
helm upgrade --install armo armo-components/ -n armo-system --create-namespace --set clusterName=$cluster_name --set cloud_provider_engine=eks --set createKubescapeServiceAccount=false --set cloudRegion=$cluster_region

View File

@@ -0,0 +1,49 @@
#!/bin/bash
# GCP
# Attach the Kubescape service account to a GCP service account with the get cluster permission
# Prerequisites:
# gcloud
# Workload Identity enabled on the cluster.
# Node pool with --workload-metadata=GKE_METADATA where the pod can run.
# CLUSTER_NAME and CLUSTER_REGION environment variables.
[ -z $CLUSTER_NAME ] && >&2 echo "Please set the CLUSTER_NAME environment variable" && exit 1
[ -z $CLUSTER_REGION ] && >&2 echo "Please set the CLUSTER_REGION environment variable" && exit 1
# Create GCP service account
gcloud iam service-accounts create kubescape --display-name=kubescape
# Set environment variables
echo 'Set environment variables'
export kubescape_namespace=armo-system
export kubescape_serviceaccount=armo-kubescape-service-account
# Get current GCP project
echo 'Get current GCP project'
export gcp_project=$(gcloud config get-value project)
sleep 5
# Get service account email
echo 'Get service account email'
export gcp_service_account=$(gcloud iam service-accounts list --filter="email ~ kubescape@" --format="value(email)")
# Create custome cluster.get role
echo 'Create custome cluster.get role'
export custom_role_name=$(gcloud iam roles create kubescape --project=$gcp_project --title='Armo kubernetes' --description='Allow clusters.get to Kubernetes armo service account' --permissions=container.clusters.get --stage=GA --format='value(name)')
# Attach policies to the service account
echo 'Attach policies to the service account'
gcloud --quiet projects add-iam-policy-binding $gcp_project --member serviceAccount:$gcp_service_account --role $custom_role_name >/dev/null
gcloud --quiet projects add-iam-policy-binding $gcp_project --member serviceAccount:$gcp_service_account --role roles/storage.objectViewer >/dev/null
# If there are missing permissions, use this role instead
# gcloud --quiet projects add-iam-policy-binding $gcp_project --member serviceAccount:$gcp_service_account --role roles/container.clusterViewer
# Bind the GCP kubescape service account to kubescape kubernetes service account
gcloud iam service-accounts add-iam-policy-binding $gcp_service_account --role roles/iam.workloadIdentityUser --member "serviceAccount:${gcp_project}.svc.id.goog[${kubescape_namespace}/${kubescape_serviceaccount}]"
# Install/Upgrade Kubescape chart
echo 'Install/Upgrade Kubescape chart'
helm upgrade --install armo armo-components/ -n armo-system --create-namespace --set cloud_provider_engine=gke --set gke_service_account=$gcp_service_account --set cloudRegion=$CLUSTER_REGION --set clusterName=$CLUSTER_NAME --set gkeProject=$gcp_project

11
go.mod
View File

@@ -3,10 +3,10 @@ module github.com/armosec/kubescape
go 1.17
require (
github.com/armosec/armoapi-go v0.0.40
github.com/armosec/k8s-interface v0.0.50
github.com/armosec/opa-utils v0.0.91
github.com/armosec/rbac-utils v0.0.11
github.com/armosec/armoapi-go v0.0.41
github.com/armosec/k8s-interface v0.0.56
github.com/armosec/opa-utils v0.0.99
github.com/armosec/rbac-utils v0.0.12
github.com/armosec/utils-go v0.0.3
github.com/briandowns/spinner v1.18.0
github.com/enescakir/emoji v1.0.0
@@ -19,6 +19,7 @@ require (
github.com/satori/go.uuid v1.2.0
github.com/spf13/cobra v1.2.1
github.com/stretchr/testify v1.7.0
go.uber.org/zap v1.19.1
gopkg.in/yaml.v2 v2.4.0
k8s.io/api v0.22.2
k8s.io/apimachinery v0.22.2
@@ -35,6 +36,7 @@ require (
github.com/Azure/go-autorest/logger v0.2.1 // indirect
github.com/Azure/go-autorest/tracing v0.6.0 // indirect
github.com/OneOfOne/xxhash v1.2.8 // indirect
github.com/armosec/armo-interfaces v0.0.3 // indirect
github.com/armosec/utils-k8s-go v0.0.1 // indirect
github.com/aws/aws-sdk-go v1.41.11 // indirect
github.com/coreos/go-oidc v2.2.1+incompatible // indirect
@@ -76,7 +78,6 @@ require (
go.opencensus.io v0.23.0 // 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-20210711020723-a769d52b0f97 // indirect
golang.org/x/net v0.0.0-20210825183410-e898025ed96a // indirect
golang.org/x/oauth2 v0.0.0-20211005180243-6b3c2da341f1 // indirect

17
go.sum
View File

@@ -83,20 +83,23 @@ github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hC
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
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/armo-interfaces v0.0.3 h1:kG4mJIPgWBJvQFDDy8JzdqX3ASbyl8t32IuJYqB31Pk=
github.com/armosec/armo-interfaces v0.0.3/go.mod h1:7XYefhcBCFYoF5LflCZHWuUHu+JrSJbmzk0zoNv2WlU=
github.com/armosec/armoapi-go v0.0.2/go.mod h1:vIK17yoKbJRQyZXWWLe3AqfqCRITxW8qmSkApyq5xFs=
github.com/armosec/armoapi-go v0.0.23/go.mod h1:iaVVGyc23QGGzAdv4n+szGQg3Rbpixn9yQTU3qWRpaw=
github.com/armosec/armoapi-go v0.0.40 h1:KQRJXFqw95s6cV7HoGgw1x8qrRZ9eNVze//yQbo24Lk=
github.com/armosec/armoapi-go v0.0.40/go.mod h1:iaVVGyc23QGGzAdv4n+szGQg3Rbpixn9yQTU3qWRpaw=
github.com/armosec/armoapi-go v0.0.41 h1:iMkaCsME+zhE6vnCOMaqfqc0cp7pste8QFHojeGKfGg=
github.com/armosec/armoapi-go v0.0.41/go.mod h1:exk1O3rK6V+X8SSyxc06lwb0j9ILQuKAoIdz9hs6Ndw=
github.com/armosec/k8s-interface v0.0.8/go.mod h1:xxS+V5QT3gVQTwZyAMMDrYLWGrfKOpiJ7Jfhfa0w9sM=
github.com/armosec/k8s-interface v0.0.37/go.mod h1:vHxGWqD/uh6+GQb9Sqv7OGMs+Rvc2dsFVc0XtgRh1ZU=
github.com/armosec/k8s-interface v0.0.50 h1:iLPGI0j85vwKANr9QDAnba4Efjg3DyIJg15jRJdvOnc=
github.com/armosec/k8s-interface v0.0.50/go.mod h1:vHxGWqD/uh6+GQb9Sqv7OGMs+Rvc2dsFVc0XtgRh1ZU=
github.com/armosec/k8s-interface v0.0.56 h1:7dOgc3qZaI7ReLRZcJa2JZKk0rliyYi05l1vuHc6gcE=
github.com/armosec/k8s-interface v0.0.56/go.mod h1:vHxGWqD/uh6+GQb9Sqv7OGMs+Rvc2dsFVc0XtgRh1ZU=
github.com/armosec/opa-utils v0.0.64/go.mod h1:6tQP8UDq2EvEfSqh8vrUdr/9QVSCG4sJfju1SXQOn4c=
github.com/armosec/opa-utils v0.0.91 h1:8nnkySKVBQ6EPobid8FZXJLzFbVq9GtVa93oUkgEpeI=
github.com/armosec/opa-utils v0.0.91/go.mod h1:ZOXYVTtuyrV4TldcfbzgRqP6F9Drlf4hB0zr210OXgM=
github.com/armosec/opa-utils v0.0.99 h1:ZuoIPg6vbgO4J09xJZDO/yIRD59odwmK2Bm55uTvkU8=
github.com/armosec/opa-utils v0.0.99/go.mod h1:BNTjeianyXlflJMz3bZM0GimBWqmzirUf1whWR6Os04=
github.com/armosec/rbac-utils v0.0.1/go.mod h1:pQ8CBiij8kSKV7aeZm9FMvtZN28VgA7LZcYyTWimq40=
github.com/armosec/rbac-utils v0.0.11 h1:SCiVLqUeV+WGpUsWbOBt6jKkFAd62jztuzB6PIgHz7w=
github.com/armosec/rbac-utils v0.0.11/go.mod h1:Ex/IdGWhGv9HZq6Hs8N/ApzCKSIvpNe/ETqDfnuyah0=
github.com/armosec/rbac-utils v0.0.12 h1:uJpMGDyLAX129PrKHp6NPNB6lVRhE0OZIwV6ywzSDrs=
github.com/armosec/rbac-utils v0.0.12/go.mod h1:Ex/IdGWhGv9HZq6Hs8N/ApzCKSIvpNe/ETqDfnuyah0=
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=

View File

@@ -1,6 +1,4 @@
package hostsensorutils
const hostSensorYAML = `apiVersion: v1
apiVersion: v1
kind: Namespace
metadata:
labels:
@@ -62,4 +60,4 @@ spec:
name: host-filesystem
hostNetwork: true
hostPID: true
hostIPC: true`
hostIPC: true

View File

@@ -1,6 +1,7 @@
package hostsensorutils
import (
_ "embed"
"fmt"
"io"
"strings"
@@ -18,6 +19,11 @@ import (
coreapplyv1 "k8s.io/client-go/applyconfigurations/core/v1"
)
var (
//go:embed hostsensor.yaml
hostSensorYAML string
)
type HostSensorHandler struct {
HostSensorPort int32
HostSensorPodNames map[string]string //map from pod names to node names

View File

@@ -6,6 +6,7 @@ import (
"time"
"github.com/armosec/kubescape/cautils"
ksscore "github.com/armosec/kubescape/score"
"github.com/armosec/opa-utils/objectsenvelopes"
"github.com/armosec/opa-utils/reporthandling"
"github.com/armosec/opa-utils/reporthandling/apis"
@@ -71,6 +72,9 @@ func (opaHandler *OPAProcessorHandler) ProcessRulesListenner() {
// edit results
opap.updateResults()
//TODO: review this location
scorewrapper := ksscore.NewScoreWrapper(opaSessionObj)
scorewrapper.Calculate(ksscore.EPostureReportV2)
// report
*opaHandler.reportResults <- opaSessionObj
}

View File

@@ -201,6 +201,7 @@ func removeSecretData(workload workloadinterface.IWorkload) {
func removePodData(workload workloadinterface.IWorkload) {
workload.RemoveAnnotation("kubectl.kubernetes.io/last-applied-configuration")
workloadinterface.RemoveFromMap(workload.GetObject(), "metadata", "managedFields")
workloadinterface.RemoveFromMap(workload.GetObject(), "status")
containers, err := workload.GetContainers()
if err != nil || len(containers) == 0 {

View File

@@ -2,6 +2,7 @@ package policyhandler
import (
"fmt"
"strings"
"github.com/armosec/kubescape/cautils"
"github.com/armosec/opa-utils/reporthandling"
@@ -15,7 +16,7 @@ func (policyHandler *PolicyHandler) getPolicies(notification *reporthandling.Pol
return err
}
if len(frameworks) == 0 {
return fmt.Errorf("failed to download policies, please ARMO team for more information")
return fmt.Errorf("failed to download policies: '%s'. Make sure the policy exist and you spelled it correctly. For more information, please feel free to contact ARMO team", strings.Join(policyIdentifierToSlice(notification.Rules), ","))
}
policiesAndResources.Frameworks = frameworks
@@ -70,3 +71,11 @@ func (policyHandler *PolicyHandler) getScanPolicies(notification *reporthandling
}
return frameworks, nil
}
func policyIdentifierToSlice(rules []reporthandling.PolicyIdentifier) []string {
s := []string{}
for i := range rules {
s = append(s, fmt.Sprintf("%s: %s", rules[i].Kind, rules[i].Name))
}
return s
}

View File

@@ -0,0 +1,104 @@
package resourcehandler
import (
"os"
"strings"
"github.com/armosec/k8s-interface/cloudsupport"
"github.com/armosec/k8s-interface/k8sinterface"
)
var (
KS_KUBE_CLUSTER_ENV_VAR = "KS_KUBE_CLUSTER"
KS_CLOUD_PROVIDER_ENV_VAR = "KS_CLOUD_PROVIDER"
KS_CLOUD_REGION_ENV_VAR = "KS_CLOUD_REGION"
KS_GKE_PROJECT_ENV_VAR = "KS_GKE_PROJECT"
)
type ICloudProvider interface {
getKubeCluster() string
getRegion(cluster string, provider string) (string, error)
getProject(cluster string, provider string) (string, error)
getKubeClusterName() string
}
func initCloudProvider() ICloudProvider {
switch getCloudProvider() {
case "gke", "gcp":
return NewGKEProviderContext()
case "eks", "aws":
return NewEKSProviderContext()
}
return NewEmptyCloudProvider()
}
func getCloudProvider() string {
var provider string
if isEnvVars() {
provider = getCloudProviderFromEnvVar()
} else {
provider = getCloudProviderFromContext()
}
return strings.ToLower(provider)
}
func getCloudProviderFromContext() string {
return cloudsupport.GetCloudProvider(getClusterFromContext())
}
func getClusterFromContext() string {
context := k8sinterface.GetCurrentContext()
if context == nil {
return ""
}
cluster := context.Cluster
if cluster != "" {
return cluster
}
return k8sinterface.GetClusterName()
}
func getCloudProviderFromEnvVar() string {
val, present := os.LookupEnv(KS_CLOUD_PROVIDER_ENV_VAR)
if present {
return val
}
return ""
}
func isEnvVars() bool {
_, present := os.LookupEnv(KS_KUBE_CLUSTER_ENV_VAR)
if !present {
return false
}
_, present = os.LookupEnv(KS_CLOUD_PROVIDER_ENV_VAR)
if !present {
return false
}
_, present = os.LookupEnv(KS_CLOUD_REGION_ENV_VAR)
return present
}
type EmptyCloudProvider struct {
}
func NewEmptyCloudProvider() *EmptyCloudProvider {
return &EmptyCloudProvider{}
}
func (emptyCloudProvider *EmptyCloudProvider) getKubeCluster() string {
return getClusterFromContext()
}
func (emptyCloudProvider *EmptyCloudProvider) getKubeClusterName() string {
return emptyCloudProvider.getKubeCluster()
}
func (emptyCloudProvider *EmptyCloudProvider) getRegion(cluster string, provider string) (string, error) {
return "", nil
}
func (emptyCloudProvider *EmptyCloudProvider) getProject(cluster string, provider string) (string, error) {
return "", nil
}

View File

@@ -0,0 +1,95 @@
package resourcehandler
import (
"fmt"
"os"
"strings"
"github.com/armosec/k8s-interface/k8sinterface"
)
type EKSProviderEnvVar struct {
}
func NewEKSProviderEnvVar() *EKSProviderEnvVar {
return &EKSProviderEnvVar{}
}
func (eksProviderEnvVar *EKSProviderEnvVar) getKubeClusterName() string {
return eksProviderEnvVar.getKubeCluster()
}
func (eksProviderEnvVar *EKSProviderEnvVar) getKubeCluster() string {
val, present := os.LookupEnv(KS_KUBE_CLUSTER_ENV_VAR)
if present {
return val
}
return ""
}
func (eksProviderEnvVar *EKSProviderEnvVar) getRegion(cluster string, provider string) (string, error) {
return eksProviderEnvVar.getRegionForEKS(cluster)
}
func (eksProviderEnvVar *EKSProviderEnvVar) getProject(cluster string, provider string) (string, error) {
return "", nil
}
func (eksProviderEnvVar *EKSProviderEnvVar) getRegionForEKS(cluster string) (string, error) {
region, present := os.LookupEnv(KS_CLOUD_REGION_ENV_VAR)
if present {
return region, nil
}
splittedClusterContext := strings.Split(cluster, ".")
if len(splittedClusterContext) < 2 {
return "", fmt.Errorf("error: failed to get region")
}
region = splittedClusterContext[1]
return region, nil
}
// ------------------------------------- EKSProviderContext -------------------------
type EKSProviderContext struct {
}
func NewEKSProviderContext() *EKSProviderContext {
return &EKSProviderContext{}
}
func (eksProviderContext *EKSProviderContext) getKubeClusterName() string {
cluster := k8sinterface.GetCurrentContext().Cluster
var splittedCluster []string
if cluster != "" {
splittedCluster = strings.Split(cluster, ".")
if len(splittedCluster) > 1 {
return splittedCluster[0]
}
}
splittedCluster = strings.Split(k8sinterface.GetClusterName(), ".")
if len(splittedCluster) > 1 {
return splittedCluster[0]
}
return ""
}
func (eksProviderContext *EKSProviderContext) getKubeCluster() string {
cluster := k8sinterface.GetCurrentContext().Cluster
if cluster != "" {
return cluster
}
return k8sinterface.GetClusterName()
}
func (eksProviderContext *EKSProviderContext) getRegion(cluster string, provider string) (string, error) {
splittedClusterContext := strings.Split(cluster, ".")
if len(splittedClusterContext) < 2 {
return "", fmt.Errorf("error: failed to get region")
}
region := splittedClusterContext[1]
return region, nil
}
func (eksProviderContext *EKSProviderContext) getProject(cluster string, provider string) (string, error) {
return "", nil
}

View File

@@ -0,0 +1,125 @@
package resourcehandler
import (
"fmt"
"os"
"strings"
"github.com/armosec/k8s-interface/k8sinterface"
)
type GKEProviderEnvVar struct {
}
func NewGKEProviderEnvVar() *GKEProviderEnvVar {
return &GKEProviderEnvVar{}
}
func (gkeProvider *GKEProviderEnvVar) getKubeClusterName() string {
return gkeProvider.getKubeCluster()
}
func (gkeProvider *GKEProviderEnvVar) getKubeCluster() string {
val, present := os.LookupEnv(KS_KUBE_CLUSTER_ENV_VAR)
if present {
return val
}
return ""
}
func (gkeProvider *GKEProviderEnvVar) getRegion(cluster string, provider string) (string, error) {
return gkeProvider.getRegionForGKE(cluster)
}
func (gkeProvider *GKEProviderEnvVar) getProject(cluster string, provider string) (string, error) {
return gkeProvider.getProjectForGKE(cluster)
}
func (gkeProvider *GKEProviderEnvVar) getProjectForGKE(cluster string) (string, error) {
project, present := os.LookupEnv(KS_GKE_PROJECT_ENV_VAR)
if present {
return project, nil
}
parsedName := strings.Split(cluster, "_")
if len(parsedName) < 3 {
return "", fmt.Errorf("error: failed to parse cluster name")
}
project = parsedName[1]
return project, nil
}
func (gkeProvider *GKEProviderEnvVar) getRegionForGKE(cluster string) (string, error) {
region, present := os.LookupEnv(KS_CLOUD_REGION_ENV_VAR)
if present {
return region, nil
}
parsedName := strings.Split(cluster, "_")
if len(parsedName) < 3 {
return "", fmt.Errorf("error: failed to parse cluster name")
}
region = parsedName[2]
return region, nil
}
// ------------------------------ GKEProviderContext --------------------------------------------------------
type GKEProviderContext struct {
}
func NewGKEProviderContext() *GKEProviderContext {
return &GKEProviderContext{}
}
func (gkeProviderContext *GKEProviderContext) getKubeClusterName() string {
cluster := k8sinterface.GetCurrentContext().Cluster
parsedName := strings.Split(cluster, "_")
if len(parsedName) < 3 {
return ""
}
clusterName := parsedName[3]
if clusterName != "" {
return clusterName
}
cluster = k8sinterface.GetClusterName()
parsedName = strings.Split(cluster, "_")
if len(parsedName) < 3 {
return ""
}
clusterName = parsedName[3]
return clusterName
}
func (gkeProviderContext *GKEProviderContext) getKubeCluster() string {
cluster := k8sinterface.GetCurrentContext().Cluster
if cluster != "" {
return cluster
}
return k8sinterface.GetClusterName()
}
func (gkeProviderContext *GKEProviderContext) getRegion(cluster string, provider string) (string, error) {
return gkeProviderContext.getRegionForGKE(cluster)
}
func (gkeProviderContext *GKEProviderContext) getProject(cluster string, provider string) (string, error) {
return gkeProviderContext.getProjectForGKE(cluster)
}
func (gkeProviderContext *GKEProviderContext) getProjectForGKE(cluster string) (string, error) {
parsedName := strings.Split(cluster, "_")
if len(parsedName) < 3 {
return "", fmt.Errorf("error: failed to parse cluster name")
}
project := parsedName[1]
return project, nil
}
func (gkeProviderContext *GKEProviderContext) getRegionForGKE(cluster string) (string, error) {
parsedName := strings.Split(cluster, "_")
if len(parsedName) < 3 {
return "", fmt.Errorf("error: failed to parse cluster name")
}
region := parsedName[2]
return region, nil
}

View File

@@ -193,11 +193,21 @@ func (k8sHandler *K8sResourceHandler) collectRbacResources(allResources map[stri
}
func getCloudProviderDescription(allResources map[string]workloadinterface.IMetadata, k8sResourcesMap *cautils.K8SResources) error {
if cloudsupport.IsRunningInCloudProvider() {
wl, err := cloudsupport.GetDescriptiveInfoFromCloudProvider()
cloudProvider := initCloudProvider()
cluster := cloudProvider.getKubeCluster()
clusterName := cloudProvider.getKubeClusterName()
provider := getCloudProvider()
region, err := cloudProvider.getRegion(cluster, provider)
if err != nil {
return err
}
project, err := cloudProvider.getProject(cluster, provider)
if err != nil {
return err
}
if provider != "" {
wl, err := cloudsupport.GetDescriptiveInfoFromCloudProvider(clusterName, provider, region, project)
if err != nil {
cluster := k8sinterface.GetCurrentContext().Cluster
provider := cloudsupport.GetCloudProvider(cluster)
// Return error with useful info on how to configure credentials for getting cloud provider info
switch provider {
case "gke":

View File

@@ -20,9 +20,6 @@ type IPrinter interface {
ActionPrint(opaSessionObj *cautils.OPASessionObj)
SetWriter(outputFile string)
Score(score float32)
// FinalizeData convert 'opaSessionObj' data to be ready for printing/reporting
FinalizeData(opaSessionObj *cautils.OPASessionObj)
}
func GetWriter(outputFile string) *os.File {

View File

@@ -26,6 +26,8 @@ func (jsonPrinter *JsonPrinter) Score(score float32) {
}
func (jsonPrinter *JsonPrinter) ActionPrint(opaSessionObj *cautils.OPASessionObj) {
cautils.ReportV2ToV1(opaSessionObj)
var postureReportStr []byte
var err error
@@ -41,6 +43,3 @@ func (jsonPrinter *JsonPrinter) ActionPrint(opaSessionObj *cautils.OPASessionObj
}
jsonPrinter.writer.Write(postureReportStr)
}
func (jsonPrinter *JsonPrinter) FinalizeData(opaSessionObj *cautils.OPASessionObj) {
reportV2ToV1(opaSessionObj)
}

View File

@@ -26,11 +26,9 @@ func (junitPrinter *JunitPrinter) Score(score float32) {
fmt.Fprintf(os.Stderr, "\nOverall risk-score (0- Excellent, 100- All failed): %d\n", int(score))
}
func (junitPrinter *JunitPrinter) FinalizeData(opaSessionObj *cautils.OPASessionObj) {
reportV2ToV1(opaSessionObj)
}
func (junitPrinter *JunitPrinter) ActionPrint(opaSessionObj *cautils.OPASessionObj) {
cautils.ReportV2ToV1(opaSessionObj)
junitResult, err := convertPostureReportToJunitResult(opaSessionObj.PostureReport)
if err != nil {
fmt.Println("Failed to convert posture report object!")

View File

@@ -31,6 +31,8 @@ func NewPrettyPrinter(verboseMode bool) *PrettyPrinter {
}
func (prettyPrinter *PrettyPrinter) ActionPrint(opaSessionObj *cautils.OPASessionObj) {
cautils.ReportV2ToV1(opaSessionObj)
// score := calculatePostureScore(opaSessionObj.PostureReport)
failedResources := []string{}
warningResources := []string{}
@@ -67,9 +69,6 @@ func (prettyPrinter *PrettyPrinter) SetWriter(outputFile string) {
prettyPrinter.writer = printer.GetWriter(outputFile)
}
func (prettyPrinter *PrettyPrinter) FinalizeData(opaSessionObj *cautils.OPASessionObj) {
reportV2ToV1(opaSessionObj)
}
func (prettyPrinter *PrettyPrinter) Score(score float32) {
}

View File

@@ -1,6 +1,9 @@
package v1
import "github.com/armosec/kubescape/resultshandling/printer"
import (
"github.com/armosec/kubescape/resultshandling/printer"
"github.com/armosec/kubescape/resultshandling/printer/v2/controlmapping"
)
var INDENT = " "
@@ -13,6 +16,6 @@ func GetPrinter(printFormat string, verboseMode bool) printer.IPrinter {
case printer.PrometheusFormat:
return NewPrometheusPrinter(verboseMode)
default:
return NewPrettyPrinter(verboseMode)
return controlmapping.NewPrettyPrinter(verboseMode)
}
}

View File

@@ -29,10 +29,6 @@ func (prometheusPrinter *PrometheusPrinter) Score(score float32) {
fmt.Printf("\n# Overall risk-score (0- Excellent, 100- All failed)\nkubescape_score %d\n", int(score))
}
func (prometheusPrinter *PrometheusPrinter) FinalizeData(opaSessionObj *cautils.OPASessionObj) {
reportV2ToV1(opaSessionObj)
}
func (printer *PrometheusPrinter) printResources(allResources map[string]workloadinterface.IMetadata, resourcesIDs *reporthandling.ResourcesIDs, frameworkName, controlName string) {
printer.printDetails(allResources, resourcesIDs.GetFailedResources(), frameworkName, controlName, "failed")
printer.printDetails(allResources, resourcesIDs.GetWarningResources(), frameworkName, controlName, "excluded")
@@ -90,6 +86,8 @@ func (printer *PrometheusPrinter) printReports(allResources map[string]workloadi
}
func (printer *PrometheusPrinter) ActionPrint(opaSessionObj *cautils.OPASessionObj) {
cautils.ReportV2ToV1(opaSessionObj)
err := printer.printReports(opaSessionObj.AllResources, opaSessionObj.PostureReport.FrameworkReports)
if err != nil {
fmt.Println(err)

View File

@@ -22,7 +22,7 @@ func groupByNamespaceOrKind(resources []WorkloadSummary, status func(workloadSum
case workloadinterface.TypeWorkloadObject:
ns := ""
if resources[i].resource.GetNamespace() != "" {
ns = "Namescape " + resources[i].resource.GetNamespace()
ns = "Namespace " + resources[i].resource.GetNamespace()
}
if r, ok := mapResources[ns]; ok {
r = append(r, resources[i])

View File

@@ -0,0 +1,244 @@
package controlmapping
import (
"fmt"
"os"
"sort"
"strings"
"github.com/armosec/k8s-interface/workloadinterface"
"github.com/armosec/kubescape/cautils"
"github.com/armosec/kubescape/resultshandling/printer"
"github.com/armosec/opa-utils/objectsenvelopes"
"github.com/armosec/opa-utils/reporthandling/apis"
"github.com/armosec/opa-utils/reporthandling/results/v1/reportsummary"
"github.com/enescakir/emoji"
"github.com/olekukonko/tablewriter"
)
type PrettyPrinter struct {
writer *os.File
verboseMode bool
sortedControlNames []string
}
func NewPrettyPrinter(verboseMode bool) *PrettyPrinter {
return &PrettyPrinter{
verboseMode: verboseMode,
}
}
func (prettyPrinter *PrettyPrinter) ActionPrint(opaSessionObj *cautils.OPASessionObj) {
prettyPrinter.sortedControlNames = getSortedControlsNames(opaSessionObj.Report.SummaryDetails.Controls) // ListControls().All())
prettyPrinter.printResults(&opaSessionObj.Report.SummaryDetails.Controls, opaSessionObj.AllResources)
prettyPrinter.printSummaryTable(&opaSessionObj.Report.SummaryDetails)
}
func (prettyPrinter *PrettyPrinter) SetWriter(outputFile string) {
prettyPrinter.writer = printer.GetWriter(outputFile)
}
func (prettyPrinter *PrettyPrinter) Score(score float32) {
}
func (prettyPrinter *PrettyPrinter) printResults(controls *reportsummary.ControlSummaries, allResources map[string]workloadinterface.IMetadata) {
for i := 0; i < len(prettyPrinter.sortedControlNames); i++ {
controlSummary := controls.GetControl(reportsummary.EControlCriteriaName, prettyPrinter.sortedControlNames[i]) // summaryDetails.Controls ListControls().All() Controls.GetControl(ca)
prettyPrinter.printTitle(controlSummary)
prettyPrinter.printResources(controlSummary, allResources)
if controlSummary.GetStatus().IsSkipped() {
prettyPrinter.printSummary(prettyPrinter.sortedControlNames[i], controlSummary)
}
}
}
func (prettyPrinter *PrettyPrinter) printSummary(controlName string, controlSummary reportsummary.IControlSummary) {
if controlSummary.GetStatus().IsSkipped() {
return
}
cautils.SimpleDisplay(prettyPrinter.writer, "Summary - ")
cautils.SuccessDisplay(prettyPrinter.writer, "Passed:%v ", controlSummary.NumberOfResources().Passed())
cautils.WarningDisplay(prettyPrinter.writer, "Excluded:%v ", controlSummary.NumberOfResources().Excluded())
cautils.FailureDisplay(prettyPrinter.writer, "Failed:%v ", controlSummary.NumberOfResources().Failed())
cautils.InfoDisplay(prettyPrinter.writer, "Total:%v\n", controlSummary.NumberOfResources().All())
if controlSummary.GetStatus().IsFailed() {
cautils.DescriptionDisplay(prettyPrinter.writer, "Remediation: %v\n", controlSummary.GetRemediation())
}
cautils.DescriptionDisplay(prettyPrinter.writer, "\n")
}
func (prettyPrinter *PrettyPrinter) printTitle(controlSummary reportsummary.IControlSummary) {
cautils.InfoDisplay(prettyPrinter.writer, "[control: %s - %s] ", controlSummary.GetName(), getControlURL(controlSummary.GetID()))
switch controlSummary.GetStatus().Status() {
case apis.StatusSkipped:
cautils.InfoDisplay(prettyPrinter.writer, "skipped %v\n", emoji.ConfusedFace)
case apis.StatusFailed:
cautils.FailureDisplay(prettyPrinter.writer, "failed %v\n", emoji.SadButRelievedFace)
case apis.StatusExcluded:
cautils.WarningDisplay(prettyPrinter.writer, "excluded %v\n", emoji.NeutralFace)
default:
cautils.SuccessDisplay(prettyPrinter.writer, "passed %v\n", emoji.ThumbsUp)
}
cautils.DescriptionDisplay(prettyPrinter.writer, "Description: %s\n", controlSummary.GetDescription())
}
func (prettyPrinter *PrettyPrinter) printResources(controlSummary reportsummary.IControlSummary, allResources map[string]workloadinterface.IMetadata) {
workloadsSummary := listResultSummary(controlSummary, allResources)
failedWorkloads := groupByNamespaceOrKind(workloadsSummary, workloadSummaryFailed)
excludedWorkloads := groupByNamespaceOrKind(workloadsSummary, workloadSummaryExclude)
var passedWorkloads map[string][]WorkloadSummary
if prettyPrinter.verboseMode {
passedWorkloads = groupByNamespaceOrKind(workloadsSummary, workloadSummaryPassed)
}
if len(failedWorkloads) > 0 {
cautils.FailureDisplay(prettyPrinter.writer, "Failed:\n")
prettyPrinter.printGroupedResources(failedWorkloads)
}
if len(excludedWorkloads) > 0 {
cautils.WarningDisplay(prettyPrinter.writer, "Excluded:\n")
prettyPrinter.printGroupedResources(excludedWorkloads)
}
if len(passedWorkloads) > 0 {
cautils.SuccessDisplay(prettyPrinter.writer, "Passed:\n")
prettyPrinter.printGroupedResources(passedWorkloads)
}
}
func (prettyPrinter *PrettyPrinter) printGroupedResources(workloads map[string][]WorkloadSummary) {
indent := " "
for title, rsc := range workloads {
prettyPrinter.printGroupedResource(indent, title, rsc)
}
}
func (prettyPrinter *PrettyPrinter) printGroupedResource(indent string, title string, rsc []WorkloadSummary) {
preIndent := indent
if title != "" {
cautils.SimpleDisplay(prettyPrinter.writer, "%s%s\n", indent, title)
indent += indent
}
resources := []string{}
for r := range rsc {
relatedObjectsStr := generateRelatedObjectsStr(rsc[r]) // TODO -
resources = append(resources, fmt.Sprintf("%s%s - %s %s", indent, rsc[r].resource.GetKind(), rsc[r].resource.GetName(), relatedObjectsStr))
}
sort.Strings(resources)
for i := range resources {
cautils.SimpleDisplay(prettyPrinter.writer, resources[i]+"\n")
}
indent = preIndent
}
func generateRelatedObjectsStr(workload WorkloadSummary) string {
relatedStr := ""
if workload.resource.GetObjectType() == workloadinterface.TypeWorkloadObject {
relatedObjects := objectsenvelopes.NewRegoResponseVectorObject(workload.resource.GetObject()).GetRelatedObjects()
for i, related := range relatedObjects {
if ns := related.GetNamespace(); i == 0 && ns != "" {
relatedStr += fmt.Sprintf("Namespace - %s, ", ns)
}
relatedStr += fmt.Sprintf("%s - %s, ", related.GetKind(), related.GetName())
}
}
if relatedStr != "" {
relatedStr = fmt.Sprintf(" [%s]", relatedStr[:len(relatedStr)-2])
}
return relatedStr
}
func generateRow(controlSummary reportsummary.IControlSummary) []string {
row := []string{controlSummary.GetName()}
row = append(row, fmt.Sprintf("%d", controlSummary.NumberOfResources().Failed()))
row = append(row, fmt.Sprintf("%d", controlSummary.NumberOfResources().Excluded()))
row = append(row, fmt.Sprintf("%d", controlSummary.NumberOfResources().All()))
if !controlSummary.GetStatus().IsSkipped() {
row = append(row, fmt.Sprintf("%d", int(controlSummary.GetScore()))+"%")
} else {
row = append(row, "skipped")
}
return row
}
func generateHeader() []string {
return []string{"Control Name", "Failed Resources", "Excluded Resources", "All Resources", "% risk-score"}
}
func generateFooter(summaryDetails *reportsummary.SummaryDetails) []string {
// Control name | # failed resources | all resources | % success
row := []string{}
row = append(row, "Resource Summary") //fmt.Sprintf(""%d", numControlers"))
row = append(row, fmt.Sprintf("%d", summaryDetails.NumberOfResources().Failed()))
row = append(row, fmt.Sprintf("%d", summaryDetails.NumberOfResources().Excluded()))
row = append(row, fmt.Sprintf("%d", summaryDetails.NumberOfResources().All()))
row = append(row, fmt.Sprintf("%.2f%s", summaryDetails.Score, "%"))
return row
}
func (prettyPrinter *PrettyPrinter) printSummaryTable(summaryDetails *reportsummary.SummaryDetails) {
// For control scan framework will be nil
prettyPrinter.printFramework(summaryDetails.ListFrameworks().All())
summaryTable := tablewriter.NewWriter(prettyPrinter.writer)
summaryTable.SetAutoWrapText(false)
summaryTable.SetHeader(generateHeader())
summaryTable.SetHeaderLine(true)
alignments := []int{tablewriter.ALIGN_LEFT, tablewriter.ALIGN_CENTER, tablewriter.ALIGN_CENTER, tablewriter.ALIGN_CENTER, tablewriter.ALIGN_CENTER}
summaryTable.SetColumnAlignment(alignments)
for i := 0; i < len(prettyPrinter.sortedControlNames); i++ {
summaryTable.Append(generateRow(summaryDetails.Controls.GetControl(reportsummary.EControlCriteriaName, prettyPrinter.sortedControlNames[i])))
}
summaryTable.SetFooter(generateFooter(summaryDetails))
// summaryTable.SetFooter(generateFooter())
summaryTable.Render()
}
func (prettyPrinter *PrettyPrinter) printFramework(frameworks []reportsummary.IPolicies) {
if len(frameworks) == 1 {
if frameworks[0].GetName() != "" {
cautils.InfoTextDisplay(prettyPrinter.writer, fmt.Sprintf("FRAMEWORK %s\n", frameworks[0].GetName()))
}
} else if len(frameworks) > 1 {
p := "FRAMEWORKS: "
i := 0
for ; i < len(frameworks)-1; i++ {
p += fmt.Sprintf("%s (risk: %.2f), ", frameworks[i].GetName(), frameworks[i].GetScore())
}
p += fmt.Sprintf("%s (risk: %.2f)\n", frameworks[i].GetName(), frameworks[i].GetScore())
cautils.InfoTextDisplay(prettyPrinter.writer, p)
}
}
func getSortedControlsNames(controls reportsummary.ControlSummaries) []string {
controlNames := make([]string, 0, len(controls))
for k := range controls {
c := controls[k]
controlNames = append(controlNames, c.GetName())
}
sort.Strings(controlNames)
return controlNames
}
// func getSortedControlsNames(controls []reportsummary.IPolicies) []string {
// controlNames := make([]string, 0, len(controls))
// for k := range controls {
// controlNames = append(controlNames, controls[k].Get())
// }
// sort.Strings(controlNames)
// return controlNames
// }
func getControlURL(controlID string) string {
return fmt.Sprintf("https://hub.armo.cloud/docs/%s", strings.ToLower(controlID))
}

View File

@@ -0,0 +1,101 @@
package controlmapping
import (
"github.com/armosec/k8s-interface/k8sinterface"
"github.com/armosec/k8s-interface/workloadinterface"
"github.com/armosec/opa-utils/objectsenvelopes"
"github.com/armosec/opa-utils/reporthandling/apis"
"github.com/armosec/opa-utils/reporthandling/results/v1/reportsummary"
)
type WorkloadSummary struct {
resource workloadinterface.IMetadata
status apis.ScanningStatus
}
func workloadSummaryFailed(workloadSummary *WorkloadSummary) bool {
return workloadSummary.status == apis.StatusFailed
}
func workloadSummaryExclude(workloadSummary *WorkloadSummary) bool {
return workloadSummary.status == apis.StatusExcluded
}
func workloadSummaryPassed(workloadSummary *WorkloadSummary) bool {
return workloadSummary.status == apis.StatusPassed
}
// Group workloads by namespace - return {"namespace": <[]WorkloadSummary>}
func groupByNamespaceOrKind(resources []WorkloadSummary, status func(workloadSummary *WorkloadSummary) bool) map[string][]WorkloadSummary {
mapResources := make(map[string][]WorkloadSummary)
for i := range resources {
if !status(&resources[i]) {
continue
}
t := resources[i].resource.GetObjectType()
if t == objectsenvelopes.TypeRegoResponseVectorObject && !isKindToBeGrouped(resources[i].resource.GetKind()) {
t = workloadinterface.TypeWorkloadObject
}
switch t { // TODO - find a better way to defind the groups
case workloadinterface.TypeWorkloadObject:
ns := ""
if resources[i].resource.GetNamespace() != "" {
ns = "Namespace " + resources[i].resource.GetNamespace()
}
if r, ok := mapResources[ns]; ok {
r = append(r, resources[i])
mapResources[ns] = r
} else {
mapResources[ns] = []WorkloadSummary{resources[i]}
}
case objectsenvelopes.TypeRegoResponseVectorObject:
group := resources[i].resource.GetKind() + "s"
if r, ok := mapResources[group]; ok {
r = append(r, resources[i])
mapResources[group] = r
} else {
mapResources[group] = []WorkloadSummary{resources[i]}
}
default:
group, _ := k8sinterface.SplitApiVersion(resources[i].resource.GetApiVersion())
if r, ok := mapResources[group]; ok {
r = append(r, resources[i])
mapResources[group] = r
} else {
mapResources[group] = []WorkloadSummary{resources[i]}
}
}
}
return mapResources
}
func isKindToBeGrouped(kind string) bool {
if kind == "Group" || kind == "User" {
return true
}
return false
}
func listResultSummary(controlSummary reportsummary.IControlSummary, allResources map[string]workloadinterface.IMetadata) []WorkloadSummary {
workloadsSummary := []WorkloadSummary{}
workloadsSummary = append(workloadsSummary, newListWorkloadsSummary(allResources, controlSummary.ListResourcesIDs().Failed(), apis.StatusFailed)...)
workloadsSummary = append(workloadsSummary, newListWorkloadsSummary(allResources, controlSummary.ListResourcesIDs().Excluded(), apis.StatusExcluded)...)
workloadsSummary = append(workloadsSummary, newListWorkloadsSummary(allResources, controlSummary.ListResourcesIDs().Passed(), apis.StatusPassed)...)
return workloadsSummary
}
func newListWorkloadsSummary(allResources map[string]workloadinterface.IMetadata, resourcesIDs []string, status apis.ScanningStatus) []WorkloadSummary {
workloadsSummary := []WorkloadSummary{}
for _, i := range resourcesIDs {
if r, ok := allResources[i]; ok {
workloadsSummary = append(workloadsSummary, WorkloadSummary{
resource: r,
status: status,
})
}
}
return workloadsSummary
}

View File

@@ -1,18 +1,21 @@
package v2
import "github.com/armosec/kubescape/resultshandling/printer"
import (
"github.com/armosec/kubescape/resultshandling/printer"
"github.com/armosec/kubescape/resultshandling/printer/v2/resourcemapping"
)
var INDENT = " "
func GetPrinter(printFormat string, verboseMode bool) printer.IPrinter {
switch printFormat {
case printer.JsonFormat:
return NewJsonPrinter()
return resourcemapping.NewJsonPrinter()
case printer.JunitResultFormat:
return NewJunitPrinter()
// case printer.PrometheusFormat:
// return NewPrometheusPrinter(verboseMode)
default:
return NewPrettyPrinter(verboseMode)
return resourcemapping.NewPrettyPrinter(verboseMode)
}
}

View File

@@ -1,4 +1,4 @@
package v2
package resourcemapping
import (
"encoding/json"
@@ -37,5 +37,5 @@ func (jsonPrinter *JsonPrinter) ActionPrint(opaSessionObj *cautils.OPASessionObj
}
func (jsonPrinter *JsonPrinter) FinalizeData(opaSessionObj *cautils.OPASessionObj) {
finalizeReport(opaSessionObj)
// finalizeReport(opaSessionObj)
}

View File

@@ -1,4 +1,4 @@
package v2
package resourcemapping
import (
"fmt"
@@ -89,7 +89,7 @@ func (prettyPrinter *PrettyPrinter) SetWriter(outputFile string) {
}
func (prettyPrinter *PrettyPrinter) FinalizeData(opaSessionObj *cautils.OPASessionObj) {
finalizeReport(opaSessionObj)
// finalizeReport(opaSessionObj)
}
func (prettyPrinter *PrettyPrinter) Score(score float32) {
}

View File

@@ -3,8 +3,8 @@ package v2
import (
"github.com/armosec/k8s-interface/workloadinterface"
"github.com/armosec/kubescape/cautils"
"github.com/armosec/opa-utils/reporthandling"
"github.com/armosec/opa-utils/reporthandling/results/v1/resourcesresults"
reporthandlingv2 "github.com/armosec/opa-utils/reporthandling/v2"
)
// finalizeV2Report finalize the results objects by copying data from map to lists
@@ -16,7 +16,7 @@ func finalizeReport(opaSessionObj *cautils.OPASessionObj) {
}
if len(opaSessionObj.Report.Resources) == 0 {
opaSessionObj.Report.Resources = make([]reporthandlingv2.Resource, len(opaSessionObj.AllResources))
opaSessionObj.Report.Resources = make([]reporthandling.Resource, len(opaSessionObj.AllResources))
finalizeResources(opaSessionObj.Report.Resources, opaSessionObj.AllResources)
opaSessionObj.AllResources = nil
}
@@ -30,13 +30,15 @@ func finalizeResults(results []resourcesresults.Result, resourcesResult map[stri
}
}
func finalizeResources(resources []reporthandlingv2.Resource, allResources map[string]workloadinterface.IMetadata) {
func finalizeResources(resources []reporthandling.Resource, allResources map[string]workloadinterface.IMetadata) {
index := 0
for resourceID := range allResources {
resources[index] = reporthandlingv2.Resource{
ResourceID: resourceID,
Object: allResources[resourceID],
if obj, ok := allResources[resourceID]; ok {
r := *reporthandling.NewResource(obj.GetObject())
r.ResourceID = resourceID
resources[index] = r
}
index++
}
}

View File

@@ -6,6 +6,7 @@ import (
"net/http"
"net/url"
"os"
"strings"
"github.com/armosec/k8s-interface/workloadinterface"
"github.com/armosec/kubescape/cautils"
@@ -23,6 +24,7 @@ type ReportEventReceiver struct {
eventReceiverURL *url.URL
token string
customerAdminEMail string
message string
}
func NewReportEventReceiver(tenantConfig *cautils.ConfigObj) *ReportEventReceiver {
@@ -36,16 +38,26 @@ func NewReportEventReceiver(tenantConfig *cautils.ConfigObj) *ReportEventReceive
}
func (report *ReportEventReceiver) ActionSendReport(opaSessionObj *cautils.OPASessionObj) error {
if report.customerGUID == "" || report.clusterName == "" {
return fmt.Errorf("missing account ID or cluster name. AccountID: '%s', Cluster name: '%s'", report.customerGUID, report.clusterName)
if opaSessionObj.PostureReport == nil && opaSessionObj.Report != nil {
cautils.ReportV2ToV1(opaSessionObj)
}
if report.customerGUID == "" {
report.message = "WARNING: Failed to publish results. Reason: Unknown accout ID. Run kubescape with the '--account <account ID>' flag. Contact ARMO team for more details"
return nil
}
if report.clusterName == "" {
report.message = "WARNING: Failed to publish results because the cluster name is Unknown. If you are scanning YAML files the results are not submitted to the Kubescape SaaS"
return nil
}
opaSessionObj.PostureReport.ReportID = uuid.NewV4().String()
opaSessionObj.PostureReport.CustomerGUID = report.clusterName
opaSessionObj.PostureReport.ClusterName = report.customerGUID
opaSessionObj.PostureReport.CustomerGUID = report.customerGUID
opaSessionObj.PostureReport.ClusterName = report.clusterName
if err := report.prepareReport(opaSessionObj.PostureReport, opaSessionObj.AllResources); err != nil {
return err
report.message = err.Error()
return nil
}
return nil
}
@@ -74,6 +86,8 @@ func (report *ReportEventReceiver) prepareReport(postureReport *reporthandling.P
if err := report.sendResources(host, postureReport, allResources); err != nil {
return err
}
report.generateMessage()
return nil
}
@@ -112,14 +126,16 @@ func (report *ReportEventReceiver) sendReport(host string, postureReport *report
if err != nil {
return fmt.Errorf("in 'sendReport' failed to json.Marshal, reason: %v", err)
}
// fmt.Printf("\n\n%s\n\n", reqBody)
msg, err := getter.HttpPost(report.httpClient, host, nil, reqBody)
if err != nil {
return fmt.Errorf("%s, %v:%s", host, err, msg)
}
return err
return nil
}
func (report *ReportEventReceiver) DisplayReportURL() {
func (report *ReportEventReceiver) generateMessage() {
message := "You can see the results in a user-friendly UI, choose your preferred compliance framework, check risk results history and trends, manage exceptions, get remediation recommendations and much more by registering here:"
u := url.URL{}
@@ -127,7 +143,7 @@ func (report *ReportEventReceiver) DisplayReportURL() {
u.Host = getter.GetArmoAPIConnector().GetFrontendURL()
if report.customerAdminEMail != "" {
cautils.InfoTextDisplay(os.Stderr, fmt.Sprintf("\n\n%s %s/risk/%s\n(Account: %s)\n\n", message, u.String(), report.clusterName, report.customerGUID))
report.message = fmt.Sprintf("%s %s/risk/%s\n(Account: %s)", message, u.String(), report.clusterName, maskID(report.customerGUID))
return
}
u.Path = "account/sign-up"
@@ -136,5 +152,27 @@ func (report *ReportEventReceiver) DisplayReportURL() {
q.Add("customerGUID", report.customerGUID)
u.RawQuery = q.Encode()
cautils.InfoTextDisplay(os.Stderr, fmt.Sprintf("\n\n%s %s\n\n", message, u.String()))
report.message = fmt.Sprintf("%s %s", message, u.String())
}
func (report *ReportEventReceiver) DisplayReportURL() {
cautils.InfoTextDisplay(os.Stderr, fmt.Sprintf("\n\n%s\n\n", report.message))
}
func maskID(id string) string {
sep := "-"
splitted := strings.Split(id, sep)
if len(splitted) != 5 {
return ""
}
str := splitted[0][:4]
splitted[0] = splitted[0][4:]
for i := range splitted {
for j := 0; j < len(splitted[i]); j++ {
str += "X"
}
str += sep
}
return strings.TrimSuffix(str, sep)
}

View File

@@ -11,6 +11,7 @@ import (
"github.com/armosec/kubescape/cautils/getter"
uuid "github.com/satori/go.uuid"
"github.com/armosec/opa-utils/reporthandling"
"github.com/armosec/opa-utils/reporthandling/results/v1/resourcesresults"
reporthandlingv2 "github.com/armosec/opa-utils/reporthandling/v2"
)
@@ -24,6 +25,7 @@ type ReportEventReceiver struct {
eventReceiverURL *url.URL
token string
customerAdminEMail string
message string
}
func NewReportEventReceiver(tenantConfig *cautils.ConfigObj) *ReportEventReceiver {
@@ -37,16 +39,24 @@ func NewReportEventReceiver(tenantConfig *cautils.ConfigObj) *ReportEventReceive
}
func (report *ReportEventReceiver) ActionSendReport(opaSessionObj *cautils.OPASessionObj) error {
finalizeReport(opaSessionObj)
if report.customerGUID == "" || report.clusterName == "" {
return fmt.Errorf("missing accout ID or cluster name. AccountID: '%s', Cluster name: '%s'", report.customerGUID, report.clusterName)
if report.customerGUID == "" {
report.message = "WARNING: Failed to publish results. Reason: Unknown accout ID. Run kubescape with the '--account <account ID>' flag. Contact ARMO team for more details"
return nil
}
if report.clusterName == "" {
report.message = "WARNING: Failed to publish results because the cluster name is Unknown. If you are scanning YAML files the results are not submitted to the Kubescape SaaS"
return nil
}
opaSessionObj.Report.ReportID = uuid.NewV4().String()
opaSessionObj.Report.CustomerGUID = report.clusterName
opaSessionObj.Report.ClusterName = report.customerGUID
opaSessionObj.Report.CustomerGUID = report.customerGUID
opaSessionObj.Report.ClusterName = report.clusterName
if err := report.prepareReport(opaSessionObj.Report); err != nil {
return err
report.message = err.Error()
} else {
report.generateMessage()
}
return nil
}
@@ -66,24 +76,23 @@ func (report *ReportEventReceiver) prepareReport(postureReport *reporthandlingv2
cautils.StartSpinner()
defer cautils.StopSpinner()
// send framework results
if err := report.sendReport(host, postureReport); err != nil {
return err
}
reportCounter := 0
// send resources
if err := report.sendResults(host, postureReport); err != nil {
if err := report.sendResources(host, postureReport, &reportCounter, false); err != nil {
return err
}
// reportCounter++
// // send results
// if err := report.sendResults(host, postureReport, &reportCounter, true); err != nil {
// return err
// }
// send resources
if err := report.sendResources(host, postureReport); err != nil {
return err
}
return nil
}
func (report *ReportEventReceiver) sendResources(host string, postureReport *reporthandlingv2.PostureReport) error {
func (report *ReportEventReceiver) sendResources(host string, postureReport *reporthandlingv2.PostureReport, reportCounter *int, isLastReport bool) error {
splittedPostureReport := setSubReport(postureReport)
counter := 0
@@ -96,12 +105,14 @@ func (report *ReportEventReceiver) sendResources(host string, postureReport *rep
if counter+len(r) >= MAX_REPORT_SIZE && len(splittedPostureReport.Resources) > 0 {
// send report
if err := report.sendReport(host, splittedPostureReport); err != nil {
if err := report.sendReport(host, splittedPostureReport, *reportCounter, false); err != nil {
return err
}
*reportCounter++
// delete resources
splittedPostureReport.Resources = []reporthandlingv2.Resource{}
splittedPostureReport.Resources = []reporthandling.Resource{}
splittedPostureReport.Results = []resourcesresults.Result{}
// restart counter
counter = 0
@@ -111,28 +122,23 @@ func (report *ReportEventReceiver) sendResources(host string, postureReport *rep
splittedPostureReport.Resources = append(splittedPostureReport.Resources, v)
}
return report.sendReport(host, splittedPostureReport)
}
func (report *ReportEventReceiver) sendResults(host string, postureReport *reporthandlingv2.PostureReport) error {
splittedPostureReport := setSubReport(postureReport)
counter := 0
for _, v := range postureReport.Results {
r, err := json.Marshal(v)
if err != nil {
return fmt.Errorf("failed to unmarshal resource '%s', reason: %v", v.GetResourceID(), err)
}
if counter+len(r) >= MAX_REPORT_SIZE && len(splittedPostureReport.Resources) > 0 {
if counter+len(r) >= MAX_REPORT_SIZE && len(splittedPostureReport.Results) > 0 {
// send report
if err := report.sendReport(host, splittedPostureReport); err != nil {
if err := report.sendReport(host, splittedPostureReport, *reportCounter, false); err != nil {
return err
}
*reportCounter++
// delete results
splittedPostureReport.Results = []resourcesresults.Result{}
splittedPostureReport.Resources = []reporthandling.Resource{}
// restart counter
counter = 0
@@ -142,12 +148,46 @@ func (report *ReportEventReceiver) sendResults(host string, postureReport *repor
splittedPostureReport.Results = append(splittedPostureReport.Results, v)
}
return report.sendReport(host, splittedPostureReport)
return report.sendReport(host, splittedPostureReport, *reportCounter, true)
}
func (report *ReportEventReceiver) sendReport(host string, postureReport *reporthandlingv2.PostureReport) error {
splittedPostureReport := setSubReport(postureReport)
splittedPostureReport.SummaryDetails = postureReport.SummaryDetails
// func (report *ReportEventReceiver) sendResults(host string, postureReport *reporthandlingv2.PostureReport, reportCounter *int, isLastReport bool) error {
// splittedPostureReport := setSubReport(postureReport)
// counter := 0
// for _, v := range postureReport.Results {
// r, err := json.Marshal(v)
// if err != nil {
// return fmt.Errorf("failed to unmarshal resource '%s', reason: %v", v.GetResourceID(), err)
// }
// if counter+len(r) >= MAX_REPORT_SIZE && len(splittedPostureReport.Resources) > 0 {
// // send report
// if err := report.sendReport(host, splittedPostureReport, *reportCounter, false); err != nil {
// return err
// }
// *reportCounter++
// // delete results
// splittedPostureReport.Results = []resourcesresults.Result{}
// // restart counter
// counter = 0
// }
// counter += len(r)
// splittedPostureReport.Results = append(splittedPostureReport.Results, v)
// }
// return report.sendReport(host, splittedPostureReport, *reportCounter, isLastReport)
// }
func (report *ReportEventReceiver) sendReport(host string, postureReport *reporthandlingv2.PostureReport, counter int, isLastReport bool) error {
postureReport.PaginationInfo = reporthandlingv2.PaginationMarks{
ReportNumber: counter,
IsLastReport: isLastReport,
}
reqBody, err := json.Marshal(postureReport)
if err != nil {
return fmt.Errorf("in 'sendReport' failed to json.Marshal, reason: %v", err)
@@ -159,7 +199,7 @@ func (report *ReportEventReceiver) sendReport(host string, postureReport *report
return err
}
func (report *ReportEventReceiver) DisplayReportURL() {
func (report *ReportEventReceiver) generateMessage() {
message := "You can see the results in a user-friendly UI, choose your preferred compliance framework, check risk results history and trends, manage exceptions, get remediation recommendations and much more by registering here:"
u := url.URL{}
@@ -167,7 +207,7 @@ func (report *ReportEventReceiver) DisplayReportURL() {
u.Host = getter.GetArmoAPIConnector().GetFrontendURL()
if report.customerAdminEMail != "" {
cautils.InfoTextDisplay(os.Stderr, fmt.Sprintf("\n\n%s %s/risk/%s\n(Account: %s)\n\n", message, u.String(), report.clusterName, report.customerGUID))
report.message = fmt.Sprintf("%s %s/risk/%s\n(Account: %s)", message, u.String(), report.clusterName, maskID(report.customerGUID))
return
}
u.Path = "account/sign-up"
@@ -176,5 +216,9 @@ func (report *ReportEventReceiver) DisplayReportURL() {
q.Add("customerGUID", report.customerGUID)
u.RawQuery = q.Encode()
cautils.InfoTextDisplay(os.Stderr, fmt.Sprintf("\n\n%s %s\n\n", message, u.String()))
report.message = fmt.Sprintf("%s %s", message, u.String())
}
func (report *ReportEventReceiver) DisplayReportURL() {
cautils.InfoTextDisplay(os.Stderr, fmt.Sprintf("\n\n%s\n\n", report.message))
}

View File

@@ -15,7 +15,8 @@ func (report *ReportEventReceiver) initEventReceiverURL() {
urlObj.Scheme = "https"
urlObj.Host = getter.GetArmoAPIConnector().GetReportReceiverURL()
urlObj.Path = "/k8s/postureReport"
urlObj.Path = "/k8s/v2/postureReport"
q := urlObj.Query()
q.Add("customerGUID", uuid.FromStringOrNil(report.customerGUID).String())
q.Add("clusterName", report.clusterName)
@@ -27,7 +28,7 @@ func (report *ReportEventReceiver) initEventReceiverURL() {
func hostToString(host *url.URL, reportID string) string {
q := host.Query()
q.Add("reportID", reportID) // TODO - do we add the reportID?
q.Add("reportGUID", reportID) // TODO - do we add the reportID?
host.RawQuery = q.Encode()
return host.String()
}
@@ -38,6 +39,11 @@ func setSubReport(postureReport *reporthandlingv2.PostureReport) *reporthandling
ClusterName: postureReport.ClusterName,
ReportID: postureReport.ReportID,
ReportGenerationTime: postureReport.ReportGenerationTime,
SummaryDetails: postureReport.SummaryDetails,
Attributes: postureReport.Attributes,
ClusterCloudProvider: postureReport.ClusterCloudProvider,
JobID: postureReport.JobID,
ClusterAPIServerInfo: postureReport.ClusterAPIServerInfo,
}
}
func iMetaToResource(obj workloadinterface.IMetadata) *reporthandling.Resource {

View File

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

View File

@@ -0,0 +1,65 @@
package v2
import (
"strings"
"github.com/armosec/k8s-interface/workloadinterface"
"github.com/armosec/kubescape/cautils"
"github.com/armosec/opa-utils/reporthandling"
"github.com/armosec/opa-utils/reporthandling/results/v1/resourcesresults"
)
// finalizeV2Report finalize the results objects by copying data from map to lists
func finalizeReport(opaSessionObj *cautils.OPASessionObj) {
opaSessionObj.PostureReport = nil
if len(opaSessionObj.Report.Results) == 0 {
opaSessionObj.Report.Results = make([]resourcesresults.Result, len(opaSessionObj.ResourcesResult))
finalizeResults(opaSessionObj.Report.Results, opaSessionObj.ResourcesResult)
opaSessionObj.ResourcesResult = nil
}
if len(opaSessionObj.Report.Resources) == 0 {
opaSessionObj.Report.Resources = make([]reporthandling.Resource, len(opaSessionObj.AllResources))
finalizeResources(opaSessionObj.Report.Resources, opaSessionObj.AllResources)
opaSessionObj.AllResources = nil
}
}
func finalizeResults(results []resourcesresults.Result, resourcesResult map[string]resourcesresults.Result) {
index := 0
for resourceID := range resourcesResult {
results[index] = resourcesResult[resourceID]
index++
}
}
func finalizeResources(resources []reporthandling.Resource, allResources map[string]workloadinterface.IMetadata) {
index := 0
for resourceID := range allResources {
if obj, ok := allResources[resourceID]; ok {
r := *reporthandling.NewResource(obj.GetObject())
r.ResourceID = resourceID
resources[index] = r
}
index++
}
}
func maskID(id string) string {
sep := "-"
splitted := strings.Split(id, sep)
if len(splitted) != 5 {
return ""
}
str := splitted[0][:4]
splitted[0] = splitted[0][4:]
for i := range splitted {
for j := 0; j < len(splitted[i]); j++ {
str += "X"
}
str += sep
}
return strings.TrimSuffix(str, sep)
}

View File

@@ -27,22 +27,13 @@ func (resultsHandler *ResultsHandler) HandleResults(scanInfo *cautils.ScanInfo)
opaSessionObj := <-*resultsHandler.opaSessionObj
resultsHandler.printerObj.FinalizeData(opaSessionObj)
resultsHandler.printerObj.ActionPrint(opaSessionObj)
if err := resultsHandler.reporterObj.ActionSendReport(opaSessionObj); err != nil {
fmt.Println(err)
}
// TODO - get score from table
var score float32 = 0
for i := range opaSessionObj.PostureReport.FrameworkReports {
score += opaSessionObj.PostureReport.FrameworkReports[i].Score
}
score /= float32(len(opaSessionObj.PostureReport.FrameworkReports))
resultsHandler.printerObj.Score(score)
return score
return opaSessionObj.Report.SummaryDetails.Score
}
// CalculatePostureScore calculate final score

43
score/score.go Normal file
View File

@@ -0,0 +1,43 @@
package score
import (
"fmt"
"github.com/armosec/opa-utils/score"
"github.com/armosec/kubescape/cautils"
)
/* provides a wrapper for scoreUtils, since there's no common interface between postureReportV1 and PostureReportV2
and the need of concrete objects
I've decided to create scoreWrapper that will allow calculating score regardless (as long as opaSessionObj is there)
*/
type ScoreWrapper struct {
scoreUtil *score.ScoreUtil
opaSessionObj *cautils.OPASessionObj
}
type PostureReportVersion string
const (
EPostureReportV1 PostureReportVersion = "v1"
EPostureReportV2 PostureReportVersion = "V2"
)
func (su *ScoreWrapper) Calculate(reportVersion PostureReportVersion) error {
switch reportVersion {
case EPostureReportV1:
return su.scoreUtil.Calculate(su.opaSessionObj.PostureReport.FrameworkReports)
case EPostureReportV2:
return su.scoreUtil.CalculatePostureReportV2(su.opaSessionObj.Report)
}
return fmt.Errorf("unsupported score calculator")
}
func NewScoreWrapper(opaSessionObj *cautils.OPASessionObj) *ScoreWrapper {
return &ScoreWrapper{
scoreUtil: score.NewScore(opaSessionObj.AllResources),
opaSessionObj: opaSessionObj,
}
}

1
score/score_test.go Normal file
View File

@@ -0,0 +1 @@
package score