Compare commits

..

17 Commits

Author SHA1 Message Date
dwertent
b44a73aea5 udpate armo api types 2022-03-27 17:07:17 +03:00
dwertent
74dc714736 use scan ID 2022-03-27 15:51:29 +03:00
dwertent
83751e22cc fixed report sending 2022-03-27 15:37:11 +03:00
dwertent
db5fdd75c4 inserting scan source 2022-03-25 16:08:07 +03:00
dwertent
4be2104d4b adding githubusercontent tests 2022-03-24 12:37:00 +02:00
dwertent
b6bab7618f loading github token from env 2022-03-24 12:15:54 +02:00
dwertent
3e1fda6f3b fixed test 2022-03-24 11:12:28 +02:00
dwertent
8487a031ee fixed url scanning 2022-03-24 09:45:35 +02:00
dwertent
5a335d4f1c update error display 2022-03-23 09:05:36 +02:00
dwertent
5770a823d6 update readme 2022-03-23 08:47:24 +02:00
Rotem Refael
52d7be9108 Add kubescape covarage 2022-03-22 17:42:20 +02:00
David Wertenteil
9512b9c6c4 Merge pull request #463 from Daniel-GrunbergerCA/dev
fix json printer
2022-03-22 09:14:59 +02:00
DanielGrunbergerCA
da9ab642ec rm list initializtion 2022-03-22 09:07:55 +02:00
DanielGrunbergerCA
718ca1c7ab fix json printer 2022-03-22 08:55:36 +02:00
DanielGrunbergerCA
ee3742c5a0 update opa-utils version 2022-03-21 15:36:17 +02:00
DanielGrunbergerCA
7eef843a7a fix resources in report 2022-03-21 15:11:26 +02:00
David Wertenteil
b4a8b06f07 Merge pull request #462 from dwertent/master
update get context
2022-03-21 09:09:12 +02:00
39 changed files with 627 additions and 281 deletions

View File

@@ -38,6 +38,8 @@ jobs:
run: cd cmd && go test -v ./...
- name: Test core pkg
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: cd core && go test -v ./...
- name: Test httphandler pkg

View File

@@ -22,6 +22,8 @@ jobs:
run: cd cmd && go test -v ./...
- name: Test core pkg
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: cd core && go test -v ./...
- name: Test httphandler pkg

View File

@@ -23,6 +23,8 @@ jobs:
run: cd cmd && go test -v ./...
- name: Test core pkg
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: cd core && go test -v ./...
- name: Test httphandler pkg

View File

@@ -1,7 +1,8 @@
<img src="docs/kubescape.png" width="300" alt="logo" align="center">
<br>
![build](https://github.com/armosec/kubescape/actions/workflows/build.yaml/badge.svg) ![GitHub tag (latest by date)](https://img.shields.io/github/v/tag/armosec/kubescape?label=release&color=blue) ![GitHub go.mod Go version](https://img.shields.io/github/go-mod/go-version/armosec/kubescape?color=yellow)
[![build](https://github.com/armosec/kubescape/actions/workflows/build.yaml/badge.svg)](https://github.com/armosec/kubescape/actions/workflows/build.yaml)
[![Go Report Card](https://goreportcard.com/badge/github.com/armosec/kubescape)](https://goreportcard.com/report/github.com/armosec/kubescape)
Kubescape is a K8s open-source tool providing a multi-cloud K8s single pane of glass, including risk analysis, security compliance, RBAC visualizer and image vulnerabilities scanning.
@@ -11,10 +12,18 @@ Kubescape integrates natively with other DevOps tools, including Jenkins, Circle
</br>
# CLI Interface:
<!-- # Kubescape Coverage
<img src="docs/ksfromcodetodeploy.png">
</br> -->
# Kubescape CLI:
<img src="docs/demo.gif">
<!-- # [Web Interface:](https://portal.armo.cloud/)
</br>
<!-- # Kubescape overview:
<img src="docs/ARMO-header-2022.gif"> -->
# TL;DR
@@ -228,6 +237,7 @@ kubescape download framework nsa --output /path/nsa.json
kubescape scan framework nsa --use-from /path/nsa.json
```
## Scan Periodically using Helm - Contributed by [@yonahd](https://github.com/yonahd)
[Please follow the instructions here](https://hub.armo.cloud/docs/installation-of-armo-in-cluster)
[helm chart repo](https://github.com/armosec/armo-helm)
@@ -257,6 +267,7 @@ Now you can submit the results to the Kubescape SaaS version -
kubescape submit results path/to/results.json
```
# Integrations
## VS Code Extension
@@ -265,6 +276,7 @@ kubescape submit results path/to/results.json
Scan the YAML files while writing them using the [vs code extension](https://github.com/armosec/vscode-kubescape/blob/master/README.md)
# Under the hood
## Technology

View File

@@ -7,7 +7,7 @@ replace github.com/armosec/kubescape/core => ../core
require (
github.com/armosec/k8s-interface v0.0.68
github.com/armosec/kubescape/core v0.0.0-00010101000000-000000000000
github.com/armosec/opa-utils v0.0.120
github.com/armosec/opa-utils v0.0.127
github.com/armosec/rbac-utils v0.0.14
github.com/google/uuid v1.3.0
github.com/mattn/go-isatty v0.0.14
@@ -88,6 +88,7 @@ require (
github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0 // indirect
github.com/ruudk/golang-pdf417 v0.0.0-20181029194003-1af4ab5afa58 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/whilp/git-urls v1.0.0 // indirect
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
github.com/yashtewari/glob-intersection v0.0.0-20180916065949-5c77d914dd0b // indirect

View File

@@ -109,8 +109,8 @@ github.com/armosec/k8s-interface v0.0.66/go.mod h1:vwprS8qn/iowd5yf0JHpqDsLA5I8W
github.com/armosec/k8s-interface v0.0.68 h1:6CtSakISiI47YHkxh+Va9FzZQIBkWa6g9sbiNxq1Zkk=
github.com/armosec/k8s-interface v0.0.68/go.mod h1:PeWn41C2uenZi+xfZdyFF/zG5wXACA00htQyknDUWDE=
github.com/armosec/opa-utils v0.0.64/go.mod h1:6tQP8UDq2EvEfSqh8vrUdr/9QVSCG4sJfju1SXQOn4c=
github.com/armosec/opa-utils v0.0.120 h1:WAtgm2U1o9fgA/2pjYNy+igqNC6ju3/CxQ8qRHdO+5k=
github.com/armosec/opa-utils v0.0.120/go.mod h1:gap+EaLG5rnyqvIRGxtdNDC9y7VvoGNm90zK8Ls7avQ=
github.com/armosec/opa-utils v0.0.127 h1:uVyH/+pjpiA0oBO6QODjtdCiPgHx7KQ/TcxxkPvAl4c=
github.com/armosec/opa-utils v0.0.127/go.mod h1:gap+EaLG5rnyqvIRGxtdNDC9y7VvoGNm90zK8Ls7avQ=
github.com/armosec/rbac-utils v0.0.1/go.mod h1:pQ8CBiij8kSKV7aeZm9FMvtZN28VgA7LZcYyTWimq40=
github.com/armosec/rbac-utils v0.0.14 h1:CKYKcgqJEXWF2Hen/B1pVGtS3nDAG1wp9dDv6oNtq90=
github.com/armosec/rbac-utils v0.0.14/go.mod h1:Ex/IdGWhGv9HZq6Hs8N/ApzCKSIvpNe/ETqDfnuyah0=
@@ -774,6 +774,8 @@ github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqri
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU=
github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM=
github.com/whilp/git-urls v1.0.0 h1:95f6UMWN5FKW71ECsXRUd3FVYiXdrE7aX4NZKcPmIjU=
github.com/whilp/git-urls v1.0.0/go.mod h1:J16SAmobsqc3Qcy98brfl5f5+e0clUvg1krgwk/qCfE=
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo=
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=

View File

@@ -44,7 +44,7 @@ func initLoggerLevel() {
}
func initCacheDir() {
if rootInfo.CacheDir == getter.DefaultLocalStore {
if rootInfo.CacheDir != getter.DefaultLocalStore {
getter.DefaultLocalStore = rootInfo.CacheDir
} else if cacheDir := os.Getenv("KS_CACHE_DIR"); cacheDir != "" {
getter.DefaultLocalStore = cacheDir

View File

@@ -11,6 +11,7 @@ import (
"github.com/armosec/k8s-interface/k8sinterface"
"github.com/armosec/kubescape/core/cautils/getter"
"github.com/armosec/kubescape/core/cautils/logger"
corev1 "k8s.io/api/core/v1"
)
@@ -135,7 +136,10 @@ func (lc *LocalConfig) UpdateCachedConfig() error {
}
func (lc *LocalConfig) DeleteCachedConfig() error {
return DeleteConfigFile()
if err := DeleteConfigFile(); err != nil {
logger.L().Warning(err.Error())
}
return nil
}
func getTenantConfigFromBE(backendAPI getter.IBackend, configObj *ConfigObj) error {
@@ -257,10 +261,10 @@ func (c *ClusterConfig) UpdateCachedConfig() error {
func (c *ClusterConfig) DeleteCachedConfig() error {
if err := c.deleteConfigMap(); err != nil {
return err
logger.L().Warning(err.Error())
}
if err := DeleteConfigFile(); err != nil {
return err
logger.L().Warning(err.Error())
}
return nil
}

View File

@@ -7,7 +7,6 @@ import (
apis "github.com/armosec/opa-utils/reporthandling/apis"
"github.com/armosec/opa-utils/reporthandling/results/v1/resourcesresults"
reporthandlingv2 "github.com/armosec/opa-utils/reporthandling/v2"
v2 "github.com/armosec/opa-utils/reporthandling/v2"
)
// K8SResources map[<api group>/<api version>/<resource>][]<resourceID>
@@ -20,6 +19,7 @@ type OPASessionObj struct {
Policies []reporthandling.Framework // list of frameworks to scan
AllResources map[string]workloadinterface.IMetadata // all scanned resources, map[<rtesource ID>]<resource>
ResourcesResult map[string]resourcesresults.Result // resources scan results, map[<rtesource ID>]<resource result>
ResourceSource map[string]string // resources sources, map[<rtesource ID>]<resource result>
PostureReport *reporthandling.PostureReport // scan results v1 - Remove
Report *reporthandlingv2.PostureReport // scan results v2 - Remove
Exceptions []armotypes.PostureExceptionPolicy // list of exceptions to apply on scan results
@@ -27,9 +27,10 @@ type OPASessionObj struct {
Metadata *reporthandlingv2.Metadata
InfoMap map[string]apis.StatusInfo // Map errors of resources to StatusInfo
ResourceToControlsMap map[string][]string // map[<apigroup/apiversion/resource>] = [<control_IDs>]
SessionID string // SessionID
}
func NewOPASessionObj(frameworks []reporthandling.Framework, k8sResources *K8SResources) *OPASessionObj {
func NewOPASessionObj(frameworks []reporthandling.Framework, k8sResources *K8SResources, scanInfo *ScanInfo) *OPASessionObj {
return &OPASessionObj{
Report: &reporthandlingv2.PostureReport{},
Policies: frameworks,
@@ -38,11 +39,13 @@ func NewOPASessionObj(frameworks []reporthandling.Framework, k8sResources *K8SRe
ResourcesResult: make(map[string]resourcesresults.Result),
InfoMap: make(map[string]apis.StatusInfo),
ResourceToControlsMap: make(map[string][]string),
ResourceSource: make(map[string]string),
SessionID: scanInfo.ScanID,
PostureReport: &reporthandling.PostureReport{
ClusterName: ClusterName,
CustomerGUID: CustomerGUID,
},
Metadata: &v2.Metadata{},
Metadata: scanInfoToScanMetadata(scanInfo),
}
}

View File

@@ -2,10 +2,6 @@ package cautils
// CA environment vars
var (
CustomerGUID = ""
ClusterName = ""
EventReceiverURL = ""
NotificationServerURL = ""
DashboardBackendURL = ""
RestAPIPort = "4001"
CustomerGUID = ""
ClusterName = ""
)

View File

@@ -26,7 +26,7 @@ const (
JSON_FILE_FORMAT FileFormat = "json"
)
func LoadResourcesFromFiles(inputPatterns []string) ([]workloadinterface.IMetadata, error) {
func LoadResourcesFromFiles(inputPatterns []string) (map[string][]workloadinterface.IMetadata, error) {
files, errs := listFiles(inputPatterns)
if len(errs) > 0 {
logger.L().Error(fmt.Sprintf("%v", errs))
@@ -42,8 +42,8 @@ func LoadResourcesFromFiles(inputPatterns []string) ([]workloadinterface.IMetada
return workloads, nil
}
func loadFiles(filePaths []string) ([]workloadinterface.IMetadata, []error) {
workloads := []workloadinterface.IMetadata{}
func loadFiles(filePaths []string) (map[string][]workloadinterface.IMetadata, []error) {
workloads := make(map[string][]workloadinterface.IMetadata, 0)
errs := []error{}
for i := range filePaths {
f, err := loadFile(filePaths[i])
@@ -54,7 +54,12 @@ func loadFiles(filePaths []string) ([]workloadinterface.IMetadata, []error) {
w, e := ReadFile(f, GetFileFormat(filePaths[i]))
errs = append(errs, e...)
if w != nil {
workloads = append(workloads, w...)
if _, ok := workloads[filePaths[i]]; !ok {
workloads[filePaths[i]] = []workloadinterface.IMetadata{}
}
wSlice := workloads[filePaths[i]]
wSlice = append(wSlice, w...)
workloads[filePaths[i]] = wSlice
}
}
return workloads, errs

View File

@@ -23,6 +23,20 @@ func TestListFiles(t *testing.T) {
assert.Equal(t, 12, len(files))
}
func TestLoadResourcesFromFiles(t *testing.T) {
workloads, err := LoadResourcesFromFiles([]string{onlineBoutiquePath()})
assert.NoError(t, err)
assert.Equal(t, 12, len(workloads))
for i, w := range workloads {
switch filepath.Base(i) {
case "adservice.yaml":
assert.Equal(t, 2, len(w))
assert.Equal(t, "apps/v1//Deployment/adservice", w[0].GetID())
assert.Equal(t, "/v1//Service/adservice", w[1].GetID())
}
}
}
func TestLoadFiles(t *testing.T) {
files, _ := listFiles([]string{onlineBoutiquePath()})
_, err := loadFiles(files)

View File

@@ -8,10 +8,12 @@ import (
"path/filepath"
"strings"
"github.com/armosec/k8s-interface/k8sinterface"
"github.com/armosec/kubescape/core/cautils/getter"
"github.com/armosec/kubescape/core/cautils/logger"
"github.com/armosec/kubescape/core/cautils/logger/helpers"
"github.com/armosec/opa-utils/reporthandling"
reporthandlingv2 "github.com/armosec/opa-utils/reporthandling/v2"
)
const (
@@ -78,7 +80,7 @@ type ScanInfo struct {
Silent bool // Silent mode - Do not print progress logs
FailThreshold float32 // Failure score threshold
Submit bool // Submit results to Armo BE
ReportID string // Report id of the current scan
ScanID string // Report id of the current scan
HostSensorEnabled BoolPtrFlag // Deploy ARMO K8s host scanner to collect data from certain controls
HostSensorYamlPath string // Path to hostsensor file
Local bool // Do not submit results
@@ -187,3 +189,41 @@ func (scanInfo *ScanInfo) contains(policyName string) bool {
}
return false
}
func scanInfoToScanMetadata(scanInfo *ScanInfo) *reporthandlingv2.Metadata {
metadata := &reporthandlingv2.Metadata{}
metadata.ClusterMetadata.ContextName = k8sinterface.GetClusterName()
metadata.ScanMetadata.Format = scanInfo.Format
metadata.ScanMetadata.Submit = scanInfo.Submit
// TODO - Add excluded and included namespaces
// if len(scanInfo.ExcludedNamespaces) > 1 {
// opaSessionObj.Metadata.ScanMetadata.ExcludedNamespaces = strings.Split(scanInfo.ExcludedNamespaces[1:], ",")
// }
// if len(scanInfo.IncludeNamespaces) > 1 {
// opaSessionObj.Metadata.ScanMetadata.IncludeNamespaces = strings.Split(scanInfo.IncludeNamespaces[1:], ",")
// }
// scan type
if len(scanInfo.PolicyIdentifier) > 0 {
metadata.ScanMetadata.TargetType = string(scanInfo.PolicyIdentifier[0].Kind)
}
// append frameworks
for _, policy := range scanInfo.PolicyIdentifier {
metadata.ScanMetadata.TargetNames = append(metadata.ScanMetadata.TargetNames, policy.Name)
}
metadata.ScanMetadata.VerboseMode = scanInfo.VerboseMode
metadata.ScanMetadata.FailThreshold = scanInfo.FailThreshold
metadata.ScanMetadata.HostScanner = scanInfo.HostSensorEnabled.GetBool()
metadata.ScanMetadata.VerboseMode = scanInfo.VerboseMode
metadata.ScanMetadata.ControlsInputs = scanInfo.ControlsInputs
metadata.ScanMetadata.ScanningTarget = reporthandlingv2.Cluster
if scanInfo.GetScanningEnvironment() == ScanLocalFiles {
metadata.ScanMetadata.ScanningTarget = reporthandlingv2.Files
}
return metadata
}

View File

@@ -5,6 +5,7 @@ import (
"github.com/armosec/armoapi-go/armotypes"
"github.com/armosec/k8s-interface/k8sinterface"
"github.com/google/uuid"
"github.com/armosec/kubescape/core/cautils"
"github.com/armosec/kubescape/core/cautils/getter"
@@ -31,6 +32,9 @@ type componentInterfaces struct {
}
func getInterfaces(scanInfo *cautils.ScanInfo) componentInterfaces {
if scanInfo.ScanID == "" {
scanInfo.ScanID = uuid.NewString()
}
// ================== setup k8s interface object ======================================
var k8s *k8sinterface.KubernetesApi
@@ -86,7 +90,7 @@ func getInterfaces(scanInfo *cautils.ScanInfo) componentInterfaces {
// ================== setup reporter & printer objects ======================================
// reporting behavior - setup reporter
reportHandler := getReporter(tenantConfig, scanInfo.ReportID, scanInfo.Submit, scanInfo.FrameworkScan, len(scanInfo.InputPatterns) == 0)
reportHandler := getReporter(tenantConfig, scanInfo.ScanID, scanInfo.Submit, scanInfo.FrameworkScan, len(scanInfo.InputPatterns) == 0)
// setup printer
printerHandler := resultshandling.NewPrinter(scanInfo.Format, scanInfo.FormatVersion, scanInfo.VerboseMode)

View File

@@ -20,7 +20,7 @@ func (ks *Kubescape) Submit(submitInterfaces cliinterfaces.SubmitInterfaces) err
return err
}
// report
if err := submitInterfaces.Reporter.ActionSendReport(&cautils.OPASessionObj{PostureReport: postureReport, AllResources: allresources}); err != nil {
if err := submitInterfaces.Reporter.Submit(&cautils.OPASessionObj{PostureReport: postureReport, AllResources: allresources}); err != nil {
return err
}
logger.L().Success("Data has been submitted successfully")

View File

@@ -5,7 +5,7 @@ go 1.17
require (
github.com/armosec/armoapi-go v0.0.58
github.com/armosec/k8s-interface v0.0.68
github.com/armosec/opa-utils v0.0.120
github.com/armosec/opa-utils v0.0.127
github.com/armosec/rbac-utils v0.0.14
github.com/armosec/utils-go v0.0.3
github.com/armosec/utils-k8s-go v0.0.3
@@ -19,6 +19,7 @@ require (
github.com/olekukonko/tablewriter v0.0.5
github.com/open-policy-agent/opa v0.38.0
github.com/stretchr/testify v1.7.0
github.com/whilp/git-urls v1.0.0
go.uber.org/zap v1.21.0
golang.org/x/mod v0.5.1
gopkg.in/yaml.v2 v2.4.0

View File

@@ -109,8 +109,8 @@ github.com/armosec/k8s-interface v0.0.66/go.mod h1:vwprS8qn/iowd5yf0JHpqDsLA5I8W
github.com/armosec/k8s-interface v0.0.68 h1:6CtSakISiI47YHkxh+Va9FzZQIBkWa6g9sbiNxq1Zkk=
github.com/armosec/k8s-interface v0.0.68/go.mod h1:PeWn41C2uenZi+xfZdyFF/zG5wXACA00htQyknDUWDE=
github.com/armosec/opa-utils v0.0.64/go.mod h1:6tQP8UDq2EvEfSqh8vrUdr/9QVSCG4sJfju1SXQOn4c=
github.com/armosec/opa-utils v0.0.120 h1:WAtgm2U1o9fgA/2pjYNy+igqNC6ju3/CxQ8qRHdO+5k=
github.com/armosec/opa-utils v0.0.120/go.mod h1:gap+EaLG5rnyqvIRGxtdNDC9y7VvoGNm90zK8Ls7avQ=
github.com/armosec/opa-utils v0.0.127 h1:uVyH/+pjpiA0oBO6QODjtdCiPgHx7KQ/TcxxkPvAl4c=
github.com/armosec/opa-utils v0.0.127/go.mod h1:gap+EaLG5rnyqvIRGxtdNDC9y7VvoGNm90zK8Ls7avQ=
github.com/armosec/rbac-utils v0.0.1/go.mod h1:pQ8CBiij8kSKV7aeZm9FMvtZN28VgA7LZcYyTWimq40=
github.com/armosec/rbac-utils v0.0.14 h1:CKYKcgqJEXWF2Hen/B1pVGtS3nDAG1wp9dDv6oNtq90=
github.com/armosec/rbac-utils v0.0.14/go.mod h1:Ex/IdGWhGv9HZq6Hs8N/ApzCKSIvpNe/ETqDfnuyah0=
@@ -771,6 +771,8 @@ github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqri
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU=
github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM=
github.com/whilp/git-urls v1.0.0 h1:95f6UMWN5FKW71ECsXRUd3FVYiXdrE7aX4NZKcPmIjU=
github.com/whilp/git-urls v1.0.0/go.mod h1:J16SAmobsqc3Qcy98brfl5f5+e0clUvg1krgwk/qCfE=
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo=
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=

View File

@@ -16,12 +16,12 @@ var (
MapResourceToApiGroup = map[string]string{
KubeletConfiguration: "hostdata.kubescape.cloud/v1beta0",
OsReleaseFile: "hostdata.kubescape.cloud/v1beta0/",
KubeletCommandLine: "hostdata.kubescape.cloud/v1beta0/",
KernelVersion: "hostdata.kubescape.cloud/v1beta0/",
LinuxSecurityHardeningStatus: "hostdata.kubescape.cloud/v1beta0/",
OpenPortsList: "hostdata.kubescape.cloud/v1beta0/",
LinuxKernelVariables: "hostdata.kubescape.cloud/v1beta0/",
OsReleaseFile: "hostdata.kubescape.cloud/v1beta0",
KubeletCommandLine: "hostdata.kubescape.cloud/v1beta0",
KernelVersion: "hostdata.kubescape.cloud/v1beta0",
LinuxSecurityHardeningStatus: "hostdata.kubescape.cloud/v1beta0",
OpenPortsList: "hostdata.kubescape.cloud/v1beta0",
LinuxKernelVariables: "hostdata.kubescape.cloud/v1beta0",
}
)

View File

@@ -1,10 +1,9 @@
package opaprocessor
import (
"fmt"
"github.com/armosec/kubescape/core/cautils"
"github.com/armosec/kubescape/core/cautils/logger"
"github.com/armosec/kubescape/core/cautils/logger/helpers"
"github.com/armosec/k8s-interface/k8sinterface"
"github.com/armosec/k8s-interface/workloadinterface"
@@ -90,9 +89,9 @@ func getArmoObjects(k8sResources *cautils.ArmoResources, allResources map[string
groupResources := k8sinterface.ResourceGroupToString(groups, version, resource)
for _, groupResource := range groupResources {
if k8sObj, ok := (*k8sResources)[groupResource]; ok {
if k8sObj == nil {
logger.L().Debug(fmt.Sprintf("resource '%s' is nil, probably failed to pull the resource", groupResource))
}
// if k8sObj == nil {
// logger.L().Debug(fmt.Sprintf("resource '%s' is nil, probably failed to pull the resource", groupResource))
// }
for i := range k8sObj {
k8sObjects = append(k8sObjects, allResources[k8sObj[i]])
}
@@ -117,7 +116,7 @@ func getKubernetesObjects(k8sResources *cautils.K8SResources, allResources map[s
for _, groupResource := range groupResources {
if k8sObj, ok := (*k8sResources)[groupResource]; ok {
if k8sObj == nil {
logger.L().Debug(fmt.Sprintf("resource '%s' is nil, probably failed to pull the resource", groupResource))
logger.L().Debug("skipping", helpers.String("resource", groupResource))
}
for i := range k8sObj {
k8sObjects = append(k8sObjects, allResources[k8sObj[i]])

View File

@@ -3,7 +3,6 @@ package policyhandler
import (
"fmt"
"github.com/armosec/k8s-interface/k8sinterface"
"github.com/armosec/kubescape/core/cautils"
"github.com/armosec/kubescape/core/pkg/resourcehandler"
"github.com/armosec/opa-utils/reporthandling"
@@ -24,7 +23,8 @@ func NewPolicyHandler(resourceHandler resourcehandler.IResourceHandler) *PolicyH
}
func (policyHandler *PolicyHandler) CollectResources(notification *reporthandling.PolicyNotification, scanInfo *cautils.ScanInfo) (*cautils.OPASessionObj, error) {
opaSessionObj := cautils.NewOPASessionObj(nil, nil)
opaSessionObj := cautils.NewOPASessionObj(nil, nil, scanInfo)
// validate notification
// TODO
policyHandler.getters = &scanInfo.Getters
@@ -49,8 +49,6 @@ func (policyHandler *PolicyHandler) CollectResources(notification *reporthandlin
func (policyHandler *PolicyHandler) getResources(notification *reporthandling.PolicyNotification, opaSessionObj *cautils.OPASessionObj, scanInfo *cautils.ScanInfo) error {
opaSessionObj.Report.ClusterAPIServerInfo = policyHandler.resourceHandler.GetClusterAPIServerInfo()
scanInfoToScanMetadata(opaSessionObj, scanInfo)
resourcesMap, allResources, armoResources, err := policyHandler.resourceHandler.GetResources(opaSessionObj, &notification.Designators)
if err != nil {
return err
@@ -62,31 +60,3 @@ func (policyHandler *PolicyHandler) getResources(notification *reporthandling.Po
return nil
}
func scanInfoToScanMetadata(opaSessionObj *cautils.OPASessionObj, scanInfo *cautils.ScanInfo) {
opaSessionObj.Metadata.ClusterMetadata.ContextName = k8sinterface.GetClusterName()
opaSessionObj.Metadata.ScanMetadata.Format = scanInfo.Format
opaSessionObj.Metadata.ScanMetadata.Submit = scanInfo.Submit
// TODO - Add excluded and included namespaces
// if len(scanInfo.ExcludedNamespaces) > 1 {
// opaSessionObj.Metadata.ScanMetadata.ExcludedNamespaces = strings.Split(scanInfo.ExcludedNamespaces[1:], ",")
// }
// if len(scanInfo.IncludeNamespaces) > 1 {
// opaSessionObj.Metadata.ScanMetadata.IncludeNamespaces = strings.Split(scanInfo.IncludeNamespaces[1:], ",")
// }
// scan type
if len(scanInfo.PolicyIdentifier) > 0 {
opaSessionObj.Metadata.ScanMetadata.TargetType = string(scanInfo.PolicyIdentifier[0].Kind)
}
// append frameworks
for _, policy := range scanInfo.PolicyIdentifier {
opaSessionObj.Metadata.ScanMetadata.TargetNames = append(opaSessionObj.Metadata.ScanMetadata.TargetNames, policy.Name)
}
opaSessionObj.Metadata.ScanMetadata.VerboseMode = scanInfo.VerboseMode
opaSessionObj.Metadata.ScanMetadata.FailThreshold = scanInfo.FailThreshold
opaSessionObj.Metadata.ScanMetadata.HostScanner = scanInfo.HostSensorEnabled.GetBool()
opaSessionObj.Metadata.ScanMetadata.VerboseMode = scanInfo.VerboseMode
opaSessionObj.Metadata.ScanMetadata.ControlsInputs = scanInfo.ControlsInputs
}

View File

@@ -32,6 +32,7 @@ func (fileHandler *FileResourceHandler) GetResources(sessionObj *cautils.OPASess
// map resources based on framework required resources: map["/group/version/kind"][]<k8s workloads ids>
k8sResources := setK8sResourceMap(sessionObj.Policies)
allResources := map[string]workloadinterface.IMetadata{}
workloadIDToSource := make(map[string]string, 0)
armoResources := &cautils.ArmoResources{}
workloads := []workloadinterface.IMetadata{}
@@ -41,8 +42,11 @@ func (fileHandler *FileResourceHandler) GetResources(sessionObj *cautils.OPASess
if err != nil {
return nil, allResources, nil, err
}
if w != nil {
workloads = append(workloads, w...)
for source, ws := range w {
workloads = append(workloads, ws...)
for i := range ws {
workloadIDToSource[ws[i].GetID()] = source
}
}
// load resources from url
@@ -50,13 +54,17 @@ func (fileHandler *FileResourceHandler) GetResources(sessionObj *cautils.OPASess
if err != nil {
return nil, allResources, nil, err
}
if w != nil {
workloads = append(workloads, w...)
for source, ws := range w {
workloads = append(workloads, ws...)
for i := range ws {
workloadIDToSource[ws[i].GetID()] = source
}
}
if len(workloads) == 0 {
return nil, allResources, nil, fmt.Errorf("empty list of workloads - no workloads found")
}
sessionObj.ResourceSource = workloadIDToSource
// map all resources: map["/group/version/kind"][]<k8s workloads>
mappedResources := mapResources(workloads)

View File

@@ -4,15 +4,27 @@ import (
"encoding/json"
"fmt"
"net/http"
"os"
"path/filepath"
"strings"
"github.com/armosec/kubescape/core/cautils/getter"
giturls "github.com/whilp/git-urls"
"k8s.io/utils/strings/slices"
)
type IRepository interface {
parse(fullURL string) error
setBranch(string) error
setTree() error
getYamlFromTree() []string
setIsFile(bool)
getIsFile() bool
getBranch() string
getTree() tree
getFilesFromTree([]string) []string
}
type innerTree struct {
@@ -23,19 +35,24 @@ type tree struct {
}
type GitHubRepository struct {
// name string // <org>/<repo>
host string
name string // <org>/<repo>
owner string //
repo string //
branch string
path string
token string
isFile bool
tree tree
}
type githubDefaultBranchAPI struct {
DefaultBranch string `json:"default_branch"`
}
func NewGitHubRepository(rep string) *GitHubRepository {
func NewGitHubRepository() *GitHubRepository {
return &GitHubRepository{
host: "github",
name: rep,
host: "github.com",
token: os.Getenv("GITHUB_TOKEN"),
}
}
@@ -45,79 +62,143 @@ func ScanRepository(command string, branchOptional string) ([]string, error) {
return nil, err
}
err = repo.setBranch(branchOptional)
if err != nil {
if err := repo.parse(command); err != nil {
return nil, err
}
err = repo.setTree()
if err != nil {
if err := repo.setBranch(branchOptional); err != nil {
return nil, err
}
if err := repo.setTree(); err != nil {
return nil, err
}
// get all paths that are of the yaml type, and build them into a valid url
return repo.getYamlFromTree(), nil
return repo.getFilesFromTree([]string{"yaml", "yml", "json"}), nil
}
func getHostAndRepoName(url string) (string, string, error) {
splitUrl := strings.Split(url, "/")
if len(splitUrl) != 5 {
return "", "", fmt.Errorf("failed to pars url: %s", url)
func getHost(fullURL string) (string, error) {
parsedURL, err := giturls.Parse(fullURL)
if err != nil {
return "", err
}
hostUrl := splitUrl[2] // github.com, gitlab.com, etc.
repository := splitUrl[3] + "/" + strings.Split(splitUrl[4], ".")[0] // user/reposetory
return hostUrl, repository, nil
return parsedURL.Host, nil
}
func getRepository(url string) (IRepository, error) {
hostUrl, repoName, err := getHostAndRepoName(url)
func getRepository(fullURL string) (IRepository, error) {
hostUrl, err := getHost(fullURL)
if err != nil {
return nil, err
}
var repo IRepository
switch repoHost := strings.Split(hostUrl, ".")[0]; repoHost {
case "github":
repo = NewGitHubRepository(repoName)
switch hostUrl {
case "github.com":
repo = NewGitHubRepository()
case "raw.githubusercontent.com":
repo = NewGitHubRepository()
repo.setIsFile(true)
default:
return nil, fmt.Errorf("unknown repository host: %s", repoHost)
return nil, fmt.Errorf("unknown repository host: %s", hostUrl)
}
// Returns the host-url, and the part of the user and repository from the url
return repo, nil
}
func (g *GitHubRepository) parse(fullURL string) error {
parsedURL, err := giturls.Parse(fullURL)
if err != nil {
return err
}
index := 0
splittedRepo := strings.FieldsFunc(parsedURL.Path, func(c rune) bool { return c == '/' })
if len(splittedRepo) < 2 {
return fmt.Errorf("expecting <user>/<repo> in url path, received: '%s'", parsedURL.Path)
}
g.owner = splittedRepo[index]
index += 1
g.repo = splittedRepo[index]
index += 1
// root of repo
if len(splittedRepo) < index+1 {
return nil
}
// is file or dir
switch splittedRepo[index] {
case "blob":
g.isFile = true
index += 1
case "tree":
g.isFile = false
index += 1
}
if len(splittedRepo) < index+1 {
return nil
}
g.branch = splittedRepo[index]
index += 1
if len(splittedRepo) < index+1 {
return nil
}
g.path = strings.Join(splittedRepo[index:], "/")
return nil
}
func (g *GitHubRepository) getBranch() string { return g.branch }
func (g *GitHubRepository) getTree() tree { return g.tree }
func (g *GitHubRepository) setIsFile(isFile bool) { g.isFile = isFile }
func (g *GitHubRepository) getIsFile() bool { return g.isFile }
func (g *GitHubRepository) setBranch(branchOptional string) error {
// Checks whether the repository type is a master or another type.
// By default it is "master", unless the branchOptional came with a value
if branchOptional == "" {
body, err := getter.HttpGetter(&http.Client{}, g.defaultBranchAPI(), nil)
if err != nil {
return err
}
var data githubDefaultBranchAPI
err = json.Unmarshal([]byte(body), &data)
if err != nil {
return err
}
g.branch = data.DefaultBranch
} else {
if branchOptional != "" {
g.branch = branchOptional
}
if g.branch != "" {
return nil
}
body, err := getter.HttpGetter(&http.Client{}, g.defaultBranchAPI(), g.getHeaders())
if err != nil {
return err
}
var data githubDefaultBranchAPI
err = json.Unmarshal([]byte(body), &data)
if err != nil {
return err
}
g.branch = data.DefaultBranch
return nil
}
func (g *GitHubRepository) defaultBranchAPI() string {
return fmt.Sprintf("https://api.github.com/repos/%s", g.name)
func joinOwnerNRepo(owner, repo string) string {
return fmt.Sprintf("%s/%s", owner, repo)
}
func (g *GitHubRepository) defaultBranchAPI() string {
return fmt.Sprintf("https://api.github.com/repos/%s", joinOwnerNRepo(g.owner, g.repo))
}
func (g *GitHubRepository) getHeaders() map[string]string {
if g.token == "" {
return nil
}
return map[string]string{"Authorization": fmt.Sprintf("token %s", g.token)}
}
func (g *GitHubRepository) setTree() error {
body, err := getter.HttpGetter(&http.Client{}, g.treeAPI(), nil)
if g.isFile {
return nil
}
body, err := getter.HttpGetter(&http.Client{}, g.treeAPI(), g.getHeaders())
if err != nil {
return err
}
@@ -135,14 +216,24 @@ func (g *GitHubRepository) setTree() error {
}
func (g *GitHubRepository) treeAPI() string {
return fmt.Sprintf("https://api.github.com/repos/%s/git/trees/%s?recursive=1", g.name, g.branch)
return fmt.Sprintf("https://api.github.com/repos/%s/git/trees/%s?recursive=1", joinOwnerNRepo(g.owner, g.repo), g.branch)
}
// return a list of yaml for a given repository tree
func (g *GitHubRepository) getYamlFromTree() []string {
func (g *GitHubRepository) getFilesFromTree(filesExtensions []string) []string {
var urls []string
if g.isFile {
if slices.Contains(filesExtensions, getFileExtension(g.path)) {
return []string{fmt.Sprintf("%s/%s", g.rowYamlUrl(), g.path)}
} else {
return []string{}
}
}
for _, path := range g.tree.InnerTrees {
if strings.HasSuffix(path.Path, ".yaml") {
if g.path != "" && !strings.HasPrefix(path.Path, g.path) {
continue
}
if slices.Contains(filesExtensions, getFileExtension(path.Path)) {
urls = append(urls, fmt.Sprintf("%s/%s", g.rowYamlUrl(), path.Path))
}
}
@@ -150,5 +241,9 @@ func (g *GitHubRepository) getYamlFromTree() []string {
}
func (g *GitHubRepository) rowYamlUrl() string {
return fmt.Sprintf("https://raw.githubusercontent.com/%s/%s", g.name, g.branch)
return fmt.Sprintf("https://raw.githubusercontent.com/%s/%s", joinOwnerNRepo(g.owner, g.repo), g.branch)
}
func getFileExtension(path string) string {
return strings.TrimPrefix(filepath.Ext(path), ".")
}

View File

@@ -0,0 +1,140 @@
package resourcehandler
import (
"testing"
"github.com/stretchr/testify/assert"
)
var (
urlA = "https://github.com/armosec/kubescape"
urlB = "https://github.com/armosec/kubescape/blob/master/examples/online-boutique/adservice.yaml"
urlC = "https://github.com/armosec/kubescape/tree/master/examples/online-boutique"
urlD = "https://raw.githubusercontent.com/armosec/kubescape/master/examples/online-boutique/adservice.yaml"
)
func TestScanRepository(t *testing.T) {
{
files, err := ScanRepository(urlA, "")
assert.NoError(t, err)
assert.Less(t, 0, len(files))
}
{
files, err := ScanRepository(urlB, "")
assert.NoError(t, err)
assert.Less(t, 0, len(files))
}
{
files, err := ScanRepository(urlC, "")
assert.NoError(t, err)
assert.Less(t, 0, len(files))
}
{
files, err := ScanRepository(urlD, "")
assert.NoError(t, err)
assert.Equal(t, 1, len(files))
}
}
func TestGetHost(t *testing.T) {
{
host, err := getHost(urlA)
assert.NoError(t, err)
assert.Equal(t, "github.com", host)
}
{
host, err := getHost(urlB)
assert.NoError(t, err)
assert.Equal(t, "github.com", host)
}
{
host, err := getHost(urlC)
assert.NoError(t, err)
assert.Equal(t, "github.com", host)
}
{
host, err := getHost(urlD)
assert.NoError(t, err)
assert.Equal(t, "raw.githubusercontent.com", host)
}
}
func TestGithubSetBranch(t *testing.T) {
{
gh := NewGitHubRepository()
assert.NoError(t, gh.parse(urlA))
assert.NoError(t, gh.setBranch(""))
assert.Equal(t, "master", gh.getBranch())
}
{
gh := NewGitHubRepository()
assert.NoError(t, gh.parse(urlB))
err := gh.setBranch("dev")
assert.NoError(t, err)
assert.Equal(t, "dev", gh.getBranch())
}
}
func TestGithubSetTree(t *testing.T) {
{
gh := NewGitHubRepository()
assert.NoError(t, gh.parse(urlA))
assert.NoError(t, gh.setBranch(""))
err := gh.setTree()
assert.NoError(t, err)
assert.Less(t, 0, len(gh.getTree().InnerTrees))
}
}
func TestGithubGetYamlFromTree(t *testing.T) {
{
gh := NewGitHubRepository()
assert.NoError(t, gh.parse(urlA))
assert.NoError(t, gh.setBranch(""))
assert.NoError(t, gh.setTree())
files := gh.getFilesFromTree([]string{"yaml"})
assert.Less(t, 0, len(files))
}
{
gh := NewGitHubRepository()
assert.NoError(t, gh.parse(urlB))
assert.NoError(t, gh.setBranch(""))
assert.NoError(t, gh.setTree())
files := gh.getFilesFromTree([]string{"yaml"})
assert.Equal(t, 1, len(files))
}
{
gh := NewGitHubRepository()
assert.NoError(t, gh.parse(urlC))
assert.NoError(t, gh.setBranch(""))
assert.NoError(t, gh.setTree())
files := gh.getFilesFromTree([]string{"yaml"})
assert.Equal(t, 12, len(files))
}
}
func TestGithubParse(t *testing.T) {
{
gh := NewGitHubRepository()
assert.NoError(t, gh.parse(urlA))
assert.Equal(t, "armosec/kubescape", joinOwnerNRepo(gh.owner, gh.repo))
}
{
gh := NewGitHubRepository()
assert.NoError(t, gh.parse(urlB))
assert.Equal(t, "armosec/kubescape", joinOwnerNRepo(gh.owner, gh.repo))
assert.Equal(t, "master", gh.branch)
assert.Equal(t, "examples/online-boutique/adservice.yaml", gh.path)
assert.True(t, gh.isFile)
assert.Equal(t, 1, len(gh.getFilesFromTree([]string{"yaml"})))
assert.Equal(t, 0, len(gh.getFilesFromTree([]string{"yml"})))
}
{
gh := NewGitHubRepository()
assert.NoError(t, gh.parse(urlC))
assert.Equal(t, "armosec/kubescape", joinOwnerNRepo(gh.owner, gh.repo))
assert.Equal(t, "master", gh.branch)
assert.Equal(t, "examples/online-boutique", gh.path)
assert.False(t, gh.isFile)
}
}

View File

@@ -12,7 +12,7 @@ import (
"github.com/armosec/kubescape/core/cautils/logger"
)
func loadResourcesFromUrl(inputPatterns []string) ([]workloadinterface.IMetadata, error) {
func loadResourcesFromUrl(inputPatterns []string) (map[string][]workloadinterface.IMetadata, error) {
urls := listUrls(inputPatterns)
if len(urls) == 0 {
return nil, nil
@@ -29,14 +29,10 @@ func listUrls(patterns []string) []string {
urls := []string{}
for i := range patterns {
if strings.HasPrefix(patterns[i], "http") {
if !cautils.IsYaml(patterns[i]) && !cautils.IsJson(patterns[i]) { // if url of repo
if yamls, err := ScanRepository(patterns[i], ""); err == nil { // TODO - support branch
urls = append(urls, yamls...)
} else {
logger.L().Error(err.Error())
}
} else { // url of single file
urls = append(urls, patterns[i])
if yamls, err := ScanRepository(patterns[i], ""); err == nil { // TODO - support branch
urls = append(urls, yamls...)
} else {
logger.L().Error(err.Error())
}
}
}
@@ -44,8 +40,8 @@ func listUrls(patterns []string) []string {
return urls
}
func downloadFiles(urls []string) ([]workloadinterface.IMetadata, []error) {
workloads := []workloadinterface.IMetadata{}
func downloadFiles(urls []string) (map[string][]workloadinterface.IMetadata, []error) {
workloads := make(map[string][]workloadinterface.IMetadata, 0)
errs := []error{}
for i := range urls {
f, err := downloadFile(urls[i])
@@ -56,7 +52,12 @@ func downloadFiles(urls []string) ([]workloadinterface.IMetadata, []error) {
w, e := cautils.ReadFile(f, cautils.GetFileFormat(urls[i]))
errs = append(errs, e...)
if w != nil {
workloads = append(workloads, w...)
if _, ok := workloads[urls[i]]; !ok {
workloads[urls[i]] = make([]workloadinterface.IMetadata, 0)
}
wSlice := workloads[urls[i]]
wSlice = append(wSlice, w...)
workloads[urls[i]] = wSlice
}
}
return workloads, errs

View File

@@ -0,0 +1,66 @@
package resourcehandler
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestLoadResourcesFromUrl(t *testing.T) {
{
workloads, err := loadResourcesFromUrl([]string{"https://github.com/armosec/kubescape/tree/master/examples/online-boutique"})
assert.NoError(t, err)
assert.Equal(t, 12, len(workloads))
for i, w := range workloads {
switch i {
case "https://raw.githubusercontent.com/armosec/kubescape/master/examples/online-boutique/adservice.yaml":
assert.Equal(t, 2, len(w))
assert.Equal(t, "apps/v1//Deployment/adservice", w[0].GetID())
assert.Equal(t, "/v1//Service/adservice", w[1].GetID())
}
}
}
{
workloads, err := loadResourcesFromUrl([]string{"https://github.com/armosec/kubescape"})
assert.NoError(t, err)
assert.Less(t, 12, len(workloads))
for i, w := range workloads {
switch i {
case "https://raw.githubusercontent.com/armosec/kubescape/master/examples/online-boutique/adservice.yaml":
assert.Equal(t, 2, len(w))
assert.Equal(t, "apps/v1//Deployment/adservice", w[0].GetID())
assert.Equal(t, "/v1//Service/adservice", w[1].GetID())
}
}
}
{
workloads, err := loadResourcesFromUrl([]string{"https://github.com/armosec/kubescape/blob/master/examples/online-boutique/adservice.yaml"})
assert.NoError(t, err)
assert.Equal(t, 1, len(workloads))
for i, w := range workloads {
switch i {
case "https://raw.githubusercontent.com/armosec/kubescape/master/examples/online-boutique/adservice.yaml":
assert.Equal(t, 2, len(w))
assert.Equal(t, "apps/v1//Deployment/adservice", w[0].GetID())
assert.Equal(t, "/v1//Service/adservice", w[1].GetID())
}
}
}
{
workloads, err := loadResourcesFromUrl([]string{"https://raw.githubusercontent.com/armosec/kubescape/master/examples/online-boutique/adservice.yaml"})
assert.NoError(t, err)
assert.Equal(t, 1, len(workloads))
for i, w := range workloads {
switch i {
case "https://raw.githubusercontent.com/armosec/kubescape/master/examples/online-boutique/adservice.yaml":
assert.Equal(t, 2, len(w))
assert.Equal(t, "apps/v1//Deployment/adservice", w[0].GetID())
assert.Equal(t, "/v1//Service/adservice", w[1].GetID())
}
}
}
}

View File

@@ -26,8 +26,7 @@ func DataToJson(data *cautils.OPASessionObj) *reporthandlingv2.PostureReport {
report.Results = make([]resourcesresults.Result, len(data.ResourcesResult))
finalizeResults(report.Results, data.ResourcesResult)
report.Resources = make([]reporthandling.Resource, 0) // do not initialize slice length
finalizeResources(report.Resources, report.Results, data.AllResources)
report.Resources = finalizeResources(report.Results, data.AllResources)
return &report
}
@@ -53,7 +52,8 @@ func mapInfoToPrintInfo(controls reportsummary.ControlSummaries) map[string]stri
return infoToPrintInfoMap
}
func finalizeResources(resources []reporthandling.Resource, results []resourcesresults.Result, allResources map[string]workloadinterface.IMetadata) {
func finalizeResources(results []resourcesresults.Result, allResources map[string]workloadinterface.IMetadata) []reporthandling.Resource {
resources := make([]reporthandling.Resource, 0)
for i := range results {
if obj, ok := allResources[results[i].ResourceID]; ok {
r := *reporthandling.NewResource(obj.GetObject())
@@ -61,6 +61,7 @@ func finalizeResources(resources []reporthandling.Resource, results []resourcesr
resources = append(resources, r)
}
}
return resources
}
func logOUtputFile(fileName string) {

View File

@@ -3,8 +3,9 @@ package reporter
import "github.com/armosec/kubescape/core/cautils"
type IReport interface {
ActionSendReport(opaSessionObj *cautils.OPASessionObj) error
Submit(opaSessionObj *cautils.OPASessionObj) error
SetCustomerGUID(customerGUID string)
SetClusterName(clusterName string)
DisplayReportURL()
GetURL() string
}

View File

@@ -38,7 +38,7 @@ func NewReportEventReceiver(tenantConfig *cautils.ConfigObj) *ReportEventReceive
}
}
func (report *ReportEventReceiver) ActionSendReport(opaSessionObj *cautils.OPASessionObj) error {
func (report *ReportEventReceiver) Submit(opaSessionObj *cautils.OPASessionObj) error {
if opaSessionObj.PostureReport == nil && opaSessionObj.Report != nil {
cautils.ReportV2ToV1(opaSessionObj)
}
@@ -157,6 +157,9 @@ func (report *ReportEventReceiver) generateMessage() {
report.message = fmt.Sprintf("%s %s", message, u.String())
}
func (report *ReportEventReceiver) GetURL() string {
return getter.GetArmoAPIConnector().GetFrontendURL()
}
func (report *ReportEventReceiver) DisplayReportURL() {
cautils.InfoTextDisplay(os.Stderr, fmt.Sprintf("\n\n%s\n\n", report.message))
}

View File

@@ -21,7 +21,7 @@ func NewReportMock(query, message string) *ReportMock {
message: message,
}
}
func (reportMock *ReportMock) ActionSendReport(opaSessionObj *cautils.OPASessionObj) error {
func (reportMock *ReportMock) Submit(opaSessionObj *cautils.OPASessionObj) error {
return nil
}
@@ -31,6 +31,10 @@ func (reportMock *ReportMock) SetCustomerGUID(customerGUID string) {
func (reportMock *ReportMock) SetClusterName(clusterName string) {
}
func (reportMock *ReportMock) GetURL() string {
return getter.GetArmoAPIConnector().GetFrontendURL()
}
func (reportMock *ReportMock) DisplayReportURL() {
u := fmt.Sprintf("https://%s/account/login", getter.GetArmoAPIConnector().GetFrontendURL())
if reportMock.query != "" {

View File

@@ -7,11 +7,11 @@ import (
"net/url"
"os"
"github.com/armosec/k8s-interface/workloadinterface"
"github.com/armosec/kubescape/core/cautils"
"github.com/armosec/kubescape/core/cautils/getter"
"github.com/armosec/kubescape/core/cautils/logger"
"github.com/armosec/kubescape/core/cautils/logger/helpers"
"github.com/google/uuid"
"github.com/armosec/opa-utils/reporthandling"
"github.com/armosec/opa-utils/reporthandling/results/v1/resourcesresults"
@@ -42,34 +42,29 @@ func NewReportEventReceiver(tenantConfig *cautils.ConfigObj, reportID string) *R
}
}
func (report *ReportEventReceiver) ActionSendReport(opaSessionObj *cautils.OPASessionObj) error {
finalizeReport(opaSessionObj)
func (report *ReportEventReceiver) Submit(opaSessionObj *cautils.OPASessionObj) error {
if report.customerGUID == "" {
logger.L().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 opaSessionObj.Metadata.ScanMetadata.ScanningTarget == reporthandlingv2.Cluster && report.clusterName == "" {
if report.clusterName == "" {
logger.L().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
}
if opaSessionObj.Report.ReportID == "" {
opaSessionObj.Report.ReportID = uuid.NewString()
}
opaSessionObj.Report.ReportID = uuid.NewString()
opaSessionObj.Report.CustomerGUID = report.customerGUID
opaSessionObj.Report.ClusterName = report.clusterName
opaSessionObj.Report.Metadata = *opaSessionObj.Metadata
if err := report.prepareReport(opaSessionObj.Report); err != nil {
logger.L().Error("failed to publish results", helpers.Error(err))
} else {
err := report.prepareReport(opaSessionObj)
if err == nil {
report.generateMessage()
} else {
logger.L().Debug(err.Error()) // print original error only in debug mode
err = fmt.Errorf("failed to submit scan results. url: '%s'", report.GetURL())
}
logger.L().Debug("", helpers.String("account ID", report.customerGUID))
return nil
return err
}
func (report *ReportEventReceiver) SetCustomerGUID(customerGUID string) {
@@ -80,80 +75,108 @@ func (report *ReportEventReceiver) SetClusterName(clusterName string) {
report.clusterName = cautils.AdoptClusterName(clusterName) // clean cluster name
}
func (report *ReportEventReceiver) prepareReport(postureReport *reporthandlingv2.PostureReport) error {
func (report *ReportEventReceiver) prepareReport(opaSessionObj *cautils.OPASessionObj) error {
report.initEventReceiverURL()
host := hostToString(report.eventReceiverURL, postureReport.ReportID)
host := hostToString(report.eventReceiverURL, report.reportID)
cautils.StartSpinner()
reportCounter := 0
// send resources
err := report.sendResources(host, postureReport, &reportCounter, false)
err := report.sendResources(host, opaSessionObj)
cautils.StopSpinner()
return err
}
func (report *ReportEventReceiver) sendResources(host string, postureReport *reporthandlingv2.PostureReport, reportCounter *int, isLastReport bool) error {
splittedPostureReport := setSubReport(postureReport)
func (report *ReportEventReceiver) GetURL() string {
u := url.URL{}
u.Scheme = "https"
u.Host = getter.GetArmoAPIConnector().GetFrontendURL()
if report.customerAdminEMail != "" || report.token == "" { // data has been submitted
u.Path = fmt.Sprintf("configuration-scanning/%s", report.clusterName)
} else {
u.Path = "account/sign-up"
q := u.Query()
q.Add("invitationToken", report.token)
q.Add("customerGUID", report.customerGUID)
u.RawQuery = q.Encode()
}
return u.String()
}
func (report *ReportEventReceiver) sendResources(host string, opaSessionObj *cautils.OPASessionObj) error {
splittedPostureReport := report.setSubReport(opaSessionObj)
counter := 0
for _, v := range postureReport.Resources {
r, err := json.Marshal(v)
if err != nil {
return fmt.Errorf("failed to unmarshal resource '%s', reason: %v", v.ResourceID, 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 resources
splittedPostureReport.Resources = []reporthandling.Resource{}
splittedPostureReport.Results = []resourcesresults.Result{}
// restart counter
counter = 0
}
counter += len(r)
splittedPostureReport.Resources = append(splittedPostureReport.Resources, v)
reportCounter := 0
if err := report.setResources(splittedPostureReport, opaSessionObj.AllResources, &counter, &reportCounter, host); err != nil {
return err
}
if err := report.setResults(splittedPostureReport, opaSessionObj.ResourcesResult, &counter, &reportCounter, host); err != nil {
return err
}
for _, v := range postureReport.Results {
return report.sendReport(host, splittedPostureReport, reportCounter, true)
}
func (report *ReportEventReceiver) setResults(reportObj *reporthandlingv2.PostureReport, results map[string]resourcesresults.Result, counter, reportCounter *int, host string) error {
for _, v := range 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.Results) > 0 {
if *counter+len(r) >= MAX_REPORT_SIZE && len(reportObj.Results) > 0 {
// send report
if err := report.sendReport(host, splittedPostureReport, *reportCounter, false); err != nil {
if err := report.sendReport(host, reportObj, *reportCounter, false); err != nil {
return err
}
*reportCounter++
// delete results
splittedPostureReport.Results = []resourcesresults.Result{}
splittedPostureReport.Resources = []reporthandling.Resource{}
reportObj.Results = []resourcesresults.Result{}
reportObj.Resources = []reporthandling.Resource{}
// restart counter
counter = 0
*counter = 0
}
counter += len(r)
splittedPostureReport.Results = append(splittedPostureReport.Results, v)
*counter += len(r)
reportObj.Results = append(reportObj.Results, v)
}
return report.sendReport(host, splittedPostureReport, *reportCounter, true)
return nil
}
func (report *ReportEventReceiver) setResources(reportObj *reporthandlingv2.PostureReport, allResources map[string]workloadinterface.IMetadata, counter, reportCounter *int, host string) error {
for resourceID, v := range allResources {
resource := reporthandling.NewResourceIMetadata(v)
r, err := json.Marshal(resource)
if err != nil {
return fmt.Errorf("failed to unmarshal resource '%s', reason: %v", resourceID, err)
}
if *counter+len(r) >= MAX_REPORT_SIZE && len(reportObj.Resources) > 0 {
// send report
if err := report.sendReport(host, reportObj, *reportCounter, false); err != nil {
return err
}
*reportCounter++
// delete resources
reportObj.Resources = []reporthandling.Resource{}
reportObj.Results = []resourcesresults.Result{}
// restart counter
*counter = 0
}
*counter += len(r)
reportObj.Resources = append(reportObj.Resources, *resource)
}
return nil
}
func (report *ReportEventReceiver) sendReport(host string, postureReport *reporthandlingv2.PostureReport, counter int, isLastReport bool) error {
postureReport.PaginationInfo = reporthandlingv2.PaginationMarks{
ReportNumber: counter,
@@ -171,26 +194,12 @@ func (report *ReportEventReceiver) sendReport(host string, postureReport *report
}
func (report *ReportEventReceiver) generateMessage() {
u := url.URL{}
u.Scheme = "https"
u.Host = getter.GetArmoAPIConnector().GetFrontendURL()
if report.customerAdminEMail != "" || report.token == "" { // data has been submitted
u.Path = fmt.Sprintf("configuration-scanning/%s", report.clusterName)
} else {
u.Path = "account/sign-up"
q := u.Query()
q.Add("invitationToken", report.token)
q.Add("customerGUID", report.customerGUID)
u.RawQuery = q.Encode()
}
report.message = ""
sep := "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
report.message = sep
report.message += " << WOW! Now you can see the scan results on the web >>\n\n"
report.message += fmt.Sprintf(" %s\n", u.String())
report.message += fmt.Sprintf(" %s\n", report.GetURL())
report.message += sep
}

View File

@@ -3,9 +3,8 @@ package v2
import (
"net/url"
"github.com/armosec/k8s-interface/workloadinterface"
"github.com/armosec/kubescape/core/cautils"
"github.com/armosec/kubescape/core/cautils/getter"
"github.com/armosec/opa-utils/reporthandling"
reporthandlingv2 "github.com/armosec/opa-utils/reporthandling/v2"
"github.com/google/uuid"
)
@@ -33,23 +32,19 @@ func hostToString(host *url.URL, reportID string) string {
return host.String()
}
func setSubReport(postureReport *reporthandlingv2.PostureReport) *reporthandlingv2.PostureReport {
return &reporthandlingv2.PostureReport{
CustomerGUID: postureReport.CustomerGUID,
ClusterName: postureReport.ClusterName,
ReportID: postureReport.ReportID,
ReportGenerationTime: postureReport.ReportGenerationTime,
SummaryDetails: postureReport.SummaryDetails,
Attributes: postureReport.Attributes,
ClusterCloudProvider: postureReport.ClusterCloudProvider,
JobID: postureReport.JobID,
ClusterAPIServerInfo: postureReport.ClusterAPIServerInfo,
Metadata: postureReport.Metadata,
func (report *ReportEventReceiver) setSubReport(opaSessionObj *cautils.OPASessionObj) *reporthandlingv2.PostureReport {
reportObj := &reporthandlingv2.PostureReport{
CustomerGUID: report.customerGUID,
ClusterName: report.clusterName,
ReportID: report.reportID,
ReportGenerationTime: opaSessionObj.Report.ReportGenerationTime,
SummaryDetails: opaSessionObj.Report.SummaryDetails,
Attributes: opaSessionObj.Report.Attributes,
ClusterAPIServerInfo: opaSessionObj.Report.ClusterAPIServerInfo,
}
}
func iMetaToResource(obj workloadinterface.IMetadata) *reporthandling.Resource {
return &reporthandling.Resource{
ResourceID: obj.GetID(),
Object: obj.GetObject(),
if opaSessionObj.Metadata != nil {
reportObj.ClusterCloudProvider = opaSessionObj.Metadata.ClusterMetadata.CloudProvider
reportObj.Metadata = *opaSessionObj.Metadata
}
return reportObj
}

View File

@@ -2,47 +2,8 @@ package v2
import (
"strings"
"github.com/armosec/k8s-interface/workloadinterface"
"github.com/armosec/kubescape/core/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, 0) // do not set slice length
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) {
for resourceID := range allResources {
if obj, ok := allResources[resourceID]; ok {
r := *reporthandling.NewResource(obj.GetObject())
r.ResourceID = resourceID
resources = append(resources, r)
}
}
}
func maskID(id string) string {
sep := "-"
splitted := strings.Split(id, sep)

View File

@@ -60,7 +60,7 @@ func (resultsHandler *ResultsHandler) HandleResults() {
resultsHandler.printerObj.ActionPrint(resultsHandler.scanData)
if err := resultsHandler.reporterObj.ActionSendReport(resultsHandler.scanData); err != nil {
if err := resultsHandler.reporterObj.Submit(resultsHandler.scanData); err != nil {
logger.L().Error(err.Error())
}

BIN
docs/ksfromcodetodeploy.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 967 KiB

View File

@@ -6,6 +6,7 @@ replace github.com/armosec/kubescape/core => ../core
require (
github.com/armosec/kubescape/core v0.0.0-00010101000000-000000000000
github.com/armosec/opa-utils v0.0.127
github.com/armosec/utils-go v0.0.3
github.com/google/uuid v1.3.0
github.com/gorilla/mux v1.8.0
@@ -24,7 +25,6 @@ require (
github.com/OneOfOne/xxhash v1.2.8 // indirect
github.com/armosec/armoapi-go v0.0.58 // indirect
github.com/armosec/k8s-interface v0.0.68 // indirect
github.com/armosec/opa-utils v0.0.120 // indirect
github.com/armosec/rbac-utils v0.0.14 // indirect
github.com/armosec/utils-k8s-go v0.0.3 // indirect
github.com/aws/aws-sdk-go v1.41.11 // indirect
@@ -89,6 +89,7 @@ require (
github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0 // indirect
github.com/ruudk/golang-pdf417 v0.0.0-20181029194003-1af4ab5afa58 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/whilp/git-urls v1.0.0 // indirect
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
github.com/yashtewari/glob-intersection v0.0.0-20180916065949-5c77d914dd0b // indirect

View File

@@ -109,8 +109,8 @@ github.com/armosec/k8s-interface v0.0.66/go.mod h1:vwprS8qn/iowd5yf0JHpqDsLA5I8W
github.com/armosec/k8s-interface v0.0.68 h1:6CtSakISiI47YHkxh+Va9FzZQIBkWa6g9sbiNxq1Zkk=
github.com/armosec/k8s-interface v0.0.68/go.mod h1:PeWn41C2uenZi+xfZdyFF/zG5wXACA00htQyknDUWDE=
github.com/armosec/opa-utils v0.0.64/go.mod h1:6tQP8UDq2EvEfSqh8vrUdr/9QVSCG4sJfju1SXQOn4c=
github.com/armosec/opa-utils v0.0.120 h1:WAtgm2U1o9fgA/2pjYNy+igqNC6ju3/CxQ8qRHdO+5k=
github.com/armosec/opa-utils v0.0.120/go.mod h1:gap+EaLG5rnyqvIRGxtdNDC9y7VvoGNm90zK8Ls7avQ=
github.com/armosec/opa-utils v0.0.127 h1:uVyH/+pjpiA0oBO6QODjtdCiPgHx7KQ/TcxxkPvAl4c=
github.com/armosec/opa-utils v0.0.127/go.mod h1:gap+EaLG5rnyqvIRGxtdNDC9y7VvoGNm90zK8Ls7avQ=
github.com/armosec/rbac-utils v0.0.1/go.mod h1:pQ8CBiij8kSKV7aeZm9FMvtZN28VgA7LZcYyTWimq40=
github.com/armosec/rbac-utils v0.0.14 h1:CKYKcgqJEXWF2Hen/B1pVGtS3nDAG1wp9dDv6oNtq90=
github.com/armosec/rbac-utils v0.0.14/go.mod h1:Ex/IdGWhGv9HZq6Hs8N/ApzCKSIvpNe/ETqDfnuyah0=
@@ -772,6 +772,8 @@ github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqri
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU=
github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM=
github.com/whilp/git-urls v1.0.0 h1:95f6UMWN5FKW71ECsXRUd3FVYiXdrE7aX4NZKcPmIjU=
github.com/whilp/git-urls v1.0.0/go.mod h1:J16SAmobsqc3Qcy98brfl5f5+e0clUvg1krgwk/qCfE=
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo=
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=

View File

@@ -57,7 +57,7 @@ func getPrometheusDefaultScanCommand(scanID string) *cautils.ScanInfo {
scanInfo := defaultScanInfo()
scanInfo.FrameworkScan = true
scanInfo.ScanAll = true // scan all frameworks
scanInfo.ReportID = scanID // scan ID
scanInfo.ScanID = scanID // scan ID
scanInfo.FailThreshold = 100 // Do not fail scanning
scanInfo.Output = scanID // results output
scanInfo.Format = envToString("KS_FORMAT", "prometheus") // default output should be json

View File

@@ -11,7 +11,7 @@ func TestGetPrometheusDefaultScanCommand(t *testing.T) {
scanID := "1234"
scanInfo := getPrometheusDefaultScanCommand(scanID)
assert.Equal(t, scanID, scanInfo.ReportID)
assert.Equal(t, scanID, scanInfo.ScanID)
assert.Equal(t, scanID, scanInfo.Output)
assert.Equal(t, "prometheus", scanInfo.Format)
// assert.False(t, *scanInfo.HostSensorEnabled.Get())

View File

@@ -77,7 +77,7 @@ func findFile(targetDir string, fileName string) (string, error) {
func getScanCommand(scanRequest *PostScanRequest, scanID string) *cautils.ScanInfo {
scanInfo := scanRequest.ToScanInfo()
scanInfo.ReportID = scanID
scanInfo.ScanID = scanID
// *** start ***
// Set default format