-
Notifications
You must be signed in to change notification settings - Fork 12
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
add a producer that reads dependabot alerts from github
- Loading branch information
1 parent
b6e71cc
commit 489e48d
Showing
4 changed files
with
286 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
# Producer: GitHub Code Scanning | ||
|
||
<!--lint disable maximum-line-length--> | ||
|
||
This producer [queries the GitHub Code Scanning API](https://docs.github.com/en/rest/code-scanning/code-scanning?apiVersion=2022-11-28#list-code-scanning-alerts-for-a-repository) to produce SAST findings. | ||
|
||
## Parameters | ||
|
||
All parameters are **required**. | ||
|
||
| Name | Type | Default | Description | | ||
| ------------------------------------------------ | -------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | ||
| `producer-github-code-scanning-repository-owner` | `string` | N/A | The owner of the repository to scan. | | ||
| `producer-github-code-scanning-repository-name` | `string` | N/A | The name of the repository to scan. | | ||
| `producer-github-code-scanning-github-token` | `string` | N/A | The GitHub token to use for scanning. Must have "Code scanning alerts" repository permissions (read) ([More Information](https://docs.github.com/en/rest/code-scanning/code-scanning?apiVersion=2022-11-28#list-code-scanning-alerts-for-a-repository)). | | ||
|
||
<!--lint enable maximum-line-length--> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,144 @@ | ||
package main | ||
|
||
import ( | ||
"context" | ||
"flag" | ||
"log" | ||
"log/slog" | ||
"strconv" | ||
"strings" | ||
|
||
"github.com/google/go-github/v65/github" | ||
"github.com/package-url/packageurl-go" | ||
|
||
v1 "github.com/ocurity/dracon/api/proto/v1" | ||
"github.com/ocurity/dracon/components/producers" | ||
wrapper "github.com/ocurity/dracon/pkg/github" | ||
) | ||
|
||
var ( | ||
// RepositoryOwner is the owner of the GitHub repository | ||
RepositoryOwner string | ||
|
||
// RepositoryName is the name of the GitHub repository | ||
RepositoryName string | ||
|
||
// GitHubToken is the GitHub token used to authenticate | ||
GitHubToken string | ||
|
||
// Ref is the Ref/branch to get alerts for | ||
Ref string | ||
|
||
// Severity, if specified, only code scanning alerts with this severity will be returned. Possible values are: critical, high, medium, low, warning, note, error | ||
Severity string | ||
|
||
// Ecosystem is a comma separated list of at least one of composer, go, maven, npm, nuget, pip, pub, rubygems, rust | ||
Ecosystem string | ||
) | ||
|
||
func main() { | ||
flag.StringVar(&RepositoryOwner, "repository-owner", "", "The owner of the GitHub repository") | ||
flag.StringVar(&RepositoryName, "repository-name", "", "The name of the GitHub repository") | ||
flag.StringVar(&GitHubToken, "github-token", "", "The GitHub token used to authenticate with the API") | ||
flag.StringVar(&Ref, "reference", "", "The Ref/branch to get alerts for") | ||
flag.StringVar(&Severity, "severity", "", "If specified, only code scanning alerts with this severity will be returned. Possible values are: critical, high, medium, low, warning, note, error") | ||
flag.StringVar(&Ecosystem, "ecosystem", "", "If specified, a comma separated list of at least one of composer, go, maven, npm, nuget, pip, pub, rubygems, rust") | ||
if err := producers.ParseFlags(); err != nil { | ||
log.Fatal(err) | ||
} | ||
|
||
alerts, err := listAlertsForRepo(RepositoryOwner, RepositoryName, GitHubToken) | ||
if err != nil { | ||
log.Fatal(err) | ||
} | ||
|
||
issues := parseIssues(alerts) | ||
|
||
if err := producers.WriteDraconOut( | ||
"github-code-scanning", | ||
issues, | ||
); err != nil { | ||
log.Fatal(err) | ||
} | ||
} | ||
|
||
func listAlertsForRepo(owner, repo, token string) ([]*github.DependabotAlert, error) { | ||
apiClient := wrapper.NewClient(token) | ||
open := "open" | ||
opt := &github.ListAlertsOptions{ | ||
State: &open, | ||
Severity: &Severity, | ||
Ecosystem: &Ecosystem, | ||
|
||
ListOptions: github.ListOptions{ | ||
PerPage: 30, | ||
}, | ||
} | ||
|
||
var allAlerts []*github.DependabotAlert | ||
for { | ||
alerts, resp, err := apiClient.ListRepoDependabotAlerts(context.Background(), owner, repo, opt) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
allAlerts = append(allAlerts, alerts...) | ||
|
||
if resp.NextPage == 0 { | ||
break | ||
} | ||
opt.ListOptions.Page = resp.NextPage | ||
} | ||
|
||
slog.Info("Successfully fetched alerts", "count", len(allAlerts), "repository", owner+"/"+repo) | ||
|
||
return allAlerts, nil | ||
} | ||
|
||
func parseIssues(alerts []*github.DependabotAlert) []*v1.Issue { | ||
issues := []*v1.Issue{} | ||
for _, alert := range alerts { | ||
ecosystem := *(alert.GetSecurityVulnerability().Package.Ecosystem) | ||
if ecosystem == "pip" { | ||
ecosystem = "pypi" | ||
} | ||
cwe := []int32{} | ||
for _, c := range alert.SecurityAdvisory.CWEs { | ||
numberOnly := strings.ReplaceAll(*c.CWEID, "CWE-", "") | ||
cweNum, err := strconv.Atoi(numberOnly) | ||
|
||
if err != nil { | ||
slog.Error("could not extract cwe number from ", slog.String("cweID", *c.CWEID)) | ||
continue | ||
} | ||
cwe = append(cwe, int32(cweNum)) | ||
} | ||
issue := &v1.Issue{ | ||
Target: producers.GetPURLTarget(ecosystem, "", *alert.GetSecurityVulnerability().Package.Name, "", packageurl.Qualifiers{}, ""), | ||
Cve: *alert.GetSecurityAdvisory().CVEID, | ||
Title: *alert.GetSecurityAdvisory().Summary, | ||
Description: *alert.GetSecurityAdvisory().Description, | ||
Severity: parseGitHubSeverity(*alert.GetSecurityAdvisory().Severity), | ||
Cvss: *alert.SecurityAdvisory.GetCVSS().Score, | ||
Cwe: cwe, | ||
} | ||
issues = append(issues, issue) | ||
} | ||
|
||
return issues | ||
} | ||
|
||
func parseGitHubSeverity(severity string) v1.Severity { | ||
switch severity { | ||
case "low": | ||
return v1.Severity_SEVERITY_LOW | ||
case "medium": | ||
return v1.Severity_SEVERITY_MEDIUM | ||
case "high": | ||
return v1.Severity_SEVERITY_HIGH | ||
case "critical": | ||
return v1.Severity_SEVERITY_CRITICAL | ||
default: | ||
return v1.Severity_SEVERITY_UNSPECIFIED | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
package main | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/google/go-github/v65/github" | ||
"github.com/stretchr/testify/require" | ||
|
||
v1 "github.com/ocurity/dracon/api/proto/v1" | ||
) | ||
|
||
func TestParseIssues(t *testing.T) { | ||
require.Fail(t, "unimplemented") | ||
|
||
alerts := []*github.DependabotAlert{} | ||
|
||
issues := parseIssues(alerts) | ||
|
||
expected := []*v1.Issue{ | ||
{ | ||
Target: "file://spec-main/api-session-spec.ts:917-918", | ||
Type: "1", | ||
Title: "Test description", | ||
Severity: v1.Severity_SEVERITY_LOW, | ||
Cvss: 0, | ||
Confidence: v1.Confidence_CONFIDENCE_UNSPECIFIED, | ||
Description: "Test message", | ||
Source: "https://example.com", | ||
Cwe: []int32{22}, | ||
}, | ||
} | ||
|
||
require.Equal(t, expected, issues) | ||
} | ||
|
||
func TestParseGitHubSeverity(t *testing.T) { | ||
testCases := []struct { | ||
name string | ||
severity string | ||
expected v1.Severity | ||
}{ | ||
{ | ||
name: "low severity", | ||
severity: "low", | ||
expected: v1.Severity_SEVERITY_LOW, | ||
}, | ||
{ | ||
name: "medium severity", | ||
severity: "medium", | ||
expected: v1.Severity_SEVERITY_MEDIUM, | ||
}, | ||
{ | ||
name: "high severity", | ||
severity: "high", | ||
expected: v1.Severity_SEVERITY_HIGH, | ||
}, | ||
{ | ||
name: "critical severity", | ||
severity: "critical", | ||
expected: v1.Severity_SEVERITY_CRITICAL, | ||
}, | ||
{ | ||
name: "unspecified severity", | ||
severity: "unknown", | ||
expected: v1.Severity_SEVERITY_UNSPECIFIED, | ||
}, | ||
{ | ||
name: "empty severity", | ||
severity: "", | ||
expected: v1.Severity_SEVERITY_UNSPECIFIED, | ||
}, | ||
} | ||
|
||
for _, tc := range testCases { | ||
t.Run(tc.name, func(t *testing.T) { | ||
severity := parseGitHubSeverity(tc.severity) | ||
require.Equal(t, tc.expected, severity) | ||
}) | ||
} | ||
|
||
} | ||
|
||
func TestListAlertsForRepo(t *testing.T) { | ||
require.Fail(t, "unimplemented") | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
--- | ||
apiVersion: tekton.dev/v1beta1 | ||
kind: Task | ||
metadata: | ||
name: producer-github-code-scanning | ||
labels: | ||
v1.dracon.ocurity.com/component: producer | ||
v1.dracon.ocurity.com/test-type: sast | ||
spec: | ||
description: Retrieve a GitHub Code Scanning report from a GitHub repository. | ||
params: | ||
- name: producer-github-code-scanning-repository-owner | ||
description: The owner of the repository to scan. | ||
type: string | ||
- name: producer-github-code-scanning-repository-name | ||
description: The name of the repository to scan. | ||
type: string | ||
- name: producer-github-code-scanning-github-token | ||
description: The GitHub token to use for scanning. Must have "Code scanning alerts" repository permissions (read). | ||
type: string | ||
volumes: | ||
- name: scratch | ||
emptyDir: {} | ||
workspaces: | ||
- name: output | ||
description: The workspace containing the source-code to scan. | ||
steps: | ||
- name: produce-issues | ||
imagePullPolicy: IfNotPresent | ||
image: '{{ default "ghcr.io/ocurity/dracon" .Values.image.registry }}/components/producers/github-code-scanning:{{ .Chart.AppVersion }}' | ||
command: ["/app/components/producers/github-code-scanning/github-code-scanning-parser"] | ||
args: | ||
- "-in=/scratch/out.json" | ||
- "-out=$(workspaces.output.path)/.dracon/producers/github-code-scanning.pb" | ||
- "-github-token=$(params.producer-github-code-scanning-github-token)" | ||
- "-repository-owner=$(params.producer-github-code-scanning-repository-owner)" | ||
- "-repository-name=$(params.producer-github-code-scanning-repository-name)" | ||
volumeMounts: | ||
- mountPath: /scratch | ||
name: scratch |