diff --git a/pkg/commands/process/settings/ruleLoader.go b/pkg/commands/process/settings/ruleLoader.go index 01be008bd..48f8f4975 100644 --- a/pkg/commands/process/settings/ruleLoader.go +++ b/pkg/commands/process/settings/ruleLoader.go @@ -10,7 +10,9 @@ import ( "net/http" "os" "path/filepath" + "strconv" "strings" + "time" "gopkg.in/yaml.v3" ) @@ -24,6 +26,21 @@ func LoadRuleDefinitionsFromGitHub(ruleDefinitions map[string]RuleDefinition, fo return tagName, err } defer resp.Body.Close() + headers := resp.Header + + if headers.Get("x-ratelimit-remaining") == "0" { + resetString := headers.Get("x-ratelimit-reset") + unixTime, err := strconv.ParseInt(resetString, 10, 64) + if err != nil { + return tagName, fmt.Errorf("rules download is rate limited please wait until: %s", resetString) + } + tm := time.Unix(unixTime, 0) + return tagName, fmt.Errorf("rules download is rate limited please wait until: %s", tm.Format("2006-01-02 15:04:05")) + } + + if resp.StatusCode != 200 { + return tagName, errors.New("rules download returned non 200 status code - could not download rules") + } // Decode the response JSON to get the URL of the asset we want to download type Asset struct { @@ -40,6 +57,10 @@ func LoadRuleDefinitionsFromGitHub(ruleDefinitions map[string]RuleDefinition, fo return tagName, err } + if release.TagName == "" { + return tagName, errors.New("could not find valid release for rules") + } + bearerRulesDir := bearerRulesDir() if _, err := os.Stat(bearerRulesDir); errors.Is(err, os.ErrNotExist) { err := os.Mkdir(bearerRulesDir, os.ModePerm) diff --git a/pkg/commands/process/settings/rules.go b/pkg/commands/process/settings/rules.go index 57778678b..a77a05104 100644 --- a/pkg/commands/process/settings/rules.go +++ b/pkg/commands/process/settings/rules.go @@ -9,6 +9,7 @@ import ( "github.com/bearer/bearer/pkg/flag" "github.com/bearer/bearer/pkg/report/customdetectors" + "github.com/bearer/bearer/pkg/util/output" "github.com/bearer/bearer/pkg/util/set" "github.com/rs/zerolog/log" "gopkg.in/yaml.v3" @@ -99,7 +100,8 @@ func loadRules( tagVersion, err := LoadRuleDefinitionsFromGitHub(definitions, foundLanguages) if err != nil { - return result, fmt.Errorf("error loading rules: %s", err) + output.Fatal(fmt.Sprintf("Error loading rules: %s", err)) + // sysexit } result.BearerRulesVersion = tagVersion diff --git a/pkg/report/output/security/.snapshots/TestBuildReportString b/pkg/report/output/security/.snapshots/TestBuildReportString index ac54aa999..13889fdd8 100644 --- a/pkg/report/output/security/.snapshots/TestBuildReportString +++ b/pkg/report/output/security/.snapshots/TestBuildReportString @@ -5,7 +5,7 @@ Summary Report ===================================== Rules: - - 2 default rules applied (https://docs.bearer.com/reference/rules) + - 2 default rules applied (https://docs.bearer.com/reference/rules) [TEST] - 1 custom rules applied CRITICAL: Sensitive data sent to Rails loggers detected. [CWE-209, CWE-532] diff --git a/pkg/report/output/security/.snapshots/TestNoRulesBuildReportString b/pkg/report/output/security/.snapshots/TestNoRulesBuildReportString new file mode 100644 index 000000000..1f5758966 --- /dev/null +++ b/pkg/report/output/security/.snapshots/TestNoRulesBuildReportString @@ -0,0 +1,8 @@ + + +Summary Report + +===================================== + +Zero rules found. A security report requires rules to function. Please check configuration. + diff --git a/pkg/report/output/security/security.go b/pkg/report/output/security/security.go index 40737d7ca..deb7f3a4d 100644 --- a/pkg/report/output/security/security.go +++ b/pkg/report/output/security/security.go @@ -265,6 +265,10 @@ func BuildReportString(config settings.Config, results *Results, lineOfCodeOutpu config, ) + if rulesAvailableCount == 0 { + return reportStr, false + } + failures := map[string]map[string]bool{ types.LevelCritical: make(map[string]bool), types.LevelHigh: make(map[string]bool), @@ -385,20 +389,25 @@ func writeRuleListToString( languages map[string]*gocloc.Language, config settings.Config, ) int { - // list rules that were run - reportStr.WriteString("\n\nRules: \n") - defaultRuleCount, customRuleCount := countRules(rules, languages, config, false) builtInCount, _ := countRules(builtInRules, languages, config, true) defaultRuleCount = defaultRuleCount + builtInCount + totalRuleCount := defaultRuleCount + customRuleCount - reportStr.WriteString(fmt.Sprintf(" - %d default rules applied ", defaultRuleCount)) - reportStr.WriteString(color.HiBlackString("(https://docs.bearer.com/reference/rules)\n")) + if totalRuleCount == 0 { + reportStr.WriteString("\n\nZero rules found. A security report requires rules to function. Please check configuration.\n") + return 0 + } + reportStr.WriteString("\n\nRules: \n") + if defaultRuleCount > 0 { + reportStr.WriteString(fmt.Sprintf(" - %d default rules applied ", defaultRuleCount)) + reportStr.WriteString(color.HiBlackString(fmt.Sprintf("(https://docs.bearer.com/reference/rules) [%s]\n", config.BearerRulesVersion))) + } if customRuleCount > 0 { reportStr.WriteString(fmt.Sprintf(" - %d custom rules applied", customRuleCount)) } - return defaultRuleCount + customRuleCount + return totalRuleCount } func writeApiClientResultToString( diff --git a/pkg/report/output/security/security_test.go b/pkg/report/output/security/security_test.go index 2867596c5..fae2dee23 100644 --- a/pkg/report/output/security/security_test.go +++ b/pkg/report/output/security/security_test.go @@ -24,6 +24,8 @@ func TestBuildReportString(t *testing.T) { "warning": true, }, }) + // set rule version + config.BearerRulesVersion = "TEST" // new rules are added customRule := &settings.Rule{ @@ -68,6 +70,46 @@ func TestBuildReportString(t *testing.T) { cupaloy.SnapshotT(t, stringBuilder.String()) } +func TestNoRulesBuildReportString(t *testing.T) { + config, err := generateConfig(flag.ReportOptions{ + Report: "security", + Severity: map[string]bool{ + "critical": true, + "high": true, + "medium": true, + "low": true, + "warning": true, + }, + }) + // set rule version + config.BearerRulesVersion = "TEST" + config.Rules = map[string]*settings.Rule{} + + if err != nil { + t.Fatalf("failed to generate config:%s", err) + } + + dataflow := dummyDataflow() + + results, err := security.GetOutput(&dataflow, config) + if err != nil { + t.Fatalf("failed to generate security output err:%s", err) + } + + dummyGoclocLanguage := gocloc.Language{} + dummyGoclocResult := gocloc.Result{ + Total: &dummyGoclocLanguage, + Files: map[string]*gocloc.ClocFile{}, + Languages: map[string]*gocloc.Language{ + "Ruby": {}, + }, + MaxPathLength: 0, + } + + stringBuilder, _ := security.BuildReportString(config, results, &dummyGoclocResult, &dataflow) + cupaloy.SnapshotT(t, stringBuilder.String()) +} + func TestGetOutput(t *testing.T) { config, err := generateConfig(flag.ReportOptions{ Report: "security",