mirror of
https://github.com/kubescape/kubescape.git
synced 2026-04-15 06:58:11 +00:00
fixed rbac submit
This commit is contained in:
@@ -12,7 +12,7 @@ import (
|
||||
|
||||
func getExceptionsCmd(ks meta.IKubescape, submitInfo *metav1.Submit) *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "exceptions <full path to exceptins file>",
|
||||
Use: "exceptions <full path to exceptions file>",
|
||||
Short: "Submit exceptions to the Kubescape SaaS version",
|
||||
Args: func(cmd *cobra.Command, args []string) error {
|
||||
if len(args) != 1 {
|
||||
|
||||
+23
-6
@@ -1,27 +1,40 @@
|
||||
package submit
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/armosec/k8s-interface/k8sinterface"
|
||||
"github.com/armosec/kubescape/v2/core/cautils"
|
||||
"github.com/armosec/kubescape/v2/core/cautils/getter"
|
||||
"github.com/armosec/kubescape/v2/core/meta"
|
||||
"github.com/armosec/kubescape/v2/core/meta/cliinterfaces"
|
||||
v1 "github.com/armosec/kubescape/v2/core/meta/datastructures/v1"
|
||||
reporterv2 "github.com/armosec/kubescape/v2/core/pkg/resultshandling/reporter/v2"
|
||||
logger "github.com/dwertent/go-logger"
|
||||
"github.com/dwertent/go-logger/helpers"
|
||||
|
||||
reporterv1 "github.com/armosec/kubescape/v2/core/pkg/resultshandling/reporter/v1"
|
||||
"github.com/google/uuid"
|
||||
|
||||
"github.com/armosec/rbac-utils/rbacscanner"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var (
|
||||
rbacExamples = `
|
||||
# Submit cluster's Role-Based Access Control(RBAC)
|
||||
kubescape submit rbac
|
||||
|
||||
# Submit cluster's Role-Based Access Control(RBAC) with account ID
|
||||
kubescape submit rbac --account-id <account-id>
|
||||
`
|
||||
)
|
||||
|
||||
// getRBACCmd represents the RBAC command
|
||||
func getRBACCmd(ks meta.IKubescape, submitInfo *v1.Submit) *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "rbac \nExample:\n$ kubescape submit rbac",
|
||||
Short: "Submit cluster's Role-Based Access Control(RBAC)",
|
||||
Long: ``,
|
||||
Use: "rbac",
|
||||
Example: rbacExamples,
|
||||
Short: "Submit cluster's Role-Based Access Control(RBAC)",
|
||||
Long: ``,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
|
||||
k8s := k8sinterface.NewKubernetesApi()
|
||||
@@ -32,11 +45,15 @@ func getRBACCmd(ks meta.IKubescape, submitInfo *v1.Submit) *cobra.Command {
|
||||
logger.L().Error("failed setting account ID", helpers.Error(err))
|
||||
}
|
||||
|
||||
if clusterConfig.GetAccountID() == "" {
|
||||
return fmt.Errorf("account ID is not set, run 'kubescape submit rbac --account-id <account-id>'")
|
||||
}
|
||||
|
||||
// list RBAC
|
||||
rbacObjects := cautils.NewRBACObjects(rbacscanner.NewRbacScannerFromK8sAPI(k8s, clusterConfig.GetAccountID(), clusterConfig.GetContextName()))
|
||||
|
||||
// submit resources
|
||||
r := reporterv1.NewReportEventReceiver(clusterConfig.GetConfigObj())
|
||||
r := reporterv2.NewReportEventReceiver(clusterConfig.GetConfigObj(), uuid.NewString(), reporterv2.SubmitContextRBAC)
|
||||
|
||||
submitInterfaces := cliinterfaces.SubmitInterfaces{
|
||||
ClusterConfig: clusterConfig,
|
||||
|
||||
+12
-32
@@ -4,20 +4,18 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
reporthandlingv2 "github.com/armosec/opa-utils/reporthandling/v2"
|
||||
"github.com/google/uuid"
|
||||
|
||||
"github.com/armosec/k8s-interface/workloadinterface"
|
||||
"github.com/armosec/kubescape/v2/core/meta"
|
||||
"github.com/armosec/kubescape/v2/core/meta/cliinterfaces"
|
||||
v1 "github.com/armosec/kubescape/v2/core/meta/datastructures/v1"
|
||||
"github.com/armosec/kubescape/v2/core/pkg/resultshandling/reporter"
|
||||
reporterv1 "github.com/armosec/kubescape/v2/core/pkg/resultshandling/reporter/v1"
|
||||
reporterv2 "github.com/armosec/kubescape/v2/core/pkg/resultshandling/reporter/v2"
|
||||
logger "github.com/dwertent/go-logger"
|
||||
"github.com/dwertent/go-logger/helpers"
|
||||
|
||||
"github.com/armosec/opa-utils/reporthandling"
|
||||
"github.com/google/uuid"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
@@ -37,19 +35,13 @@ func NewResultsObject(customerGUID, clusterName, filePath string) *ResultsObject
|
||||
}
|
||||
}
|
||||
|
||||
func (resultsObject *ResultsObject) SetResourcesReport() (*reporthandling.PostureReport, error) {
|
||||
func (resultsObject *ResultsObject) SetResourcesReport() (*reporthandlingv2.PostureReport, error) {
|
||||
// load framework results from json file
|
||||
frameworkReports, err := loadResultsFromFile(resultsObject.filePath)
|
||||
report, err := loadResultsFromFile(resultsObject.filePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &reporthandling.PostureReport{
|
||||
FrameworkReports: frameworkReports,
|
||||
ReportID: uuid.NewString(),
|
||||
ReportGenerationTime: time.Now().UTC(),
|
||||
CustomerGUID: resultsObject.customerGUID,
|
||||
ClusterName: resultsObject.clusterName,
|
||||
}, nil
|
||||
return report, nil
|
||||
}
|
||||
|
||||
func (resultsObject *ResultsObject) ListAllResources() (map[string]workloadinterface.IMetadata, error) {
|
||||
@@ -76,15 +68,7 @@ func getResultsCmd(ks meta.IKubescape, submitInfo *v1.Submit) *cobra.Command {
|
||||
|
||||
resultsObjects := NewResultsObject(clusterConfig.GetAccountID(), clusterConfig.GetContextName(), args[0])
|
||||
|
||||
// submit resources
|
||||
var r reporter.IReport
|
||||
switch formatVersion {
|
||||
case "v2":
|
||||
r = reporterv2.NewReportEventReceiver(clusterConfig.GetConfigObj(), "")
|
||||
default:
|
||||
logger.L().Warning("Deprecated results version. run with '--format-version' flag", helpers.String("your version", formatVersion), helpers.String("latest version", "v2"))
|
||||
r = reporterv1.NewReportEventReceiver(clusterConfig.GetConfigObj())
|
||||
}
|
||||
r := reporterv2.NewReportEventReceiver(clusterConfig.GetConfigObj(), uuid.NewString(), reporterv2.SubmitContextScan)
|
||||
|
||||
submitInterfaces := cliinterfaces.SubmitInterfaces{
|
||||
ClusterConfig: clusterConfig,
|
||||
@@ -102,18 +86,14 @@ func getResultsCmd(ks meta.IKubescape, submitInfo *v1.Submit) *cobra.Command {
|
||||
|
||||
return resultsCmd
|
||||
}
|
||||
func loadResultsFromFile(filePath string) ([]reporthandling.FrameworkReport, error) {
|
||||
frameworkReports := []reporthandling.FrameworkReport{}
|
||||
func loadResultsFromFile(filePath string) (*reporthandlingv2.PostureReport, error) {
|
||||
report := &reporthandlingv2.PostureReport{}
|
||||
f, err := os.ReadFile(filePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = json.Unmarshal(f, &frameworkReports); err != nil {
|
||||
frameworkReport := reporthandling.FrameworkReport{}
|
||||
if err = json.Unmarshal(f, &frameworkReport); err != nil {
|
||||
return frameworkReports, err
|
||||
}
|
||||
frameworkReports = append(frameworkReports, frameworkReport)
|
||||
if err = json.Unmarshal(f, report); err != nil {
|
||||
return report, fmt.Errorf("failed to unmarshal results file: %s, make sure you run kubescape with '--format=json --format-version=v2'", err.Error())
|
||||
}
|
||||
return frameworkReports, nil
|
||||
return report, nil
|
||||
}
|
||||
|
||||
@@ -20,7 +20,6 @@ type OPASessionObj struct {
|
||||
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]reporthandling.Source // 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>]
|
||||
@@ -41,11 +40,7 @@ func NewOPASessionObj(frameworks []reporthandling.Framework, k8sResources *K8SRe
|
||||
ResourceToControlsMap: make(map[string][]string),
|
||||
ResourceSource: make(map[string]reporthandling.Source),
|
||||
SessionID: scanInfo.ScanID,
|
||||
PostureReport: &reporthandling.PostureReport{
|
||||
ClusterName: ClusterName,
|
||||
CustomerGUID: CustomerGUID,
|
||||
},
|
||||
Metadata: scanInfoToScanMetadata(scanInfo),
|
||||
Metadata: scanInfoToScanMetadata(scanInfo),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,12 +51,6 @@ func NewOPASessionObjMock() *OPASessionObj {
|
||||
AllResources: make(map[string]workloadinterface.IMetadata),
|
||||
ResourcesResult: make(map[string]resourcesresults.Result),
|
||||
Report: &reporthandlingv2.PostureReport{},
|
||||
PostureReport: &reporthandling.PostureReport{
|
||||
ClusterName: "",
|
||||
CustomerGUID: "",
|
||||
ReportID: "",
|
||||
JobID: "",
|
||||
},
|
||||
Metadata: &reporthandlingv2.Metadata{
|
||||
ScanMetadata: reporthandlingv2.ScanMetadata{
|
||||
ScanningTarget: 0,
|
||||
|
||||
+12
-4
@@ -4,8 +4,9 @@ import (
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
reporthandlingv2 "github.com/armosec/opa-utils/reporthandling/v2"
|
||||
|
||||
"github.com/armosec/k8s-interface/workloadinterface"
|
||||
"github.com/armosec/opa-utils/reporthandling"
|
||||
"github.com/armosec/rbac-utils/rbacscanner"
|
||||
"github.com/armosec/rbac-utils/rbacutils"
|
||||
"github.com/google/uuid"
|
||||
@@ -19,12 +20,19 @@ func NewRBACObjects(scanner *rbacscanner.RbacScannerFromK8sAPI) *RBACObjects {
|
||||
return &RBACObjects{scanner: scanner}
|
||||
}
|
||||
|
||||
func (rbacObjects *RBACObjects) SetResourcesReport() (*reporthandling.PostureReport, error) {
|
||||
return &reporthandling.PostureReport{
|
||||
func (rbacObjects *RBACObjects) SetResourcesReport() (*reporthandlingv2.PostureReport, error) {
|
||||
return &reporthandlingv2.PostureReport{
|
||||
ReportID: uuid.NewString(),
|
||||
ReportGenerationTime: time.Now().UTC(),
|
||||
CustomerGUID: rbacObjects.scanner.CustomerGUID,
|
||||
ClusterName: rbacObjects.scanner.ClusterName,
|
||||
Metadata: reporthandlingv2.Metadata{
|
||||
ContextMetadata: reporthandlingv2.ContextMetadata{
|
||||
ClusterContextMetadata: &reporthandlingv2.ClusterMetadata{
|
||||
ContextName: rbacObjects.scanner.ClusterName,
|
||||
},
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -53,7 +61,7 @@ func (rbacObjects *RBACObjects) rbacObjectsToResources(resources *rbacutils.Rbac
|
||||
************************************************************************************************************************
|
||||
*/
|
||||
|
||||
// wrap rbac aggregated objects in IMetadata and add to allresources
|
||||
// wrap rbac aggregated objects in IMetadata and add to AllResources
|
||||
// TODO - DEPRECATE SA2WLIDmap
|
||||
SA2WLIDmapIMeta, err := rbacutils.SA2WLIDmapIMetadataWrapper(resources.SA2WLIDmap)
|
||||
if err != nil {
|
||||
|
||||
@@ -7,10 +7,8 @@ import (
|
||||
"github.com/armosec/opa-utils/reporthandling/results/v1/reportsummary"
|
||||
)
|
||||
|
||||
func ReportV2ToV1(opaSessionObj *OPASessionObj) {
|
||||
if len(opaSessionObj.PostureReport.FrameworkReports) > 0 {
|
||||
return // report already converted
|
||||
}
|
||||
func ReportV2ToV1(opaSessionObj *OPASessionObj) *reporthandling.PostureReport {
|
||||
report := &reporthandling.PostureReport{}
|
||||
// opaSessionObj.PostureReport.ClusterCloudProvider = opaSessionObj.Report.ClusterCloudProvider
|
||||
|
||||
frameworks := []reporthandling.FrameworkReport{}
|
||||
@@ -53,7 +51,8 @@ func ReportV2ToV1(opaSessionObj *OPASessionObj) {
|
||||
// scoreutil := score.NewScore(opaSessionObj.AllResources)
|
||||
// scoreutil.Calculate(frameworks)
|
||||
|
||||
opaSessionObj.PostureReport.FrameworkReports = frameworks
|
||||
report.FrameworkReports = frameworks
|
||||
return report
|
||||
}
|
||||
|
||||
func controlReportV2ToV1(opaSessionObj *OPASessionObj, frameworkName string, controls map[string]reportsummary.ControlSummary) []reporthandling.ControlReport {
|
||||
|
||||
@@ -48,9 +48,13 @@ func getRBACHandler(tenantConfig cautils.ITenantConfig, k8s *k8sinterface.Kubern
|
||||
return nil
|
||||
}
|
||||
|
||||
func getReporter(tenantConfig cautils.ITenantConfig, reportID string, submit, fwScan bool) reporter.IReport {
|
||||
func getReporter(tenantConfig cautils.ITenantConfig, reportID string, submit, fwScan bool, scanningContext cautils.ScanningContext) reporter.IReport {
|
||||
if submit {
|
||||
return reporterv2.NewReportEventReceiver(tenantConfig.GetConfigObj(), reportID)
|
||||
submitData := reporterv2.SubmitContextScan
|
||||
if scanningContext != cautils.ContextCluster {
|
||||
submitData = reporterv2.SubmitContextRBAC
|
||||
}
|
||||
return reporterv2.NewReportEventReceiver(tenantConfig.GetConfigObj(), reportID, submitData)
|
||||
}
|
||||
if tenantConfig.GetAccountID() == "" {
|
||||
// Add link only when scanning a cluster using a framework
|
||||
|
||||
+1
-1
@@ -86,7 +86,7 @@ func getInterfaces(scanInfo *cautils.ScanInfo) componentInterfaces {
|
||||
// ================== setup reporter & printer objects ======================================
|
||||
|
||||
// reporting behavior - setup reporter
|
||||
reportHandler := getReporter(tenantConfig, scanInfo.ScanID, scanInfo.Submit, scanInfo.FrameworkScan)
|
||||
reportHandler := getReporter(tenantConfig, scanInfo.ScanID, scanInfo.Submit, scanInfo.FrameworkScan, scanInfo.GetScanningContext())
|
||||
|
||||
// setup printer
|
||||
printerHandler := resultshandling.NewPrinter(scanInfo.Format, scanInfo.FormatVersion, scanInfo.VerboseMode, cautils.ViewTypes(scanInfo.View))
|
||||
|
||||
+8
-2
@@ -4,6 +4,7 @@ import (
|
||||
"github.com/armosec/kubescape/v2/core/cautils"
|
||||
"github.com/armosec/kubescape/v2/core/cautils/getter"
|
||||
"github.com/armosec/kubescape/v2/core/meta/cliinterfaces"
|
||||
|
||||
logger "github.com/dwertent/go-logger"
|
||||
"github.com/dwertent/go-logger/helpers"
|
||||
)
|
||||
@@ -11,7 +12,7 @@ import (
|
||||
func (ks *Kubescape) Submit(submitInterfaces cliinterfaces.SubmitInterfaces) error {
|
||||
|
||||
// list resources
|
||||
postureReport, err := submitInterfaces.SubmitObjects.SetResourcesReport()
|
||||
report, err := submitInterfaces.SubmitObjects.SetResourcesReport()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -20,7 +21,12 @@ func (ks *Kubescape) Submit(submitInterfaces cliinterfaces.SubmitInterfaces) err
|
||||
return err
|
||||
}
|
||||
// report
|
||||
if err := submitInterfaces.Reporter.Submit(&cautils.OPASessionObj{PostureReport: postureReport, AllResources: allresources}); err != nil {
|
||||
o := &cautils.OPASessionObj{
|
||||
Report: report,
|
||||
AllResources: allresources,
|
||||
Metadata: &report.Metadata,
|
||||
}
|
||||
if err := submitInterfaces.Reporter.Submit(o); err != nil {
|
||||
return err
|
||||
}
|
||||
logger.L().Success("Data has been submitted successfully")
|
||||
|
||||
@@ -4,11 +4,11 @@ import (
|
||||
"github.com/armosec/k8s-interface/workloadinterface"
|
||||
"github.com/armosec/kubescape/v2/core/cautils"
|
||||
"github.com/armosec/kubescape/v2/core/pkg/resultshandling/reporter"
|
||||
"github.com/armosec/opa-utils/reporthandling"
|
||||
reporthandlingv2 "github.com/armosec/opa-utils/reporthandling/v2"
|
||||
)
|
||||
|
||||
type ISubmitObjects interface {
|
||||
SetResourcesReport() (*reporthandling.PostureReport, error)
|
||||
SetResourcesReport() (*reporthandlingv2.PostureReport, error)
|
||||
ListAllResources() (map[string]workloadinterface.IMetadata, error)
|
||||
}
|
||||
|
||||
|
||||
@@ -6,12 +6,10 @@ import (
|
||||
"github.com/armosec/armoapi-go/armotypes"
|
||||
"github.com/armosec/kubescape/v2/core/cautils"
|
||||
"github.com/armosec/kubescape/v2/core/mocks"
|
||||
"github.com/armosec/opa-utils/objectsenvelopes"
|
||||
"github.com/armosec/opa-utils/reporthandling"
|
||||
"github.com/armosec/opa-utils/resources"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/armosec/k8s-interface/k8sinterface"
|
||||
"github.com/armosec/k8s-interface/workloadinterface"
|
||||
// _ "k8s.io/client-go/plugin/pkg/client/auth"
|
||||
)
|
||||
@@ -19,43 +17,6 @@ import (
|
||||
func NewOPAProcessorMock() *OPAProcessor {
|
||||
return &OPAProcessor{}
|
||||
}
|
||||
func TestProcess(t *testing.T) {
|
||||
|
||||
// set k8s
|
||||
k8sResources := make(cautils.K8SResources)
|
||||
allResources := make(map[string]workloadinterface.IMetadata)
|
||||
imetaObj := objectsenvelopes.ListMapToMeta(k8sinterface.ConvertUnstructuredSliceToMap(k8sinterface.V1KubeSystemNamespaceMock().Items))
|
||||
for i := range imetaObj {
|
||||
allResources[imetaObj[i].GetID()] = imetaObj[i]
|
||||
}
|
||||
k8sResources["/v1/pods"] = workloadinterface.ListMetaIDs(imetaObj)
|
||||
|
||||
// set opaSessionObj
|
||||
opaSessionObj := cautils.NewOPASessionObjMock()
|
||||
opaSessionObj.Policies = []reporthandling.Framework{*reporthandling.MockFrameworkA()}
|
||||
policies := ConvertFrameworksToPolicies(opaSessionObj.Policies, "")
|
||||
|
||||
opaSessionObj.K8SResources = &k8sResources
|
||||
opaSessionObj.AllResources = allResources
|
||||
|
||||
opap := NewOPAProcessor(opaSessionObj, resources.NewRegoDependenciesDataMock()) // ,
|
||||
opap.Process(policies)
|
||||
opap.updateResults()
|
||||
for _, f := range opap.PostureReport.FrameworkReports {
|
||||
for _, c := range f.ControlReports {
|
||||
for _, r := range c.RuleReports {
|
||||
for _, rr := range r.RuleResponses {
|
||||
// t.Errorf("AlertMessage: %v", rr.AlertMessage)
|
||||
if rr.Exception != nil {
|
||||
t.Errorf("Exception: %v", rr.Exception)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestProcessResourcesResult(t *testing.T) {
|
||||
|
||||
// set k8s
|
||||
|
||||
@@ -27,15 +27,15 @@ func (jsonPrinter *JsonPrinter) Score(score float32) {
|
||||
}
|
||||
|
||||
func (jsonPrinter *JsonPrinter) ActionPrint(opaSessionObj *cautils.OPASessionObj) {
|
||||
cautils.ReportV2ToV1(opaSessionObj)
|
||||
report := cautils.ReportV2ToV1(opaSessionObj)
|
||||
|
||||
var postureReportStr []byte
|
||||
var err error
|
||||
|
||||
if len(opaSessionObj.PostureReport.FrameworkReports) == 1 {
|
||||
postureReportStr, err = json.Marshal(opaSessionObj.PostureReport.FrameworkReports[0])
|
||||
if len(report.FrameworkReports) == 1 {
|
||||
postureReportStr, err = json.Marshal(report.FrameworkReports[0])
|
||||
} else {
|
||||
postureReportStr, err = json.Marshal(opaSessionObj.PostureReport.FrameworkReports)
|
||||
postureReportStr, err = json.Marshal(report.FrameworkReports)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
|
||||
@@ -87,9 +87,9 @@ func (printer *PrometheusPrinter) printReports(allResources map[string]workloadi
|
||||
}
|
||||
|
||||
func (printer *PrometheusPrinter) ActionPrint(opaSessionObj *cautils.OPASessionObj) {
|
||||
cautils.ReportV2ToV1(opaSessionObj)
|
||||
report := cautils.ReportV2ToV1(opaSessionObj)
|
||||
|
||||
err := printer.printReports(opaSessionObj.AllResources, opaSessionObj.PostureReport.FrameworkReports)
|
||||
err := printer.printReports(opaSessionObj.AllResources, report.FrameworkReports)
|
||||
if err != nil {
|
||||
logger.L().Fatal(err.Error())
|
||||
}
|
||||
|
||||
@@ -1,185 +0,0 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
|
||||
"github.com/armosec/k8s-interface/workloadinterface"
|
||||
"github.com/armosec/kubescape/v2/core/cautils"
|
||||
"github.com/armosec/kubescape/v2/core/cautils/getter"
|
||||
v2 "github.com/armosec/kubescape/v2/core/pkg/resultshandling/reporter/v2"
|
||||
"github.com/armosec/opa-utils/reporthandling"
|
||||
logger "github.com/dwertent/go-logger"
|
||||
"github.com/dwertent/go-logger/helpers"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
const MAX_REPORT_SIZE = 2097152 // 2 MB
|
||||
|
||||
type ReportEventReceiver struct {
|
||||
httpClient *http.Client
|
||||
clusterName string
|
||||
customerGUID string
|
||||
eventReceiverURL *url.URL
|
||||
token string
|
||||
customerAdminEMail string
|
||||
message string
|
||||
}
|
||||
|
||||
func NewReportEventReceiver(tenantConfig *cautils.ConfigObj) *ReportEventReceiver {
|
||||
return &ReportEventReceiver{
|
||||
httpClient: &http.Client{},
|
||||
clusterName: tenantConfig.ClusterName,
|
||||
customerGUID: tenantConfig.AccountID,
|
||||
token: tenantConfig.Token,
|
||||
customerAdminEMail: tenantConfig.CustomerAdminEMail,
|
||||
}
|
||||
}
|
||||
|
||||
func (report *ReportEventReceiver) Submit(opaSessionObj *cautils.OPASessionObj) error {
|
||||
if opaSessionObj.PostureReport == nil && opaSessionObj.Report != nil {
|
||||
cautils.ReportV2ToV1(opaSessionObj)
|
||||
}
|
||||
|
||||
if report.customerGUID == "" {
|
||||
report.message = "WARNING: Failed to publish results. Reason: Unknown accout ID. Run kubescape with the '--account <account ID>' flag. Please feel free to contact ARMO team for more details"
|
||||
return nil
|
||||
}
|
||||
if report.clusterName == "" {
|
||||
report.message = "WARNING: Failed to publish results because the cluster name is Unknown. If you are scanning YAML files the results are not submitted to the Kubescape SaaS.Please feel free to contact ARMO team for more details"
|
||||
return nil
|
||||
}
|
||||
opaSessionObj.PostureReport.ReportID = uuid.NewString()
|
||||
opaSessionObj.PostureReport.CustomerGUID = report.customerGUID
|
||||
opaSessionObj.PostureReport.ClusterName = report.clusterName
|
||||
|
||||
if err := report.prepareReport(opaSessionObj.PostureReport, opaSessionObj.AllResources); err != nil {
|
||||
report.message = err.Error()
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (report *ReportEventReceiver) SetCustomerGUID(customerGUID string) {
|
||||
report.customerGUID = customerGUID
|
||||
}
|
||||
|
||||
func (report *ReportEventReceiver) SetClusterName(clusterName string) {
|
||||
report.clusterName = cautils.AdoptClusterName(clusterName) // clean cluster name
|
||||
}
|
||||
|
||||
func (report *ReportEventReceiver) prepareReport(postureReport *reporthandling.PostureReport, allResources map[string]workloadinterface.IMetadata) error {
|
||||
report.initEventReceiverURL()
|
||||
host := hostToString(report.eventReceiverURL, postureReport.ReportID)
|
||||
|
||||
cautils.StartSpinner()
|
||||
|
||||
// send framework results
|
||||
if err := report.sendReport(host, postureReport); err != nil {
|
||||
cautils.StopSpinner()
|
||||
return err
|
||||
}
|
||||
|
||||
// send resources
|
||||
if err := report.sendResources(host, postureReport, allResources); err != nil {
|
||||
cautils.StopSpinner()
|
||||
return err
|
||||
}
|
||||
cautils.StopSpinner()
|
||||
report.generateMessage()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (report *ReportEventReceiver) sendResources(host string, postureReport *reporthandling.PostureReport, allResources map[string]workloadinterface.IMetadata) error {
|
||||
splittedPostureReport := setPaginationReport(postureReport)
|
||||
counter := 0
|
||||
|
||||
for _, v := range allResources {
|
||||
r, err := json.Marshal(*iMetaToResource(v))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to unmarshal resource '%s', reason: %v", v.GetID(), err)
|
||||
}
|
||||
|
||||
if counter+len(r) >= MAX_REPORT_SIZE && len(splittedPostureReport.Resources) > 0 {
|
||||
|
||||
// send report
|
||||
if err := report.sendReport(host, splittedPostureReport); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// delete resources
|
||||
splittedPostureReport.Resources = []reporthandling.Resource{}
|
||||
|
||||
// restart counter
|
||||
counter = 0
|
||||
}
|
||||
|
||||
counter += len(r)
|
||||
splittedPostureReport.Resources = append(splittedPostureReport.Resources, *iMetaToResource(v))
|
||||
}
|
||||
|
||||
return report.sendReport(host, splittedPostureReport)
|
||||
}
|
||||
func (report *ReportEventReceiver) sendReport(host string, postureReport *reporthandling.PostureReport) error {
|
||||
reqBody, err := json.Marshal(postureReport)
|
||||
if err != nil {
|
||||
return fmt.Errorf("in 'sendReport' failed to json.Marshal, reason: %v", err)
|
||||
}
|
||||
|
||||
msg, err := getter.HttpPost(report.httpClient, host, nil, reqBody)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s, %v:%s", host, err, msg)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (report *ReportEventReceiver) generateMessage() {
|
||||
message := "You can see the results in a user-friendly UI, choose your preferred compliance framework, check risk results history and trends, manage exceptions, get remediation recommendations and much more by registering here:"
|
||||
|
||||
u := url.URL{}
|
||||
u.Host = getter.GetArmoAPIConnector().GetFrontendURL()
|
||||
v2.ParseHost(&u)
|
||||
|
||||
if report.customerAdminEMail != "" {
|
||||
logger.L().Debug("", helpers.String("account ID", report.customerGUID))
|
||||
report.message = fmt.Sprintf("%s %s/configuration-scanning/%s", message, u.String(), report.clusterName)
|
||||
return
|
||||
}
|
||||
u.Path = "account/sign-up"
|
||||
q := u.Query()
|
||||
q.Add("invitationToken", report.token)
|
||||
q.Add("customerGUID", report.customerGUID)
|
||||
|
||||
u.RawQuery = q.Encode()
|
||||
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))
|
||||
}
|
||||
|
||||
// func maskID(id string) string {
|
||||
// sep := "-"
|
||||
// splitted := strings.Split(id, sep)
|
||||
// if len(splitted) != 5 {
|
||||
// return ""
|
||||
// }
|
||||
// str := splitted[0][:4]
|
||||
// splitted[0] = splitted[0][4:]
|
||||
// for i := range splitted {
|
||||
// for j := 0; j < len(splitted[i]); j++ {
|
||||
// str += "X"
|
||||
// }
|
||||
// str += sep
|
||||
// }
|
||||
|
||||
// return strings.TrimSuffix(str, sep)
|
||||
// }
|
||||
@@ -1,48 +0,0 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
|
||||
"github.com/armosec/k8s-interface/workloadinterface"
|
||||
"github.com/armosec/kubescape/v2/core/cautils/getter"
|
||||
v2 "github.com/armosec/kubescape/v2/core/pkg/resultshandling/reporter/v2"
|
||||
"github.com/armosec/opa-utils/reporthandling"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
func (report *ReportEventReceiver) initEventReceiverURL() {
|
||||
urlObj := url.URL{}
|
||||
urlObj.Host = getter.GetArmoAPIConnector().GetReportReceiverURL()
|
||||
v2.ParseHost(&urlObj)
|
||||
|
||||
urlObj.Path = "/k8s/postureReport"
|
||||
q := urlObj.Query()
|
||||
q.Add("customerGUID", uuid.MustParse(report.customerGUID).String())
|
||||
q.Add("clusterName", report.clusterName)
|
||||
|
||||
urlObj.RawQuery = q.Encode()
|
||||
|
||||
report.eventReceiverURL = &urlObj
|
||||
}
|
||||
|
||||
func hostToString(host *url.URL, reportID string) string {
|
||||
q := host.Query()
|
||||
q.Add("reportID", reportID) // TODO - do we add the reportID?
|
||||
host.RawQuery = q.Encode()
|
||||
return host.String()
|
||||
}
|
||||
|
||||
func setPaginationReport(postureReport *reporthandling.PostureReport) *reporthandling.PostureReport {
|
||||
return &reporthandling.PostureReport{
|
||||
CustomerGUID: postureReport.CustomerGUID,
|
||||
ClusterName: postureReport.ClusterName,
|
||||
ReportID: postureReport.ReportID,
|
||||
ReportGenerationTime: postureReport.ReportGenerationTime,
|
||||
}
|
||||
}
|
||||
func iMetaToResource(obj workloadinterface.IMetadata) *reporthandling.Resource {
|
||||
return &reporthandling.Resource{
|
||||
ResourceID: obj.GetID(),
|
||||
Object: obj.GetObject(),
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestHostToString(t *testing.T) {
|
||||
host := url.URL{
|
||||
Scheme: "https",
|
||||
Host: "report.eudev3.cyberarmorsoft.com",
|
||||
Path: "k8srestapi/v1/postureReport",
|
||||
RawQuery: "cluster=openrasty_seal-7fvz&customerGUID=5d817063-096f-4d91-b39b-8665240080af",
|
||||
}
|
||||
expectedHost := "https://report.eudev3.cyberarmorsoft.com/k8srestapi/v1/postureReport?cluster=openrasty_seal-7fvz&customerGUID=5d817063-096f-4d91-b39b-8665240080af&reportID=ffdd2a00-4dc8-4bf3-b97a-a6d4fd198a41"
|
||||
receivedHost := hostToString(&host, "ffdd2a00-4dc8-4bf3-b97a-a6d4fd198a41")
|
||||
if receivedHost != expectedHost {
|
||||
t.Errorf("%s != %s", receivedHost, expectedHost)
|
||||
}
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
package v2
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/armosec/kubescape/v2/core/cautils"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestGetURL(t *testing.T) {
|
||||
// Test submit and registered url
|
||||
{
|
||||
reporter := NewReportEventReceiver(
|
||||
&cautils.ConfigObj{
|
||||
AccountID: "1234",
|
||||
Token: "token",
|
||||
CustomerAdminEMail: "my@email",
|
||||
ClusterName: "test",
|
||||
},
|
||||
"",
|
||||
)
|
||||
assert.Equal(t, "https://cloud.armosec.io/configuration-scanning/test?utm_campaign=Submit&utm_medium=CLI&utm_source=GitHub", reporter.GetURL())
|
||||
}
|
||||
|
||||
// Test submit and NOT registered url
|
||||
{
|
||||
|
||||
reporter := NewReportEventReceiver(
|
||||
&cautils.ConfigObj{
|
||||
AccountID: "1234",
|
||||
Token: "token",
|
||||
ClusterName: "test",
|
||||
},
|
||||
"",
|
||||
)
|
||||
assert.Equal(t, "https://cloud.armosec.io/account/sign-up?customerGUID=1234&invitationToken=token&utm_campaign=Submit&utm_medium=CLI&utm_source=GitHub", reporter.GetURL())
|
||||
}
|
||||
// Test None submit url
|
||||
{
|
||||
reporter := NewReportMock(NO_SUBMIT_QUERY, "")
|
||||
assert.Equal(t, "https://cloud.armosec.io/account/sign-up?utm_source=GitHub&utm_medium=CLI&utm_campaign=no_submit", reporter.GetURL())
|
||||
}
|
||||
// Test None report url
|
||||
{
|
||||
reporter := NewReportMock("", "")
|
||||
assert.Equal(t, "https://cloud.armosec.io/account/sign-up", reporter.GetURL())
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,14 @@ import (
|
||||
|
||||
const MAX_REPORT_SIZE = 2097152 // 2 MB
|
||||
|
||||
type SubmitContext string
|
||||
|
||||
const (
|
||||
SubmitContextScan SubmitContext = "scan"
|
||||
SubmitContextRBAC SubmitContext = "rbac"
|
||||
SubmitContextRepository SubmitContext = "repository"
|
||||
)
|
||||
|
||||
type ReportEventReceiver struct {
|
||||
httpClient *http.Client
|
||||
clusterName string
|
||||
@@ -29,9 +37,10 @@ type ReportEventReceiver struct {
|
||||
customerAdminEMail string
|
||||
message string
|
||||
reportID string
|
||||
submitContext SubmitContext
|
||||
}
|
||||
|
||||
func NewReportEventReceiver(tenantConfig *cautils.ConfigObj, reportID string) *ReportEventReceiver {
|
||||
func NewReportEventReceiver(tenantConfig *cautils.ConfigObj, reportID string, submitContext SubmitContext) *ReportEventReceiver {
|
||||
return &ReportEventReceiver{
|
||||
httpClient: &http.Client{},
|
||||
clusterName: tenantConfig.ClusterName,
|
||||
@@ -39,6 +48,7 @@ func NewReportEventReceiver(tenantConfig *cautils.ConfigObj, reportID string) *R
|
||||
token: tenantConfig.Token,
|
||||
customerAdminEMail: tenantConfig.CustomerAdminEMail,
|
||||
reportID: reportID,
|
||||
submitContext: submitContext,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,21 +105,11 @@ func (report *ReportEventReceiver) prepareReport(opaSessionObj *cautils.OPASessi
|
||||
func (report *ReportEventReceiver) GetURL() string {
|
||||
u := url.URL{}
|
||||
u.Host = getter.GetArmoAPIConnector().GetFrontendURL()
|
||||
ParseHost(&u)
|
||||
|
||||
parseHost(&u)
|
||||
report.addPathURL(&u)
|
||||
|
||||
q := u.Query()
|
||||
|
||||
if report.customerAdminEMail != "" || report.token == "" { // data has been submitted
|
||||
if report.clusterName != "" {
|
||||
u.Path = fmt.Sprintf("configuration-scanning/%s", report.clusterName)
|
||||
} else {
|
||||
u.Path = fmt.Sprintf("repositories-scan/%s", report.reportID)
|
||||
}
|
||||
} else {
|
||||
u.Path = "account/sign-up"
|
||||
q.Add("invitationToken", report.token)
|
||||
q.Add("customerGUID", report.customerGUID)
|
||||
}
|
||||
|
||||
q.Add("utm_source", "GitHub")
|
||||
q.Add("utm_medium", "CLI")
|
||||
q.Add("utm_campaign", "Submit")
|
||||
@@ -226,3 +226,24 @@ func (report *ReportEventReceiver) DisplayReportURL() {
|
||||
cautils.InfoTextDisplay(os.Stderr, fmt.Sprintf("\n\n%s\n\n", report.message))
|
||||
}
|
||||
}
|
||||
|
||||
func (report *ReportEventReceiver) addPathURL(urlObj *url.URL) {
|
||||
if report.customerAdminEMail != "" || report.token == "" { // data has been submitted
|
||||
switch report.submitContext {
|
||||
case SubmitContextScan:
|
||||
urlObj.Path = fmt.Sprintf("configuration-scanning/%s", report.clusterName)
|
||||
case SubmitContextRBAC:
|
||||
urlObj.Path = fmt.Sprintf("rbac-visualizer")
|
||||
case SubmitContextRepository:
|
||||
urlObj.Path = fmt.Sprintf("repository-scanning/%s", report.clusterName)
|
||||
}
|
||||
return
|
||||
}
|
||||
urlObj.Path = "account/sign-up"
|
||||
|
||||
q := urlObj.Query()
|
||||
q.Add("invitationToken", report.token)
|
||||
q.Add("customerGUID", report.customerGUID)
|
||||
urlObj.RawQuery = q.Encode()
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
package v2
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"testing"
|
||||
|
||||
"github.com/armosec/kubescape/v2/core/cautils"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestReportEventReceiver_addPathURL(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
report *ReportEventReceiver
|
||||
urlObj *url.URL
|
||||
want *url.URL
|
||||
}{
|
||||
{
|
||||
name: "add scan path",
|
||||
report: &ReportEventReceiver{
|
||||
clusterName: "test",
|
||||
customerGUID: "FFFF",
|
||||
token: "XXXX",
|
||||
customerAdminEMail: "test@test",
|
||||
reportID: "1234",
|
||||
submitContext: SubmitContextScan,
|
||||
},
|
||||
urlObj: &url.URL{
|
||||
Scheme: "https",
|
||||
Host: "localhost:8080",
|
||||
},
|
||||
want: &url.URL{
|
||||
Scheme: "https",
|
||||
Host: "localhost:8080",
|
||||
Path: "configuration-scanning/test",
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
tt.report.addPathURL(tt.urlObj)
|
||||
assert.Equal(t, tt.want.String(), tt.urlObj.String())
|
||||
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetURL(t *testing.T) {
|
||||
// Test submit and registered url
|
||||
{
|
||||
reporter := NewReportEventReceiver(
|
||||
&cautils.ConfigObj{
|
||||
AccountID: "1234",
|
||||
Token: "token",
|
||||
CustomerAdminEMail: "my@email",
|
||||
ClusterName: "test",
|
||||
},
|
||||
"",
|
||||
SubmitContextScan,
|
||||
)
|
||||
assert.Equal(t, "https://cloud.armosec.io/configuration-scanning/test?utm_campaign=Submit&utm_medium=CLI&utm_source=GitHub", reporter.GetURL())
|
||||
}
|
||||
|
||||
// Test rbac submit and registered url
|
||||
{
|
||||
reporter := NewReportEventReceiver(
|
||||
&cautils.ConfigObj{
|
||||
AccountID: "1234",
|
||||
Token: "token",
|
||||
CustomerAdminEMail: "my@email",
|
||||
ClusterName: "test",
|
||||
},
|
||||
"",
|
||||
SubmitContextRBAC,
|
||||
)
|
||||
assert.Equal(t, "https://cloud.armosec.io/rbac-visualizer?utm_campaign=Submit&utm_medium=CLI&utm_source=GitHub", reporter.GetURL())
|
||||
}
|
||||
|
||||
// Test repo submit and registered url
|
||||
{
|
||||
reporter := NewReportEventReceiver(
|
||||
&cautils.ConfigObj{
|
||||
AccountID: "1234",
|
||||
Token: "token",
|
||||
CustomerAdminEMail: "my@email",
|
||||
ClusterName: "test",
|
||||
},
|
||||
"",
|
||||
SubmitContextRepository,
|
||||
)
|
||||
assert.Equal(t, "https://cloud.armosec.io/repository-scanning/test?utm_campaign=Submit&utm_medium=CLI&utm_source=GitHub", reporter.GetURL())
|
||||
}
|
||||
|
||||
// Test submit and NOT registered url
|
||||
{
|
||||
|
||||
reporter := NewReportEventReceiver(
|
||||
&cautils.ConfigObj{
|
||||
AccountID: "1234",
|
||||
Token: "token",
|
||||
ClusterName: "test",
|
||||
},
|
||||
"",
|
||||
SubmitContextScan,
|
||||
)
|
||||
assert.Equal(t, "https://cloud.armosec.io/account/sign-up?customerGUID=1234&invitationToken=token&utm_campaign=Submit&utm_medium=CLI&utm_source=GitHub", reporter.GetURL())
|
||||
}
|
||||
// Test None submit url
|
||||
{
|
||||
reporter := NewReportMock(NO_SUBMIT_QUERY, "")
|
||||
assert.Equal(t, "https://cloud.armosec.io/account/sign-up?utm_source=GitHub&utm_medium=CLI&utm_campaign=no_submit", reporter.GetURL())
|
||||
}
|
||||
// Test None report url
|
||||
{
|
||||
reporter := NewReportMock("", "")
|
||||
assert.Equal(t, "https://cloud.armosec.io/account/sign-up", reporter.GetURL())
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,8 @@ import (
|
||||
func (report *ReportEventReceiver) initEventReceiverURL() {
|
||||
urlObj := url.URL{}
|
||||
urlObj.Host = getter.GetArmoAPIConnector().GetReportReceiverURL()
|
||||
ParseHost(&urlObj)
|
||||
parseHost(&urlObj)
|
||||
|
||||
urlObj.Path = "/k8s/v2/postureReport"
|
||||
q := urlObj.Query()
|
||||
q.Add("customerGUID", uuid.MustParse(report.customerGUID).String())
|
||||
|
||||
@@ -23,7 +23,7 @@ func maskID(id string) string {
|
||||
return strings.TrimSuffix(str, sep)
|
||||
}
|
||||
|
||||
func ParseHost(urlObj *url.URL) {
|
||||
func parseHost(urlObj *url.URL) {
|
||||
if strings.Contains(urlObj.Host, "http://") {
|
||||
urlObj.Scheme = "http"
|
||||
urlObj.Host = strings.Replace(urlObj.Host, "http://", "", 1)
|
||||
|
||||
@@ -11,27 +11,27 @@ func TestParseHost(t *testing.T) {
|
||||
urlObj := url.URL{}
|
||||
|
||||
urlObj.Host = "http://localhost:7555"
|
||||
ParseHost(&urlObj)
|
||||
parseHost(&urlObj)
|
||||
assert.Equal(t, "http", urlObj.Scheme)
|
||||
assert.Equal(t, "localhost:7555", urlObj.Host)
|
||||
|
||||
urlObj.Host = "https://localhost:7555"
|
||||
ParseHost(&urlObj)
|
||||
parseHost(&urlObj)
|
||||
assert.Equal(t, "https", urlObj.Scheme)
|
||||
assert.Equal(t, "localhost:7555", urlObj.Host)
|
||||
|
||||
urlObj.Host = "http://portal-dev.armo.cloud"
|
||||
ParseHost(&urlObj)
|
||||
parseHost(&urlObj)
|
||||
assert.Equal(t, "http", urlObj.Scheme)
|
||||
assert.Equal(t, "portal-dev.armo.cloud", urlObj.Host)
|
||||
|
||||
urlObj.Host = "https://portal-dev.armo.cloud"
|
||||
ParseHost(&urlObj)
|
||||
parseHost(&urlObj)
|
||||
assert.Equal(t, "https", urlObj.Scheme)
|
||||
assert.Equal(t, "portal-dev.armo.cloud", urlObj.Host)
|
||||
|
||||
urlObj.Host = "portal-dev.armo.cloud"
|
||||
ParseHost(&urlObj)
|
||||
parseHost(&urlObj)
|
||||
assert.Equal(t, "https", urlObj.Scheme)
|
||||
assert.Equal(t, "portal-dev.armo.cloud", urlObj.Host)
|
||||
|
||||
|
||||
@@ -26,8 +26,6 @@ const (
|
||||
|
||||
func (su *ScoreWrapper) Calculate(reportVersion PostureReportVersion) error {
|
||||
switch reportVersion {
|
||||
case EPostureReportV1:
|
||||
return su.scoreUtil.Calculate(su.opaSessionObj.PostureReport.FrameworkReports)
|
||||
case EPostureReportV2:
|
||||
return su.scoreUtil.CalculatePostureReportV2(su.opaSessionObj.Report)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user