mirror of
https://github.com/kubescape/kubescape.git
synced 2026-04-15 06:58:11 +00:00
Compare commits
43 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
db5fdd75c4 | ||
|
|
4be2104d4b | ||
|
|
b6bab7618f | ||
|
|
3e1fda6f3b | ||
|
|
8487a031ee | ||
|
|
5a335d4f1c | ||
|
|
5770a823d6 | ||
|
|
52d7be9108 | ||
|
|
9512b9c6c4 | ||
|
|
da9ab642ec | ||
|
|
718ca1c7ab | ||
|
|
ee3742c5a0 | ||
|
|
7eef843a7a | ||
|
|
b4a8b06f07 | ||
|
|
4e13609985 | ||
|
|
2e5e4328f6 | ||
|
|
d98a11a8fa | ||
|
|
bdb25cbb66 | ||
|
|
369804cb6e | ||
|
|
1b08a92095 | ||
|
|
e787454d53 | ||
|
|
31d1ba663a | ||
|
|
c3731d8ff6 | ||
|
|
c5b46beb1a | ||
|
|
c5ca576c98 | ||
|
|
eae6458b42 | ||
|
|
aa1aa913b6 | ||
|
|
44084592cb | ||
|
|
6cacfb7b16 | ||
|
|
306d3a7081 | ||
|
|
442530061f | ||
|
|
961a6f6ebc | ||
|
|
0d0c8e1b97 | ||
|
|
5b843ba2c4 | ||
|
|
e16885a044 | ||
|
|
b47c128eb3 | ||
|
|
fae73b827a | ||
|
|
5009e6ef47 | ||
|
|
c4450d3259 | ||
|
|
0c3339f1c9 | ||
|
|
faee3d5ad6 | ||
|
|
a279963b28 | ||
|
|
353a39d66a |
2
.github/workflows/build.yaml
vendored
2
.github/workflows/build.yaml
vendored
@@ -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
|
||||
|
||||
2
.github/workflows/build_dev.yaml
vendored
2
.github/workflows/build_dev.yaml
vendored
@@ -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
|
||||
|
||||
2
.github/workflows/master_pr_checks.yaml
vendored
2
.github/workflows/master_pr_checks.yaml
vendored
@@ -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
|
||||
|
||||
22
README.md
22
README.md
@@ -12,8 +12,20 @@ Kubescape integrates natively with other DevOps tools, including Jenkins, Circle
|
||||
|
||||
</br>
|
||||
|
||||
<!-- # Kubescape Coverage
|
||||
<img src="docs/ksfromcodetodeploy.png">
|
||||
|
||||
</br> -->
|
||||
|
||||
|
||||
# Kubescape CLI:
|
||||
<img src="docs/demo.gif">
|
||||
|
||||
</br>
|
||||
|
||||
<!-- # Kubescape overview:
|
||||
<img src="docs/ARMO-header-2022.gif"> -->
|
||||
|
||||
# TL;DR
|
||||
## Install:
|
||||
```
|
||||
@@ -255,6 +267,16 @@ Now you can submit the results to the Kubescape SaaS version -
|
||||
kubescape submit results path/to/results.json
|
||||
```
|
||||
|
||||
|
||||
# Integrations
|
||||
|
||||
## VS Code Extension
|
||||
|
||||
 
|
||||
|
||||
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
|
||||
|
||||
@@ -35,10 +35,10 @@ RUN addgroup -S ks && adduser -S ks -G ks
|
||||
USER ks
|
||||
WORKDIR /home/ks/
|
||||
|
||||
COPY --from=builder /work/httphandler/build/ubuntu-latest/kubescape /usr/bin/kubescape
|
||||
COPY --from=builder /work/build/ubuntu-latest/kubescape /usr/bin/kscli
|
||||
COPY --from=builder /work/httphandler/build/ubuntu-latest/kubescape /usr/bin/ksserver
|
||||
COPY --from=builder /work/build/ubuntu-latest/kubescape /usr/bin/kubescape
|
||||
|
||||
RUN mkdir /home/ks/.kubescape && chmod 777 -R /home/ks/.kubescape
|
||||
COPY --from=builder /work/artifacts/ /home/ks/.kubescape
|
||||
|
||||
ENTRYPOINT ["kubescape"]
|
||||
ENTRYPOINT ["ksserver"]
|
||||
|
||||
@@ -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.118
|
||||
github.com/armosec/opa-utils v0.0.126
|
||||
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
|
||||
|
||||
@@ -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.118 h1:ZX1crwVQmo+sDv+jmTNLbDYfApUBzlgPhD8QI2GCJX0=
|
||||
github.com/armosec/opa-utils v0.0.118/go.mod h1:gap+EaLG5rnyqvIRGxtdNDC9y7VvoGNm90zK8Ls7avQ=
|
||||
github.com/armosec/opa-utils v0.0.126 h1:AVqVWdYcdaqZ8jK92nmZhVy43vh//ITBBjLA4722Vl8=
|
||||
github.com/armosec/opa-utils v0.0.126/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=
|
||||
|
||||
13
cmd/root.go
13
cmd/root.go
@@ -22,6 +22,8 @@ import (
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var rootInfo cautils.RootInfo
|
||||
|
||||
var ksExamples = `
|
||||
# Scan command
|
||||
kubescape scan --submit
|
||||
@@ -43,7 +45,6 @@ func NewDefaultKubescapeCommand() *cobra.Command {
|
||||
}
|
||||
|
||||
func getRootCmd(ks meta.IKubescape) *cobra.Command {
|
||||
var rootInfo cautils.RootInfo
|
||||
|
||||
rootCmd := &cobra.Command{
|
||||
Use: "kubescape",
|
||||
@@ -53,8 +54,8 @@ func getRootCmd(ks meta.IKubescape) *cobra.Command {
|
||||
Example: ksExamples,
|
||||
}
|
||||
|
||||
rootCmd.PersistentFlags().StringVar(&armoBEURLsDep, "environment", "", envFlagUsage)
|
||||
rootCmd.PersistentFlags().StringVar(&armoBEURLs, "env", "", envFlagUsage)
|
||||
rootCmd.PersistentFlags().StringVar(&rootInfo.ArmoBEURLsDep, "environment", "", envFlagUsage)
|
||||
rootCmd.PersistentFlags().StringVar(&rootInfo.ArmoBEURLs, "env", "", envFlagUsage)
|
||||
rootCmd.PersistentFlags().MarkDeprecated("environment", "use 'env' instead")
|
||||
rootCmd.PersistentFlags().MarkHidden("environment")
|
||||
rootCmd.PersistentFlags().MarkHidden("env")
|
||||
@@ -66,11 +67,7 @@ func getRootCmd(ks meta.IKubescape) *cobra.Command {
|
||||
rootCmd.PersistentFlags().StringVar(&rootInfo.CacheDir, "cache-dir", getter.DefaultLocalStore, "Cache directory [$KS_CACHE_DIR]")
|
||||
rootCmd.PersistentFlags().BoolVarP(&rootInfo.DisableColor, "disable-color", "", false, "Disable Color output for logging")
|
||||
|
||||
// Initialize
|
||||
initLogger(&rootInfo)
|
||||
initLoggerLevel(&rootInfo)
|
||||
initEnvironment(&rootInfo)
|
||||
initCacheDir(&rootInfo)
|
||||
cobra.OnInitialize(initLogger, initLoggerLevel, initEnvironment, initCacheDir)
|
||||
|
||||
// Supported commands
|
||||
rootCmd.AddCommand(scan.GetScanCommand(ks))
|
||||
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"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"
|
||||
@@ -13,12 +12,9 @@ import (
|
||||
"github.com/mattn/go-isatty"
|
||||
)
|
||||
|
||||
var armoBEURLs = ""
|
||||
var armoBEURLsDep = ""
|
||||
|
||||
const envFlagUsage = "Send report results to specific URL. Format:<ReportReceiver>,<Backend>,<Frontend>.\n\t\tExample:report.armo.cloud,api.armo.cloud,portal.armo.cloud"
|
||||
|
||||
func initLogger(rootInfo *cautils.RootInfo) {
|
||||
func initLogger() {
|
||||
logger.DisableColor(rootInfo.DisableColor)
|
||||
|
||||
if rootInfo.LoggerName == "" {
|
||||
@@ -36,8 +32,8 @@ func initLogger(rootInfo *cautils.RootInfo) {
|
||||
logger.InitLogger(rootInfo.LoggerName)
|
||||
|
||||
}
|
||||
func initLoggerLevel(rootInfo *cautils.RootInfo) {
|
||||
if rootInfo.Logger != helpers.InfoLevel.String() {
|
||||
func initLoggerLevel() {
|
||||
if rootInfo.Logger == helpers.InfoLevel.String() {
|
||||
} else if l := os.Getenv("KS_LOGGER"); l != "" {
|
||||
rootInfo.Logger = l
|
||||
}
|
||||
@@ -47,8 +43,8 @@ func initLoggerLevel(rootInfo *cautils.RootInfo) {
|
||||
}
|
||||
}
|
||||
|
||||
func initCacheDir(rootInfo *cautils.RootInfo) {
|
||||
if rootInfo.CacheDir == getter.DefaultLocalStore {
|
||||
func initCacheDir() {
|
||||
if rootInfo.CacheDir != getter.DefaultLocalStore {
|
||||
getter.DefaultLocalStore = rootInfo.CacheDir
|
||||
} else if cacheDir := os.Getenv("KS_CACHE_DIR"); cacheDir != "" {
|
||||
getter.DefaultLocalStore = cacheDir
|
||||
@@ -58,11 +54,11 @@ func initCacheDir(rootInfo *cautils.RootInfo) {
|
||||
|
||||
logger.L().Debug("cache dir updated", helpers.String("path", getter.DefaultLocalStore))
|
||||
}
|
||||
func initEnvironment(rootInfo *cautils.RootInfo) {
|
||||
if armoBEURLsDep != "" {
|
||||
armoBEURLs = armoBEURLsDep
|
||||
func initEnvironment() {
|
||||
if rootInfo.ArmoBEURLs == "" {
|
||||
rootInfo.ArmoBEURLs = rootInfo.ArmoBEURLsDep
|
||||
}
|
||||
urlSlices := strings.Split(armoBEURLs, ",")
|
||||
urlSlices := strings.Split(rootInfo.ArmoBEURLs, ",")
|
||||
if len(urlSlices) != 1 && len(urlSlices) < 3 {
|
||||
logger.L().Fatal("expected at least 3 URLs (report, api, frontend, auth)")
|
||||
}
|
||||
@@ -85,7 +81,7 @@ func initEnvironment(rootInfo *cautils.RootInfo) {
|
||||
armoERURL := urlSlices[0] // mandatory
|
||||
armoBEURL := urlSlices[1] // mandatory
|
||||
armoFEURL := urlSlices[2] // mandatory
|
||||
if len(urlSlices) <= 4 {
|
||||
if len(urlSlices) >= 4 {
|
||||
armoAUTHURL = urlSlices[3]
|
||||
}
|
||||
getter.SetARMOAPIConnector(getter.NewARMOAPICustomized(armoERURL, armoBEURL, armoFEURL, armoAUTHURL))
|
||||
|
||||
@@ -4,35 +4,48 @@ import (
|
||||
"github.com/armosec/armoapi-go/armotypes"
|
||||
"github.com/armosec/k8s-interface/workloadinterface"
|
||||
"github.com/armosec/opa-utils/reporthandling"
|
||||
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"
|
||||
)
|
||||
|
||||
// K8SResources map[<api group>/<api version>/<resource>][]<resourceID>
|
||||
type K8SResources map[string][]string
|
||||
type ArmoResources map[string][]string
|
||||
|
||||
type OPASessionObj struct {
|
||||
K8SResources *K8SResources // input k8s objects
|
||||
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>
|
||||
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
|
||||
RegoInputData RegoInputData // input passed to rgo for scanning. map[<control name>][<input arguments>]
|
||||
K8SResources *K8SResources // input k8s objects
|
||||
ArmoResource *ArmoResources // input ARMO objects
|
||||
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
|
||||
RegoInputData RegoInputData // input passed to rgo for scanning. map[<control name>][<input arguments>]
|
||||
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,
|
||||
K8SResources: k8sResources,
|
||||
AllResources: make(map[string]workloadinterface.IMetadata),
|
||||
ResourcesResult: make(map[string]resourcesresults.Result),
|
||||
Report: &reporthandlingv2.PostureReport{},
|
||||
Policies: frameworks,
|
||||
K8SResources: k8sResources,
|
||||
AllResources: make(map[string]workloadinterface.IMetadata),
|
||||
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: scanInfoToScanMetadata(scanInfo),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -11,8 +11,7 @@ func ReportV2ToV1(opaSessionObj *OPASessionObj) {
|
||||
if len(opaSessionObj.PostureReport.FrameworkReports) > 0 {
|
||||
return // report already converted
|
||||
}
|
||||
|
||||
opaSessionObj.PostureReport.ClusterCloudProvider = opaSessionObj.Report.ClusterCloudProvider
|
||||
// opaSessionObj.PostureReport.ClusterCloudProvider = opaSessionObj.Report.ClusterCloudProvider
|
||||
|
||||
frameworks := []reporthandling.FrameworkReport{}
|
||||
|
||||
|
||||
89
core/cautils/rootinfo.go
Normal file
89
core/cautils/rootinfo.go
Normal file
@@ -0,0 +1,89 @@
|
||||
package cautils
|
||||
|
||||
type RootInfo struct {
|
||||
Logger string // logger level
|
||||
LoggerName string // logger name ("pretty"/"zap"/"none")
|
||||
CacheDir string // cached dir
|
||||
DisableColor bool // Disable Color
|
||||
|
||||
ArmoBEURLs string // armo url
|
||||
ArmoBEURLsDep string // armo url
|
||||
}
|
||||
|
||||
// func (rootInfo *RootInfo) InitLogger() {
|
||||
// logger.DisableColor(rootInfo.DisableColor)
|
||||
|
||||
// if rootInfo.LoggerName == "" {
|
||||
// if l := os.Getenv("KS_LOGGER_NAME"); l != "" {
|
||||
// rootInfo.LoggerName = l
|
||||
// } else {
|
||||
// if isatty.IsTerminal(os.Stdout.Fd()) {
|
||||
// rootInfo.LoggerName = "pretty"
|
||||
// } else {
|
||||
// rootInfo.LoggerName = "zap"
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// logger.InitLogger(rootInfo.LoggerName)
|
||||
|
||||
// }
|
||||
// func (rootInfo *RootInfo) InitLoggerLevel() error {
|
||||
// if rootInfo.Logger == helpers.InfoLevel.String() {
|
||||
// } else if l := os.Getenv("KS_LOGGER"); l != "" {
|
||||
// rootInfo.Logger = l
|
||||
// }
|
||||
|
||||
// if err := logger.L().SetLevel(rootInfo.Logger); err != nil {
|
||||
// return fmt.Errorf("supported levels: %s", strings.Join(helpers.SupportedLevels(), "/"))
|
||||
// }
|
||||
// return nil
|
||||
// }
|
||||
|
||||
// func (rootInfo *RootInfo) InitCacheDir() error {
|
||||
// if rootInfo.CacheDir == getter.DefaultLocalStore {
|
||||
// getter.DefaultLocalStore = rootInfo.CacheDir
|
||||
// } else if cacheDir := os.Getenv("KS_CACHE_DIR"); cacheDir != "" {
|
||||
// getter.DefaultLocalStore = cacheDir
|
||||
// } else {
|
||||
// return nil // using default cache dir location
|
||||
// }
|
||||
|
||||
// // TODO create dir if not found exist
|
||||
// // logger.L().Debug("cache dir updated", helpers.String("path", getter.DefaultLocalStore))
|
||||
// return nil
|
||||
// }
|
||||
// func (rootInfo *RootInfo) InitEnvironment() error {
|
||||
|
||||
// urlSlices := strings.Split(rootInfo.ArmoBEURLs, ",")
|
||||
// if len(urlSlices) != 1 && len(urlSlices) < 3 {
|
||||
// return fmt.Errorf("expected at least 2 URLs (report,api,frontend,auth)")
|
||||
// }
|
||||
// switch len(urlSlices) {
|
||||
// case 1:
|
||||
// switch urlSlices[0] {
|
||||
// case "dev", "development":
|
||||
// getter.SetARMOAPIConnector(getter.NewARMOAPIDev())
|
||||
// case "stage", "staging":
|
||||
// getter.SetARMOAPIConnector(getter.NewARMOAPIStaging())
|
||||
// case "":
|
||||
// getter.SetARMOAPIConnector(getter.NewARMOAPIProd())
|
||||
// default:
|
||||
// return fmt.Errorf("unknown environment")
|
||||
// }
|
||||
// case 2:
|
||||
// armoERURL := urlSlices[0] // mandatory
|
||||
// armoBEURL := urlSlices[1] // mandatory
|
||||
// getter.SetARMOAPIConnector(getter.NewARMOAPICustomized(armoERURL, armoBEURL, "", ""))
|
||||
// case 3, 4:
|
||||
// var armoAUTHURL string
|
||||
// armoERURL := urlSlices[0] // mandatory
|
||||
// armoBEURL := urlSlices[1] // mandatory
|
||||
// armoFEURL := urlSlices[2] // mandatory
|
||||
// if len(urlSlices) <= 4 {
|
||||
// armoAUTHURL = urlSlices[3]
|
||||
// }
|
||||
// getter.SetARMOAPIConnector(getter.NewARMOAPICustomized(armoERURL, armoBEURL, armoFEURL, armoAUTHURL))
|
||||
// }
|
||||
// return nil
|
||||
// }
|
||||
@@ -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 (
|
||||
@@ -38,6 +40,12 @@ func (bpf *BoolPtrFlag) String() string {
|
||||
func (bpf *BoolPtrFlag) Get() *bool {
|
||||
return bpf.valPtr
|
||||
}
|
||||
func (bpf *BoolPtrFlag) GetBool() bool {
|
||||
if bpf.valPtr == nil {
|
||||
return false
|
||||
}
|
||||
return *bpf.valPtr
|
||||
}
|
||||
|
||||
func (bpf *BoolPtrFlag) SetBool(val bool) {
|
||||
bpf.valPtr = &val
|
||||
@@ -53,13 +61,6 @@ func (bpf *BoolPtrFlag) Set(val string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type RootInfo struct {
|
||||
Logger string // logger level
|
||||
LoggerName string // logger name ("pretty"/"zap"/"none")
|
||||
CacheDir string // cached dir
|
||||
DisableColor bool // Disable Color
|
||||
}
|
||||
|
||||
// TODO - UPDATE
|
||||
type ScanInfo struct {
|
||||
Getters // TODO - remove from object
|
||||
@@ -79,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
|
||||
@@ -188,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
|
||||
}
|
||||
|
||||
52
core/cautils/workloadmappingutils.go
Normal file
52
core/cautils/workloadmappingutils.go
Normal file
@@ -0,0 +1,52 @@
|
||||
package cautils
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/armosec/opa-utils/reporthandling/apis"
|
||||
)
|
||||
|
||||
var (
|
||||
ImageVulnResources = []string{"ImageVulnerabilities"}
|
||||
HostSensorResources = []string{"KubeletConfiguration",
|
||||
"KubeletCommandLine",
|
||||
"OsReleaseFile",
|
||||
"KernelVersion",
|
||||
"LinuxSecurityHardeningStatus",
|
||||
"OpenPortsList",
|
||||
"LinuxKernelVariables"}
|
||||
CloudResources = []string{"ClusterDescribe"}
|
||||
)
|
||||
|
||||
func MapArmoResource(armoResourceMap *ArmoResources, resources []string) []string {
|
||||
var hostResources []string
|
||||
for k := range *armoResourceMap {
|
||||
for _, resource := range resources {
|
||||
if strings.Contains(k, resource) {
|
||||
hostResources = append(hostResources, k)
|
||||
}
|
||||
}
|
||||
}
|
||||
return hostResources
|
||||
}
|
||||
|
||||
func MapHostResources(armoResourceMap *ArmoResources) []string {
|
||||
return MapArmoResource(armoResourceMap, HostSensorResources)
|
||||
}
|
||||
|
||||
func MapImageVulnResources(armoResourceMap *ArmoResources) []string {
|
||||
return MapArmoResource(armoResourceMap, ImageVulnResources)
|
||||
}
|
||||
|
||||
func MapCloudResources(armoResourceMap *ArmoResources) []string {
|
||||
return MapArmoResource(armoResourceMap, CloudResources)
|
||||
}
|
||||
|
||||
func SetInfoMapForResources(info string, resources []string, errorMap map[string]apis.StatusInfo) {
|
||||
for _, resource := range resources {
|
||||
errorMap[resource] = apis.StatusInfo{
|
||||
InnerInfo: info,
|
||||
InnerStatus: apis.StatusSkipped,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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.118
|
||||
github.com/armosec/opa-utils v0.0.126
|
||||
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,12 +19,14 @@ 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
|
||||
k8s.io/api v0.23.4
|
||||
k8s.io/apimachinery v0.23.4
|
||||
k8s.io/client-go v0.23.4
|
||||
k8s.io/utils v0.0.0-20211116205334-6203023598ed
|
||||
sigs.k8s.io/yaml v1.3.0
|
||||
)
|
||||
|
||||
@@ -116,7 +118,6 @@ require (
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
|
||||
k8s.io/klog/v2 v2.30.0 // indirect
|
||||
k8s.io/kube-openapi v0.0.0-20211115234752-e816edb12b65 // indirect
|
||||
k8s.io/utils v0.0.0-20211116205334-6203023598ed // indirect
|
||||
sigs.k8s.io/controller-runtime v0.11.1 // indirect
|
||||
sigs.k8s.io/json v0.0.0-20211020170558-c049b76a60c6 // indirect
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.2.1 // indirect
|
||||
|
||||
@@ -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.118 h1:ZX1crwVQmo+sDv+jmTNLbDYfApUBzlgPhD8QI2GCJX0=
|
||||
github.com/armosec/opa-utils v0.0.118/go.mod h1:gap+EaLG5rnyqvIRGxtdNDC9y7VvoGNm90zK8Ls7avQ=
|
||||
github.com/armosec/opa-utils v0.0.126 h1:AVqVWdYcdaqZ8jK92nmZhVy43vh//ITBBjLA4722Vl8=
|
||||
github.com/armosec/opa-utils v0.0.126/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=
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"github.com/armosec/kubescape/core/cautils/logger"
|
||||
"github.com/armosec/kubescape/core/cautils/logger/helpers"
|
||||
"github.com/armosec/opa-utils/objectsenvelopes/hostsensor"
|
||||
"github.com/armosec/opa-utils/reporthandling/apis"
|
||||
"sigs.k8s.io/yaml"
|
||||
)
|
||||
|
||||
@@ -156,56 +157,77 @@ func (hsh *HostSensorHandler) GetKubeletConfigurations() ([]hostsensor.HostSenso
|
||||
return res, err
|
||||
}
|
||||
|
||||
func (hsh *HostSensorHandler) CollectResources() ([]hostsensor.HostSensorDataEnvelope, error) {
|
||||
func (hsh *HostSensorHandler) CollectResources() ([]hostsensor.HostSensorDataEnvelope, map[string]apis.StatusInfo, error) {
|
||||
res := make([]hostsensor.HostSensorDataEnvelope, 0)
|
||||
infoMap := make(map[string]apis.StatusInfo)
|
||||
if hsh.DaemonSet == nil {
|
||||
return res, nil
|
||||
return res, nil, nil
|
||||
}
|
||||
|
||||
var kcData []hostsensor.HostSensorDataEnvelope
|
||||
var err error
|
||||
logger.L().Debug("Accessing host scanner")
|
||||
kcData, err := hsh.GetKubeletConfigurations()
|
||||
kcData, err = hsh.GetKubeletConfigurations()
|
||||
if err != nil {
|
||||
return kcData, err
|
||||
addInfoToMap(KubeletConfiguration, infoMap, err)
|
||||
logger.L().Warning(err.Error())
|
||||
}
|
||||
if len(kcData) > 0 {
|
||||
res = append(res, kcData...)
|
||||
}
|
||||
res = append(res, kcData...)
|
||||
//
|
||||
kcData, err = hsh.GetKubeletCommandLine()
|
||||
if err != nil {
|
||||
return kcData, err
|
||||
addInfoToMap(KubeletCommandLine, infoMap, err)
|
||||
logger.L().Warning(err.Error())
|
||||
}
|
||||
if len(kcData) > 0 {
|
||||
res = append(res, kcData...)
|
||||
}
|
||||
res = append(res, kcData...)
|
||||
//
|
||||
kcData, err = hsh.GetOsReleaseFile()
|
||||
if err != nil {
|
||||
return kcData, err
|
||||
addInfoToMap(OsReleaseFile, infoMap, err)
|
||||
logger.L().Warning(err.Error())
|
||||
}
|
||||
if len(kcData) > 0 {
|
||||
res = append(res, kcData...)
|
||||
}
|
||||
res = append(res, kcData...)
|
||||
//
|
||||
kcData, err = hsh.GetKernelVersion()
|
||||
if err != nil {
|
||||
return kcData, err
|
||||
addInfoToMap(KernelVersion, infoMap, err)
|
||||
logger.L().Warning(err.Error())
|
||||
}
|
||||
if len(kcData) > 0 {
|
||||
res = append(res, kcData...)
|
||||
}
|
||||
res = append(res, kcData...)
|
||||
//
|
||||
kcData, err = hsh.GetLinuxSecurityHardeningStatus()
|
||||
if err != nil {
|
||||
return kcData, err
|
||||
addInfoToMap(LinuxSecurityHardeningStatus, infoMap, err)
|
||||
logger.L().Warning(err.Error())
|
||||
}
|
||||
if len(kcData) > 0 {
|
||||
res = append(res, kcData...)
|
||||
}
|
||||
res = append(res, kcData...)
|
||||
//
|
||||
kcData, err = hsh.GetOpenPortsList()
|
||||
if err != nil {
|
||||
return kcData, err
|
||||
addInfoToMap(OpenPortsList, infoMap, err)
|
||||
logger.L().Warning(err.Error())
|
||||
}
|
||||
if len(kcData) > 0 {
|
||||
res = append(res, kcData...)
|
||||
}
|
||||
res = append(res, kcData...)
|
||||
// GetKernelVariables
|
||||
kcData, err = hsh.GetKernelVariables()
|
||||
if err != nil {
|
||||
return kcData, err
|
||||
addInfoToMap(LinuxKernelVariables, infoMap, err)
|
||||
logger.L().Warning(err.Error())
|
||||
}
|
||||
if len(kcData) > 0 {
|
||||
res = append(res, kcData...)
|
||||
}
|
||||
res = append(res, kcData...)
|
||||
// finish
|
||||
|
||||
logger.L().Debug("Done reading information from host scanner")
|
||||
return res, nil
|
||||
return res, infoMap, nil
|
||||
}
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
package hostsensorutils
|
||||
|
||||
import "github.com/armosec/opa-utils/objectsenvelopes/hostsensor"
|
||||
import (
|
||||
"github.com/armosec/opa-utils/objectsenvelopes/hostsensor"
|
||||
"github.com/armosec/opa-utils/reporthandling/apis"
|
||||
)
|
||||
|
||||
type IHostSensor interface {
|
||||
Init() error
|
||||
TearDown() error
|
||||
CollectResources() ([]hostsensor.HostSensorDataEnvelope, error)
|
||||
CollectResources() ([]hostsensor.HostSensorDataEnvelope, map[string]apis.StatusInfo, error)
|
||||
GetNamespace() string
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package hostsensorutils
|
||||
|
||||
import (
|
||||
"github.com/armosec/opa-utils/objectsenvelopes/hostsensor"
|
||||
"github.com/armosec/opa-utils/reporthandling/apis"
|
||||
)
|
||||
|
||||
type HostSensorHandlerMock struct {
|
||||
@@ -15,8 +16,8 @@ func (hshm *HostSensorHandlerMock) TearDown() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (hshm *HostSensorHandlerMock) CollectResources() ([]hostsensor.HostSensorDataEnvelope, error) {
|
||||
return []hostsensor.HostSensorDataEnvelope{}, nil
|
||||
func (hshm *HostSensorHandlerMock) CollectResources() ([]hostsensor.HostSensorDataEnvelope, map[string]apis.StatusInfo, error) {
|
||||
return []hostsensor.HostSensorDataEnvelope{}, nil, nil
|
||||
}
|
||||
|
||||
func (hshm *HostSensorHandlerMock) GetNamespace() string {
|
||||
|
||||
35
core/pkg/hostsensorutils/utils.go
Normal file
35
core/pkg/hostsensorutils/utils.go
Normal file
@@ -0,0 +1,35 @@
|
||||
package hostsensorutils
|
||||
|
||||
import (
|
||||
"github.com/armosec/k8s-interface/k8sinterface"
|
||||
"github.com/armosec/opa-utils/reporthandling/apis"
|
||||
)
|
||||
|
||||
var (
|
||||
KubeletConfiguration = "KubeletConfiguration"
|
||||
OsReleaseFile = "OsReleaseFile"
|
||||
KernelVersion = "KernelVersion"
|
||||
LinuxSecurityHardeningStatus = "LinuxSecurityHardeningStatus"
|
||||
OpenPortsList = "OpenPortsList"
|
||||
LinuxKernelVariables = "LinuxKernelVariables"
|
||||
KubeletCommandLine = "KubeletCommandLine"
|
||||
|
||||
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/",
|
||||
}
|
||||
)
|
||||
|
||||
func addInfoToMap(resource string, infoMap map[string]apis.StatusInfo, err error) {
|
||||
group, version := k8sinterface.SplitApiVersion(MapResourceToApiGroup[resource])
|
||||
r := k8sinterface.JoinResourceTriplets(group, version, resource)
|
||||
infoMap[r] = apis.StatusInfo{
|
||||
InnerStatus: apis.StatusSkipped,
|
||||
InnerInfo: err.Error(),
|
||||
}
|
||||
}
|
||||
@@ -133,7 +133,7 @@ func (opap *OPAProcessor) processRule(rule *reporthandling.PolicyRule) (map[stri
|
||||
|
||||
postureControlInputs := opap.regoDependenciesData.GetFilteredPostureControlInputs(rule.ConfigInputs) // get store
|
||||
|
||||
inputResources, err := reporthandling.RegoResourcesAggregator(rule, getAllSupportedObjects(opap.K8SResources, opap.AllResources, rule))
|
||||
inputResources, err := reporthandling.RegoResourcesAggregator(rule, getAllSupportedObjects(opap.K8SResources, opap.ArmoResource, opap.AllResources, rule))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error getting aggregated k8sObjects: %s", err.Error())
|
||||
}
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
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"
|
||||
"github.com/armosec/opa-utils/reporthandling"
|
||||
"github.com/armosec/opa-utils/reporthandling/apis"
|
||||
resources "github.com/armosec/opa-utils/resources"
|
||||
)
|
||||
|
||||
@@ -46,8 +46,9 @@ func (opap *OPAProcessor) updateResults() {
|
||||
}
|
||||
|
||||
// set result summary
|
||||
opap.Report.SummaryDetails.InitResourcesSummary()
|
||||
|
||||
// map control to error
|
||||
controlToInfoMap := mapControlToInfo(opap.ResourceToControlsMap, opap.InfoMap)
|
||||
opap.Report.SummaryDetails.InitResourcesSummary(controlToInfoMap)
|
||||
// for f := range opap.PostureReport.FrameworkReports {
|
||||
// // set exceptions
|
||||
// exceptions.SetFrameworkExceptions(&opap.PostureReport.FrameworkReports[f], opap.Exceptions, cautils.ClusterName)
|
||||
@@ -60,13 +61,50 @@ func (opap *OPAProcessor) updateResults() {
|
||||
// }
|
||||
}
|
||||
|
||||
func getAllSupportedObjects(k8sResources *cautils.K8SResources, allResources map[string]workloadinterface.IMetadata, rule *reporthandling.PolicyRule) []workloadinterface.IMetadata {
|
||||
func mapControlToInfo(mapResourceToControls map[string][]string, infoMap map[string]apis.StatusInfo) map[string]apis.StatusInfo {
|
||||
controlToInfoMap := make(map[string]apis.StatusInfo)
|
||||
for resource, statusInfo := range infoMap {
|
||||
controls := mapResourceToControls[resource]
|
||||
for _, control := range controls {
|
||||
controlToInfoMap[control] = statusInfo
|
||||
}
|
||||
}
|
||||
return controlToInfoMap
|
||||
}
|
||||
|
||||
func getAllSupportedObjects(k8sResources *cautils.K8SResources, armoResources *cautils.ArmoResources, allResources map[string]workloadinterface.IMetadata, rule *reporthandling.PolicyRule) []workloadinterface.IMetadata {
|
||||
k8sObjects := []workloadinterface.IMetadata{}
|
||||
k8sObjects = append(k8sObjects, getKubernetesObjects(k8sResources, allResources, rule.Match)...)
|
||||
k8sObjects = append(k8sObjects, getKubernetesObjects(k8sResources, allResources, rule.DynamicMatch)...)
|
||||
k8sObjects = append(k8sObjects, getArmoObjects(armoResources, allResources, rule.DynamicMatch)...)
|
||||
return k8sObjects
|
||||
}
|
||||
|
||||
func getArmoObjects(k8sResources *cautils.ArmoResources, allResources map[string]workloadinterface.IMetadata, match []reporthandling.RuleMatchObjects) []workloadinterface.IMetadata {
|
||||
k8sObjects := []workloadinterface.IMetadata{}
|
||||
|
||||
for m := range match {
|
||||
for _, groups := range match[m].APIGroups {
|
||||
for _, version := range match[m].APIVersions {
|
||||
for _, resource := range match[m].Resources {
|
||||
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))
|
||||
// }
|
||||
for i := range k8sObj {
|
||||
k8sObjects = append(k8sObjects, allResources[k8sObj[i]])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return filterOutChildResources(k8sObjects, match)
|
||||
}
|
||||
|
||||
func getKubernetesObjects(k8sResources *cautils.K8SResources, allResources map[string]workloadinterface.IMetadata, match []reporthandling.RuleMatchObjects) []workloadinterface.IMetadata {
|
||||
k8sObjects := []workloadinterface.IMetadata{}
|
||||
|
||||
@@ -78,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]])
|
||||
|
||||
@@ -23,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
|
||||
@@ -46,15 +47,16 @@ 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()
|
||||
resourcesMap, allResources, err := policyHandler.resourceHandler.GetResources(opaSessionObj.Policies, ¬ification.Designators)
|
||||
|
||||
resourcesMap, allResources, armoResources, err := policyHandler.resourceHandler.GetResources(opaSessionObj, ¬ification.Designators)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
opaSessionObj.K8SResources = resourcesMap
|
||||
opaSessionObj.AllResources = allResources
|
||||
opaSessionObj.ArmoResource = armoResources
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -60,6 +60,9 @@ func responseObjectToVulnerabilities(vulnerabilitiesList containerscan.Vulnerabi
|
||||
vulnerabilities[i].Relevancy = vulnerabilityEntry.Relevancy
|
||||
vulnerabilities[i].Severity = vulnerabilityEntry.Severity
|
||||
vulnerabilities[i].UrgentCount = vulnerabilityEntry.UrgentCount
|
||||
vulnerabilities[i].Categories = registryvulnerabilities.Categories{
|
||||
IsRCE: vulnerabilityEntry.Categories.IsRCE,
|
||||
}
|
||||
}
|
||||
return vulnerabilities
|
||||
}
|
||||
|
||||
@@ -23,6 +23,10 @@ type FixedIn struct {
|
||||
ImgTag string `json:"imageTag"`
|
||||
Version string `json:"version"`
|
||||
}
|
||||
type Categories struct {
|
||||
IsRCE bool `json:"isRce"`
|
||||
}
|
||||
|
||||
type Vulnerability struct {
|
||||
Name string `json:"name"`
|
||||
RelatedPackageName string `json:"packageName"`
|
||||
@@ -36,6 +40,7 @@ type Vulnerability struct {
|
||||
UrgentCount int `json:"urgent"`
|
||||
NeglectedCount int `json:"neglected"`
|
||||
HealthStatus string `json:"healthStatus"`
|
||||
Categories Categories `json:"categories"`
|
||||
}
|
||||
|
||||
type ContainerImageVulnerabilityReport struct {
|
||||
|
||||
@@ -10,7 +10,6 @@ import (
|
||||
|
||||
"github.com/armosec/k8s-interface/k8sinterface"
|
||||
"github.com/armosec/kubescape/core/cautils"
|
||||
"github.com/armosec/opa-utils/reporthandling"
|
||||
)
|
||||
|
||||
// FileResourceHandler handle resources from files and URLs
|
||||
@@ -27,36 +26,45 @@ func NewFileResourceHandler(inputPatterns []string, registryAdaptors *RegistryAd
|
||||
}
|
||||
}
|
||||
|
||||
func (fileHandler *FileResourceHandler) GetResources(frameworks []reporthandling.Framework, designator *armotypes.PortalDesignator) (*cautils.K8SResources, map[string]workloadinterface.IMetadata, error) {
|
||||
func (fileHandler *FileResourceHandler) GetResources(sessionObj *cautils.OPASessionObj, designator *armotypes.PortalDesignator) (*cautils.K8SResources, map[string]workloadinterface.IMetadata, *cautils.ArmoResources, error) {
|
||||
|
||||
// build resources map
|
||||
// map resources based on framework required resources: map["/group/version/kind"][]<k8s workloads ids>
|
||||
k8sResources := setResourceMap(frameworks)
|
||||
k8sResources := setK8sResourceMap(sessionObj.Policies)
|
||||
allResources := map[string]workloadinterface.IMetadata{}
|
||||
workloadIDToSource := make(map[string]string, 0)
|
||||
armoResources := &cautils.ArmoResources{}
|
||||
|
||||
workloads := []workloadinterface.IMetadata{}
|
||||
|
||||
// load resource from local file system
|
||||
w, err := cautils.LoadResourcesFromFiles(fileHandler.inputPatterns)
|
||||
if err != nil {
|
||||
return nil, allResources, err
|
||||
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
|
||||
w, err = loadResourcesFromUrl(fileHandler.inputPatterns)
|
||||
if err != nil {
|
||||
return nil, allResources, err
|
||||
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, fmt.Errorf("empty list of workloads - no workloads found")
|
||||
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)
|
||||
@@ -73,11 +81,11 @@ func (fileHandler *FileResourceHandler) GetResources(frameworks []reporthandling
|
||||
}
|
||||
}
|
||||
|
||||
if err := fileHandler.registryAdaptors.collectImagesVulnerabilities(k8sResources, allResources); err != nil {
|
||||
if err := fileHandler.registryAdaptors.collectImagesVulnerabilities(k8sResources, allResources, armoResources); err != nil {
|
||||
cautils.WarningDisplay(os.Stderr, "Warning: failed to collect images vulnerabilities: %s\n", err.Error())
|
||||
}
|
||||
|
||||
return k8sResources, allResources, nil
|
||||
return k8sResources, allResources, armoResources, nil
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
"github.com/armosec/kubescape/core/cautils/logger/helpers"
|
||||
"github.com/armosec/kubescape/core/pkg/hostsensorutils"
|
||||
"github.com/armosec/opa-utils/objectsenvelopes"
|
||||
"github.com/armosec/opa-utils/reporthandling"
|
||||
"github.com/armosec/opa-utils/reporthandling/apis"
|
||||
|
||||
"github.com/armosec/k8s-interface/cloudsupport"
|
||||
"github.com/armosec/k8s-interface/k8sinterface"
|
||||
@@ -44,46 +44,88 @@ func NewK8sResourceHandler(k8s *k8sinterface.KubernetesApi, fieldSelector IField
|
||||
}
|
||||
}
|
||||
|
||||
func (k8sHandler *K8sResourceHandler) GetResources(frameworks []reporthandling.Framework, designator *armotypes.PortalDesignator) (*cautils.K8SResources, map[string]workloadinterface.IMetadata, error) {
|
||||
func (k8sHandler *K8sResourceHandler) GetResources(sessionObj *cautils.OPASessionObj, designator *armotypes.PortalDesignator) (*cautils.K8SResources, map[string]workloadinterface.IMetadata, *cautils.ArmoResources, error) {
|
||||
allResources := map[string]workloadinterface.IMetadata{}
|
||||
|
||||
// get k8s resources
|
||||
logger.L().Info("Accessing Kubernetes objects")
|
||||
|
||||
cautils.StartSpinner()
|
||||
|
||||
resourceToControl := make(map[string][]string)
|
||||
// build resources map
|
||||
// map resources based on framework required resources: map["/group/version/kind"][]<k8s workloads ids>
|
||||
k8sResourcesMap := setResourceMap(frameworks)
|
||||
k8sResourcesMap := setK8sResourceMap(sessionObj.Policies)
|
||||
|
||||
// get namespace and labels from designator (ignore cluster labels)
|
||||
_, namespace, labels := armotypes.DigestPortalDesignator(designator)
|
||||
|
||||
// pull k8s recourses
|
||||
armoResourceMap := setArmoResourceMap(sessionObj.Policies, resourceToControl)
|
||||
|
||||
// map of armo resources to control_ids
|
||||
sessionObj.ResourceToControlsMap = resourceToControl
|
||||
|
||||
if err := k8sHandler.pullResources(k8sResourcesMap, allResources, namespace, labels); err != nil {
|
||||
cautils.StopSpinner()
|
||||
return k8sResourcesMap, allResources, err
|
||||
return k8sResourcesMap, allResources, armoResourceMap, err
|
||||
}
|
||||
|
||||
if err := k8sHandler.registryAdaptors.collectImagesVulnerabilities(k8sResourcesMap, allResources); err != nil {
|
||||
logger.L().Warning("failed to collect image vulnerabilities", helpers.Error(err))
|
||||
numberOfWorkerNodes, err := k8sHandler.pullWorkerNodesNumber()
|
||||
|
||||
if err != nil {
|
||||
logger.L().Debug("failed to collect worker nodes number", helpers.Error(err))
|
||||
} else {
|
||||
sessionObj.Metadata.ClusterMetadata.NumberOfWorkerNodes = numberOfWorkerNodes
|
||||
}
|
||||
|
||||
if err := k8sHandler.collectHostResources(allResources, k8sResourcesMap); err != nil {
|
||||
logger.L().Warning("failed to collect host scanner resources", helpers.Error(err))
|
||||
imgVulnResources := cautils.MapImageVulnResources(armoResourceMap)
|
||||
// check that controls use image vulnerability resources
|
||||
if len(imgVulnResources) > 0 {
|
||||
if err := k8sHandler.registryAdaptors.collectImagesVulnerabilities(k8sResourcesMap, allResources, armoResourceMap); err != nil {
|
||||
logger.L().Warning("failed to collect image vulnerabilities", helpers.Error(err))
|
||||
}
|
||||
}
|
||||
|
||||
hostResources := cautils.MapHostResources(armoResourceMap)
|
||||
// check that controls use host sensor resources
|
||||
if len(hostResources) > 0 {
|
||||
if sessionObj.Metadata.ScanMetadata.HostScanner {
|
||||
infoMap, err := k8sHandler.collectHostResources(allResources, armoResourceMap)
|
||||
if err != nil {
|
||||
logger.L().Warning("failed to collect host scanner resources", helpers.Error(err))
|
||||
cautils.SetInfoMapForResources(err.Error(), hostResources, sessionObj.InfoMap)
|
||||
} else if k8sHandler.hostSensorHandler == nil {
|
||||
// using hostSensor mock
|
||||
cautils.SetInfoMapForResources("failed to init host scanner", hostResources, sessionObj.InfoMap)
|
||||
} else {
|
||||
sessionObj.InfoMap = infoMap
|
||||
}
|
||||
} else {
|
||||
cautils.SetInfoMapForResources("enable-host-scan flag not used", hostResources, sessionObj.InfoMap)
|
||||
}
|
||||
}
|
||||
|
||||
if err := k8sHandler.collectRbacResources(allResources); err != nil {
|
||||
logger.L().Warning("failed to collect rbac resources", helpers.Error(err))
|
||||
}
|
||||
if err := getCloudProviderDescription(allResources, k8sResourcesMap); err != nil {
|
||||
logger.L().Warning("failed to collect cloud data", helpers.Error(err))
|
||||
|
||||
cloudResources := cautils.MapCloudResources(armoResourceMap)
|
||||
// check that controls use cloud resources
|
||||
if len(cloudResources) > 0 {
|
||||
provider, err := getCloudProviderDescription(allResources, armoResourceMap)
|
||||
if err != nil {
|
||||
cautils.SetInfoMapForResources(err.Error(), cloudResources, sessionObj.InfoMap)
|
||||
logger.L().Warning("failed to collect cloud data", helpers.Error(err))
|
||||
}
|
||||
if provider != "" {
|
||||
sessionObj.Metadata.ClusterMetadata.CloudProvider = provider
|
||||
}
|
||||
}
|
||||
|
||||
cautils.StopSpinner()
|
||||
logger.L().Success("Accessed to Kubernetes objects")
|
||||
|
||||
return k8sResourcesMap, allResources, nil
|
||||
return k8sResourcesMap, allResources, armoResourceMap, nil
|
||||
}
|
||||
|
||||
func (k8sHandler *K8sResourceHandler) GetClusterAPIServerInfo() *version.Info {
|
||||
@@ -180,12 +222,11 @@ func ConvertMapListToMeta(resourceMap []map[string]interface{}) []workloadinterf
|
||||
// }
|
||||
// return nil
|
||||
// }
|
||||
func (k8sHandler *K8sResourceHandler) collectHostResources(allResources map[string]workloadinterface.IMetadata, resourcesMap *cautils.K8SResources) error {
|
||||
func (k8sHandler *K8sResourceHandler) collectHostResources(allResources map[string]workloadinterface.IMetadata, armoResourceMap *cautils.ArmoResources) (map[string]apis.StatusInfo, error) {
|
||||
logger.L().Debug("Collecting host scanner resources")
|
||||
|
||||
hostResources, err := k8sHandler.hostSensorHandler.CollectResources()
|
||||
hostResources, infoMap, err := k8sHandler.hostSensorHandler.CollectResources()
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for rscIdx := range hostResources {
|
||||
@@ -193,13 +234,13 @@ func (k8sHandler *K8sResourceHandler) collectHostResources(allResources map[stri
|
||||
groupResource := k8sinterface.JoinResourceTriplets(group, version, hostResources[rscIdx].GetKind())
|
||||
allResources[hostResources[rscIdx].GetID()] = &hostResources[rscIdx]
|
||||
|
||||
grpResourceList, ok := (*resourcesMap)[groupResource]
|
||||
grpResourceList, ok := (*armoResourceMap)[groupResource]
|
||||
if !ok {
|
||||
grpResourceList = make([]string, 0)
|
||||
}
|
||||
(*resourcesMap)[groupResource] = append(grpResourceList, hostResources[rscIdx].GetID())
|
||||
(*armoResourceMap)[groupResource] = append(grpResourceList, hostResources[rscIdx].GetID())
|
||||
}
|
||||
return nil
|
||||
return infoMap, nil
|
||||
}
|
||||
|
||||
func (k8sHandler *K8sResourceHandler) collectRbacResources(allResources map[string]workloadinterface.IMetadata) error {
|
||||
@@ -218,20 +259,19 @@ func (k8sHandler *K8sResourceHandler) collectRbacResources(allResources map[stri
|
||||
return nil
|
||||
}
|
||||
|
||||
func getCloudProviderDescription(allResources map[string]workloadinterface.IMetadata, k8sResourcesMap *cautils.K8SResources) error {
|
||||
func getCloudProviderDescription(allResources map[string]workloadinterface.IMetadata, armoResourceMap *cautils.ArmoResources) (string, error) {
|
||||
logger.L().Debug("Collecting cloud data")
|
||||
|
||||
cloudProvider := initCloudProvider()
|
||||
cluster := cloudProvider.getKubeCluster()
|
||||
clusterName := cloudProvider.getKubeClusterName()
|
||||
provider := getCloudProvider()
|
||||
region, err := cloudProvider.getRegion(cluster, provider)
|
||||
if err != nil {
|
||||
return err
|
||||
return provider, err
|
||||
}
|
||||
project, err := cloudProvider.getProject(cluster, provider)
|
||||
if err != nil {
|
||||
return err
|
||||
return provider, err
|
||||
}
|
||||
|
||||
if provider != "" {
|
||||
@@ -242,17 +282,33 @@ func getCloudProviderDescription(allResources map[string]workloadinterface.IMeta
|
||||
// Return error with useful info on how to configure credentials for getting cloud provider info
|
||||
switch provider {
|
||||
case "gke":
|
||||
return fmt.Errorf("could not get descriptive information about gke cluster: %s using sdk client. See https://developers.google.com/accounts/docs/application-default-credentials for more information", cluster)
|
||||
return provider, fmt.Errorf("could not get descriptive information about gke cluster: %s using sdk client. See https://developers.google.com/accounts/docs/application-default-credentials for more information", cluster)
|
||||
case "eks":
|
||||
return fmt.Errorf("could not get descriptive information about eks cluster: %s using sdk client. Check out how to configure credentials in https://docs.aws.amazon.com/sdk-for-go/api/", cluster)
|
||||
return provider, fmt.Errorf("could not get descriptive information about eks cluster: %s using sdk client. Check out how to configure credentials in https://docs.aws.amazon.com/sdk-for-go/api/", cluster)
|
||||
case "aks":
|
||||
return fmt.Errorf("could not get descriptive information about aks cluster: %s. %v", cluster, err.Error())
|
||||
return provider, fmt.Errorf("could not get descriptive information about aks cluster: %s. %v", cluster, err.Error())
|
||||
}
|
||||
return err
|
||||
return provider, err
|
||||
}
|
||||
allResources[wl.GetID()] = wl
|
||||
(*k8sResourcesMap)[fmt.Sprintf("%s/%s", wl.GetApiVersion(), wl.GetKind())] = []string{wl.GetID()}
|
||||
(*armoResourceMap)[fmt.Sprintf("%s/%s", wl.GetApiVersion(), wl.GetKind())] = []string{wl.GetID()}
|
||||
}
|
||||
return nil
|
||||
return provider, nil
|
||||
|
||||
}
|
||||
|
||||
func (k8sHandler *K8sResourceHandler) pullWorkerNodesNumber() (int, error) {
|
||||
// labels used for control plane
|
||||
listOptions := metav1.ListOptions{
|
||||
LabelSelector: "!node-role.kubernetes.io/control-plane,!node-role.kubernetes.io/master",
|
||||
}
|
||||
nodesList, err := k8sHandler.k8s.KubernetesClient.CoreV1().Nodes().List(context.TODO(), listOptions)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
nodesNumber := 0
|
||||
if nodesList != nil {
|
||||
nodesNumber = len(nodesList.Items)
|
||||
}
|
||||
return nodesNumber, nil
|
||||
}
|
||||
|
||||
@@ -4,14 +4,23 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/armosec/kubescape/core/cautils"
|
||||
"github.com/armosec/kubescape/core/pkg/hostsensorutils"
|
||||
"github.com/armosec/opa-utils/reporthandling"
|
||||
"k8s.io/utils/strings/slices"
|
||||
|
||||
"github.com/armosec/k8s-interface/k8sinterface"
|
||||
)
|
||||
|
||||
func setResourceMap(frameworks []reporthandling.Framework) *cautils.K8SResources {
|
||||
var (
|
||||
ClusterDescribe = "ClusterDescribe"
|
||||
|
||||
MapResourceToApiGroupCloud = map[string][]string{
|
||||
ClusterDescribe: {"container.googleapis.com/v1", "eks.amazonaws.com/v1"}}
|
||||
)
|
||||
|
||||
func setK8sResourceMap(frameworks []reporthandling.Framework) *cautils.K8SResources {
|
||||
k8sResources := make(cautils.K8SResources)
|
||||
complexMap := setComplexResourceMap(frameworks)
|
||||
complexMap := setComplexK8sResourceMap(frameworks)
|
||||
for group := range complexMap {
|
||||
for version := range complexMap[group] {
|
||||
for resource := range complexMap[group][version] {
|
||||
@@ -25,33 +34,87 @@ func setResourceMap(frameworks []reporthandling.Framework) *cautils.K8SResources
|
||||
return &k8sResources
|
||||
}
|
||||
|
||||
func setArmoResourceMap(frameworks []reporthandling.Framework, resourceToControl map[string][]string) *cautils.ArmoResources {
|
||||
armoResources := make(cautils.ArmoResources)
|
||||
complexMap := setComplexArmoResourceMap(frameworks, resourceToControl)
|
||||
for group := range complexMap {
|
||||
for version := range complexMap[group] {
|
||||
for resource := range complexMap[group][version] {
|
||||
groupResources := k8sinterface.ResourceGroupToString(group, version, resource)
|
||||
for _, groupResource := range groupResources {
|
||||
armoResources[groupResource] = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return &armoResources
|
||||
}
|
||||
|
||||
func convertComplexResourceMap(frameworks []reporthandling.Framework) map[string]map[string]map[string]interface{} {
|
||||
k8sResources := make(map[string]map[string]map[string]interface{})
|
||||
for _, framework := range frameworks {
|
||||
for _, control := range framework.Controls {
|
||||
for _, rule := range control.Rules {
|
||||
for _, match := range rule.Match {
|
||||
insertK8sResources(k8sResources, match)
|
||||
insertResources(k8sResources, match)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return k8sResources
|
||||
}
|
||||
func setComplexResourceMap(frameworks []reporthandling.Framework) map[string]map[string]map[string]interface{} {
|
||||
func setComplexK8sResourceMap(frameworks []reporthandling.Framework) map[string]map[string]map[string]interface{} {
|
||||
k8sResources := make(map[string]map[string]map[string]interface{})
|
||||
for _, framework := range frameworks {
|
||||
for _, control := range framework.Controls {
|
||||
for _, rule := range control.Rules {
|
||||
for _, match := range rule.Match {
|
||||
insertK8sResources(k8sResources, match)
|
||||
insertResources(k8sResources, match)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return k8sResources
|
||||
}
|
||||
func insertK8sResources(k8sResources map[string]map[string]map[string]interface{}, match reporthandling.RuleMatchObjects) {
|
||||
|
||||
// [group][versionn][resource]
|
||||
func setComplexArmoResourceMap(frameworks []reporthandling.Framework, resourceToControls map[string][]string) map[string]map[string]map[string]interface{} {
|
||||
k8sResources := make(map[string]map[string]map[string]interface{})
|
||||
for _, framework := range frameworks {
|
||||
for _, control := range framework.Controls {
|
||||
for _, rule := range control.Rules {
|
||||
for _, match := range rule.DynamicMatch {
|
||||
insertArmoResourcesAndControls(k8sResources, match, resourceToControls, control)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return k8sResources
|
||||
}
|
||||
|
||||
func mapArmoResourceToApiGroup(resource string) []string {
|
||||
if val, ok := hostsensorutils.MapResourceToApiGroup[resource]; ok {
|
||||
return []string{val}
|
||||
}
|
||||
return MapResourceToApiGroupCloud[resource]
|
||||
}
|
||||
|
||||
func insertControls(resource string, resourceToControl map[string][]string, control reporthandling.Control) {
|
||||
armoResources := mapArmoResourceToApiGroup(resource)
|
||||
for _, armoResource := range armoResources {
|
||||
group, version := k8sinterface.SplitApiVersion(armoResource)
|
||||
r := k8sinterface.JoinResourceTriplets(group, version, resource)
|
||||
if _, ok := resourceToControl[r]; !ok {
|
||||
resourceToControl[r] = append(resourceToControl[r], control.ControlID)
|
||||
} else {
|
||||
if !slices.Contains(resourceToControl[r], control.ControlID) {
|
||||
resourceToControl[r] = append(resourceToControl[r], control.ControlID)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func insertResources(k8sResources map[string]map[string]map[string]interface{}, match reporthandling.RuleMatchObjects) {
|
||||
for _, apiGroup := range match.APIGroups {
|
||||
if v, ok := k8sResources[apiGroup]; !ok || v == nil {
|
||||
k8sResources[apiGroup] = make(map[string]map[string]interface{})
|
||||
@@ -69,6 +132,25 @@ func insertK8sResources(k8sResources map[string]map[string]map[string]interface{
|
||||
}
|
||||
}
|
||||
|
||||
func insertArmoResourcesAndControls(k8sResources map[string]map[string]map[string]interface{}, match reporthandling.RuleMatchObjects, resourceToControl map[string][]string, control reporthandling.Control) {
|
||||
for _, apiGroup := range match.APIGroups {
|
||||
if v, ok := k8sResources[apiGroup]; !ok || v == nil {
|
||||
k8sResources[apiGroup] = make(map[string]map[string]interface{})
|
||||
}
|
||||
for _, apiVersions := range match.APIVersions {
|
||||
if v, ok := k8sResources[apiGroup][apiVersions]; !ok || v == nil {
|
||||
k8sResources[apiGroup][apiVersions] = make(map[string]interface{})
|
||||
}
|
||||
for _, resource := range match.Resources {
|
||||
if _, ok := k8sResources[apiGroup][apiVersions][resource]; !ok {
|
||||
k8sResources[apiGroup][apiVersions][resource] = nil
|
||||
}
|
||||
insertControls(resource, resourceToControl, control)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func getGroupNVersion(apiVersion string) (string, string) {
|
||||
gv := strings.Split(apiVersion, "/")
|
||||
group, version := "", ""
|
||||
|
||||
@@ -13,7 +13,7 @@ func TestGetK8sResources(t *testing.T) {
|
||||
func TestSetResourceMap(t *testing.T) {
|
||||
k8sinterface.InitializeMapResourcesMock()
|
||||
framework := reporthandling.MockFrameworkA()
|
||||
k8sResources := setResourceMap([]reporthandling.Framework{*framework})
|
||||
k8sResources := setK8sResourceMap([]reporthandling.Framework{*framework})
|
||||
resources := k8sinterface.ResourceGroupToString("*", "v1", "Pod")
|
||||
if len(resources) == 0 {
|
||||
t.Error("expected resources")
|
||||
@@ -43,9 +43,9 @@ func TestInsertK8sResources(t *testing.T) {
|
||||
APIVersions: []string{"v1"},
|
||||
Resources: []string{"secrets"},
|
||||
}
|
||||
insertK8sResources(k8sResources, match1)
|
||||
insertK8sResources(k8sResources, match2)
|
||||
insertK8sResources(k8sResources, match3)
|
||||
insertResources(k8sResources, match1)
|
||||
insertResources(k8sResources, match2)
|
||||
insertResources(k8sResources, match3)
|
||||
|
||||
apiGroup1, ok := k8sResources["apps"]
|
||||
if !ok {
|
||||
|
||||
@@ -34,7 +34,7 @@ func NewRegistryAdaptors() (*RegistryAdaptors, error) {
|
||||
return registryAdaptors, nil
|
||||
}
|
||||
|
||||
func (registryAdaptors *RegistryAdaptors) collectImagesVulnerabilities(k8sResourcesMap *cautils.K8SResources, allResources map[string]workloadinterface.IMetadata) error {
|
||||
func (registryAdaptors *RegistryAdaptors) collectImagesVulnerabilities(k8sResourcesMap *cautils.K8SResources, allResources map[string]workloadinterface.IMetadata, armoResourceMap *cautils.ArmoResources) error {
|
||||
logger.L().Debug("Collecting images vulnerabilities")
|
||||
|
||||
// list cluster images
|
||||
@@ -64,7 +64,7 @@ func (registryAdaptors *RegistryAdaptors) collectImagesVulnerabilities(k8sResour
|
||||
for i := range metaObjs {
|
||||
allResources[metaObjs[i].GetID()] = metaObjs[i]
|
||||
}
|
||||
(*k8sResourcesMap)[k8sinterface.JoinResourceTriplets(ImagevulnerabilitiesObjectGroup, ImagevulnerabilitiesObjectVersion, ImagevulnerabilitiesObjectKind)] = workloadinterface.ListMetaIDs(metaObjs)
|
||||
(*armoResourceMap)[k8sinterface.JoinResourceTriplets(ImagevulnerabilitiesObjectGroup, ImagevulnerabilitiesObjectVersion, ImagevulnerabilitiesObjectKind)] = workloadinterface.ListMetaIDs(metaObjs)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -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,139 @@ 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)
|
||||
body, err := getter.HttpGetter(&http.Client{}, g.treeAPI(), g.getHeaders())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -135,14 +212,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 +237,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), ".")
|
||||
}
|
||||
|
||||
140
core/pkg/resourcehandler/repositoryscanner_test.go
Normal file
140
core/pkg/resourcehandler/repositoryscanner_test.go
Normal 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)
|
||||
}
|
||||
}
|
||||
@@ -4,11 +4,10 @@ import (
|
||||
"github.com/armosec/armoapi-go/armotypes"
|
||||
"github.com/armosec/k8s-interface/workloadinterface"
|
||||
"github.com/armosec/kubescape/core/cautils"
|
||||
"github.com/armosec/opa-utils/reporthandling"
|
||||
"k8s.io/apimachinery/pkg/version"
|
||||
)
|
||||
|
||||
type IResourceHandler interface {
|
||||
GetResources([]reporthandling.Framework, *armotypes.PortalDesignator) (*cautils.K8SResources, map[string]workloadinterface.IMetadata, error)
|
||||
GetResources(*cautils.OPASessionObj, *armotypes.PortalDesignator) (*cautils.K8SResources, map[string]workloadinterface.IMetadata, *cautils.ArmoResources, error)
|
||||
GetClusterAPIServerInfo() *version.Info
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
66
core/pkg/resourcehandler/urlloader_test.go
Normal file
66
core/pkg/resourcehandler/urlloader_test.go
Normal 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())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
"github.com/armosec/opa-utils/reporthandling/results/v1/reportsummary"
|
||||
)
|
||||
|
||||
func generateRow(controlSummary reportsummary.IControlSummary) []string {
|
||||
func generateRow(controlSummary reportsummary.IControlSummary, infoToPrintInfoMap map[string]string) []string {
|
||||
row := []string{controlSummary.GetName()}
|
||||
row = append(row, fmt.Sprintf("%d", controlSummary.NumberOfResources().Failed()))
|
||||
row = append(row, fmt.Sprintf("%d", controlSummary.NumberOfResources().Excluded()))
|
||||
@@ -15,8 +15,14 @@ func generateRow(controlSummary reportsummary.IControlSummary) []string {
|
||||
|
||||
if !controlSummary.GetStatus().IsSkipped() {
|
||||
row = append(row, fmt.Sprintf("%d", int(controlSummary.GetScore()))+"%")
|
||||
row = append(row, "")
|
||||
} else {
|
||||
row = append(row, "skipped")
|
||||
row = append(row, string(controlSummary.GetStatus().Status()))
|
||||
if controlSummary.GetStatus().IsSkipped() {
|
||||
row = append(row, infoToPrintInfoMap[controlSummary.GetStatus().Info()])
|
||||
} else {
|
||||
row = append(row, "")
|
||||
}
|
||||
}
|
||||
return row
|
||||
}
|
||||
@@ -32,5 +38,5 @@ func getSortedControlsNames(controls reportsummary.ControlSummaries) []string {
|
||||
}
|
||||
|
||||
func getControlTableHeaders() []string {
|
||||
return []string{"CONTROL NAME", "FAILED RESOURCES", "EXCLUDED RESOURCES", "ALL RESOURCES", "% RISK-SCORE"}
|
||||
return []string{"CONTROL NAME", "FAILED RESOURCES", "EXCLUDED RESOURCES", "ALL RESOURCES", "% RISK-SCORE", "INFO"}
|
||||
}
|
||||
|
||||
@@ -32,7 +32,6 @@ func (jsonPrinter *JsonPrinter) ActionPrint(opaSessionObj *cautils.OPASessionObj
|
||||
if err != nil {
|
||||
logger.L().Fatal("failed to Marshal posture report object")
|
||||
}
|
||||
jsonPrinter.writer.Write(r)
|
||||
|
||||
logOUtputFile(jsonPrinter.writer.Name())
|
||||
if _, err := jsonPrinter.writer.Write(r); err != nil {
|
||||
|
||||
@@ -54,15 +54,37 @@ func (pdfPrinter *PdfPrinter) SetWriter(outputFile string) {
|
||||
func (pdfPrinter *PdfPrinter) Score(score float32) {
|
||||
fmt.Fprintf(os.Stderr, "\nOverall risk-score (0- Excellent, 100- All failed): %d\n", int(score))
|
||||
}
|
||||
func (pdfPrinter *PdfPrinter) printInfo(m pdf.Maroto, summaryDetails *reportsummary.SummaryDetails, infoMap map[string]string) {
|
||||
emptyRowCounter := 1
|
||||
for key, val := range infoMap {
|
||||
if val != "" {
|
||||
m.Row(5, func() {
|
||||
m.Col(1, func() {
|
||||
m.Text(fmt.Sprintf("%v", val))
|
||||
})
|
||||
m.Col(12, func() {
|
||||
m.Text(fmt.Sprintf("%v", key))
|
||||
})
|
||||
})
|
||||
if emptyRowCounter < len(infoMap) {
|
||||
m.Row(2.5, func() {})
|
||||
emptyRowCounter++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (pdfPrinter *PdfPrinter) ActionPrint(opaSessionObj *cautils.OPASessionObj) {
|
||||
pdfPrinter.sortedControlNames = getSortedControlsNames(opaSessionObj.Report.SummaryDetails.Controls)
|
||||
|
||||
infoToPrintInfoMap := mapInfoToPrintInfo(opaSessionObj.Report.SummaryDetails.Controls)
|
||||
m := pdf.NewMaroto(consts.Portrait, consts.A4)
|
||||
pdfPrinter.printHeader(m)
|
||||
pdfPrinter.printFramework(m, opaSessionObj.Report.SummaryDetails.ListFrameworks())
|
||||
pdfPrinter.printTable(m, &opaSessionObj.Report.SummaryDetails)
|
||||
pdfPrinter.printFinalResult(m, &opaSessionObj.Report.SummaryDetails)
|
||||
pdfPrinter.printInfo(m, &opaSessionObj.Report.SummaryDetails, infoToPrintInfoMap)
|
||||
|
||||
// Extrat output buffer.
|
||||
outBuff, err := m.Output()
|
||||
@@ -129,12 +151,13 @@ func (pdfPrinter *PdfPrinter) printFramework(m pdf.Maroto, frameworks []reportsu
|
||||
// Create pdf table
|
||||
func (pdfPrinter *PdfPrinter) printTable(m pdf.Maroto, summaryDetails *reportsummary.SummaryDetails) {
|
||||
headers := getControlTableHeaders()
|
||||
infoToPrintInfoMap := mapInfoToPrintInfo(summaryDetails.Controls)
|
||||
controls := make([][]string, len(pdfPrinter.sortedControlNames))
|
||||
for i := range controls {
|
||||
controls[i] = make([]string, len(headers))
|
||||
}
|
||||
for i := 0; i < len(pdfPrinter.sortedControlNames); i++ {
|
||||
controls[i] = generateRow(summaryDetails.Controls.GetControl(reportsummary.EControlCriteriaName, pdfPrinter.sortedControlNames[i]))
|
||||
controls[i] = generateRow(summaryDetails.Controls.GetControl(reportsummary.EControlCriteriaName, pdfPrinter.sortedControlNames[i]), infoToPrintInfoMap)
|
||||
}
|
||||
|
||||
m.TableList(headers, controls, props.TableList{
|
||||
|
||||
@@ -83,10 +83,17 @@ func (prettyPrinter *PrettyPrinter) printTitle(controlSummary reportsummary.ICon
|
||||
cautils.FailureDisplay(prettyPrinter.writer, "failed %v\n", emoji.SadButRelievedFace)
|
||||
case apis.StatusExcluded:
|
||||
cautils.WarningDisplay(prettyPrinter.writer, "excluded %v\n", emoji.NeutralFace)
|
||||
case apis.StatusIrrelevant:
|
||||
cautils.SuccessDisplay(prettyPrinter.writer, "irrelevant %v\n", emoji.ConfusedFace)
|
||||
case apis.StatusError:
|
||||
cautils.WarningDisplay(prettyPrinter.writer, "error %v\n", emoji.ConfusedFace)
|
||||
default:
|
||||
cautils.SuccessDisplay(prettyPrinter.writer, "passed %v\n", emoji.ThumbsUp)
|
||||
}
|
||||
cautils.DescriptionDisplay(prettyPrinter.writer, "Description: %s\n", controlSummary.GetDescription())
|
||||
if controlSummary.GetStatus().Info() != "" {
|
||||
cautils.WarningDisplay(prettyPrinter.writer, "Reason: %v\n", controlSummary.GetStatus().Info())
|
||||
}
|
||||
}
|
||||
func (prettyPrinter *PrettyPrinter) printResources(controlSummary reportsummary.IControlSummary, allResources map[string]workloadinterface.IMetadata) {
|
||||
|
||||
@@ -166,6 +173,7 @@ func generateFooter(summaryDetails *reportsummary.SummaryDetails) []string {
|
||||
row = append(row, fmt.Sprintf("%d", summaryDetails.NumberOfResources().Excluded()))
|
||||
row = append(row, fmt.Sprintf("%d", summaryDetails.NumberOfResources().All()))
|
||||
row = append(row, fmt.Sprintf("%.2f%s", summaryDetails.Score, "%"))
|
||||
row = append(row, " ")
|
||||
|
||||
return row
|
||||
}
|
||||
@@ -175,30 +183,37 @@ func (prettyPrinter *PrettyPrinter) printSummaryTable(summaryDetails *reportsumm
|
||||
summaryTable.SetAutoWrapText(false)
|
||||
summaryTable.SetHeader(getControlTableHeaders())
|
||||
summaryTable.SetHeaderLine(true)
|
||||
alignments := []int{tablewriter.ALIGN_LEFT, tablewriter.ALIGN_CENTER, tablewriter.ALIGN_CENTER, tablewriter.ALIGN_CENTER, tablewriter.ALIGN_CENTER}
|
||||
alignments := []int{tablewriter.ALIGN_LEFT, tablewriter.ALIGN_CENTER, tablewriter.ALIGN_CENTER, tablewriter.ALIGN_CENTER, tablewriter.ALIGN_CENTER, tablewriter.ALIGN_CENTER}
|
||||
summaryTable.SetColumnAlignment(alignments)
|
||||
|
||||
infoToPrintInfoMap := mapInfoToPrintInfo(summaryDetails.Controls)
|
||||
for i := 0; i < len(prettyPrinter.sortedControlNames); i++ {
|
||||
summaryTable.Append(generateRow(summaryDetails.Controls.GetControl(reportsummary.EControlCriteriaName, prettyPrinter.sortedControlNames[i])))
|
||||
summaryTable.Append(generateRow(summaryDetails.Controls.GetControl(reportsummary.EControlCriteriaName, prettyPrinter.sortedControlNames[i]), infoToPrintInfoMap))
|
||||
}
|
||||
|
||||
summaryTable.SetFooter(generateFooter(summaryDetails))
|
||||
|
||||
// summaryTable.SetFooter(generateFooter())
|
||||
cautils.InfoTextDisplay(prettyPrinter.writer, frameworksScoresToString(summaryDetails.ListFrameworks()))
|
||||
summaryTable.Render()
|
||||
|
||||
prettyPrinter.printInfo(infoToPrintInfoMap)
|
||||
// For control scan framework will be nil
|
||||
cautils.InfoTextDisplay(prettyPrinter.writer, frameworksScoresToString(summaryDetails.ListFrameworks()))
|
||||
}
|
||||
|
||||
func (prettyPrinter *PrettyPrinter) printInfo(infoToPrintInfoMap map[string]string) {
|
||||
for info, stars := range infoToPrintInfoMap {
|
||||
cautils.WarningDisplay(prettyPrinter.writer, fmt.Sprintf("%s - %s\n", stars, info))
|
||||
}
|
||||
}
|
||||
|
||||
func frameworksScoresToString(frameworks []reportsummary.IFrameworkSummary) string {
|
||||
if len(frameworks) == 1 {
|
||||
if frameworks[0].GetName() != "" {
|
||||
return fmt.Sprintf("FRAMEWORK %s\n", frameworks[0].GetName())
|
||||
return fmt.Sprintf("\nFRAMEWORK %s\n", frameworks[0].GetName())
|
||||
// cautils.InfoTextDisplay(prettyPrinter.writer, ))
|
||||
}
|
||||
} else if len(frameworks) > 1 {
|
||||
p := "FRAMEWORKS: "
|
||||
p := "\nFRAMEWORKS: "
|
||||
i := 0
|
||||
for ; i < len(frameworks)-1; i++ {
|
||||
p += fmt.Sprintf("%s (risk: %.2f), ", frameworks[i].GetName(), frameworks[i].GetScore())
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"github.com/armosec/kubescape/core/cautils/logger"
|
||||
"github.com/armosec/kubescape/core/cautils/logger/helpers"
|
||||
"github.com/armosec/opa-utils/reporthandling"
|
||||
"github.com/armosec/opa-utils/reporthandling/results/v1/reportsummary"
|
||||
"github.com/armosec/opa-utils/reporthandling/results/v1/resourcesresults"
|
||||
reporthandlingv2 "github.com/armosec/opa-utils/reporthandling/v2"
|
||||
)
|
||||
@@ -25,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
|
||||
}
|
||||
@@ -38,7 +38,22 @@ func finalizeResults(results []resourcesresults.Result, resourcesResult map[stri
|
||||
}
|
||||
}
|
||||
|
||||
func finalizeResources(resources []reporthandling.Resource, results []resourcesresults.Result, allResources map[string]workloadinterface.IMetadata) {
|
||||
func mapInfoToPrintInfo(controls reportsummary.ControlSummaries) map[string]string {
|
||||
infoToPrintInfoMap := make(map[string]string)
|
||||
starCount := "*"
|
||||
for _, control := range controls {
|
||||
if control.GetStatus().IsSkipped() && control.GetStatus().Info() != "" {
|
||||
if _, ok := infoToPrintInfoMap[control.GetStatus().Info()]; !ok {
|
||||
infoToPrintInfoMap[control.GetStatus().Info()] = starCount
|
||||
starCount += starCount
|
||||
}
|
||||
}
|
||||
}
|
||||
return infoToPrintInfoMap
|
||||
}
|
||||
|
||||
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())
|
||||
@@ -46,6 +61,7 @@ func finalizeResources(resources []reporthandling.Resource, results []resourcesr
|
||||
resources = append(resources, r)
|
||||
}
|
||||
}
|
||||
return resources
|
||||
}
|
||||
|
||||
func logOUtputFile(fileName string) {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
@@ -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 != "" {
|
||||
|
||||
@@ -11,7 +11,6 @@ import (
|
||||
"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,33 +41,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
|
||||
|
||||
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) {
|
||||
@@ -79,26 +74,43 @@ 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)
|
||||
counter := 0
|
||||
func (report *ReportEventReceiver) GetURL() string {
|
||||
u := url.URL{}
|
||||
u.Scheme = "https"
|
||||
u.Host = getter.GetArmoAPIConnector().GetFrontendURL()
|
||||
|
||||
for _, v := range postureReport.Resources {
|
||||
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
|
||||
reportCounter := 0
|
||||
|
||||
for _, v := range opaSessionObj.Report.Resources {
|
||||
r, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to unmarshal resource '%s', reason: %v", v.ResourceID, err)
|
||||
@@ -107,10 +119,10 @@ func (report *ReportEventReceiver) sendResources(host string, postureReport *rep
|
||||
if counter+len(r) >= MAX_REPORT_SIZE && len(splittedPostureReport.Resources) > 0 {
|
||||
|
||||
// send report
|
||||
if err := report.sendReport(host, splittedPostureReport, *reportCounter, false); err != nil {
|
||||
if err := report.sendReport(host, splittedPostureReport, reportCounter, false); err != nil {
|
||||
return err
|
||||
}
|
||||
*reportCounter++
|
||||
reportCounter++
|
||||
|
||||
// delete resources
|
||||
splittedPostureReport.Resources = []reporthandling.Resource{}
|
||||
@@ -124,7 +136,7 @@ func (report *ReportEventReceiver) sendResources(host string, postureReport *rep
|
||||
splittedPostureReport.Resources = append(splittedPostureReport.Resources, v)
|
||||
}
|
||||
|
||||
for _, v := range postureReport.Results {
|
||||
for _, v := range opaSessionObj.Report.Results {
|
||||
r, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to unmarshal resource '%s', reason: %v", v.GetResourceID(), err)
|
||||
@@ -133,10 +145,10 @@ func (report *ReportEventReceiver) sendResources(host string, postureReport *rep
|
||||
if counter+len(r) >= MAX_REPORT_SIZE && len(splittedPostureReport.Results) > 0 {
|
||||
|
||||
// send report
|
||||
if err := report.sendReport(host, splittedPostureReport, *reportCounter, false); err != nil {
|
||||
if err := report.sendReport(host, splittedPostureReport, reportCounter, false); err != nil {
|
||||
return err
|
||||
}
|
||||
*reportCounter++
|
||||
reportCounter++
|
||||
|
||||
// delete results
|
||||
splittedPostureReport.Results = []resourcesresults.Result{}
|
||||
@@ -150,7 +162,7 @@ func (report *ReportEventReceiver) sendResources(host string, postureReport *rep
|
||||
splittedPostureReport.Results = append(splittedPostureReport.Results, v)
|
||||
}
|
||||
|
||||
return report.sendReport(host, splittedPostureReport, *reportCounter, true)
|
||||
return report.sendReport(host, splittedPostureReport, reportCounter, true)
|
||||
}
|
||||
|
||||
func (report *ReportEventReceiver) sendReport(host string, postureReport *reporthandlingv2.PostureReport, counter int, isLastReport bool) error {
|
||||
@@ -170,26 +182,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
|
||||
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ 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"
|
||||
@@ -33,17 +34,17 @@ func hostToString(host *url.URL, reportID string) string {
|
||||
return host.String()
|
||||
}
|
||||
|
||||
func setSubReport(postureReport *reporthandlingv2.PostureReport) *reporthandlingv2.PostureReport {
|
||||
func (report *ReportEventReceiver) setSubReport(opaSessionObj *cautils.OPASessionObj) *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,
|
||||
CustomerGUID: report.customerGUID,
|
||||
ClusterName: report.clusterName,
|
||||
ReportID: report.reportID,
|
||||
ReportGenerationTime: opaSessionObj.Report.ReportGenerationTime,
|
||||
SummaryDetails: opaSessionObj.Report.SummaryDetails,
|
||||
Attributes: opaSessionObj.Report.Attributes,
|
||||
ClusterCloudProvider: opaSessionObj.Report.ClusterCloudProvider,
|
||||
ClusterAPIServerInfo: opaSessionObj.Report.ClusterAPIServerInfo,
|
||||
Metadata: *opaSessionObj.Metadata,
|
||||
}
|
||||
}
|
||||
func iMetaToResource(obj workloadinterface.IMetadata) *reporthandling.Resource {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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/ARMO-header-2022.gif
Normal file
BIN
docs/ARMO-header-2022.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 14 MiB |
BIN
docs/ksfromcodetodeploy.png
Normal file
BIN
docs/ksfromcodetodeploy.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 967 KiB |
@@ -2,7 +2,7 @@
|
||||
|
||||
1. Deploy kubescape microservice
|
||||
```bash
|
||||
kubectl apply -f https://raw.githubusercontent.com/armosec/kubescape/master/httphandler/examples/prometheus/kubescape.yaml
|
||||
kubectl apply -f ks-deployment.yaml
|
||||
```
|
||||
> **NOTE** Make sure the configurations suit your cluster (e.g. `serviceType`, namespace, etc.)
|
||||
|
||||
|
||||
111
httphandler/examples/microservice/ks-deployment.yaml
Normal file
111
httphandler/examples/microservice/ks-deployment.yaml
Normal file
@@ -0,0 +1,111 @@
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
labels:
|
||||
app: kubescape
|
||||
name: ks-scanner
|
||||
---
|
||||
# ------------------- Kubescape Service Account ------------------- #
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
labels:
|
||||
app: kubescape
|
||||
name: kubescape-discovery
|
||||
namespace: ks-scanner
|
||||
---
|
||||
# ------------------- Kubescape Cluster Role & Cluster Role Binding ------------------- #
|
||||
kind: ClusterRole
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
metadata:
|
||||
name: kubescape-discovery-clusterroles
|
||||
# "namespace" omitted since ClusterRoles are not namespaced
|
||||
rules:
|
||||
- apiGroups: ["*"]
|
||||
resources: ["*"]
|
||||
verbs: ["get", "list", "describe"]
|
||||
|
||||
---
|
||||
kind: ClusterRoleBinding
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
metadata:
|
||||
name: kubescape-discovery-role-binding
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: ClusterRole
|
||||
name: kubescape-discovery-clusterroles
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: kubescape-discovery
|
||||
namespace: ks-scanner
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: kubescape-service
|
||||
namespace: ks-scanner
|
||||
labels:
|
||||
app: kubescape-service
|
||||
spec:
|
||||
type: NodePort
|
||||
ports:
|
||||
- port: 8080
|
||||
name: http
|
||||
targetPort: 8080
|
||||
protocol: TCP
|
||||
selector:
|
||||
app: kubescape
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: kubescape
|
||||
namespace: ks-scanner
|
||||
labels:
|
||||
app: kubescape
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: kubescape
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: kubescape
|
||||
spec:
|
||||
serviceAccountName: kubescape-discovery
|
||||
containers:
|
||||
- name: kubescape
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /livez
|
||||
port: 8080
|
||||
initialDelaySeconds: 3
|
||||
periodSeconds: 3
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /readyz
|
||||
port: 8080
|
||||
initialDelaySeconds: 3
|
||||
periodSeconds: 3
|
||||
image: quay.io/armosec/kubescape:prometheus.v2
|
||||
env:
|
||||
- name: KS_DEFAULT_CONFIGMAP_NAMESPACE
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
apiVersion: v1
|
||||
fieldPath: metadata.namespace
|
||||
ports:
|
||||
- containerPort: 8080
|
||||
name: http
|
||||
protocol: TCP
|
||||
command:
|
||||
- ksserver
|
||||
resources:
|
||||
requests:
|
||||
cpu: 10m
|
||||
memory: 100Mi
|
||||
limits:
|
||||
cpu: 500m
|
||||
memory: 500Mi
|
||||
@@ -107,7 +107,7 @@ spec:
|
||||
name: http
|
||||
protocol: TCP
|
||||
command:
|
||||
- kubescape
|
||||
- ksserver
|
||||
resources:
|
||||
requests:
|
||||
cpu: 10m
|
||||
|
||||
@@ -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.126
|
||||
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.118 // 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
|
||||
|
||||
@@ -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.118 h1:ZX1crwVQmo+sDv+jmTNLbDYfApUBzlgPhD8QI2GCJX0=
|
||||
github.com/armosec/opa-utils v0.0.118/go.mod h1:gap+EaLG5rnyqvIRGxtdNDC9y7VvoGNm90zK8Ls7avQ=
|
||||
github.com/armosec/opa-utils v0.0.126 h1:AVqVWdYcdaqZ8jK92nmZhVy43vh//ITBBjLA4722Vl8=
|
||||
github.com/armosec/opa-utils v0.0.126/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=
|
||||
|
||||
@@ -1,18 +1,23 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"github.com/armosec/kubescape/core/cautils"
|
||||
"github.com/armosec/opa-utils/reporthandling"
|
||||
)
|
||||
|
||||
type PostScanRequest struct {
|
||||
Format string `json:"format"` // Format results (table, json, junit ...) - default json
|
||||
ExcludedNamespaces []string `json:"excludedNamespaces"` // used for host scanner namespace
|
||||
IncludeNamespaces []string `json:"includeNamespaces"` // DEPRECATED?
|
||||
FailThreshold float32 `json:"failThreshold"` // Failure score threshold
|
||||
Submit bool `json:"submit"` // Submit results to Armo BE - default will
|
||||
HostScanner bool `json:"hostScanner"` // Deploy ARMO K8s host scanner to collect data from certain controls
|
||||
KeepLocal bool `json:"keepLocal"` // Do not submit results
|
||||
Account string `json:"account"` // account ID
|
||||
UseCachedArtifacts bool `json:"useCachedArtifacts"` // Use the cached artifacts instead of downloading
|
||||
Logger string `json:"-"` // logger level - debug/info/error - default is debug
|
||||
TargetType string `json:"-"` // framework/control - default is framework
|
||||
TargetNames []string `json:"-"` // default is all
|
||||
Format string `json:"format"` // Format results (table, json, junit ...) - default json
|
||||
Account string `json:"account"` // account ID
|
||||
Logger string `json:"-"` // logger level - debug/info/error - default is debug
|
||||
FailThreshold float32 `json:"failThreshold"` // Failure score threshold
|
||||
ExcludedNamespaces []string `json:"excludedNamespaces"` // used for host scanner namespace
|
||||
IncludeNamespaces []string `json:"includeNamespaces"` // DEPRECATED?
|
||||
TargetNames []string `json:"targetNames"` // default is all
|
||||
TargetType *reporthandling.NotificationPolicyKind `json:"targetType"` // framework/control - default is framework
|
||||
Submit cautils.BoolPtrFlag `json:"submit"` // Submit results to Armo BE - default will
|
||||
HostScanner cautils.BoolPtrFlag `json:"hostScanner"` // Deploy ARMO K8s host scanner to collect data from certain controls
|
||||
KeepLocal cautils.BoolPtrFlag `json:"keepLocal"` // Do not submit results
|
||||
UseCachedArtifacts cautils.BoolPtrFlag `json:"useCachedArtifacts"` // Use the cached artifacts instead of downloading
|
||||
// UseExceptions string // Load file with exceptions configuration
|
||||
// ControlsInputs string // Load file with inputs for controls
|
||||
// VerboseMode bool // Display all of the input resources and not only failed resources
|
||||
|
||||
@@ -5,31 +5,47 @@ import (
|
||||
|
||||
"github.com/armosec/kubescape/core/cautils"
|
||||
"github.com/armosec/kubescape/core/cautils/getter"
|
||||
"github.com/armosec/opa-utils/reporthandling"
|
||||
)
|
||||
|
||||
func (scanRequest *PostScanRequest) ToScanInfo() *cautils.ScanInfo {
|
||||
scanInfo := cautils.ScanInfo{}
|
||||
scanInfo.Account = scanRequest.Account
|
||||
scanInfo.ExcludedNamespaces = strings.Join(scanRequest.ExcludedNamespaces, ",")
|
||||
scanInfo.IncludeNamespaces = strings.Join(scanRequest.IncludeNamespaces, ",")
|
||||
scanInfo.FailThreshold = scanRequest.FailThreshold // TODO - handle default
|
||||
scanInfo := defaultScanInfo()
|
||||
|
||||
scanInfo.Format = scanRequest.Format // TODO - handle default
|
||||
if scanRequest.TargetType != nil && len(scanRequest.TargetNames) > 0 {
|
||||
if *scanRequest.TargetType == reporthandling.KindFramework {
|
||||
scanInfo.FrameworkScan = true
|
||||
}
|
||||
scanInfo.SetPolicyIdentifiers(scanRequest.TargetNames, *scanRequest.TargetType)
|
||||
scanInfo.ScanAll = false
|
||||
} else {
|
||||
scanInfo.ScanAll = true
|
||||
}
|
||||
|
||||
if scanRequest.UseCachedArtifacts {
|
||||
if scanRequest.Account != "" {
|
||||
scanInfo.Account = scanRequest.Account
|
||||
}
|
||||
if len(scanRequest.ExcludedNamespaces) > 0 {
|
||||
scanInfo.ExcludedNamespaces = strings.Join(scanRequest.ExcludedNamespaces, ",")
|
||||
}
|
||||
if len(scanRequest.IncludeNamespaces) > 0 {
|
||||
scanInfo.IncludeNamespaces = strings.Join(scanRequest.IncludeNamespaces, ",")
|
||||
}
|
||||
|
||||
if scanRequest.Format == "" {
|
||||
scanInfo.Format = scanRequest.Format // TODO - handle default
|
||||
}
|
||||
|
||||
if scanRequest.UseCachedArtifacts.Get() != nil && !*scanRequest.UseCachedArtifacts.Get() {
|
||||
scanInfo.UseArtifactsFrom = getter.DefaultLocalStore // Load files from cache (this will prevent kubescape fom downloading the artifacts every time)
|
||||
}
|
||||
|
||||
scanInfo.Local = scanRequest.KeepLocal
|
||||
scanInfo.Submit = scanRequest.Submit
|
||||
scanInfo.HostSensorEnabled.SetBool(scanRequest.HostScanner)
|
||||
if scanRequest.KeepLocal.Get() != nil {
|
||||
scanInfo.Local = *scanRequest.KeepLocal.Get() // Load files from cache (this will prevent kubescape fom downloading the artifacts every time)
|
||||
}
|
||||
if scanRequest.Submit.Get() != nil {
|
||||
scanInfo.Submit = *scanRequest.Submit.Get()
|
||||
}
|
||||
scanInfo.HostSensorEnabled = scanRequest.HostScanner
|
||||
|
||||
return &scanInfo
|
||||
return scanInfo
|
||||
}
|
||||
|
||||
/*
|
||||
err := clihandler.ScanCliSetup(&scanInfo)
|
||||
if err != nil {
|
||||
logger.L().Fatal(err.Error())
|
||||
}
|
||||
*/
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -110,6 +110,7 @@ func (handler *HTTPHandler) Scan(w http.ResponseWriter, r *http.Request) {
|
||||
}()
|
||||
|
||||
wg.Wait()
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write(response)
|
||||
}
|
||||
func (handler *HTTPHandler) Results(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -132,8 +133,8 @@ func (handler *HTTPHandler) Results(w http.ResponseWriter, r *http.Request) {
|
||||
if handler.state.isBusy() { // if requested ID is still scanning
|
||||
if scanID == handler.state.getID() {
|
||||
logger.L().Info("scan in process", helpers.String("ID", scanID))
|
||||
w.Write([]byte(handler.state.getID()))
|
||||
w.WriteHeader(http.StatusOK) // Should we return ok?
|
||||
w.Write([]byte(handler.state.getID()))
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -146,11 +147,11 @@ func (handler *HTTPHandler) Results(w http.ResponseWriter, r *http.Request) {
|
||||
defer removeResultsFile(scanID)
|
||||
}
|
||||
if res, err := readResultsFile(scanID); err != nil {
|
||||
w.Write([]byte(err.Error()))
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
w.Write([]byte(err.Error()))
|
||||
} else {
|
||||
w.Write(res)
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write(res)
|
||||
}
|
||||
case http.MethodDelete:
|
||||
logger.L().Info("deleting results", helpers.String("ID", scanID))
|
||||
|
||||
@@ -79,12 +79,6 @@ func getScanCommand(scanRequest *PostScanRequest, scanID string) *cautils.ScanIn
|
||||
scanInfo := scanRequest.ToScanInfo()
|
||||
scanInfo.ReportID = scanID
|
||||
|
||||
// *** start ***
|
||||
// TODO - support frameworks/controls and support scanning single frameworks/controls
|
||||
scanInfo.FrameworkScan = true
|
||||
scanInfo.ScanAll = true
|
||||
// *** end ***
|
||||
|
||||
// *** start ***
|
||||
// Set default format
|
||||
if scanInfo.Format == "" {
|
||||
@@ -103,6 +97,7 @@ func getScanCommand(scanRequest *PostScanRequest, scanID string) *cautils.ScanIn
|
||||
|
||||
func defaultScanInfo() *cautils.ScanInfo {
|
||||
scanInfo := &cautils.ScanInfo{}
|
||||
scanInfo.FailThreshold = 100
|
||||
scanInfo.Account = envToString("KS_ACCOUNT", "") // publish results to Kubescape SaaS
|
||||
scanInfo.ExcludedNamespaces = envToString("KS_EXCLUDE_NAMESPACES", "") // namespace to exclude
|
||||
scanInfo.IncludeNamespaces = envToString("KS_INCLUDE_NAMESPACES", "") // namespace to include
|
||||
|
||||
Reference in New Issue
Block a user