diff --git a/querying/github.go b/querying/github.go index 9239042..2e919fc 100644 --- a/querying/github.go +++ b/querying/github.go @@ -2,6 +2,7 @@ package querying import ( "context" + "strings" "sync" "golang.org/x/oauth2" @@ -11,6 +12,8 @@ import ( "github.com/underdog-tech/vulnbot/logger" ) +const internalTopicKeyword = "internal" + type githubClient interface { Query(context.Context, interface{}, map[string]interface{}) error } @@ -38,55 +41,6 @@ func NewGithubDataSource(conf *configs.Config) GithubDataSource { } } -type githubVulnerability struct { - SecurityAdvisory struct { - Description string - Identifiers []struct { - Type string - Value string - } - } - SecurityVulnerability struct { - Severity string - Package struct { - Ecosystem string - Name string - } - } -} - -type orgRepo struct { - Name string - Url string - VulnerabilityAlerts struct { - TotalCount int - PageInfo struct { - EndCursor githubv4.String - HasNextPage bool - } - Nodes []githubVulnerability - } `graphql:"vulnerabilityAlerts(states: OPEN, first: 100, after: $alertCursor)"` -} - -type repositoryQuery struct { - Repository orgRepo `graphql:"repository(name: $repoName, owner: $orgName)"` -} - -type orgVulnerabilityQuery struct { - Organization struct { - Name string - Login string - Repositories struct { - TotalCount int - PageInfo struct { - EndCursor githubv4.String - HasNextPage bool - } - Nodes []orgRepo - } `graphql:"repositories(orderBy: {field: NAME, direction: ASC}, isFork: false, isArchived: false, first: 100, after: $repoCursor)"` - } `graphql:"organization(login: $login)"` -} - // Ref: https://docs.github.com/en/graphql/reference/enums#securityadvisoryecosystem var githubEcosystems = map[string]configs.FindingEcosystemType{ "ACTIONS": configs.FindingEcosystemGHA, @@ -150,7 +104,7 @@ func (gh *GithubDataSource) processRepoFindings(projects *ProjectCollection, rep // Link directly to Dependabot findings. // There doesn't appear to be a GraphQL property for this link. - project.Links["GitHub"] = repo.Url + "/security/dependabot" + project.Link = repo.Url + "/security/dependabot" log.Debug().Str("project", project.Name).Msg("Processing findings for project.") @@ -198,38 +152,6 @@ func (gh *GithubDataSource) processRepoFindings(projects *ProjectCollection, rep return nil } -type orgTeam struct { - Name string - Slug string - Repositories struct { - PageInfo struct { - EndCursor githubv4.String - HasNextPage bool - } - Edges []struct { - Permission string - Node struct { - Name string - IsFork bool - IsArchived bool - } - } - } `graphql:"repositories(orderBy: {field: NAME, direction: ASC}, first: 100, after: $repoCursor)"` -} - -type orgRepoOwnerQuery struct { - Organization struct { - Teams struct { - TotalCount int - PageInfo struct { - EndCursor githubv4.String - HasNextPage bool - } - Nodes []orgTeam - } `graphql:"teams(orderBy: {field: NAME, direction: ASC}, first: 100, after: $teamCursor)"` - } `graphql:"organization(login: $login)"` -} - func (gh *GithubDataSource) gatherRepoOwners(projects *ProjectCollection) { var ownerQuery orgRepoOwnerQuery log := logger.Get() @@ -253,7 +175,7 @@ func (gh *GithubDataSource) gatherRepoOwners(projects *ProjectCollection) { } // TODO: Handle pagination of repositories owned by a team for _, repo := range team.Repositories.Edges { - if repo.Node.IsArchived || repo.Node.IsFork { + if repo.Node.IsArchived || repo.Node.IsFork || hasInternalTopic(repo.Node.RepositoryTopics) { log.Debug().Str("Repo", repo.Node.Name).Bool("IsFork", repo.Node.IsFork).Bool("IsArchived", repo.Node.IsArchived).Msg("Skipping untracked repository.") continue } @@ -272,3 +194,13 @@ func (gh *GithubDataSource) gatherRepoOwners(projects *ProjectCollection) { queryVars["teamCursor"] = githubv4.NewString(ownerQuery.Organization.Teams.PageInfo.EndCursor) } } + +// Function to check if the repository has "internal" in its topics +func hasInternalTopic(repoTopics repositoryTopics) bool { + for _, edge := range repoTopics.Edges { + if strings.Contains(strings.ToLower(edge.Node.Topic.Name), internalTopicKeyword) { + return true + } + } + return false +} diff --git a/querying/github_test.go b/querying/github_test.go index e49540b..9d3cc07 100644 --- a/querying/github_test.go +++ b/querying/github_test.go @@ -37,9 +37,7 @@ func getTestProject() querying.ProjectCollection { Projects: []*querying.Project{ { Name: "zaphod", - Links: map[string]string{ - "GitHub": "https://heart-of-gold/zaphod/security/dependabot", - }, + Link: "https://heart-of-gold/zaphod/security/dependabot", Findings: []*querying.Finding{ { Ecosystem: configs.FindingEcosystemGo, diff --git a/querying/project.go b/querying/project.go index 599353c..6248daf 100644 --- a/querying/project.go +++ b/querying/project.go @@ -28,7 +28,7 @@ type ProjectCollection struct { type Project struct { Name string Findings []*Finding - Links map[string]string + Link string Owners mapset.Set[configs.TeamConfig] mu sync.Mutex } @@ -38,7 +38,6 @@ func NewProject(name string) *Project { return &Project{ Name: name, Findings: []*Finding{}, - Links: map[string]string{}, Owners: mapset.NewSet[configs.TeamConfig](), } } diff --git a/querying/queries.go b/querying/queries.go new file mode 100644 index 0000000..3611da3 --- /dev/null +++ b/querying/queries.go @@ -0,0 +1,95 @@ +package querying + +import "github.com/shurcooL/githubv4" + +type githubVulnerability struct { + SecurityAdvisory struct { + Description string + Identifiers []struct { + Type string + Value string + } + } + SecurityVulnerability struct { + Severity string + Package struct { + Ecosystem string + Name string + } + } +} + +type repositoryTopics struct { + Edges []struct { + Node struct { + Topic struct { + Name string + } + } + } +} + +type orgRepo struct { + Name string + Url string + VulnerabilityAlerts struct { + TotalCount int + PageInfo struct { + EndCursor githubv4.String + HasNextPage bool + } + Nodes []githubVulnerability + } `graphql:"vulnerabilityAlerts(states: OPEN, first: 100, after: $alertCursor)"` +} + +type repositoryQuery struct { + Repository orgRepo `graphql:"repository(name: $repoName, owner: $orgName)"` +} + +type orgVulnerabilityQuery struct { + Organization struct { + Name string + Login string + Repositories struct { + TotalCount int + PageInfo struct { + EndCursor githubv4.String + HasNextPage bool + } + Nodes []orgRepo + } `graphql:"repositories(orderBy: {field: NAME, direction: ASC}, isFork: false, isArchived: false, first: 100, after: $repoCursor)"` + } `graphql:"organization(login: $login)"` +} + +type orgTeam struct { + Name string + Slug string + Repositories struct { + PageInfo struct { + EndCursor githubv4.String + HasNextPage bool + } + Edges []struct { + Permission string + Node struct { + Name string + IsFork bool + IsArchived bool + RepositoryTopics repositoryTopics `graphql:"repositoryTopics(first: 10, last: null)"` + } + } + } `graphql:"repositories(orderBy: {field: NAME, direction: ASC}, first: 100, after: $repoCursor)"` +} + +type orgRepoOwnerQuery struct { + Organization struct { + Teams struct { + TotalCount int + PageInfo struct { + EndCursor githubv4.String + HasNextPage bool + } + Nodes []orgTeam + } `graphql:"teams(orderBy: {field: NAME, direction: ASC}, first: 100, after: $teamCursor)"` + } `graphql:"organization(login: $login)"` +} diff --git a/reporting/slack.go b/reporting/slack.go index 4930375..3b8a25d 100644 --- a/reporting/slack.go +++ b/reporting/slack.go @@ -111,14 +111,8 @@ func (s *SlackReporter) BuildTeamRepositoryReport( if severityIcon == "" { severityIcon = configs.GetIconForSeverity(configs.FindingSeverityUndefined, s.Config.Severity) } - projLinks := make([]string, 0) - for title, link := range repoReport.Project.Links { - projLinks = append(projLinks, fmt.Sprintf("[<%s|%s>]", link, title)) - } - projName := fmt.Sprintf("%s *%s*", severityIcon, repoReport.Project.Name) - if len(projLinks) > 0 { - projName = fmt.Sprintf("%s ยท %s", projName, strings.Join(projLinks, " ")) - } + + projName := fmt.Sprintf("%s *<%s|%s>*", severityIcon, repoReport.Project.Link, repoReport.Project.Name) fields := []*slack.TextBlockObject{ slack.NewTextBlockObject(slack.MarkdownType, projName, false, false), slack.NewTextBlockObject(slack.MarkdownType, strings.Join(vulnCounts, " | "), false, false), diff --git a/reporting/slack_test.go b/reporting/slack_test.go index ce266ca..d314562 100644 --- a/reporting/slack_test.go +++ b/reporting/slack_test.go @@ -305,9 +305,7 @@ func TestSendSlackSummaryReportSendsSingleMessage(t *testing.T) { func TestBuildSlackTeamRepositoryReport(t *testing.T) { reporter := reporting.SlackReporter{Config: &configs.Config{}} proj := querying.NewProject("foo") - proj.Links = map[string]string{ - "GitHub": "https://github.com/bar/foo", - } + proj.Link = "https://github.com/bar/foo" report := reporting.NewProjectFindingSummary(proj) report.VulnsByEcosystem[configs.FindingEcosystemPython] = 15 report.VulnsBySeverity[configs.FindingSeverityCritical] = 2