Merge branch 'master' into vuln-support

This commit is contained in:
dwertent
2022-01-30 14:03:46 +02:00
39 changed files with 826 additions and 123 deletions
+5 -5
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 }}
+3 -3
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 }}
+2 -2
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
+2 -1
View File
@@ -3,4 +3,5 @@
*debug*
*vender*
*.pyc*
.idea
.idea
ca.srl
+35 -10
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,13 +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 | |
| `--cluster` | current-context | Cluster context to scan | |
| `--kube-context` | current-context | Cluster context to scan | |
| `--verbose` | `false` | Display all of the input resources and not only failed resources | `true`/`false` |
@@ -190,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
```
@@ -201,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
@@ -264,7 +283,7 @@ go build -o kubescape .
3. Run
```
./kubescape scan framework nsa
./kubescape scan --submit --enable-host-scan
```
4. Enjoy :zany_face:
@@ -320,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>
+14 -3
View File
@@ -85,8 +85,19 @@ func (lp *LoadPolicy) GetFrameworks() ([]reporthandling.Framework, error) {
}
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) {
@@ -114,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
+15 -4
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
+52 -3
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,7 +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
ClusterName string // cluster name
KubeContext string // context name
FrameworkScan bool // false if scanning control
ScanAll bool // true if scan all frameworks
}
@@ -78,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() {
+2 -2
View File
@@ -96,7 +96,7 @@ func (v *VersionCheckHandler) CheckLatestVersion(versionData *VersionCheckReques
if latestVersion.ClientUpdate != "" {
if BuildNumber != "" && BuildNumber < latestVersion.ClientUpdate {
WarningDisplay(os.Stderr, warningMessage(latestVersion.Client, latestVersion.ClientUpdate), "\n")
WarningDisplay(os.Stderr, warningMessage(latestVersion.Client, latestVersion.ClientUpdate)+"\n")
}
}
@@ -106,7 +106,7 @@ func (v *VersionCheckHandler) CheckLatestVersion(versionData *VersionCheckReques
// }
if latestVersion.Message != "" {
InfoDisplay(os.Stderr, latestVersion.Message, "\n")
InfoDisplay(os.Stderr, latestVersion.Message+"\n")
}
return nil
+1 -1
View File
@@ -51,7 +51,7 @@ func setPathandFilename(downloadInfo *cautils.DownloadInfo) {
dir, file := filepath.Split(downloadInfo.Path)
if dir == "" {
downloadInfo.Path = file
} else {
} else if strings.Contains(file, ".json") {
downloadInfo.Path = dir
downloadInfo.FileName = file
}
+3 -2
View File
@@ -35,16 +35,17 @@ var scanCmd = &cobra.Command{
}
func frameworkInitConfig() {
k8sinterface.SetClusterContextName(scanInfo.ClusterName)
k8sinterface.SetClusterContextName(scanInfo.KubeContext)
}
func init() {
cobra.OnInitialize(frameworkInitConfig)
rootCmd.AddCommand(scanCmd)
rootCmd.PersistentFlags().StringVarP(&scanInfo.ClusterName, "cluster", "", "", "Cluster name. Default will use the current-context")
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"`)
+1 -1
View File
@@ -20,7 +20,7 @@ func init() {
}
func getSubmittedClusterConfig(k8s *k8sinterface.KubernetesApi) (*cautils.ClusterConfig, error) {
clusterConfig := cautils.NewClusterConfig(k8s, getter.GetArmoAPIConnector(), scanInfo.Account, scanInfo.ClusterName) // 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
+1 -1
View File
@@ -44,7 +44,7 @@ func getInterfaces(scanInfo *cautils.ScanInfo) componentInterfaces {
}
}
tenantConfig := getTenantConfig(scanInfo.Account, scanInfo.ClusterName, k8s)
tenantConfig := getTenantConfig(scanInfo.Account, scanInfo.KubeContext, k8s)
// Set submit behavior AFTER loading tenant config
setSubmitBehavior(scanInfo, tenantConfig)
+4 -2
View File
@@ -11,9 +11,10 @@ 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
@@ -48,7 +49,8 @@ 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()
}
@@ -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
}
}
]
}
```
+68
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
+49
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
+5 -4
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.54
github.com/armosec/opa-utils v0.0.92
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/armosec/utils-k8s-go v0.0.1
github.com/briandowns/spinner v1.18.0
@@ -37,6 +37,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/aws/aws-sdk-go v1.41.11 // indirect
github.com/coreos/go-oidc v2.2.1+incompatible // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
+10 -8
View File
@@ -83,21 +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/go.mod h1:vHxGWqD/uh6+GQb9Sqv7OGMs+Rvc2dsFVc0XtgRh1ZU=
github.com/armosec/k8s-interface v0.0.54 h1:1sQeoEZA5bgpXVibXhEiTSeLd3GKY5NkTOeewdgR0Bs=
github.com/armosec/k8s-interface v0.0.54/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.92 h1:RzzORhfLx9Evc2ceFtNRoehxUFzwlvK5iMtR6fLWzZc=
github.com/armosec/opa-utils v0.0.92/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=
@@ -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
+6
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
+4
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
}
+110
View File
@@ -0,0 +1,110 @@
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":
if isEnvVars() {
return NewGKEProviderEnvVar()
}
return NewGKEProviderContext()
case "eks", "aws":
if isEnvVars() {
return NewEKSProviderEnvVar()
}
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
}
+95
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
}
+133
View File
@@ -0,0 +1,133 @@
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("failed to parse project name from cluster name: '%s'", cluster)
}
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("failed to parse region name from cluster name: '%s'", cluster)
}
region = parsedName[2]
return region, nil
}
// ------------------------------ GKEProviderContext --------------------------------------------------------
type GKEProviderContext struct {
}
func NewGKEProviderContext() *GKEProviderContext {
return &GKEProviderContext{}
}
func (gkeProviderContext *GKEProviderContext) getKubeClusterName() string {
context := k8sinterface.GetCurrentContext()
if context == nil {
return ""
}
cluster := context.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 {
context := k8sinterface.GetCurrentContext()
if context == nil {
return ""
}
cluster := context.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("failed to parse project name from cluster name: '%s'", cluster)
}
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("failed to parse region name from cluster name: '%s'", cluster)
}
region := parsedName[2]
return region, nil
}
+14 -4
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":
+1 -1
View File
@@ -107,7 +107,7 @@ func convertPostureReportToJunitResult(postureResult *reporthandling.PostureRepo
testCase.Name = controlReports.Name
testCase.Classname = "Kubescape"
testCase.Time = postureResult.ReportGenerationTime.String()
if 0 < len(controlReports.RuleReports[0].RuleResponses) {
if len(controlReports.RuleReports) > 0 {
testCase.Resources = controlReports.GetNumberOfResources()
testCase.Excluded = controlReports.GetNumberOfWarningResources()
+1 -1
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])
@@ -49,10 +49,7 @@ func (prettyPrinter *PrettyPrinter) printResults(controls *reportsummary.Control
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)
}
prettyPrinter.printSummary(prettyPrinter.sortedControlNames[i], controlSummary)
}
}
@@ -40,7 +40,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])
+8 -6
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++
}
}
@@ -38,14 +38,16 @@ func NewReportEventReceiver(tenantConfig *cautils.ConfigObj) *ReportEventReceive
}
func (report *ReportEventReceiver) ActionSendReport(opaSessionObj *cautils.OPASessionObj) error {
cautils.ReportV2ToV1(opaSessionObj)
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. Reason: Unknown cluster name. Run kubescape with the '--cluster <cluster name>' flag"
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
}
@@ -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"
)
@@ -45,7 +46,7 @@ func (report *ReportEventReceiver) ActionSendReport(opaSessionObj *cautils.OPASe
return nil
}
if report.clusterName == "" {
report.message = "WARNING: Failed to publish results. Reason: Unknown cluster name. Run kubescape with the '--cluster <cluster name>' flag"
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()
@@ -77,27 +78,21 @@ func (report *ReportEventReceiver) prepareReport(postureReport *reporthandlingv2
reportCounter := 0
// send results
if err := report.sendResults(host, postureReport, &reportCounter); err != nil {
return err
}
reportCounter++
// send resources
if err := report.sendResources(host, postureReport, &reportCounter); err != nil {
if err := report.sendResources(host, postureReport, &reportCounter, false); err != nil {
return err
}
reportCounter++
// reportCounter++
// send framework results
if err := report.sendSummary(host, postureReport, &reportCounter); err != nil {
return err
}
// // send results
// if err := report.sendResults(host, postureReport, &reportCounter, true); err != nil {
// return err
// }
return nil
}
func (report *ReportEventReceiver) sendResources(host string, postureReport *reporthandlingv2.PostureReport, reportCounter *int) error {
func (report *ReportEventReceiver) sendResources(host string, postureReport *reporthandlingv2.PostureReport, reportCounter *int, isLastReport bool) error {
splittedPostureReport := setSubReport(postureReport)
counter := 0
@@ -116,7 +111,8 @@ func (report *ReportEventReceiver) sendResources(host string, postureReport *rep
*reportCounter++
// delete resources
splittedPostureReport.Resources = []reporthandlingv2.Resource{}
splittedPostureReport.Resources = []reporthandling.Resource{}
splittedPostureReport.Results = []resourcesresults.Result{}
// restart counter
counter = 0
@@ -126,20 +122,13 @@ func (report *ReportEventReceiver) sendResources(host string, postureReport *rep
splittedPostureReport.Resources = append(splittedPostureReport.Resources, v)
}
return report.sendReport(host, splittedPostureReport, *reportCounter, false)
}
func (report *ReportEventReceiver) sendResults(host string, postureReport *reporthandlingv2.PostureReport, reportCounter *int) 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, *reportCounter, false); err != nil {
@@ -149,6 +138,7 @@ func (report *ReportEventReceiver) sendResults(host string, postureReport *repor
// delete results
splittedPostureReport.Results = []resourcesresults.Result{}
splittedPostureReport.Resources = []reporthandling.Resource{}
// restart counter
counter = 0
@@ -158,16 +148,46 @@ func (report *ReportEventReceiver) sendResults(host string, postureReport *repor
splittedPostureReport.Results = append(splittedPostureReport.Results, v)
}
return report.sendReport(host, splittedPostureReport, *reportCounter, false)
}
func (report *ReportEventReceiver) sendSummary(host string, postureReport *reporthandlingv2.PostureReport, reportCounter *int) error {
splittedPostureReport := setSubReport(postureReport)
splittedPostureReport.SummaryDetails = postureReport.SummaryDetails
return report.sendReport(host, splittedPostureReport, *reportCounter, true)
}
// func (report *ReportEventReceiver) sendResults(host string, postureReport *reporthandlingv2.PostureReport, reportCounter *int, isLastReport bool) error {
// splittedPostureReport := setSubReport(postureReport)
// counter := 0
// for _, v := range postureReport.Results {
// r, err := json.Marshal(v)
// if err != nil {
// return fmt.Errorf("failed to unmarshal resource '%s', reason: %v", v.GetResourceID(), err)
// }
// if counter+len(r) >= MAX_REPORT_SIZE && len(splittedPostureReport.Resources) > 0 {
// // send report
// if err := report.sendReport(host, splittedPostureReport, *reportCounter, false); err != nil {
// return err
// }
// *reportCounter++
// // delete results
// splittedPostureReport.Results = []resourcesresults.Result{}
// // restart counter
// counter = 0
// }
// counter += len(r)
// splittedPostureReport.Results = append(splittedPostureReport.Results, v)
// }
// return report.sendReport(host, splittedPostureReport, *reportCounter, isLastReport)
// }
func (report *ReportEventReceiver) sendReport(host string, postureReport *reporthandlingv2.PostureReport, counter int, isLastReport bool) error {
postureReport.PaginationInfo = reporthandlingv2.PaginationMarks{
ReportNumber: counter,
IsLastReport: isLastReport,
}
reqBody, err := json.Marshal(postureReport)
if err != nil {
return fmt.Errorf("in 'sendReport' failed to json.Marshal, reason: %v", err)
@@ -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 {
@@ -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)
+8 -6
View File
@@ -5,8 +5,8 @@ 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
@@ -19,7 +19,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
}
@@ -33,13 +33,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++
}
}
+1 -6
View File
@@ -33,12 +33,7 @@ func (resultsHandler *ResultsHandler) HandleResults(scanInfo *cautils.ScanInfo)
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))
score := opaSessionObj.Report.SummaryDetails.Score
resultsHandler.printerObj.Score(score)
return score
+43
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
View File
@@ -0,0 +1 @@
package score