diff --git a/cautils/getter/getpolicies.go b/cautils/getter/getpolicies.go new file mode 100644 index 00000000..10287710 --- /dev/null +++ b/cautils/getter/getpolicies.go @@ -0,0 +1,115 @@ +package getter + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "strings" + + "github.com/armosec/kubescape/cautils/opapolicy" +) + +const DefaultLocalStore = ".kubescape" + +type IPolicyGetter interface { + GetFramework(name string) (*opapolicy.Framework, error) +} + +// ======================================================================================================================= +// ======================================== DownloadReleasedPolicy ======================================================= +// ======================================================================================================================= + +// Download released version +type DownloadReleasedPolicy struct { + hostURL string + httpClient *http.Client +} + +func NewDownloadReleasedPolicy() *DownloadReleasedPolicy { + return &DownloadReleasedPolicy{ + hostURL: "", + httpClient: &http.Client{}, + } +} + +func (drp *DownloadReleasedPolicy) GetFramework(name string) (*opapolicy.Framework, error) { + drp.setURL(name) + respStr, err := HttpGetter(drp.httpClient, drp.hostURL) + if err != nil { + return nil, err + } + + framework := &opapolicy.Framework{} + err = JSONDecoder(respStr).Decode(framework) + return framework, err +} + +func (drp *DownloadReleasedPolicy) setURL(frameworkName string) { + // requestURI := "v1/armoFrameworks" + + // drp.hostURL = URLEncoder(fmt.Sprintf("%s/%s", drp.hostURL, requestURI)) +} + +// ======================================================================================================================= +// ============================================== LoadPolicy ============================================================= +// ======================================================================================================================= + +// Load policies from a local repository +type LoadPolicy struct { + filePath string +} + +func NewLoadPolicy(filePath string) *LoadPolicy { + return &LoadPolicy{ + filePath: filePath, + } +} + +func (lp *LoadPolicy) GetFramework(filename string) (*opapolicy.Framework, error) { + + framework := &opapolicy.Framework{} + f, err := ioutil.ReadFile(filename) + if err != nil { + return nil, err + } + err = json.Unmarshal(f, framework) + return framework, err +} + +// ======================================================================================================================= +// =============================================== ArmoAPI =============================================================== +// ======================================================================================================================= + +// Armo API for downloading policies +type ArmoAPI struct { + httpClient *http.Client + hostURL string +} + +func NewArmoAPI() *ArmoAPI { + return &ArmoAPI{ + httpClient: &http.Client{}, + hostURL: "https://dashbe.eustage2.cyberarmorsoft.com", + } +} +func (armoAPI *ArmoAPI) GetFramework(name string) (*opapolicy.Framework, error) { + armoAPI.setURL(name) + respStr, err := HttpGetter(armoAPI.httpClient, armoAPI.hostURL) + if err != nil { + return nil, err + } + + framework := opapolicy.Framework{} + err = JSONDecoder(respStr).Decode(&framework) + return &framework, err +} + +func (armoAPI *ArmoAPI) setURL(frameworkName string) { + requestURI := "v1/armoFrameworks" + requestURI += fmt.Sprintf("?customerGUID=%s", "11111111-1111-1111-1111-111111111111") + requestURI += fmt.Sprintf("&frameworkName=%s", strings.ToUpper(frameworkName)) + requestURI += "&getRules=true" + + armoAPI.hostURL = urlEncoder(fmt.Sprintf("%s/%s", armoAPI.hostURL, requestURI)) +} diff --git a/cautils/getter/getpoliciesutils.go b/cautils/getter/getpoliciesutils.go new file mode 100644 index 00000000..37542d41 --- /dev/null +++ b/cautils/getter/getpoliciesutils.go @@ -0,0 +1,90 @@ +package getter + +import ( + "encoding/json" + "fmt" + "io" + "net/http" + "net/url" + "strings" +) + +// JSONDecoder returns JSON decoder for given string +func JSONDecoder(origin string) *json.Decoder { + dec := json.NewDecoder(strings.NewReader(origin)) + dec.UseNumber() + return dec +} + +func HttpGetter(httpClient *http.Client, fullURL string) (string, error) { + + req, err := http.NewRequest("GET", fullURL, nil) + if err != nil { + return "", err + } + resp, err := httpClient.Do(req) + if err != nil { + return "", err + } + respStr, err := httpRespToString(resp) + if err != nil { + return "", err + } + return respStr, nil +} + +// HTTPRespToString parses the body as string and checks the HTTP status code, it closes the body reader at the end +func httpRespToString(resp *http.Response) (string, error) { + if resp == nil || resp.Body == nil { + return "", nil + } + strBuilder := strings.Builder{} + defer resp.Body.Close() + if resp.ContentLength > 0 { + strBuilder.Grow(int(resp.ContentLength)) + } + bytesNum, err := io.Copy(&strBuilder, resp.Body) + respStr := strBuilder.String() + if err != nil { + respStrNewLen := len(respStr) + if respStrNewLen > 1024 { + respStrNewLen = 1024 + } + return "", fmt.Errorf("HTTP request failed. URL: '%s', Read-ERROR: '%s', HTTP-CODE: '%s', BODY(top): '%s', HTTP-HEADERS: %v, HTTP-BODY-BUFFER-LENGTH: %v", resp.Request.URL.RequestURI(), err, resp.Status, respStr[:respStrNewLen], resp.Header, bytesNum) + } + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + respStrNewLen := len(respStr) + if respStrNewLen > 1024 { + respStrNewLen = 1024 + } + err = fmt.Errorf("HTTP request failed. URL: '%s', HTTP-ERROR: '%s', BODY: '%s', HTTP-HEADERS: %v, HTTP-BODY-BUFFER-LENGTH: %v", resp.Request.URL.RequestURI(), resp.Status, respStr[:respStrNewLen], resp.Header, bytesNum) + } + + return respStr, err +} + +// URLEncoder encode url +func urlEncoder(oldURL string) string { + fullURL := strings.Split(oldURL, "?") + baseURL, err := url.Parse(fullURL[0]) + if err != nil { + return "" + } + + // Prepare Query Parameters + if len(fullURL) > 1 { + params := url.Values{} + queryParams := strings.Split(fullURL[1], "&") + for _, i := range queryParams { + queryParam := strings.Split(i, "=") + val := "" + if len(queryParam) > 1 { + val = queryParam[1] + } + params.Add(queryParam[0], val) + } + baseURL.RawQuery = params.Encode() + } + + return baseURL.String() +} diff --git a/cautils/opapolicy/datastructures.go b/cautils/opapolicy/datastructures.go index fcc405ff..6a512499 100644 --- a/cautils/opapolicy/datastructures.go +++ b/cautils/opapolicy/datastructures.go @@ -1,7 +1,6 @@ package opapolicy import ( - "path/filepath" "time" armotypes "github.com/armosec/kubescape/cautils/armotypes" @@ -150,43 +149,3 @@ type PolicyIdentifier struct { Kind NotificationPolicyKind `json:"kind"` Name string `json:"name"` } - -type ScanInfo struct { - PolicyIdentifier PolicyIdentifier - Format string - Output string - ExcludedNamespaces string - InputPatterns []string - Silent bool -} - -func (scanInfo *ScanInfo) Init() { - // scanInfo.setSilentMode() - scanInfo.setOutputFile() - -} - -func (scanInfo *ScanInfo) setSilentMode() { - if scanInfo.Format == "json" || scanInfo.Format == "junit" { - scanInfo.Silent = true - } - if scanInfo.Output != "" { - scanInfo.Silent = true - } -} - -func (scanInfo *ScanInfo) setOutputFile() { - if scanInfo.Output == "" { - return - } - if scanInfo.Format == "json" { - if filepath.Ext(scanInfo.Output) != "json" { - scanInfo.Output += ".json" - } - } - if scanInfo.Format == "junit" { - if filepath.Ext(scanInfo.Output) != "xml" { - scanInfo.Output += ".xml" - } - } -} diff --git a/cautils/scaninfo.go b/cautils/scaninfo.go new file mode 100644 index 00000000..32545c5f --- /dev/null +++ b/cautils/scaninfo.go @@ -0,0 +1,52 @@ +package cautils + +import ( + "path/filepath" + + "github.com/armosec/kubescape/cautils/getter" + "github.com/armosec/kubescape/cautils/opapolicy" +) + +type ScanInfo struct { + PolicyGetter getter.IPolicyGetter + PolicyIdentifier opapolicy.PolicyIdentifier + Format string + Output string + ExcludedNamespaces string + InputPatterns []string + Silent bool +} + +func (scanInfo *ScanInfo) Init() { + // scanInfo.setSilentMode() + scanInfo.setOutputFile() + scanInfo.setGetter() + +} +func (scanInfo *ScanInfo) setGetter() { + scanInfo.PolicyGetter = getter.NewArmoAPI() +} +func (scanInfo *ScanInfo) setSilentMode() { + if scanInfo.Format == "json" || scanInfo.Format == "junit" { + scanInfo.Silent = true + } + if scanInfo.Output != "" { + scanInfo.Silent = true + } +} + +func (scanInfo *ScanInfo) setOutputFile() { + if scanInfo.Output == "" { + return + } + if scanInfo.Format == "json" { + if filepath.Ext(scanInfo.Output) != "json" { + scanInfo.Output += ".json" + } + } + if scanInfo.Format == "junit" { + if filepath.Ext(scanInfo.Output) != "xml" { + scanInfo.Output += ".xml" + } + } +} diff --git a/cmd/framework.go b/cmd/framework.go index b4550c1b..5e334d13 100644 --- a/cmd/framework.go +++ b/cmd/framework.go @@ -19,12 +19,12 @@ import ( "github.com/spf13/cobra" ) -var scanInfo opapolicy.ScanInfo +var scanInfo cautils.ScanInfo var supportedFrameworks = []string{"nsa"} type CLIHandler struct { policyHandler *policyhandler.PolicyHandler - scanInfo *opapolicy.ScanInfo + scanInfo *cautils.ScanInfo } var frameworkCmd = &cobra.Command{ @@ -74,7 +74,7 @@ func isValidFramework(framework string) bool { func init() { scanCmd.AddCommand(frameworkCmd) - scanInfo = opapolicy.ScanInfo{} + scanInfo = cautils.ScanInfo{} frameworkCmd.Flags().StringVarP(&scanInfo.ExcludedNamespaces, "exclude-namespaces", "e", "", "Namespaces to exclude from check") frameworkCmd.Flags().StringVarP(&scanInfo.Format, "format", "f", "pretty-printer", `Output format. supported formats: "pretty-printer"/"json"/"junit"`) frameworkCmd.Flags().StringVarP(&scanInfo.Output, "output", "o", "", "Output file. print output to file and not stdout") diff --git a/policyhandler/filesloader.go b/policyhandler/filesloader.go index 88753bc5..e3272f65 100644 --- a/policyhandler/filesloader.go +++ b/policyhandler/filesloader.go @@ -28,7 +28,7 @@ const ( JSON_FILE_FORMAT FileFormat = "json" ) -func (policyHandler *PolicyHandler) loadResources(frameworks []opapolicy.Framework, scanInfo *opapolicy.ScanInfo) (*cautils.K8SResources, error) { +func (policyHandler *PolicyHandler) loadResources(frameworks []opapolicy.Framework, scanInfo *cautils.ScanInfo) (*cautils.K8SResources, error) { workloads := []k8sinterface.IWorkload{} // load resource from local file system diff --git a/policyhandler/handlenotification.go b/policyhandler/handlenotification.go index ae148662..f63c303b 100644 --- a/policyhandler/handlenotification.go +++ b/policyhandler/handlenotification.go @@ -4,6 +4,7 @@ import ( "fmt" "github.com/armosec/kubescape/cautils" + "github.com/armosec/kubescape/cautils/getter" "github.com/armosec/kubescape/cautils/k8sinterface" @@ -25,13 +26,13 @@ func NewPolicyHandler(processPolicy *chan *cautils.OPASessionObj, k8s *k8sinterf } } -func (policyHandler *PolicyHandler) HandleNotificationRequest(notification *opapolicy.PolicyNotification, scanInfo *opapolicy.ScanInfo) error { +func (policyHandler *PolicyHandler) HandleNotificationRequest(notification *opapolicy.PolicyNotification, scanInfo *cautils.ScanInfo) error { opaSessionObj := cautils.NewOPASessionObj(nil, nil) // validate notification // TODO // get policies - frameworks, err := policyHandler.getPolicies(notification) + frameworks, err := policyHandler.getPolicies(notification, scanInfo.PolicyGetter) if err != nil { return err } @@ -54,12 +55,12 @@ func (policyHandler *PolicyHandler) HandleNotificationRequest(notification *opap return nil } -func (policyHandler *PolicyHandler) getPolicies(notification *opapolicy.PolicyNotification) ([]opapolicy.Framework, error) { +func (policyHandler *PolicyHandler) getPolicies(notification *opapolicy.PolicyNotification, policyGetter getter.IPolicyGetter) ([]opapolicy.Framework, error) { - cautils.ProgressTextDisplay("Downloading framework definitions") + cautils.ProgressTextDisplay("Downloading/Loading framework definitions") // TODO - support load policies from local file - frameworks, err := policyHandler.GetPoliciesFromBackend(notification) + frameworks, err := policyHandler.GetPoliciesFromBackend(notification, policyGetter) if err != nil { return frameworks, err } @@ -68,12 +69,12 @@ func (policyHandler *PolicyHandler) getPolicies(notification *opapolicy.PolicyNo err := fmt.Errorf("could not download any policies, please check previous logs") return frameworks, err } - cautils.SuccessTextDisplay("Downloaded framework") + cautils.SuccessTextDisplay("Downloaded/Loaded framework") return frameworks, nil } -func (policyHandler *PolicyHandler) getResources(notification *opapolicy.PolicyNotification, opaSessionObj *cautils.OPASessionObj, scanInfo *opapolicy.ScanInfo) (*cautils.K8SResources, error) { +func (policyHandler *PolicyHandler) getResources(notification *opapolicy.PolicyNotification, opaSessionObj *cautils.OPASessionObj, scanInfo *cautils.ScanInfo) (*cautils.K8SResources, error) { var k8sResources *cautils.K8SResources var err error if len(scanInfo.InputPatterns) > 0 { diff --git a/policyhandler/handlepullpolicies.go b/policyhandler/handlepullpolicies.go index d219f42d..f034ba9b 100644 --- a/policyhandler/handlepullpolicies.go +++ b/policyhandler/handlepullpolicies.go @@ -1,146 +1,28 @@ package policyhandler import ( - "encoding/json" "fmt" - "io" - "net/http" - "net/url" - "strings" + "github.com/armosec/kubescape/cautils/getter" "github.com/armosec/kubescape/cautils/opapolicy" ) -// URLEncoder encode url -func URLEncoder(oldURL string) string { - fullURL := strings.Split(oldURL, "?") - baseURL, err := url.Parse(fullURL[0]) - if err != nil { - return "" - } - - // Prepare Query Parameters - if len(fullURL) > 1 { - params := url.Values{} - queryParams := strings.Split(fullURL[1], "&") - for _, i := range queryParams { - queryParam := strings.Split(i, "=") - val := "" - if len(queryParam) > 1 { - val = queryParam[1] - } - params.Add(queryParam[0], val) - } - baseURL.RawQuery = params.Encode() - } - - return baseURL.String() -} - -type IArmoAPI interface { - OPAFRAMEWORKGet(string) ([]opapolicy.Framework, error) -} - -type ArmoAPI struct { - httpClient *http.Client - hostURL string -} - -func NewArmoAPI() *ArmoAPI { - return &ArmoAPI{ - httpClient: &http.Client{}, - hostURL: "https://dashbe.eustage2.cyberarmorsoft.com", - } -} -func (db *ArmoAPI) GetServerAddress() string { - return db.hostURL -} -func (db *ArmoAPI) GetHttpClient() *http.Client { - return db.httpClient -} -func (db *ArmoAPI) OPAFRAMEWORKGet(name string) ([]opapolicy.Framework, error) { - requestURI := "v1/armoFrameworks" - requestURI += fmt.Sprintf("?customerGUID=%s", "11111111-1111-1111-1111-111111111111") - requestURI += fmt.Sprintf("&frameworkName=%s", strings.ToUpper(name)) - requestURI += "&getRules=true" - - fullURL := URLEncoder(fmt.Sprintf("%s/%s", db.GetServerAddress(), requestURI)) - frameworkList := []opapolicy.Framework{} - - req, err := http.NewRequest("GET", fullURL, nil) - if err != nil { - return frameworkList, err - } - c := http.Client{} - resp, err := c.Do(req) - if err != nil { - return frameworkList, err - } - respStr, err := HTTPRespToString(resp) - if err != nil { - return frameworkList, err - } - if name != "" { - frameworkSingle := opapolicy.Framework{} - err = JSONDecoder(respStr).Decode(&frameworkSingle) - frameworkList = append(frameworkList, frameworkSingle) - } else { - err = JSONDecoder(respStr).Decode(&frameworkList) - } - return frameworkList, err -} - -// JSONDecoder returns JSON decoder for given string -func JSONDecoder(origin string) *json.Decoder { - dec := json.NewDecoder(strings.NewReader(origin)) - dec.UseNumber() - return dec -} - -// HTTPRespToString parses the body as string and checks the HTTP status code, it closes the body reader at the end -func HTTPRespToString(resp *http.Response) (string, error) { - if resp == nil || resp.Body == nil { - return "", nil - } - strBuilder := strings.Builder{} - defer resp.Body.Close() - if resp.ContentLength > 0 { - strBuilder.Grow(int(resp.ContentLength)) - } - bytesNum, err := io.Copy(&strBuilder, resp.Body) - respStr := strBuilder.String() - if err != nil { - respStrNewLen := len(respStr) - if respStrNewLen > 1024 { - respStrNewLen = 1024 - } - return "", fmt.Errorf("HTTP request failed. URL: '%s', Read-ERROR: '%s', HTTP-CODE: '%s', BODY(top): '%s', HTTP-HEADERS: %v, HTTP-BODY-BUFFER-LENGTH: %v", resp.Request.URL.RequestURI(), err, resp.Status, respStr[:respStrNewLen], resp.Header, bytesNum) - } - if resp.StatusCode < 200 || resp.StatusCode >= 300 { - respStrNewLen := len(respStr) - if respStrNewLen > 1024 { - respStrNewLen = 1024 - } - err = fmt.Errorf("HTTP request failed. URL: '%s', HTTP-ERROR: '%s', BODY: '%s', HTTP-HEADERS: %v, HTTP-BODY-BUFFER-LENGTH: %v", resp.Request.URL.RequestURI(), resp.Status, respStr[:respStrNewLen], resp.Header, bytesNum) - } - - return respStr, err -} - -func (policyHandler *PolicyHandler) GetPoliciesFromBackend(notification *opapolicy.PolicyNotification) ([]opapolicy.Framework, error) { +func (policyHandler *PolicyHandler) GetPoliciesFromBackend(notification *opapolicy.PolicyNotification, getPolicies getter.IPolicyGetter) ([]opapolicy.Framework, error) { var errs error - d := NewArmoAPI() + // d := getter.NewArmoAPI() frameworks := []opapolicy.Framework{} // Get - cacli opa get for _, rule := range notification.Rules { switch rule.Kind { case opapolicy.KindFramework: // backend - receivedFrameworks, err := d.OPAFRAMEWORKGet(rule.Name) + receivedFramework, err := getPolicies.GetFramework(rule.Name) if err != nil { - errs = fmt.Errorf("Could not download framework, please check if this framework exists") + errs = fmt.Errorf("could not download framework, please check if this framework exists") + } + if receivedFramework != nil { + frameworks = append(frameworks, *receivedFramework) } - frameworks = append(frameworks, receivedFrameworks...) default: err := fmt.Errorf("Missing rule kind, expected: %s", opapolicy.KindFramework)