diff --git a/.github/workflows/ci-go.yml b/.github/workflows/ci-go.yml index 9fcc37d..dd493ae 100644 --- a/.github/workflows/ci-go.yml +++ b/.github/workflows/ci-go.yml @@ -21,7 +21,10 @@ jobs: run: | go mod tidy git diff --exit-code - - name: lint - run: make lint-go + - name: golangci-lint + uses: golangci/golangci-lint-action@v8 + with: + version: v2.4.0 + args: ./cmd/... ./pkg/... ./tests/... - name: test run: make unit diff --git a/Makefile b/Makefile index 779efca..b04f2b0 100644 --- a/Makefile +++ b/Makefile @@ -20,8 +20,7 @@ cov: lint: lint-go lint-docker lint-go: - docker build --quiet --target golangci-lint -t golangci-lint:latest . - docker run --rm -v $(shell pwd):/app -w /app golangci-lint golangci-lint run ./... + golangci-lint run ./cmd/... ./pkg/... ./tests/... lint-docker: docker build --quiet --target hadolint -t hadolint:latest . diff --git a/examples/nullify.yaml b/examples/nullify.yaml index 6ed20d1..2a4e638 100644 --- a/examples/nullify.yaml +++ b/examples/nullify.yaml @@ -6,94 +6,17 @@ ignore_dirs: - dir1 ignore_paths: - data/**/* -notifications: - all-events-webhook: - events: - all: - minimum_severity: high - secret_types: [ ssh_key ] - targets: - webhook: - urls: [ https://webhook.site/123456 ] - findings-to-slack-and-email: - events: - new_code_findings: - minimum_severity: high - new_secret_findings: - types: [ ssh_key ] - new_dependency_findings: - minimum_severity: high - targets: - slack: - channels: [ "123456" ] - email: - addresses: [ notifications@nullify.ai, noreply@nullify.ai ] - repositories: - - config-file-parser - - dast-action - - cli -scheduled_notifications: - new-findings: - schedule: "0 0 * * *" - timezone: "America/Los_Angeles" - topics: - all: true - targets: - slack: - channels: [ "123456" ] - email: - addresses: [ notifications@nullify.ai, noreply@nullify.ai ] - repositories: - - config-file-parser - - dast-action - - cli -code: - auto_fix: - enabled: true - max_pull_requests_open: 2 - max_pull_request_creation_rate: - count: 1 - days: 1 - ignore: - - cwes: [ 589 ] # Potential HTTP request made with variable url - reason: HTTP requests with variables in tests don't matter - paths: [ "**/tests/*" ] - repositories: - - config-file-parser - - dast-action - - cli - - rule_ids: [ python-sql-injection ] - reason: This code won't be going live until next year but we should fix it before then - expiry: "2021-12-31" -dependencies: - auto_fix: - enabled: true - max_pull_requests_open: 2 - max_pull_request_creation_rate: - count: 1 - days: 1 - ignore: - - cves: [ CVE-2021-1234 ] - reason: This is a false positive - expiry: "2021-12-31" - - cves: [ CVE-2021-5678 ] - reason: This isn't exploitable in client applications - expiry: "2021-12-31" - repositories: - - dast-action - - cli secrets: ignore: - value: mocksecret123 reason: This is a test secret, it has no access to anything - paths: [ "**/tests/*" ] - pattern: id[0-9]+ reason: These are not secrets, they are internal identifiers - value: actualsecret123 reason: We can't remove this right now but we should expiry: "2021-12-31" - sha256: 87cbebfeebc05f7c54ac9336c4b4bbec831227a641951a4bde7edd56020f8590 # this is correct-horse-battery-staple - reason: This was allowlisted from the Nullify dashboard + reason: This was ignored from the Nullify dashboard integrations: jira: disabled: true diff --git a/go.mod b/go.mod index bcfd5f2..c82ee31 100644 --- a/go.mod +++ b/go.mod @@ -20,6 +20,5 @@ require ( require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/robfig/cron/v3 v3.0.1 golang.org/x/exp v0.0.0-20250819193227-8b4c13bb791b ) diff --git a/go.sum b/go.sum index cb954c9..3f6e94c 100644 --- a/go.sum +++ b/go.sum @@ -16,8 +16,6 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= -github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg= github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o= diff --git a/pkg/merger/merger.go b/pkg/merger/merger.go index 867c09e..17e7ec2 100644 --- a/pkg/merger/merger.go +++ b/pkg/merger/merger.go @@ -104,18 +104,10 @@ func MergeConfigFiles( config.IgnorePaths = extraConfig.IgnorePaths } - if extraConfig.Code.AutoFix != nil { - config.Code.AutoFix = extraConfig.Code.AutoFix - } - if len(extraConfig.Code.Ignore) > 0 { config.Code.Ignore = extraConfig.Code.Ignore } - if extraConfig.Dependencies.AutoFix != nil { - config.Dependencies.AutoFix = extraConfig.Dependencies.AutoFix - } - if len(extraConfig.Dependencies.Ignore) > 0 { config.Dependencies.Ignore = extraConfig.Dependencies.Ignore } @@ -142,21 +134,6 @@ func MergeConfigFiles( } } - if len(extraConfig.Notifications) > 0 && config.Notifications == nil { - config.Notifications = map[string]models.Notification{} - } - - for k, v := range extraConfig.Notifications { - config.Notifications[k] = v - } - - if len(extraConfig.ScheduledNotifications) > 0 && config.ScheduledNotifications == nil { - config.ScheduledNotifications = extraConfig.ScheduledNotifications - } - - for k, v := range extraConfig.ScheduledNotifications { - config.ScheduledNotifications[k] = v - } } return &config diff --git a/pkg/merger/merger_test.go b/pkg/merger/merger_test.go index eec8706..d5afcaf 100644 --- a/pkg/merger/merger_test.go +++ b/pkg/merger/merger_test.go @@ -34,9 +34,6 @@ func TestMergeConfigFiles(t *testing.T) { IgnoreDirs: []string{"dir1", "dir2"}, IgnorePaths: []string{"path1", "path2"}, Code: models.Code{ - AutoFix: &models.AutoFix{ - Enabled: true, - }, Ignore: []models.CodeIgnore{ { CWEs: []int{123}, @@ -44,9 +41,6 @@ func TestMergeConfigFiles(t *testing.T) { }, }, Dependencies: models.Dependencies{ - AutoFix: &models.AutoFix{ - Enabled: true, - }, Ignore: []models.DependenciesIgnore{ { CVEs: []string{"CVE-2021-1234"}, @@ -60,20 +54,6 @@ func TestMergeConfigFiles(t *testing.T) { }, }, }, - Notifications: map[string]models.Notification{ - "slack": { - Events: models.NotificationEvents{ - All: &models.NotificationEventAll{ - MinimumSeverity: models.SeverityHigh, - }, - }, - }, - }, - ScheduledNotifications: map[string]models.ScheduledNotification{ - "slack": { - Schedule: "0 0 * * *", - }, - }, Integrations: models.Integrations{ Jira: &models.Jira{ ProjectKey: "JIRINT", @@ -92,9 +72,6 @@ func TestMergeConfigFiles(t *testing.T) { IgnoreDirs: []string{"dir1", "dir2"}, IgnorePaths: []string{"path1", "path2"}, Code: models.Code{ - AutoFix: &models.AutoFix{ - Enabled: true, - }, Ignore: []models.CodeIgnore{ { CWEs: []int{123}, @@ -102,9 +79,6 @@ func TestMergeConfigFiles(t *testing.T) { }, }, Dependencies: models.Dependencies{ - AutoFix: &models.AutoFix{ - Enabled: true, - }, Ignore: []models.DependenciesIgnore{ { CVEs: []string{"CVE-2021-1234"}, @@ -118,20 +92,6 @@ func TestMergeConfigFiles(t *testing.T) { }, }, }, - Notifications: map[string]models.Notification{ - "slack": { - Events: models.NotificationEvents{ - All: &models.NotificationEventAll{ - MinimumSeverity: models.SeverityHigh, - }, - }, - }, - }, - ScheduledNotifications: map[string]models.ScheduledNotification{ - "slack": { - Schedule: "0 0 * * *", - }, - }, Integrations: models.Integrations{ Jira: &models.Jira{ ProjectKey: "JIRINT", @@ -150,9 +110,6 @@ func TestMergeConfigFiles(t *testing.T) { IgnoreDirs: []string{"dir1", "dir2"}, IgnorePaths: []string{"path1", "path2"}, Code: models.Code{ - AutoFix: &models.AutoFix{ - Enabled: true, - }, Ignore: []models.CodeIgnore{ { CWEs: []int{123}, @@ -160,9 +117,6 @@ func TestMergeConfigFiles(t *testing.T) { }, }, Dependencies: models.Dependencies{ - AutoFix: &models.AutoFix{ - Enabled: true, - }, Ignore: []models.DependenciesIgnore{ { CVEs: []string{"CVE-2021-1234"}, @@ -176,20 +130,6 @@ func TestMergeConfigFiles(t *testing.T) { }, }, }, - Notifications: map[string]models.Notification{ - "slack": { - Events: models.NotificationEvents{ - All: &models.NotificationEventAll{ - MinimumSeverity: models.SeverityHigh, - }, - }, - }, - }, - ScheduledNotifications: map[string]models.ScheduledNotification{ - "slack": { - Schedule: "0 0 * * *", - }, - }, Integrations: models.Integrations{ Jira: &models.Jira{ ProjectKey: "JIRINT", @@ -215,9 +155,6 @@ func TestMergeConfigFiles(t *testing.T) { IgnoreDirs: []string{"dir1", "dir2"}, IgnorePaths: []string{"path1", "path2"}, Code: models.Code{ - AutoFix: &models.AutoFix{ - Enabled: true, - }, Ignore: []models.CodeIgnore{ { CWEs: []int{123}, @@ -225,9 +162,6 @@ func TestMergeConfigFiles(t *testing.T) { }, }, Dependencies: models.Dependencies{ - AutoFix: &models.AutoFix{ - Enabled: true, - }, Ignore: []models.DependenciesIgnore{ { CVEs: []string{"CVE-2021-1234"}, @@ -241,20 +175,6 @@ func TestMergeConfigFiles(t *testing.T) { }, }, }, - Notifications: map[string]models.Notification{ - "slack": { - Events: models.NotificationEvents{ - All: &models.NotificationEventAll{ - MinimumSeverity: models.SeverityHigh, - }, - }, - }, - }, - ScheduledNotifications: map[string]models.ScheduledNotification{ - "slack": { - Schedule: "0 0 * * *", - }, - }, Integrations: models.Integrations{ Jira: &models.Jira{ ProjectKey: "JIRINT", diff --git a/pkg/models/autofix.go b/pkg/models/autofix.go deleted file mode 100644 index a90fc78..0000000 --- a/pkg/models/autofix.go +++ /dev/null @@ -1,13 +0,0 @@ -package models - -type AutoFix struct { - Enabled bool `yaml:"enabled,omitempty"` - MaxPullRequestsOpen *int `yaml:"max_pull_requests_open,omitempty"` - MaxPullRequestCreationRate *AutoFixPullRequestCreationRate `yaml:"max_pull_request_creation_rate,omitempty"` - Labels []string `yaml:"labels,omitempty"` -} - -type AutoFixPullRequestCreationRate struct { - Count int `yaml:"count,omitempty"` - Days int `yaml:"days,omitempty"` -} diff --git a/pkg/models/code.go b/pkg/models/code.go index 4b568f6..c563400 100644 --- a/pkg/models/code.go +++ b/pkg/models/code.go @@ -2,7 +2,6 @@ package models type Code struct { EnableFailBuilds *bool `yaml:"enable_fail_builds,omitempty"` - AutoFix *AutoFix `yaml:"auto_fix,omitempty"` Ignore []CodeIgnore `yaml:"ignore,omitempty"` } @@ -14,8 +13,10 @@ type CodeIgnore struct { CWEs []int `yaml:"cwes,omitempty"` RuleIDs []string `yaml:"rule_ids,omitempty"` Dirs []string `yaml:"dirs,omitempty"` - Paths []string `yaml:"paths,omitempty"` // global config only Repositories []string `yaml:"repositories,omitempty"` + + // TODO deprecate + Paths []string `yaml:"paths,omitempty"` } diff --git a/pkg/models/dependencies.go b/pkg/models/dependencies.go index f024f0e..a9ca680 100644 --- a/pkg/models/dependencies.go +++ b/pkg/models/dependencies.go @@ -2,7 +2,6 @@ package models type Dependencies struct { EnableFailBuilds *bool `yaml:"enable_fail_builds,omitempty"` - AutoFix *AutoFix `yaml:"auto_fix,omitempty"` Ignore []DependenciesIgnore `yaml:"ignore,omitempty"` } @@ -11,10 +10,12 @@ type DependenciesIgnore struct { Expiry string `yaml:"expiry,omitempty"` // matchers - CVEs []string `yaml:"cves,omitempty"` - Dirs []string `yaml:"dirs,omitempty"` - Paths []string `yaml:"paths,omitempty"` + CVEs []string `yaml:"cves,omitempty"` + Dirs []string `yaml:"dirs,omitempty"` // global config only Repositories []string `yaml:"repositories,omitempty"` + + // TODO deprecate + Paths []string `yaml:"paths,omitempty"` } diff --git a/pkg/models/models.go b/pkg/models/models.go index 63142f3..df3c0ed 100644 --- a/pkg/models/models.go +++ b/pkg/models/models.go @@ -11,11 +11,8 @@ type Configuration struct { IgnoreDirs []string `yaml:"ignore_dirs,omitempty"` IgnorePaths []string `yaml:"ignore_paths,omitempty"` - AutoFix *AutoFix `yaml:"auto_fix,omitempty"` - Notifications map[string]Notification `yaml:"notifications,omitempty"` - ScheduledNotifications map[string]ScheduledNotification `yaml:"scheduled_notifications,omitempty"` - Integrations Integrations `yaml:"integrations,omitempty"` + Integrations Integrations `yaml:"integrations,omitempty"` // features Code Code `yaml:"code"` diff --git a/pkg/models/notifications.go b/pkg/models/notifications.go deleted file mode 100644 index 73ea6e6..0000000 --- a/pkg/models/notifications.go +++ /dev/null @@ -1,9 +0,0 @@ -package models - -type Notification struct { - Events NotificationEvents `yaml:"events,omitempty"` - Targets NotificationTargets `yaml:"targets,omitempty"` - - // global config only - Repositories []string `yaml:"repositories,omitempty"` -} diff --git a/pkg/models/notifications_events.go b/pkg/models/notifications_events.go deleted file mode 100644 index 4b203ea..0000000 --- a/pkg/models/notifications_events.go +++ /dev/null @@ -1,40 +0,0 @@ -package models - -type NotificationEvents struct { - All *NotificationEventAll `yaml:"all,omitempty"` - NewAPIFindings *NotificationEventNewAPIFindings `yaml:"new_api_findings,omitempty"` - NewCodeFindings *NotificationEventNewCodeFindings `yaml:"new_code_findings,omitempty"` - NewDependencyFindings *NotificationEventNewDependencyFindings `yaml:"new_dependency_findings,omitempty"` - NewSecretFindings *NotificationEventNewSecretFindings `yaml:"new_secret_findings,omitempty"` -} - -type NotificationEventAll struct { - MinimumSeverity string `yaml:"minimum_severity,omitempty"` - MinimumPriority int `yaml:"minimum_priority,omitempty"` - CWEs []int `yaml:"cwes,omitempty"` - CVEs []string `yaml:"cves,omitempty"` - SecretTypes []string `yaml:"secret_types,omitempty"` -} - -type NotificationEventNewAPIFindings struct { - MinimumSeverity string `yaml:"minimum_severity,omitempty"` - MinimumPriority int `yaml:"minimum_priority,omitempty"` - CWEs []int `yaml:"cwes,omitempty"` -} - -type NotificationEventNewCodeFindings struct { - MinimumSeverity string `yaml:"minimum_severity,omitempty"` - MinimumPriority int `yaml:"minimum_priority,omitempty"` - CWEs []int `yaml:"cwes,omitempty"` -} - -type NotificationEventNewDependencyFindings struct { - MinimumSeverity string `yaml:"minimum_severity,omitempty"` - MinimumPriority int `yaml:"minimum_priority,omitempty"` - CWEs []int `yaml:"cwes,omitempty"` - CVEs []string `yaml:"cves,omitempty"` -} - -type NotificationEventNewSecretFindings struct { - Types []string `yaml:"types,omitempty"` -} diff --git a/pkg/models/notifications_targets.go b/pkg/models/notifications_targets.go deleted file mode 100644 index b9a9943..0000000 --- a/pkg/models/notifications_targets.go +++ /dev/null @@ -1,22 +0,0 @@ -package models - -type NotificationTargets struct { - Webhook *NotificationTargetWebhook `yaml:"webhook,omitempty"` - Email *NotificationTargetEmail `yaml:"email,omitempty"` - Slack *NotificationTargetSlack `yaml:"slack,omitempty"` -} - -type NotificationTargetWebhook struct { - URLs []string `yaml:"urls,omitempty"` - URL string `yaml:"url,omitempty"` -} - -type NotificationTargetEmail struct { - Address string `yaml:"address,omitempty"` - Addresses []string `yaml:"addresses,omitempty"` -} - -type NotificationTargetSlack struct { - Channel string `yaml:"channel,omitempty"` - Channels []string `yaml:"channels,omitempty"` -} diff --git a/pkg/models/scheduled_notifications.go b/pkg/models/scheduled_notifications.go deleted file mode 100644 index 881debc..0000000 --- a/pkg/models/scheduled_notifications.go +++ /dev/null @@ -1,45 +0,0 @@ -package models - -const ( - ScheduledNotificationTopicTypeAll = "all" - ScheduledNotificationTopicTypeCode = "code" - ScheduledNotificationTopicTypeIaC = "iac" - ScheduledNotificationTopicTypeDependencies = "dependencies" - ScheduledNotificationTopicTypeSecrets = "secrets" - ScheduledNotificationTopicTypeDAST = "dast" -) - -type ScheduledNotification struct { - Schedule string `yaml:"schedule,omitempty"` - Timezone string `yaml:"timezone,omitempty"` - Topics ScheduledNotificationTopics `yaml:"topics,omitempty"` - Targets ScheduledNotificationTargets `yaml:"targets,omitempty"` - - // global config only - Repositories []string `yaml:"repositories,omitempty"` -} - -type ScheduledNotificationTopics struct { - All bool `yaml:"all,omitempty"` - AllNewFindings bool `yaml:"all_new_findings,omitempty"` - NewAPIFindings bool `yaml:"new_api_findings,omitempty"` - NewCodeFindings bool `yaml:"new_code_findings,omitempty"` - NewCVEs bool `yaml:"new_cves,omitempty"` - NewSecrets bool `yaml:"new_secrets,omitempty"` - // TODO add allowlisting, fixed findings, etc -} - -type ScheduledNotificationTargets struct { - Email *ScheduledNotificationTargetEmail `yaml:"email,omitempty"` - Slack *ScheduledNotificationTargetSlack `yaml:"slack,omitempty"` -} - -type ScheduledNotificationTargetEmail struct { - Address string `yaml:"address,omitempty"` - Addresses []string `yaml:"addresses,omitempty"` -} - -type ScheduledNotificationTargetSlack struct { - Channel string `yaml:"channel,omitempty"` - Channels []string `yaml:"channels,omitempty"` -} diff --git a/pkg/models/secrets.go b/pkg/models/secrets.go index 1a9075e..a9e0c5d 100644 --- a/pkg/models/secrets.go +++ b/pkg/models/secrets.go @@ -18,6 +18,9 @@ type SecretsIgnore struct { // global config only Repositories []string `yaml:"repositories,omitempty"` + + // TODO deprecate + Paths []string `yaml:"paths,omitempty"` } type SecretsCustomPattern struct { diff --git a/pkg/parser/defaults.go b/pkg/parser/defaults.go index 2699251..b18b725 100644 --- a/pkg/parser/defaults.go +++ b/pkg/parser/defaults.go @@ -23,8 +23,6 @@ func NewDefaultConfig() *models.Configuration { Secrets: models.Secrets{ Ignore: nil, }, - Notifications: nil, - ScheduledNotifications: nil, - Integrations: models.Integrations{}, + Integrations: models.Integrations{}, } } diff --git a/pkg/parser/parse.go b/pkg/parser/parse.go index ace5c0e..69a5675 100644 --- a/pkg/parser/parse.go +++ b/pkg/parser/parse.go @@ -41,24 +41,4 @@ func sanitizeConfig(config *models.Configuration) { } config.Integrations.Jira = j } - - for name, n := range config.Notifications { - if n.Events.All != nil { - n.Events.All.MinimumSeverity = strings.ToUpper(n.Events.All.MinimumSeverity) - } - - if n.Events.NewAPIFindings != nil { - n.Events.NewAPIFindings.MinimumSeverity = strings.ToUpper(n.Events.NewAPIFindings.MinimumSeverity) - } - - if n.Events.NewCodeFindings != nil { - n.Events.NewCodeFindings.MinimumSeverity = strings.ToUpper(n.Events.NewCodeFindings.MinimumSeverity) - } - - if n.Events.NewDependencyFindings != nil { - n.Events.NewDependencyFindings.MinimumSeverity = strings.ToUpper(n.Events.NewDependencyFindings.MinimumSeverity) - } - - config.Notifications[name] = n - } } diff --git a/pkg/validator/autofix.go b/pkg/validator/autofix.go deleted file mode 100644 index 5c89298..0000000 --- a/pkg/validator/autofix.go +++ /dev/null @@ -1,37 +0,0 @@ -package validator - -import ( - "github.com/nullify-platform/config-file-parser/pkg/models" -) - -func ValidateAutoFix(config *models.Configuration) bool { - return validateAutoFix(config.Code.AutoFix) && validateAutoFix(config.Dependencies.AutoFix) -} - -func validateAutoFix(autofix *models.AutoFix) bool { - if autofix == nil { - return true - } - - if !autofix.Enabled { - return true - } - - if autofix.MaxPullRequestsOpen != nil { - if *autofix.MaxPullRequestsOpen < 0 { - return false - } - } - - if autofix.MaxPullRequestCreationRate != nil { - if autofix.MaxPullRequestCreationRate.Count < 0 { - return false - } - - if autofix.MaxPullRequestCreationRate.Days < 0 { - return false - } - } - - return true -} diff --git a/pkg/validator/autofix_test.go b/pkg/validator/autofix_test.go deleted file mode 100644 index bd1b174..0000000 --- a/pkg/validator/autofix_test.go +++ /dev/null @@ -1,55 +0,0 @@ -package validator - -import ( - "fmt" - "testing" - - "github.com/nullify-platform/config-file-parser/pkg/models" - - "github.com/stretchr/testify/assert" -) - -func TestValidAutoFix(t *testing.T) { - for _, scenario := range []struct { - name string - config *models.Configuration - expected bool - }{ - { - name: "empty config", - config: &models.Configuration{}, - expected: true, - }, - { - name: "valid", - config: &models.Configuration{ - Code: models.Code{ - AutoFix: &models.AutoFix{ - Enabled: true, - MaxPullRequestsOpen: models.Int(2), - MaxPullRequestCreationRate: &models.AutoFixPullRequestCreationRate{ - Count: 1, - Days: 1, - }, - }, - }, - Dependencies: models.Dependencies{ - AutoFix: &models.AutoFix{ - Enabled: true, - MaxPullRequestsOpen: models.Int(2), - MaxPullRequestCreationRate: &models.AutoFixPullRequestCreationRate{ - Count: 1, - Days: 1, - }, - }, - }, - }, - expected: true, - }, - } { - t.Run(scenario.name, func(t *testing.T) { - isValid := ValidateAutoFix(scenario.config) - assert.Equalf(t, isValid, scenario.expected, fmt.Sprintf("failed test: %s\n", scenario.name)) - }) - } -} diff --git a/pkg/validator/notifications.go b/pkg/validator/notifications.go deleted file mode 100644 index a272393..0000000 --- a/pkg/validator/notifications.go +++ /dev/null @@ -1,35 +0,0 @@ -package validator - -import ( - "net/mail" - - "github.com/nullify-platform/config-file-parser/pkg/models" -) - -func ValidateNotifications(config *models.Configuration) bool { - if config.Notifications == nil { - return true - } - - for _, notification := range config.Notifications { - if notification.Targets.Email == nil { - continue - } - - if notification.Targets.Email.Address != "" { - _, err := mail.ParseAddress(notification.Targets.Email.Address) - if err != nil { - return false - } - } - - for _, email := range notification.Targets.Email.Addresses { - _, err := mail.ParseAddress(email) - if err != nil { - return false - } - } - } - - return true -} diff --git a/pkg/validator/notifications_test.go b/pkg/validator/notifications_test.go deleted file mode 100644 index a7d1f94..0000000 --- a/pkg/validator/notifications_test.go +++ /dev/null @@ -1,209 +0,0 @@ -package validator - -import ( - "fmt" - "testing" - - "github.com/nullify-platform/config-file-parser/pkg/models" - - "github.com/nullify-platform/config-file-parser/pkg/parser" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestValidNotifications(t *testing.T) { - for _, scenario := range []struct { - name string - config *models.Configuration - expected bool - }{ - { - name: "empty config", - config: &models.Configuration{}, - expected: true, - }, - { - name: "empty notifications", - config: &models.Configuration{ - Notifications: map[string]models.Notification{ - "test": { - Targets: models.NotificationTargets{}, - }, - }, - }, - expected: true, - }, - { - name: "single correct email", - config: &models.Configuration{ - Notifications: map[string]models.Notification{ - "test": { - Targets: models.NotificationTargets{ - Email: &models.NotificationTargetEmail{ - Addresses: []string{"john@nullify.ai"}, - }, - }, - }, - }, - }, - expected: true, - }, - { - name: "two correct emails", - config: &models.Configuration{ - Notifications: map[string]models.Notification{ - "test": { - Targets: models.NotificationTargets{ - Email: &models.NotificationTargetEmail{ - Addresses: []string{"lisa@nullify.ai", "lisa@gmail.nullify.ai"}, - }, - }, - }, - }, - }, - expected: true, - }, - { - name: "single incorrect email", - config: &models.Configuration{ - Notifications: map[string]models.Notification{ - "test": { - Targets: models.NotificationTargets{ - Email: &models.NotificationTargetEmail{ - Addresses: []string{"john@@gmail.com"}, - }, - }, - }, - }, - }, - expected: false, - }, - { - name: "one correct and one incorrect email", - config: &models.Configuration{ - Notifications: map[string]models.Notification{ - "test": { - Targets: models.NotificationTargets{ - Email: &models.NotificationTargetEmail{ - Addresses: []string{"john@nullify.ai", "john@@gmail.com"}, - }, - }, - }, - }, - }, - expected: false, - }, - { - name: "one incorrect email", - config: &models.Configuration{ - Notifications: map[string]models.Notification{ - "test": { - Targets: models.NotificationTargets{ - Email: &models.NotificationTargetEmail{ - Addresses: []string{"helloatgmail.com"}, - }, - }, - }, - }, - }, - expected: false, - }, - } { - t.Run(scenario.name, func(t *testing.T) { - isValid := ValidateNotifications(scenario.config) - assert.Equalf(t, isValid, scenario.expected, fmt.Sprintf("failed test: %s\n", scenario.name)) - }) - } -} - -const validEmail string = ` -notifications: - test: - targets: - email: - addresses: ["hello@gmail.com"] -` - -const emptyEmail string = ` -notifications: - test: - targets: - email: - addresses: -` - -const noEmailConfig string = ` -notifications: -` - -const emailWithEmptyArray string = ` -notifications: - test: - targets: - email: - addresses: [""] -` - -const twoValidEmails string = ` -notifications: - test: - targets: - email: - addresses: ["john@nullify.ai", "lisa@gmail.com"] -` -const validAndInvalid string = ` -notifications: - test: - targets: - email: - addresses: ["john()@nullify.ai", "lisa@gmail.com"] -` - -const missingCommaIncorrectQuotes string = ` -notifications: - test: - targets: - email: - addresses: ["hello@gmail.com john@nullify.ai"] -` - -const missingComma string = ` -notifications: - test: - targets: - email: - addresses: ["hello@gmail.com" "john@nullify.ai"] -` - -func TestParsingAndValidEmails(t *testing.T) { - config1, err := parser.ParseConfiguration([]byte(validEmail)) - require.NoError(t, err) - require.Equal(t, true, ValidateNotifications(config1)) - - config2, err := parser.ParseConfiguration([]byte(emptyEmail)) - require.NoError(t, err) - require.Equal(t, true, ValidateNotifications(config2)) - - config3, err := parser.ParseConfiguration([]byte(noEmailConfig)) - require.NoError(t, err) - require.Equal(t, true, ValidateNotifications(config3)) - - config4, err := parser.ParseConfiguration([]byte(emailWithEmptyArray)) - require.NoError(t, err) - require.Equal(t, false, ValidateNotifications(config4)) - - config5, err := parser.ParseConfiguration([]byte(twoValidEmails)) - require.NoError(t, err) - require.Equal(t, true, ValidateNotifications(config5)) - - config6, err := parser.ParseConfiguration([]byte(validAndInvalid)) - require.NoError(t, err) - require.Equal(t, false, ValidateNotifications(config6)) - - config7, err := parser.ParseConfiguration([]byte(missingCommaIncorrectQuotes)) - require.NoError(t, err) - require.Equal(t, false, ValidateNotifications(config7)) - - _, err = parser.ParseConfiguration([]byte(missingComma)) - require.Error(t, err) -} diff --git a/pkg/validator/scheduled_notifications.go b/pkg/validator/scheduled_notifications.go deleted file mode 100644 index e038b03..0000000 --- a/pkg/validator/scheduled_notifications.go +++ /dev/null @@ -1,75 +0,0 @@ -package validator - -import ( - "fmt" - "net/mail" - "time" - - "github.com/nullify-platform/config-file-parser/pkg/models" - "github.com/robfig/cron/v3" -) - -func ValidateScheduledNotifications(config *models.Configuration) bool { - if config.ScheduledNotifications == nil { - return true - } - - for _, notification := range config.ScheduledNotifications { - if !validateScheduledNotificationSchedule(notification.Schedule, notification.Timezone) { - return false - } - - if !validateScheduledNotificationEmails(notification) { - return false - } - } - - return true -} - -// validateScheduledNotificationSchedule return true if provided schedule is a valid cron expression. -// The cron expression can also only trigger at most once per hour. -func validateScheduledNotificationSchedule(schedule string, timezone string) bool { - spec := "TZ=" + timezone + " " + schedule - - if timezone == "" { - spec = "TZ=UTC " + schedule - } - - p := cron.NewParser(cron.Minute | cron.Hour | cron.Dom | cron.Month | cron.Dow | cron.Descriptor) - - // TODO this function panics with the following input "TZ=UTC" - cronSchedule, err := p.Parse(spec) - if err != nil { - fmt.Printf("failed to parse cron expression: %s", err.Error()) - return false - } - - // check if the cron expression triggers more often than once per hour - start := cronSchedule.Next(time.Now()) - finish := cronSchedule.Next(start) - - return finish.Sub(start) >= time.Hour -} - -func validateScheduledNotificationEmails(notification models.ScheduledNotification) bool { - if notification.Targets.Email == nil { - return true - } - - if notification.Targets.Email.Address != "" { - _, err := mail.ParseAddress(notification.Targets.Email.Address) - if err != nil { - return false - } - } - - for _, email := range notification.Targets.Email.Addresses { - _, err := mail.ParseAddress(email) - if err != nil { - return false - } - } - - return true -} diff --git a/pkg/validator/scheduled_notifications_test.go b/pkg/validator/scheduled_notifications_test.go deleted file mode 100644 index 83a80ea..0000000 --- a/pkg/validator/scheduled_notifications_test.go +++ /dev/null @@ -1,58 +0,0 @@ -package validator - -import ( - "fmt" - "testing" - - "github.com/nullify-platform/config-file-parser/pkg/models" - - "github.com/stretchr/testify/assert" -) - -func TestValidateScheduledNotifications(t *testing.T) { - for _, scenario := range []struct { - name string - config *models.Configuration - expected bool - }{ - { - name: "empty config", - config: &models.Configuration{}, - expected: true, - }, - { - name: "empty scheduled notifications", - config: &models.Configuration{ - ScheduledNotifications: map[string]models.ScheduledNotification{}, - }, - expected: true, - }, - { - name: "cron expression triggers every 59 minutes", - config: &models.Configuration{ - ScheduledNotifications: map[string]models.ScheduledNotification{ - "test": { - Schedule: "*/59 * * * *", - }, - }, - }, - expected: false, - }, - { - name: "cron expression triggers every hour", - config: &models.Configuration{ - ScheduledNotifications: map[string]models.ScheduledNotification{ - "test": { - Schedule: "0 * * * *", - }, - }, - }, - expected: true, - }, - } { - t.Run(scenario.name, func(t *testing.T) { - isValid := ValidateScheduledNotifications(scenario.config) - assert.Equalf(t, scenario.expected, isValid, fmt.Sprintf("failed test: %s\n", scenario.name)) - }) - } -} diff --git a/pkg/validator/validate.go b/pkg/validator/validate.go index 5e225a9..798c913 100644 --- a/pkg/validator/validate.go +++ b/pkg/validator/validate.go @@ -11,10 +11,7 @@ import ( // ValidateConfig return true if provided configuration is valid func ValidateConfig(config *models.Configuration) bool { return ValidateSeverityThreshold(config) && - ValidateNotifications(config) && - ValidateScheduledNotifications(config) && - ValidatePaths(config) && - ValidateAutoFix(config) + ValidatePaths(config) } func IsConfigValid(ctx context.Context, configString string) (bool, error) { diff --git a/tests/empty_fail_build.yaml b/tests/empty_fail_build.yaml index 8a228c9..8d15264 100644 --- a/tests/empty_fail_build.yaml +++ b/tests/empty_fail_build.yaml @@ -3,74 +3,10 @@ ignore_dirs: - dir1 ignore_paths: - data/**/* -notifications: - all-events-webhook: - events: - all: - minimum_severity: high - secret_types: [ ssh_key ] - targets: - webhook: - urls: [ https://webhook.site/123456 ] - findings-to-slack-and-email: - events: - new_code_findings: - minimum_severity: high - new_secret_findings: - types: [ ssh_key ] - new_dependency_findings: - minimum_severity: high - targets: - slack: - channels: [ "123456" ] - email: - addresses: [ notifications@nullify.ai, noreply@nullify.ai ] - repositories: - - config-file-parser - - dast-action - - cli -scheduled_notifications: - new-findings: - schedule: "0 0 * * *" - topics: - all: true - targets: - slack: - channels: [ "123456" ] - email: - addresses: [ notifications@nullify.ai, noreply@nullify.ai ] - repositories: - - config-file-parser - - dast-action - - cli -code: - ignore: - - cwes: [ 589 ] # Potential HTTP request made with variable url - reason: HTTP requests with variables in tests don't matter - paths: [ "**/tests/*" ] - repositories: - - config-file-parser - - dast-action - - cli - - rule_ids: [ python-sql-injection ] - reason: This code won't be going live until next year but we should fix it before then - expiry: "2021-12-31" -dependencies: - ignore: - - cves: [ CVE-2021-1234 ] - reason: This is a false positive - expiry: "2021-12-31" - - cves: [ CVE-2021-5678 ] - reason: This isn't exploitable in client applications - expiry: "2021-12-31" - repositories: - - dast-action - - cli secrets: ignore: - value: mocksecret123 reason: This is a test secret, it has no access to anything - paths: [ "**/tests/*" ] - pattern: id[0-9]+ reason: These are not secrets, they are internal identifiers - value: actualsecret123 diff --git a/tests/integration_test.go b/tests/integration_test.go index 57ddc0f..29eaedc 100644 --- a/tests/integration_test.go +++ b/tests/integration_test.go @@ -45,105 +45,6 @@ func TestIntegration(t *testing.T) { }, }, }, - Notifications: map[string]models.Notification{ - "all-events-webhook": { - Events: models.NotificationEvents{ - All: &models.NotificationEventAll{ - MinimumSeverity: models.SeverityHigh, - SecretTypes: []string{"ssh_key"}, - }, - }, - Targets: models.NotificationTargets{ - Webhook: &models.NotificationTargetWebhook{ - URLs: []string{"https://webhook.site/123456"}, - }, - }, - }, - "findings-to-slack-and-email": { - Events: models.NotificationEvents{ - NewCodeFindings: &models.NotificationEventNewCodeFindings{ - MinimumSeverity: models.SeverityHigh, - }, - NewSecretFindings: &models.NotificationEventNewSecretFindings{ - Types: []string{"ssh_key"}, - }, - NewDependencyFindings: &models.NotificationEventNewDependencyFindings{ - MinimumSeverity: models.SeverityHigh, - }, - }, - Targets: models.NotificationTargets{ - Slack: &models.NotificationTargetSlack{ - Channels: []string{"123456"}, - }, - Email: &models.NotificationTargetEmail{ - Addresses: []string{"notifications@nullify.ai", "noreply@nullify.ai"}, - }, - }, - Repositories: []string{ - "config-file-parser", - "dast-action", - "cli", - }, - }, - }, - ScheduledNotifications: map[string]models.ScheduledNotification{ - "new-findings": { - Schedule: "0 0 * * *", - Topics: models.ScheduledNotificationTopics{ - All: true, - }, - Targets: models.ScheduledNotificationTargets{ - Email: &models.ScheduledNotificationTargetEmail{ - Addresses: []string{"notifications@nullify.ai", "noreply@nullify.ai"}, - }, - Slack: &models.ScheduledNotificationTargetSlack{ - Channels: []string{"123456"}, - }, - }, - Repositories: []string{ - "config-file-parser", - "dast-action", - "cli", - }, - }, - }, - Code: models.Code{ - Ignore: []models.CodeIgnore{ - { - CWEs: []int{589}, - Reason: "HTTP requests with variables in tests don't matter", - Paths: []string{"**/tests/*"}, - Repositories: []string{ - "config-file-parser", - "dast-action", - "cli", - }, - }, - { - RuleIDs: []string{"python-sql-injection"}, - Reason: "This code won't be going live until next year but we should fix it before then", - Expiry: "2021-12-31", - }, - }, - }, - Dependencies: models.Dependencies{ - Ignore: []models.DependenciesIgnore{ - { - CVEs: []string{"CVE-2021-1234"}, - Reason: "This is a false positive", - Expiry: "2021-12-31", - }, - { - CVEs: []string{"CVE-2021-5678"}, - Reason: "This isn't exploitable in client applications", - Expiry: "2021-12-31", - Repositories: []string{ - "dast-action", - "cli", - }, - }, - }, - }, Integrations: models.Integrations{ Jira: &models.Jira{ Disabled: false, @@ -186,105 +87,6 @@ func TestEmptyFailsBuildField(t *testing.T) { }, }, }, - Notifications: map[string]models.Notification{ - "all-events-webhook": { - Events: models.NotificationEvents{ - All: &models.NotificationEventAll{ - MinimumSeverity: models.SeverityHigh, - SecretTypes: []string{"ssh_key"}, - }, - }, - Targets: models.NotificationTargets{ - Webhook: &models.NotificationTargetWebhook{ - URLs: []string{"https://webhook.site/123456"}, - }, - }, - }, - "findings-to-slack-and-email": { - Events: models.NotificationEvents{ - NewCodeFindings: &models.NotificationEventNewCodeFindings{ - MinimumSeverity: models.SeverityHigh, - }, - NewSecretFindings: &models.NotificationEventNewSecretFindings{ - Types: []string{"ssh_key"}, - }, - NewDependencyFindings: &models.NotificationEventNewDependencyFindings{ - MinimumSeverity: models.SeverityHigh, - }, - }, - Targets: models.NotificationTargets{ - Slack: &models.NotificationTargetSlack{ - Channels: []string{"123456"}, - }, - Email: &models.NotificationTargetEmail{ - Addresses: []string{"notifications@nullify.ai", "noreply@nullify.ai"}, - }, - }, - Repositories: []string{ - "config-file-parser", - "dast-action", - "cli", - }, - }, - }, - ScheduledNotifications: map[string]models.ScheduledNotification{ - "new-findings": { - Schedule: "0 0 * * *", - Topics: models.ScheduledNotificationTopics{ - All: true, - }, - Targets: models.ScheduledNotificationTargets{ - Email: &models.ScheduledNotificationTargetEmail{ - Addresses: []string{"notifications@nullify.ai", "noreply@nullify.ai"}, - }, - Slack: &models.ScheduledNotificationTargetSlack{ - Channels: []string{"123456"}, - }, - }, - Repositories: []string{ - "config-file-parser", - "dast-action", - "cli", - }, - }, - }, - Code: models.Code{ - Ignore: []models.CodeIgnore{ - { - CWEs: []int{589}, - Reason: "HTTP requests with variables in tests don't matter", - Paths: []string{"**/tests/*"}, - Repositories: []string{ - "config-file-parser", - "dast-action", - "cli", - }, - }, - { - RuleIDs: []string{"python-sql-injection"}, - Reason: "This code won't be going live until next year but we should fix it before then", - Expiry: "2021-12-31", - }, - }, - }, - Dependencies: models.Dependencies{ - Ignore: []models.DependenciesIgnore{ - { - CVEs: []string{"CVE-2021-1234"}, - Reason: "This is a false positive", - Expiry: "2021-12-31", - }, - { - CVEs: []string{"CVE-2021-5678"}, - Reason: "This isn't exploitable in client applications", - Expiry: "2021-12-31", - Repositories: []string{ - "dast-action", - "cli", - }, - }, - }, - }, } config, err := parser.LoadFromFile("empty_fail_build.yaml") diff --git a/tests/nullify.yaml b/tests/nullify.yaml index f1ed8f1..9550c11 100644 --- a/tests/nullify.yaml +++ b/tests/nullify.yaml @@ -7,74 +7,10 @@ ignore_dirs: - dir1 ignore_paths: - data/**/* -notifications: - all-events-webhook: - events: - all: - minimum_severity: high - secret_types: [ ssh_key ] - targets: - webhook: - urls: [ https://webhook.site/123456 ] - findings-to-slack-and-email: - events: - new_code_findings: - minimum_severity: high - new_secret_findings: - types: [ ssh_key ] - new_dependency_findings: - minimum_severity: high - targets: - slack: - channels: [ "123456" ] - email: - addresses: [ notifications@nullify.ai, noreply@nullify.ai ] - repositories: - - config-file-parser - - dast-action - - cli -scheduled_notifications: - new-findings: - schedule: "0 0 * * *" - topics: - all: true - targets: - slack: - channels: [ "123456" ] - email: - addresses: [ notifications@nullify.ai, noreply@nullify.ai ] - repositories: - - config-file-parser - - dast-action - - cli -code: - ignore: - - cwes: [ 589 ] # Potential HTTP request made with variable url - reason: HTTP requests with variables in tests don't matter - paths: [ "**/tests/*" ] - repositories: - - config-file-parser - - dast-action - - cli - - rule_ids: [ python-sql-injection ] - reason: This code won't be going live until next year but we should fix it before then - expiry: "2021-12-31" -dependencies: - ignore: - - cves: [ CVE-2021-1234 ] - reason: This is a false positive - expiry: "2021-12-31" - - cves: [ CVE-2021-5678 ] - reason: This isn't exploitable in client applications - expiry: "2021-12-31" - repositories: - - dast-action - - cli secrets: ignore: - value: mocksecret123 reason: This is a test secret, it has no access to anything - paths: [ "**/tests/*" ] - pattern: id[0-9]+ reason: These are not secrets, they are internal identifiers - value: actualsecret123