diff --git a/cautils/customerloader.go b/cautils/customerloader.go new file mode 100644 index 00000000..664c3ee2 --- /dev/null +++ b/cautils/customerloader.go @@ -0,0 +1,167 @@ +package cautils + +import ( + "context" + "encoding/json" + "io/ioutil" + "net/url" + + "github.com/armosec/kubescape/cautils/getter" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/armosec/kubescape/cautils/k8sinterface" + corev1 "k8s.io/api/core/v1" +) + +const ( + configMapName = "kubescape" + configFileName = "config" +) + +type ConfigObj struct { + CustomerGUID string `json:"customerGUID"` + ClusterName string `json:"clusterName"` + Token string `json:"token"` +} +type IClusterConfig interface { + SetCustomerGUID() + SetClusterName() + + GetCustomerGUID() + GetClusterName() + + GenerateURL() string +} + +type ClusterConfig struct { + k8s *k8sinterface.KubernetesApi + defaultNS string + armoAPI *getter.ArmoAPI + configObj *ConfigObj +} + +func NewClusterConfig(k8s *k8sinterface.KubernetesApi, armoAPI *getter.ArmoAPI) *ClusterConfig { + return &ClusterConfig{ + k8s: k8s, + armoAPI: armoAPI, + defaultNS: "default", // TODO - load default namespace from k8s api + } +} +func (c *ClusterConfig) update(configObj *ConfigObj) { + c.configObj = configObj +} +func (c *ClusterConfig) SetClusterName() { + // k8sinterface.K8SConfig. +} +func (c *ClusterConfig) GenerateURL() string { + u := url.URL{} + u.Scheme = "https" + u.Host = getter.ArmoFEURL + u.Path = "account/signup" + q := u.Query() + q.Add("invitationToken", c.configObj.Token) + q.Add("customerGUID", c.configObj.CustomerGUID) + + u.RawQuery = q.Encode() + + return u.String() +} +func (c *ClusterConfig) GetClusterName() string { + return c.configObj.ClusterName +} +func (c *ClusterConfig) GetCustomerGUID() string { + return c.configObj.CustomerGUID +} +func (c *ClusterConfig) SetCustomerGUID() error { + + // get from configMap + if configObj, _ := c.loadConfigFromConfigMap(); configObj != nil { + c.update(configObj) + return nil + } + + // get from file + if configObj, _ := c.loadConfigFromFile(); configObj != nil { + c.update(configObj) + c.updateConfigMap() + return nil + } + + // get from armoBE + if tenantResponse, err := c.armoAPI.GetCustomerGUID(); tenantResponse != nil { + c.update(&ConfigObj{CustomerGUID: tenantResponse.TenantID, Token: tenantResponse.Token}) + return c.updateConfigMap() + } else { + return err + } +} + +func (c *ClusterConfig) loadConfigFromConfigMap() (*ConfigObj, error) { + if c.k8s == nil { + return nil, nil + } + configMap, err := c.k8s.KubernetesClient.CoreV1().ConfigMaps(c.defaultNS).Get(context.Background(), configMapName, metav1.GetOptions{}) + if err != nil { + return nil, err + } + + if bData, err := json.Marshal(configMap.Data); err == nil { + return readConfig(bData) + } + return nil, nil +} + +func (c *ClusterConfig) updateConfigMap() error { + configMap, err := c.k8s.KubernetesClient.CoreV1().ConfigMaps(c.defaultNS).Get(context.Background(), configMapName, metav1.GetOptions{}) + if err != nil { + configMap = &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: configMapName, + }, + } + } + + c.updateConfigMapData(configMap) + + if err != nil { + _, err = c.k8s.KubernetesClient.CoreV1().ConfigMaps(c.defaultNS).Create(context.Background(), configMap, metav1.CreateOptions{}) + } else { + _, err = c.k8s.KubernetesClient.CoreV1().ConfigMaps(configMap.Namespace).Update(context.Background(), configMap, metav1.UpdateOptions{}) + } + return err +} +func (c *ClusterConfig) updateConfigMapData(configMap *corev1.ConfigMap) { + if len(configMap.Data) == 0 { + configMap.Data = make(map[string]string) + } + m := c.ToMapString() + for k, v := range m { + if s, ok := v.(string); ok { + configMap.Data[k] = s + } + } +} +func (c *ClusterConfig) loadConfigFromFile() (*ConfigObj, error) { + dat, err := ioutil.ReadFile(configFileName) + if err != nil { + return nil, err + } + + return readConfig(dat) +} +func readConfig(dat []byte) (*ConfigObj, error) { + + if len(dat) == 0 { + return nil, nil + } + configObj := &ConfigObj{} + err := json.Unmarshal(dat, configObj) + + return configObj, err +} +func (c *ClusterConfig) ToMapString() map[string]interface{} { + m := map[string]interface{}{} + bc, _ := json.Marshal(c.configObj) + json.Unmarshal(bc, &m) + return m +} diff --git a/cautils/getter/armoapi.go b/cautils/getter/armoapi.go index f0e515ec..d9ae1b02 100644 --- a/cautils/getter/armoapi.go +++ b/cautils/getter/armoapi.go @@ -1,9 +1,7 @@ package getter import ( - "fmt" "net/http" - "strings" "github.com/armosec/kubescape/cautils/armotypes" "github.com/armosec/kubescape/cautils/opapolicy" @@ -13,16 +11,21 @@ import ( // =============================================== ArmoAPI =============================================================== // ======================================================================================================================= +const ( + ArmoBEURL = "eggdashbe.eudev3.cyberarmorsoft.com" + ArmoERURL = "report.eudev3.cyberarmorsoft.com" + ArmoFEURL = "armoui.eudev3.cyberarmorsoft.com" + // ArmoURL = "https://dashbe.euprod1.cyberarmorsoft.com" +) + // Armo API for downloading policies type ArmoAPI struct { httpClient *http.Client - baseURL string } func NewArmoAPI() *ArmoAPI { return &ArmoAPI{ httpClient: &http.Client{}, - baseURL: "https://dashbe.euprod1.cyberarmorsoft.com", } } func (armoAPI *ArmoAPI) GetFramework(name string) (*opapolicy.Framework, error) { @@ -40,15 +43,6 @@ func (armoAPI *ArmoAPI) GetFramework(name string) (*opapolicy.Framework, error) return framework, err } -func (armoAPI *ArmoAPI) getFrameworkURL(frameworkName string) 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" - - return urlEncoder(fmt.Sprintf("%s/%s", armoAPI.baseURL, requestURI)) -} - func (armoAPI *ArmoAPI) GetExceptions(customerGUID, clusterName string) ([]armotypes.PostureExceptionPolicy, error) { exceptions := []armotypes.PostureExceptionPolicy{} if customerGUID == "" { @@ -66,11 +60,21 @@ func (armoAPI *ArmoAPI) GetExceptions(customerGUID, clusterName string) ([]armot return exceptions, nil } -func (armoAPI *ArmoAPI) getExceptionsURL(customerGUID, clusterName string) string { - requestURI := "api/v1/armoPostureExceptions" - requestURI += fmt.Sprintf("?customerGUID=%s", customerGUID) - if clusterName != "" { - requestURI += fmt.Sprintf("&clusterName=%s", clusterName) +func (armoAPI *ArmoAPI) GetCustomerGUID() (*TenantResponse, error) { + respStr, err := HttpGetter(armoAPI.httpClient, armoAPI.getCustomerURL()) + if err != nil { + return nil, err } - return urlEncoder(fmt.Sprintf("%s/%s", armoAPI.baseURL, requestURI)) + tenant := &TenantResponse{} + if err = JSONDecoder(respStr).Decode(tenant); err != nil { + return nil, err + } + + return tenant, nil +} + +type TenantResponse struct { + TenantID string `json:"tenantId"` + Token string `json:"token"` + Expires string `json:"expires"` } diff --git a/cautils/getter/armoapiutils.go b/cautils/getter/armoapiutils.go new file mode 100644 index 00000000..30bbdafc --- /dev/null +++ b/cautils/getter/armoapiutils.go @@ -0,0 +1,44 @@ +package getter + +import ( + "net/url" + "strings" +) + +func (armoAPI *ArmoAPI) getFrameworkURL(frameworkName string) string { + u := url.URL{} + u.Scheme = "https" + u.Host = ArmoBEURL + u.Path = "v1/armoFrameworks" + q := u.Query() + q.Add("customerGUID", "11111111-1111-1111-1111-111111111111") + q.Add("frameworkName", strings.ToUpper(frameworkName)) + q.Add("getRules", "true") + u.RawQuery = q.Encode() + + return u.String() +} + +func (armoAPI *ArmoAPI) getExceptionsURL(customerGUID, clusterName string) string { + u := url.URL{} + u.Scheme = "https" + u.Host = ArmoBEURL + u.Path = "api/v1/armoPostureExceptions" + + q := u.Query() + q.Add("customerGUID", customerGUID) + if clusterName != "" { + q.Add("clusterName", clusterName) + } + u.RawQuery = q.Encode() + + return u.String() +} + +func (armoAPI *ArmoAPI) getCustomerURL() string { + u := url.URL{} + u.Scheme = "https" + u.Host = ArmoBEURL + u.Path = "api/v1/createTenant" + return u.String() +} diff --git a/cmd/framework.go b/cmd/framework.go index d5622148..59682b6d 100644 --- a/cmd/framework.go +++ b/cmd/framework.go @@ -10,6 +10,7 @@ import ( "github.com/armosec/kubescape/cautils" "github.com/armosec/kubescape/cautils/armotypes" + "github.com/armosec/kubescape/cautils/getter" "github.com/armosec/kubescape/cautils/k8sinterface" "github.com/armosec/kubescape/cautils/opapolicy" "github.com/armosec/kubescape/opaprocessor" @@ -115,11 +116,22 @@ func CliSetup() error { // policy handler setup policyHandler := policyhandler.NewPolicyHandler(&processNotification, k8s) - // cli handler setup - cli := NewCLIHandler(policyHandler) - if err := cli.Scan(); err != nil { - panic(err) + // load cluster config + clusterConfig := cautils.NewClusterConfig(k8s, getter.NewArmoAPI()) + if err := clusterConfig.SetCustomerGUID(); err != nil { + fmt.Println(err) } + cautils.CustomerGUID = clusterConfig.GetCustomerGUID() + cautils.ClusterName = "minikube" // clusterConfig.GetClusterName() + + // cli handler setup + go func() { + cli := NewCLIHandler(policyHandler) + if err := cli.Scan(); err != nil { + fmt.Println(err) + os.Exit(1) + } + }() // processor setup - rego run go func() { @@ -130,6 +142,9 @@ func CliSetup() error { resultsHandling := resultshandling.NewResultsHandler(&reportResults, reporter.NewReportEventReceiver(), printer.NewPrinter(scanInfo.Format, scanInfo.Output)) score := resultsHandling.HandleResults() + // print report url + fmt.Println(clusterConfig.GenerateURL()) + adjustedFailThreshold := float32(scanInfo.FailThreshold) / 100 if score < adjustedFailThreshold { return fmt.Errorf("Scan score is bellow threshold") @@ -156,12 +171,10 @@ func (clihandler *CLIHandler) Scan() error { } switch policyNotification.NotificationType { case opapolicy.TypeExecPostureScan: - go func() { - if err := clihandler.policyHandler.HandleNotificationRequest(policyNotification, clihandler.scanInfo); err != nil { - fmt.Printf("%v\n", err) - os.Exit(0) - } - }() + // + if err := clihandler.policyHandler.HandleNotificationRequest(policyNotification, clihandler.scanInfo); err != nil { + return err + } default: return fmt.Errorf("notification type '%s' Unknown", policyNotification.NotificationType) } diff --git a/opaprocessor/processorhandler.go b/opaprocessor/processorhandler.go index 3be0c4ca..6fdea3f4 100644 --- a/opaprocessor/processorhandler.go +++ b/opaprocessor/processorhandler.go @@ -13,11 +13,11 @@ import ( "github.com/armosec/kubescape/cautils/opapolicy" "github.com/armosec/kubescape/cautils/opapolicy/resources" - "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" @@ -92,6 +92,7 @@ func (opap *OPAProcessor) Process() error { } opap.PostureReport.FrameworkReports = frameworkReports + opap.PostureReport.ReportID = uuid.NewV4().String() opap.PostureReport.ReportGenerationTime = time.Now().UTC() // glog.Infof(fmt.Sprintf("Done 'Process'. reportID: %s", opap.PostureReport.ReportID)) cautils.StopSpinner() @@ -104,6 +105,7 @@ func (opap *OPAProcessor) processFramework(framework *opapolicy.Framework) (*opa frameworkReport := opapolicy.FrameworkReport{} frameworkReport.Name = framework.Name + controlReports := []opapolicy.ControlReport{} for i := range framework.Controls { controlReport, err := opap.processControl(&framework.Controls[i]) diff --git a/resultshandling/reporter/reporteventreceiverutils.go b/resultshandling/reporter/reporteventreceiverutils.go index d0ce0c43..ee26e203 100644 --- a/resultshandling/reporter/reporteventreceiverutils.go +++ b/resultshandling/reporter/reporteventreceiverutils.go @@ -8,6 +8,7 @@ import ( "strings" "github.com/armosec/kubescape/cautils" + "github.com/armosec/kubescape/cautils/getter" "github.com/gofrs/uuid" ) @@ -36,12 +37,12 @@ func initEventReceiverURL() *url.URL { urlObj := url.URL{} urlObj.Scheme = "https" - urlObj.Host = "report.euprod1.cyberarmorsoft.com" + urlObj.Host = getter.ArmoERURL urlObj.Path = "/k8s/postureReport" - q := urlObj.Query() q.Add("customerGUID", uuid.FromStringOrNil(cautils.CustomerGUID).String()) q.Add("clusterName", cautils.ClusterName) + urlObj.RawQuery = q.Encode() return &urlObj @@ -49,9 +50,7 @@ func initEventReceiverURL() *url.URL { func hostToString(host *url.URL, reportID string) string { q := host.Query() - if reportID != "" { - q.Add("reportID", reportID) // TODO - do we add the reportID? - } + q.Add("reportID", reportID) // TODO - do we add the reportID? host.RawQuery = q.Encode() return host.String() }