diff --git a/cautils/datastructures.go b/cautils/datastructures.go index 2639f734..f9c92550 100644 --- a/cautils/datastructures.go +++ b/cautils/datastructures.go @@ -13,6 +13,7 @@ type OPASessionObj struct { K8SResources *K8SResources Exceptions []armotypes.PostureExceptionPolicy PostureReport *reporthandling.PostureReport + RegoInputData RegoInputData // map[][] } func NewOPASessionObj(frameworks []reporthandling.Framework, k8sResources *K8SResources) *OPASessionObj { @@ -49,3 +50,9 @@ type Exception struct { Namespaces []string `json:"namespaces"` Regex string `json:"regex"` // not supported } + +type RegoInputData struct { + PostureControlInputs map[string][]string `json:"postureControlInputs"` + // ClusterName string `json:"clusterName"` + // K8sConfig RegoK8sConfig `json:"k8sconfig"` +} diff --git a/cautils/datastructuresmethods.go b/cautils/datastructuresmethods.go new file mode 100644 index 00000000..6cd9941e --- /dev/null +++ b/cautils/datastructuresmethods.go @@ -0,0 +1,26 @@ +package cautils + +import ( + "encoding/json" + + "github.com/open-policy-agent/opa/storage" + "github.com/open-policy-agent/opa/storage/inmem" + "github.com/open-policy-agent/opa/util" +) + +func (data *RegoInputData) SetControlsInputs(controlsInputs map[string][]string) { + data.PostureControlInputs = controlsInputs +} + +func (data *RegoInputData) TOStorage() (storage.Store, error) { + var jsonObj map[string]interface{} + bytesData, err := json.Marshal(*data) + if err != nil { + return nil, err + } + // glog.Infof("RegoDependenciesData: %s", bytesData) + if err := util.UnmarshalJSON(bytesData, &jsonObj); err != nil { + return nil, err + } + return inmem.NewFromObject(jsonObj), nil +} diff --git a/cautils/getter/armoapi.go b/cautils/getter/armoapi.go index 5c4d299d..88f50b2b 100644 --- a/cautils/getter/armoapi.go +++ b/cautils/getter/armoapi.go @@ -140,6 +140,33 @@ func (armoAPI *ArmoAPI) GetCustomerGUID(customerGUID string) (*TenantResponse, e return tenant, nil } +// ControlsInputs // map[][] +func (armoAPI *ArmoAPI) GetAccountConfig(customerGUID, clusterName string) (*armotypes.CustomerConfig, error) { + accountConfig := &armotypes.CustomerConfig{} + if customerGUID == "" { + return accountConfig, nil + } + respStr, err := HttpGetter(armoAPI.httpClient, armoAPI.getAccountConfig(customerGUID, clusterName)) + if err != nil { + return nil, err + } + + if err = JSONDecoder(respStr).Decode(&accountConfig); err != nil { + return nil, err + } + + return accountConfig, nil +} + +// ControlsInputs // map[][] +func (armoAPI *ArmoAPI) GetControlsInputs(customerGUID, clusterName string) (map[string][]string, error) { + accountConfig, err := armoAPI.GetAccountConfig(customerGUID, clusterName) + if err == nil { + return accountConfig.Settings.PostureControlInputs, nil + } + return nil, err +} + type TenantResponse struct { TenantID string `json:"tenantId"` Token string `json:"token"` diff --git a/cautils/getter/armoapiutils.go b/cautils/getter/armoapiutils.go index 001a6540..83d43513 100644 --- a/cautils/getter/armoapiutils.go +++ b/cautils/getter/armoapiutils.go @@ -35,6 +35,22 @@ func (armoAPI *ArmoAPI) getExceptionsURL(customerGUID, clusterName string) strin return u.String() } +func (armoAPI *ArmoAPI) getAccountConfig(customerGUID, clusterName string) string { + u := url.URL{} + u.Scheme = "https" + u.Host = armoAPI.apiURL + u.Path = "api/v1/customerConfiguration" + + q := u.Query() + q.Add("customerGUID", customerGUID) + if clusterName != "" { // TODO - fix customer name support in Armo BE + q.Add("clusterName", clusterName) + } + u.RawQuery = q.Encode() + + return u.String() +} + func (armoAPI *ArmoAPI) getCustomerURL() string { u := url.URL{} u.Scheme = "https" diff --git a/cautils/getter/getpolicies.go b/cautils/getter/getpolicies.go index 6d496d78..819efe02 100644 --- a/cautils/getter/getpolicies.go +++ b/cautils/getter/getpolicies.go @@ -7,7 +7,7 @@ import ( type IPolicyGetter interface { GetFramework(name string) (*reporthandling.Framework, error) - GetControl(policyName string) (*reporthandling.Control, error) + GetControl(name string) (*reporthandling.Control, error) } type IExceptionsGetter interface { @@ -16,3 +16,7 @@ type IExceptionsGetter interface { type IBackend interface { GetCustomerGUID(customerGUID string) (*TenantResponse, error) } + +type IControlsInputsGetter interface { + GetControlsInputs(customerGUID, clusterName string) (map[string][]string, error) +} diff --git a/cautils/getter/loadpolicy.go b/cautils/getter/loadpolicy.go index e71451d5..dafc2201 100644 --- a/cautils/getter/loadpolicy.go +++ b/cautils/getter/loadpolicy.go @@ -30,7 +30,7 @@ func NewLoadPolicy(filePaths []string) *LoadPolicy { func (lp *LoadPolicy) GetControl(controlName string) (*reporthandling.Control, error) { control := &reporthandling.Control{} - filePath := lp.getFileForControl() + filePath := lp.filePath() f, err := os.ReadFile(filePath) if err != nil { return nil, err @@ -79,7 +79,7 @@ func (lp *LoadPolicy) GetFramework(frameworkName string) (*reporthandling.Framew } func (lp *LoadPolicy) GetExceptions(customerGUID, clusterName string) ([]armotypes.PostureExceptionPolicy, error) { - filePath := lp.getFileForException() + filePath := lp.filePath() exception := []armotypes.PostureExceptionPolicy{} f, err := os.ReadFile(filePath) if err != nil { @@ -90,10 +90,24 @@ func (lp *LoadPolicy) GetExceptions(customerGUID, clusterName string) ([]armotyp return exception, err } -func (lp *LoadPolicy) getFileForException() string { - return lp.filePaths[0] +func (lp *LoadPolicy) GetControlsInputs(customerGUID, clusterName string) (map[string][]string, error) { + filePath := lp.filePath() + accountConfig := &armotypes.CustomerConfig{} + f, err := os.ReadFile(filePath) + if err != nil { + return nil, err + } + + if err = json.Unmarshal(f, &accountConfig); err == nil { + return accountConfig.Settings.PostureControlInputs, nil + } + return nil, err } -func (lp *LoadPolicy) getFileForControl() string { - return lp.filePaths[0] +// temporary support for a list of files +func (lp *LoadPolicy) filePath() string { + if len(lp.filePaths) > 0 { + return lp.filePaths[0] + } + return "" } diff --git a/cautils/scaninfo.go b/cautils/scaninfo.go index ca0b064d..aa4a0ad0 100644 --- a/cautils/scaninfo.go +++ b/cautils/scaninfo.go @@ -10,7 +10,8 @@ import ( type ScanInfo struct { Getters PolicyIdentifier []reporthandling.PolicyIdentifier - UseExceptions string // Load exceptions configuration + UseExceptions string // Load file with exceptions configuration + ControlsInputs string // Load file with inputs for controls UseFrom []string // Load framework from local file (instead of download). Use when running offline UseDefault bool // Load framework from cached file (instead of download). Use when running offline Format string // Format results (table, json, junit ...) @@ -26,13 +27,15 @@ type ScanInfo struct { } type Getters struct { - ExceptionsGetter getter.IExceptionsGetter - PolicyGetter getter.IPolicyGetter + ExceptionsGetter getter.IExceptionsGetter + ControlsInputsGetter getter.IControlsInputsGetter + PolicyGetter getter.IPolicyGetter } func (scanInfo *ScanInfo) Init() { scanInfo.setUseFrom() scanInfo.setUseExceptions() + scanInfo.setAccountConfig() scanInfo.setOutputFile() scanInfo.setGetter() @@ -45,7 +48,15 @@ func (scanInfo *ScanInfo) setUseExceptions() { } else { scanInfo.ExceptionsGetter = getter.GetArmoAPIConnector() } +} +func (scanInfo *ScanInfo) setAccountConfig() { + if scanInfo.ControlsInputs != "" { + // load account config from file + scanInfo.ControlsInputsGetter = getter.NewLoadPolicy([]string{scanInfo.ControlsInputs}) + } else { + scanInfo.ControlsInputsGetter = getter.GetArmoAPIConnector() + } } func (scanInfo *ScanInfo) setUseFrom() { if scanInfo.UseDefault { diff --git a/opaprocessor/processorhandler.go b/opaprocessor/processorhandler.go index d4a6b759..5e17d37b 100644 --- a/opaprocessor/processorhandler.go +++ b/opaprocessor/processorhandler.go @@ -15,42 +15,37 @@ import ( "github.com/golang/glog" "github.com/open-policy-agent/opa/ast" "github.com/open-policy-agent/opa/rego" - "github.com/open-policy-agent/opa/storage" uuid "github.com/satori/go.uuid" ) const ScoreConfigPath = "/resources/config" -var RegoK8sCredentials storage.Store - type OPAProcessorHandler struct { - processedPolicy *chan *cautils.OPASessionObj - reportResults *chan *cautils.OPASessionObj - // componentConfig cautils.ComponentConfig + processedPolicy *chan *cautils.OPASessionObj + reportResults *chan *cautils.OPASessionObj + regoDependenciesData *resources.RegoDependenciesData } type OPAProcessor struct { *cautils.OPASessionObj + regoDependenciesData *resources.RegoDependenciesData } -func NewOPAProcessor(sessionObj *cautils.OPASessionObj) *OPAProcessor { +func NewOPAProcessor(sessionObj *cautils.OPASessionObj, regoDependenciesData *resources.RegoDependenciesData) *OPAProcessor { + if regoDependenciesData != nil && sessionObj != nil { + regoDependenciesData.PostureControlInputs = sessionObj.RegoInputData.PostureControlInputs + } return &OPAProcessor{ - OPASessionObj: sessionObj, + OPASessionObj: sessionObj, + regoDependenciesData: regoDependenciesData, } } func NewOPAProcessorHandler(processedPolicy, reportResults *chan *cautils.OPASessionObj) *OPAProcessorHandler { - - regoDependenciesData := resources.NewRegoDependenciesData(k8sinterface.GetK8sConfig(), cautils.ClusterName) - store, err := regoDependenciesData.TOStorage() - if err != nil { - panic(err) - } - RegoK8sCredentials = store - return &OPAProcessorHandler{ - processedPolicy: processedPolicy, - reportResults: reportResults, + processedPolicy: processedPolicy, + reportResults: reportResults, + regoDependenciesData: resources.NewRegoDependenciesData(k8sinterface.GetK8sConfig(), cautils.ClusterName), } } @@ -58,7 +53,7 @@ func (opaHandler *OPAProcessorHandler) ProcessRulesListenner() { for { opaSessionObj := <-*opaHandler.processedPolicy - opap := NewOPAProcessor(opaSessionObj) + opap := NewOPAProcessor(opaSessionObj, opaHandler.regoDependenciesData) // process if err := opap.Process(); err != nil { @@ -203,11 +198,16 @@ func (opap *OPAProcessor) runRegoOnK8s(rule *reporthandling.PolicyRule, k8sObjec } func (opap *OPAProcessor) regoEval(inputObj []map[string]interface{}, compiledRego *ast.Compiler) ([]reporthandling.RuleResponse, error) { + store, err := opap.regoDependenciesData.TOStorage() // get store + if err != nil { + return nil, err + } + rego := rego.New( rego.Query("data.armo_builtins"), // get package name from rule rego.Compiler(compiledRego), rego.Input(inputObj), - rego.Store(RegoK8sCredentials), + rego.Store(store), ) // Run evaluation diff --git a/opaprocessor/processorhandler_test.go b/opaprocessor/processorhandler_test.go index 49e0b4f2..08e11154 100644 --- a/opaprocessor/processorhandler_test.go +++ b/opaprocessor/processorhandler_test.go @@ -5,6 +5,7 @@ import ( "github.com/armosec/kubescape/cautils" "github.com/armosec/opa-utils/reporthandling" + "github.com/armosec/opa-utils/resources" "github.com/armosec/k8s-interface/k8sinterface" // _ "k8s.io/client-go/plugin/pkg/client/auth" @@ -24,7 +25,7 @@ func TestProcess(t *testing.T) { opaSessionObj.Frameworks = []reporthandling.Framework{*reporthandling.MockFrameworkA()} opaSessionObj.K8SResources = &k8sResources - opap := NewOPAProcessor(opaSessionObj) + opap := NewOPAProcessor(opaSessionObj, resources.NewRegoDependenciesDataMock()) opap.Process() opap.updateResults() for _, f := range opap.PostureReport.FrameworkReports { diff --git a/policyhandler/handlenotification.go b/policyhandler/handlenotification.go index 3a3cfdd5..8db8963e 100644 --- a/policyhandler/handlenotification.go +++ b/policyhandler/handlenotification.go @@ -6,8 +6,6 @@ import ( "github.com/armosec/kubescape/cautils" "github.com/armosec/kubescape/resourcehandler" "github.com/armosec/opa-utils/reporthandling" - - "github.com/armosec/armoapi-go/armotypes" ) // PolicyHandler - @@ -33,15 +31,9 @@ func (policyHandler *PolicyHandler) HandleNotificationRequest(notification *repo policyHandler.getters = &scanInfo.Getters // get policies - frameworks, exceptions, err := policyHandler.getPolicies(notification) - if err != nil { + if err := policyHandler.getPolicies(notification, opaSessionObj); err != nil { return err } - if len(frameworks) == 0 { - return fmt.Errorf("empty list of frameworks") - } - opaSessionObj.Frameworks = frameworks - opaSessionObj.Exceptions = exceptions k8sResources, err := policyHandler.getResources(notification, opaSessionObj, scanInfo) if err != nil { @@ -57,25 +49,6 @@ func (policyHandler *PolicyHandler) HandleNotificationRequest(notification *repo return nil } -func (policyHandler *PolicyHandler) getPolicies(notification *reporthandling.PolicyNotification) ([]reporthandling.Framework, []armotypes.PostureExceptionPolicy, error) { - - cautils.ProgressTextDisplay("Downloading/Loading policy definitions") - - frameworks, exceptions, err := policyHandler.GetPoliciesFromBackend(notification) - if err != nil { - return frameworks, exceptions, err - } - - if len(frameworks) == 0 { - err := fmt.Errorf("could not download any policies, please check previous logs") - return frameworks, exceptions, err - } - //if notification.Rules - cautils.SuccessTextDisplay("Downloaded/Loaded policy") - - return frameworks, exceptions, nil -} - func (policyHandler *PolicyHandler) getResources(notification *reporthandling.PolicyNotification, opaSessionObj *cautils.OPASessionObj, scanInfo *cautils.ScanInfo) (*cautils.K8SResources, error) { opaSessionObj.PostureReport.ClusterAPIServerInfo = policyHandler.resourceHandler.GetClusterAPIServerInfo() diff --git a/policyhandler/handlepullpolicies.go b/policyhandler/handlepullpolicies.go index 579c05db..44f895ac 100644 --- a/policyhandler/handlepullpolicies.go +++ b/policyhandler/handlepullpolicies.go @@ -2,103 +2,71 @@ package policyhandler import ( "fmt" - "strings" - "github.com/armosec/armoapi-go/armotypes" "github.com/armosec/kubescape/cautils" "github.com/armosec/opa-utils/reporthandling" ) -func (policyHandler *PolicyHandler) GetPoliciesFromBackend(notification *reporthandling.PolicyNotification) ([]reporthandling.Framework, []armotypes.PostureExceptionPolicy, error) { - var errs error - frameworks := []reporthandling.Framework{} - exceptionPolicies := []armotypes.PostureExceptionPolicy{} - // Get - cacli opa get - rule := GetScanKind(notification) +func (policyHandler *PolicyHandler) getPolicies(notification *reporthandling.PolicyNotification, policiesAndResources *cautils.OPASessionObj) error { + cautils.ProgressTextDisplay("Downloading/Loading policy definitions") - switch rule.Kind { - case reporthandling.KindFramework: + frameworks, err := policyHandler.getScanPolicies(notification) + if err != nil { + return err + } + if len(frameworks) == 0 { + return fmt.Errorf("failed to download policies, please ARMO team for more information") + } + + policiesAndResources.Frameworks = frameworks + + // get exceptions + exceptionPolicies, err := policyHandler.getters.ExceptionsGetter.GetExceptions(cautils.CustomerGUID, cautils.ClusterName) + if err == nil { + policiesAndResources.Exceptions = exceptionPolicies + } + + // get account configuration + controlsInputs, err := policyHandler.getters.ControlsInputsGetter.GetControlsInputs(cautils.CustomerGUID, cautils.ClusterName) + if err == nil { + policiesAndResources.RegoInputData.PostureControlInputs = controlsInputs + } + + cautils.SuccessTextDisplay("Downloaded/Loaded policy") + return nil +} + +func (policyHandler *PolicyHandler) getScanPolicies(notification *reporthandling.PolicyNotification) ([]reporthandling.Framework, error) { + frameworks := []reporthandling.Framework{} + + switch getScanKind(notification) { + case reporthandling.KindFramework: // Download frameworks for _, rule := range notification.Rules { - receivedFramework, recExceptionPolicies, err := policyHandler.getFrameworkPolicies(rule.Name) + receivedFramework, err := policyHandler.getters.PolicyGetter.GetFramework(rule.Name) + if err != nil { + return frameworks, policyDownloadError(err) + } if receivedFramework != nil { frameworks = append(frameworks, *receivedFramework) - if recExceptionPolicies != nil { - exceptionPolicies = append(exceptionPolicies, recExceptionPolicies...) - } - } else if err != nil { - if strings.Contains(err.Error(), "unsupported protocol scheme") { - err = fmt.Errorf("failed to download from GitHub release, try running with `--use-default` flag") - } - return nil, nil, fmt.Errorf("kind: %v, name: %s, error: %s", rule.Kind, rule.Name, err.Error()) } } - case reporthandling.KindControl: + case reporthandling.KindControl: // Download controls f := reporthandling.Framework{} var receivedControl *reporthandling.Control - var recExceptionPolicies []armotypes.PostureExceptionPolicy var err error for _, rule := range notification.Rules { - receivedControl, recExceptionPolicies, err = policyHandler.getControl(rule.Name) - if receivedControl != nil { - f.Controls = append(f.Controls, *receivedControl) - if recExceptionPolicies != nil { - exceptionPolicies = append(exceptionPolicies, recExceptionPolicies...) - } - - } else if err != nil { - if strings.Contains(err.Error(), "unsupported protocol scheme") { - err = fmt.Errorf("failed to download from GitHub release, try running with `--use-default` flag") - } - return nil, nil, fmt.Errorf("error: %s", err.Error()) + receivedControl, err = policyHandler.getters.PolicyGetter.GetControl(rule.Name) + if err != nil { + return frameworks, policyDownloadError(err) } } + if receivedControl != nil { + f.Controls = append(f.Controls, *receivedControl) + } frameworks = append(frameworks, f) // TODO: add case for control from file default: - err := fmt.Errorf("missing rule kind, expected: %s", reporthandling.KindFramework) - errs = fmt.Errorf("%s", err.Error()) + return frameworks, fmt.Errorf("unknown policy kind") } - return frameworks, exceptionPolicies, errs -} - -func (policyHandler *PolicyHandler) getFrameworkPolicies(policyName string) (*reporthandling.Framework, []armotypes.PostureExceptionPolicy, error) { - receivedFramework, err := policyHandler.getters.PolicyGetter.GetFramework(policyName) - if err != nil { - return nil, nil, err - } - - receivedException, err := policyHandler.getters.ExceptionsGetter.GetExceptions(cautils.CustomerGUID, cautils.ClusterName) - if err != nil { - return receivedFramework, nil, err - } - - return receivedFramework, receivedException, nil -} - -func GetScanKind(notification *reporthandling.PolicyNotification) *reporthandling.PolicyIdentifier { - if len(notification.Rules) > 0 { - return ¬ification.Rules[0] - } - return nil -} - -// Get control by name -func (policyHandler *PolicyHandler) getControl(policyName string) (*reporthandling.Control, []armotypes.PostureExceptionPolicy, error) { - - control := &reporthandling.Control{} - var err error - control, err = policyHandler.getters.PolicyGetter.GetControl(policyName) - if err != nil { - return control, nil, err - } - // if control == nil { - // return control, nil, fmt.Errorf("control not found") - // } - - exceptions, err := policyHandler.getters.ExceptionsGetter.GetExceptions(cautils.CustomerGUID, cautils.ClusterName) - if err != nil { - return control, nil, err - } - - return control, exceptions, nil + return frameworks, nil } diff --git a/policyhandler/handlepullpoliciesutils.go b/policyhandler/handlepullpoliciesutils.go new file mode 100644 index 00000000..c4a4b321 --- /dev/null +++ b/policyhandler/handlepullpoliciesutils.go @@ -0,0 +1,21 @@ +package policyhandler + +import ( + "fmt" + "strings" + + "github.com/armosec/opa-utils/reporthandling" +) + +func getScanKind(notification *reporthandling.PolicyNotification) reporthandling.NotificationPolicyKind { + if len(notification.Rules) > 0 { + return notification.Rules[0].Kind + } + return "unknown" +} +func policyDownloadError(err error) error { + if strings.Contains(err.Error(), "unsupported protocol scheme") { + err = fmt.Errorf("failed to download from GitHub release, try running with `--use-default` flag") + } + return err +}