diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index d0aea435..a07c05a7 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -40,7 +40,13 @@ jobs: - name: Build env: RELEASE: v1.0.${{ github.run_number }} - run: mkdir -p build/${{ matrix.os }} && go mod tidy && go build -ldflags "-w -s -X github.com/armosec/kubescape/cmd.BuildNumber=$RELEASE" -o build/${{ matrix.os }}/kubescape # && md5sum build/${{ matrix.os }}/kubescape > build/${{ matrix.os }}/kubescape.md5 + ArmoBEServer: api.armo.cloud + ArmoERServer: report.euprod1.cyberarmorsoft.com + ArmoWebsite: portal.armo.cloud + BEServerConst: github.com/armosec/kubescape/cautils/getter.ArmoBEURL + ERServerConst: github.com/armosec/kubescape/cautils/getter.ArmoERURL + WebsiteConst: github.com/armosec/kubescape/cautils/getter.ArmoFEURL + run: mkdir -p build/${{ matrix.os }} && go mod tidy && go build -ldflags "-w -s -X github.com/armosec/kubescape/cmd.BuildNumber=$RELEASE -X $BEServerConst=$ArmoBEServer -X $ERServerConst=$ArmoERServer -X $WebsiteConst=$ArmoWebsite" -o build/${{ matrix.os }}/kubescape # && md5sum build/${{ matrix.os }}/kubescape > build/${{ matrix.os }}/kubescape.md5 - name: Upload Release binaries id: upload-release-asset diff --git a/README.md b/README.md index 8c788ea0..255acc33 100644 --- a/README.md +++ b/README.md @@ -38,8 +38,8 @@ If you wish to scan all namespaces in your cluster, remove the `--exclude-namesp | `-o`/`--output` | print to stdout | Save scan result in file | | `--use-from` | | Load local framework object from specified path. If not used will download latest | | `--use-default` | `false` | Load local framework object from default path. If not used will download latest | `true`/`false` | -| `--exceptions` | | Path to an [exceptions obj](examples/exceptions.json) | -| `--results-locally` | `false` | Kubescape sends scan results to its backend to allow users to control exceptions and maintain chronological scan results. Use –results-locally if you do not wish to use these features | | +| `--exceptions` | | Path to an [exceptions obj](examples/exceptions.json). If not set will download exceptions from Armo management portal | +| `--results-locally` | `false` | Kubescape sends scan results to Armo management portal to allow users to control exceptions and maintain chronological scan results. Use this flag if you do not wish to use these features | `true`/`false`| ## Usage & Examples diff --git a/build/Dockerfile b/build/Dockerfile index d54d28a1..008bc618 100644 --- a/build/Dockerfile +++ b/build/Dockerfile @@ -4,7 +4,7 @@ ENV GO111MODULE=on WORKDIR /work ADD . . -RUN go mod download +RUN go mod tidy RUN GOOS=linux CGO_ENABLED=0 go build -ldflags="-s -w " -installsuffix cgo -o kubescape . FROM alpine diff --git a/cautils/customerloader.go b/cautils/customerloader.go index cc9821a4..053217c7 100644 --- a/cautils/customerloader.go +++ b/cautils/customerloader.go @@ -7,6 +7,7 @@ import ( "io/ioutil" "net/url" "os" + "strings" "github.com/armosec/kubescape/cautils/getter" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -21,8 +22,9 @@ const ( ) type ConfigObj struct { - CustomerGUID string `json:"customerGUID"` - Token string `json:"token"` + CustomerGUID string `json:"customerGUID"` + Token string `json:"token"` + CustomerAdminEMail string `json:"adminMail"` } func (co *ConfigObj) Json() []byte { @@ -79,6 +81,11 @@ func (c *ClusterConfig) GenerateURL() { u := url.URL{} u.Scheme = "https" u.Host = getter.ArmoFEURL + if c.configObj.CustomerAdminEMail != "" { + msgStr := fmt.Sprintf("To view all controls and get remediations ask access permissions to %s from %s", u.String(), c.configObj.CustomerAdminEMail) + InfoTextDisplay(os.Stdout, msgStr+"\n") + return + } u.Path = "account/sign-up" q := u.Query() q.Add("invitationToken", c.configObj.Token) @@ -91,30 +98,40 @@ func (c *ClusterConfig) GenerateURL() { } func (c *ClusterConfig) GetCustomerGUID() string { - return c.configObj.CustomerGUID + if c.configObj != nil { + return c.configObj.CustomerGUID + } + return "" } 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 } - + customerGUID := c.GetCustomerGUID() // get from armoBE - if tenantResponse, err := c.armoAPI.GetCustomerGUID(); tenantResponse != nil { - c.update(&ConfigObj{CustomerGUID: tenantResponse.TenantID, Token: tenantResponse.Token}) - return c.updateConfigMap() + tenantResponse, err := c.armoAPI.GetCustomerGUID(customerGUID) + if err == nil && tenantResponse != nil { + if tenantResponse.AdminMail != "" { // this customer already belongs to some user + c.update(&ConfigObj{CustomerGUID: customerGUID, CustomerAdminEMail: tenantResponse.AdminMail}) + } else { + c.update(&ConfigObj{CustomerGUID: tenantResponse.TenantID, Token: tenantResponse.Token}) + return c.updateConfigMap() + } } else { + if err != nil && strings.Contains(err.Error(), "Invitation for tenant already exists") { + return nil + } return err } + return nil } func (c *ClusterConfig) loadConfigFromConfigMap() (*ConfigObj, error) { diff --git a/cautils/getter/armoapi.go b/cautils/getter/armoapi.go index ca60e2df..4b239e29 100644 --- a/cautils/getter/armoapi.go +++ b/cautils/getter/armoapi.go @@ -1,6 +1,7 @@ package getter import ( + "fmt" "net/http" "github.com/armosec/kubescape/cautils/armotypes" @@ -11,7 +12,7 @@ import ( // =============================================== ArmoAPI =============================================================== // ======================================================================================================================= -const ( +var ( ArmoBEURL = "eggdashbe.eudev3.cyberarmorsoft.com" ArmoERURL = "report.eudev3.cyberarmorsoft.com" ArmoFEURL = "armoui.eudev3.cyberarmorsoft.com" @@ -60,8 +61,12 @@ func (armoAPI *ArmoAPI) GetExceptions(customerGUID, clusterName string) ([]armot return exceptions, nil } -func (armoAPI *ArmoAPI) GetCustomerGUID() (*TenantResponse, error) { - respStr, err := HttpGetter(armoAPI.httpClient, armoAPI.getCustomerURL()) +func (armoAPI *ArmoAPI) GetCustomerGUID(customerGUID string) (*TenantResponse, error) { + url := armoAPI.getCustomerURL() + if customerGUID != "" { + url = fmt.Sprintf("%s?customerGUID=%s", url, customerGUID) + } + respStr, err := HttpGetter(armoAPI.httpClient, url) if err != nil { return nil, err } @@ -74,7 +79,8 @@ func (armoAPI *ArmoAPI) GetCustomerGUID() (*TenantResponse, error) { } type TenantResponse struct { - TenantID string `json:"tenantId"` - Token string `json:"token"` - Expires string `json:"expires"` + TenantID string `json:"tenantId"` + Token string `json:"token"` + Expires string `json:"expires"` + AdminMail string `json:"adminMail,omitempty"` } diff --git a/cautils/getter/armoapiutils.go b/cautils/getter/armoapiutils.go index 30bbdafc..d54264fd 100644 --- a/cautils/getter/armoapiutils.go +++ b/cautils/getter/armoapiutils.go @@ -27,9 +27,9 @@ func (armoAPI *ArmoAPI) getExceptionsURL(customerGUID, clusterName string) strin q := u.Query() q.Add("customerGUID", customerGUID) - if clusterName != "" { - q.Add("clusterName", clusterName) - } + // if clusterName != "" { // TODO - fix customer name support in Armo BE + // q.Add("clusterName", clusterName) + // } u.RawQuery = q.Encode() return u.String() diff --git a/cautils/getter/downloadreleasedpolicy.go b/cautils/getter/downloadreleasedpolicy.go index 4f9f3e8a..6853e77a 100644 --- a/cautils/getter/downloadreleasedpolicy.go +++ b/cautils/getter/downloadreleasedpolicy.go @@ -27,7 +27,9 @@ func NewDownloadReleasedPolicy() *DownloadReleasedPolicy { } func (drp *DownloadReleasedPolicy) GetFramework(name string) (*opapolicy.Framework, error) { - drp.setURL(name) + if err := drp.setURL(name); err != nil { + return nil, err + } respStr, err := HttpGetter(drp.httpClient, drp.hostURL) if err != nil { return nil, err @@ -71,12 +73,13 @@ func (drp *DownloadReleasedPolicy) setURL(frameworkName string) error { if name == frameworkName { if url, ok := asset["browser_download_url"].(string); ok { drp.hostURL = url + return nil } } } } } } - return nil + return fmt.Errorf("failed to download '%s' - not found", frameworkName) } diff --git a/cautils/opapolicy/datastructures.go b/cautils/opapolicy/datastructures.go index e41bdba5..30c031ab 100644 --- a/cautils/opapolicy/datastructures.go +++ b/cautils/opapolicy/datastructures.go @@ -41,8 +41,8 @@ type FrameworkReport struct { } type ControlReport struct { armotypes.PortalBase `json:",inline"` + ControlID string `json:"id"` Name string `json:"name"` - ID string `json:"id"` RuleReports []RuleReport `json:"ruleReports"` Remediation string `json:"remediation"` Description string `json:"description"` @@ -101,7 +101,7 @@ type PolicyRule struct { // Control represents a collection of rules which are combined together to single purpose type Control struct { armotypes.PortalBase `json:",inline"` - ID string `json:"id"` + ControlID string `json:"id"` CreationTime string `json:"creationTime"` Description string `json:"description"` Remediation string `json:"remediation"` diff --git a/cautils/opapolicy/datastructures_mock.go b/cautils/opapolicy/datastructures_mock.go index afe9ae63..db0caeb0 100644 --- a/cautils/opapolicy/datastructures_mock.go +++ b/cautils/opapolicy/datastructures_mock.go @@ -33,7 +33,8 @@ func MockFrameworkReportA() *FrameworkReport { Name: AMockFrameworkName, ControlReports: []ControlReport{ { - Name: AMockControlName, + ControlID: "C-0010", + Name: AMockControlName, RuleReports: []RuleReport{ { Name: AMockRuleName, diff --git a/cmd/framework.go b/cmd/framework.go index 50bad405..1487e0fa 100644 --- a/cmd/framework.go +++ b/cmd/framework.go @@ -94,7 +94,7 @@ func init() { frameworkCmd.Flags().StringVarP(&scanInfo.Output, "output", "o", "", "Output file. print output to file and not stdout") frameworkCmd.Flags().BoolVarP(&scanInfo.Silent, "silent", "s", false, "Silent progress messages") frameworkCmd.Flags().Uint16VarP(&scanInfo.FailThreshold, "fail-threshold", "t", 0, "Failure threshold is the percent bellow which the command fails and returns exit code -1") - frameworkCmd.Flags().BoolVarP(&scanInfo.DoNotSendResults, "results-locally", "", false, "Kubescape sends scan results to its backend to allow users to control exceptions and maintain chronological scan results. Use –results-locally if you do not wish to use these features") + frameworkCmd.Flags().BoolVarP(&scanInfo.DoNotSendResults, "results-locally", "", false, "Kubescape sends scan results to Armosec backend to allow users to control exceptions and maintain chronological scan results. Use this flag if you do not wish to use these features") } func CliSetup() error { diff --git a/install.sh b/install.sh index c2bc2765..4f630a76 100755 --- a/install.sh +++ b/install.sh @@ -1,7 +1,7 @@ #!/bin/bash set -e -echo "Installing Kubescape..." +echo -e "\033[0;36mInstalling Kubescape..." echo BASE_DIR=~/.kubescape @@ -27,18 +27,22 @@ mkdir -p $BASE_DIR OUTPUT=$BASE_DIR/$KUBESCAPE_EXEC curl --progress-bar -L $DOWNLOAD_URL -o $OUTPUT -echo -e "\033[32m[V] Downloaded Kubescape" # Ping download counter curl --silent https://us-central1-elated-pottery-310110.cloudfunctions.net/kubescape-download-counter -o /dev/null -chmod +x $OUTPUT || sudo chmod +x $OUTPUT -rm -f /usr/local/bin/$KUBESCAPE_EXEC || sudo rm -f /usr/local/bin/$KUBESCAPE_EXEC -cp $OUTPUT /usr/local/bin || sudo cp $OUTPUT /usr/local/bin +chmod +x $OUTPUT 2>/dev/null || sudo chmod +x $OUTPUT +rm -f /usr/local/bin/$KUBESCAPE_EXEC 2>/dev/null || sudo rm -f /usr/local/bin/$KUBESCAPE_EXEC +cp $OUTPUT /usr/local/bin 2>/dev/null || sudo cp $OUTPUT /usr/local/bin rm -rf $OUTPUT -echo -e "[V] Finished Installation" +echo +echo -e "\033[32mFinished Installation." + +echo -e "\033[0m" +$KUBESCAPE_EXEC version echo -echo -e "\033[35m Usage: $ $KUBESCAPE_EXEC scan framework nsa --exclude-namespaces kube-system,kube-public" -echo +echo -e "\033[35mUsage: $ $KUBESCAPE_EXEC scan framework nsa --exclude-namespaces kube-system,kube-public" + +echo -e "\033[0m" \ No newline at end of file diff --git a/opaprocessor/processorhandler.go b/opaprocessor/processorhandler.go index 49321d44..b2546ab8 100644 --- a/opaprocessor/processorhandler.go +++ b/opaprocessor/processorhandler.go @@ -123,9 +123,9 @@ func (opap *OPAProcessor) processControl(control *opapolicy.Control) (*opapolicy controlReport := opapolicy.ControlReport{} controlReport.PortalBase = control.PortalBase + controlReport.ControlID = control.ControlID controlReport.Name = control.Name - controlReport.ID = control.ID controlReport.Description = control.Description controlReport.Remediation = control.Remediation diff --git a/policyhandler/handlepullpolicies.go b/policyhandler/handlepullpolicies.go index 56afabb6..5d335ac2 100644 --- a/policyhandler/handlepullpolicies.go +++ b/policyhandler/handlepullpolicies.go @@ -3,6 +3,7 @@ package policyhandler import ( "fmt" + "github.com/armosec/kubescape/cautils" "github.com/armosec/kubescape/cautils/armotypes" "github.com/armosec/kubescape/cautils/opapolicy" ) @@ -18,7 +19,7 @@ func (policyHandler *PolicyHandler) GetPoliciesFromBackend(notification *opapoli case opapolicy.KindFramework: receivedFramework, recExceptionPolicies, err := policyHandler.getFrameworkPolicies(rule.Name) if err != nil { - errs = fmt.Errorf("%v\nKind: %v, Name: %s, error: %s", errs, rule.Kind, rule.Name, err.Error()) + return nil, nil, fmt.Errorf("kind: %v, name: %s, error: %s", rule.Kind, rule.Name, err.Error()) } if receivedFramework != nil { frameworks = append(frameworks, *receivedFramework) @@ -41,7 +42,7 @@ func (policyHandler *PolicyHandler) getFrameworkPolicies(policyName string) (*op return nil, nil, err } - receivedException, err := policyHandler.getters.ExceptionsGetter.GetExceptions("", "") + receivedException, err := policyHandler.getters.ExceptionsGetter.GetExceptions(cautils.CustomerGUID, cautils.ClusterName) if err != nil { return receivedFramework, nil, err } diff --git a/scapepkg/exceptions/exceptionprocessor.go b/scapepkg/exceptions/exceptionprocessor.go index 2c29688a..45cf7b4b 100644 --- a/scapepkg/exceptions/exceptionprocessor.go +++ b/scapepkg/exceptions/exceptionprocessor.go @@ -1,6 +1,7 @@ package exceptions import ( + "github.com/armosec/kubescape/cautils" "github.com/armosec/kubescape/cautils/k8sinterface" "github.com/armosec/kubescape/cautils/armotypes" @@ -91,9 +92,9 @@ func hasException(designator *armotypes.PortalDesignator, workload k8sinterface. return false // if designators are empty } - // if cluster != "" && cluster != ClusterName { // TODO - where do we receive cluster name from? - // return false // cluster name does not match - // } + if cluster != "" && cautils.ClusterName != "" && cluster != cautils.ClusterName { // TODO - where do we receive cluster name from? + return false // cluster name does not match + } if namespace != "" && !compareNamespace(workload, namespace) { return false // namespaces do not match