Skip to content

Commit ceb91ee

Browse files
authored
Feat: Add security code scanning section (#462)
This introduces Security / Code Scanning Results page in a given github repo; ![image](https://github.com/user-attachments/assets/f15ac421-3de0-485c-af48-2ec139e7b06e) Originally introduced here: #377 Fixes: #459
1 parent d8d43ef commit ceb91ee

20 files changed

+541
-5
lines changed

docs/sources/_index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ Watch this video to learn more about setting up the Grafana GitHub data source p
2727

2828
The plugin supports the following query types:
2929

30+
- Code Scan
3031
- Commits
3132
- Issues
3233
- Contributors

docs/sources/setup/token.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,3 +68,7 @@ You will need to define the access permissions for your **GitHub App** in order
6868
**Repositories:**
6969

7070
`metadata: read-only`, `contents: read-only`, `issues: read-only`, `pull requests: read-only`, `packages: read-only`, `repository security advisories: read-only`, `projects: read-only`
71+
72+
**Code scan**
73+
74+
`code scanning alerts: read-only, security_events: read-only`

pkg/github/client/client.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,24 @@ func (client *Client) ListWorkflows(ctx context.Context, owner, repo string, opt
151151
return wf, resp, err
152152
}
153153

154+
// ListAlertsForRepo sends a request to the GitHub rest API to list the code scanning alerts in a specific repository.
155+
func (client *Client) ListAlertsForRepo(ctx context.Context, owner, repo string, opts *googlegithub.AlertListOptions) ([]*googlegithub.Alert, *googlegithub.Response, error) {
156+
alerts, resp, err := client.restClient.CodeScanning.ListAlertsForRepo(ctx, owner, repo, opts)
157+
if err != nil {
158+
return nil, nil, addErrorSourceToError(err, resp)
159+
}
160+
return alerts, resp, err
161+
}
162+
163+
// ListAlertsForOrg sends a request to the GitHub rest API to list the code scanning alerts in a specific organization.
164+
func (client *Client) ListAlertsForOrg(ctx context.Context, owner string, opts *googlegithub.AlertListOptions) ([]*googlegithub.Alert, *googlegithub.Response, error) {
165+
alerts, resp, err := client.restClient.CodeScanning.ListAlertsForOrg(ctx, owner, opts)
166+
if err != nil {
167+
return nil, nil, addErrorSourceToError(err, resp)
168+
}
169+
return alerts, resp, err
170+
}
171+
154172
// GetWorkflowUsage returns the workflow usage for a specific workflow.
155173
func (client *Client) GetWorkflowUsage(ctx context.Context, owner, repo, workflow string, timeRange backend.TimeRange) (models.WorkflowUsage, error) {
156174
actors := make(map[string]struct{}, 0)

pkg/github/codescanning.go

Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
package github
2+
3+
import (
4+
"context"
5+
"strings"
6+
"time"
7+
8+
googlegithub "github.com/google/go-github/v72/github"
9+
"github.com/grafana/grafana-plugin-sdk-go/data"
10+
11+
"github.com/grafana/github-datasource/pkg/models"
12+
)
13+
14+
type CodeScanningWrapper []*googlegithub.Alert
15+
16+
func (alerts CodeScanningWrapper) Frames() data.Frames {
17+
frames := data.NewFrame("code_scanning_alerts",
18+
data.NewField("number", nil, []*int64{}),
19+
data.NewField("created_at", nil, []time.Time{}),
20+
data.NewField("updated_at", nil, []time.Time{}),
21+
data.NewField("dismissed_at", nil, []*time.Time{}),
22+
data.NewField("url", nil, []string{}),
23+
data.NewField("state", nil, []string{}),
24+
data.NewField("dismissed_by", nil, []string{}),
25+
data.NewField("dismissed_reason", nil, []string{}),
26+
data.NewField("dismissed_comment", nil, []string{}),
27+
data.NewField("rule_id", nil, []string{}),
28+
data.NewField("rule_severity", nil, []string{}),
29+
data.NewField("rule_security_severity_level", nil, []string{}),
30+
data.NewField("rule_description", nil, []string{}),
31+
data.NewField("rule_full_description", nil, []string{}),
32+
data.NewField("rule_tags", nil, []string{}),
33+
data.NewField("rule_help", nil, []string{}),
34+
data.NewField("tool_name", nil, []string{}),
35+
data.NewField("tool_version", nil, []string{}),
36+
data.NewField("tool_guid", nil, []string{}),
37+
)
38+
39+
for _, alert := range alerts {
40+
frames.AppendRow(
41+
func() *int64 {
42+
num := int64(alert.GetNumber())
43+
return &num
44+
}(),
45+
func() time.Time {
46+
if !alert.GetCreatedAt().Time.IsZero() {
47+
return alert.GetCreatedAt().Time
48+
}
49+
return time.Time{}
50+
}(),
51+
func() time.Time {
52+
if !alert.GetUpdatedAt().Time.IsZero() {
53+
return alert.GetUpdatedAt().Time
54+
}
55+
return time.Time{}
56+
}(),
57+
func() *time.Time {
58+
if !alert.GetDismissedAt().Time.IsZero() {
59+
t := alert.GetDismissedAt().Time
60+
return &t
61+
}
62+
return nil
63+
}(),
64+
func() string {
65+
str := alert.GetHTMLURL()
66+
return str
67+
}(),
68+
func() string {
69+
str := alert.GetState()
70+
return str
71+
}(),
72+
func() string {
73+
if alert.GetDismissedBy() != nil {
74+
str := alert.GetDismissedBy().GetLogin()
75+
return str
76+
}
77+
return ""
78+
}(),
79+
func() string {
80+
str := alert.GetDismissedReason()
81+
return str
82+
}(),
83+
func() string {
84+
str := alert.GetDismissedComment()
85+
return str
86+
}(),
87+
func() string {
88+
if alert.GetRule() != nil {
89+
return *alert.GetRule().ID
90+
}
91+
return ""
92+
}(),
93+
func() string {
94+
if alert.GetRule() != nil {
95+
return *alert.GetRule().Severity
96+
}
97+
return ""
98+
}(),
99+
func() string {
100+
if alert.GetRule() != nil && alert.GetRule().SecuritySeverityLevel != nil {
101+
return *alert.GetRule().SecuritySeverityLevel
102+
}
103+
return ""
104+
}(),
105+
func() string {
106+
if alert.GetRule() != nil && alert.GetRule().Description != nil {
107+
return *alert.GetRule().Description
108+
}
109+
return ""
110+
}(),
111+
func() string {
112+
if alert.GetRule() != nil && alert.GetRule().FullDescription != nil {
113+
return *alert.GetRule().FullDescription
114+
}
115+
return ""
116+
}(),
117+
func() string {
118+
if alert.GetRule() != nil {
119+
str := strings.Join(alert.GetRule().Tags, ", ")
120+
return str
121+
}
122+
return ""
123+
}(),
124+
func() string {
125+
if alert.GetRule() != nil && alert.GetRule().Help != nil {
126+
return *alert.GetRule().Help
127+
}
128+
return ""
129+
}(),
130+
func() string {
131+
if alert.GetTool() != nil && alert.GetTool().Name != nil {
132+
return *alert.GetTool().Name
133+
}
134+
return ""
135+
}(),
136+
func() string {
137+
if alert.GetTool() != nil && alert.GetTool().Version != nil {
138+
return *alert.GetTool().Version
139+
}
140+
return ""
141+
}(),
142+
func() string {
143+
if alert.GetTool() != nil && alert.GetTool().GUID != nil {
144+
return *alert.GetTool().GUID
145+
}
146+
return ""
147+
}(),
148+
)
149+
}
150+
151+
return data.Frames{frames}
152+
}
153+
154+
// GetCodeScanningAlerts to get a list of alerts for a repository
155+
// GET /repos/{owner}/{repo}/code-scanning/alerts
156+
// https://docs.github.com/en/rest/reference/code-scanning#get-a-list-of-code-scanning-alerts-for-a-repository
157+
func GetCodeScanningAlerts(context context.Context, c models.Client, opt models.CodeScanningOptions, from time.Time, to time.Time) (CodeScanningWrapper, error) {
158+
var alerts []*googlegithub.Alert
159+
var err error
160+
161+
// if there is no repository provided show alerts in organization level
162+
if opt.Repository == "" {
163+
alerts, _, err = c.ListAlertsForOrg(
164+
context,
165+
opt.Owner,
166+
&googlegithub.AlertListOptions{
167+
State: opt.State,
168+
Ref: opt.Ref,
169+
},
170+
)
171+
} else {
172+
alerts, _, err = c.ListAlertsForRepo(
173+
context,
174+
opt.Owner,
175+
opt.Repository,
176+
&googlegithub.AlertListOptions{
177+
State: opt.State,
178+
Ref: opt.Ref,
179+
},
180+
)
181+
}
182+
183+
if err != nil {
184+
return nil, err
185+
}
186+
187+
return CodeScanningWrapper(alerts), nil
188+
}

pkg/github/codescanning_handler.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package github
2+
3+
import (
4+
"context"
5+
6+
"github.com/grafana/grafana-plugin-sdk-go/backend"
7+
8+
"github.com/grafana/github-datasource/pkg/dfutil"
9+
"github.com/grafana/github-datasource/pkg/models"
10+
)
11+
12+
func (s *QueryHandler) handleCodeScanningRequests(ctx context.Context, q backend.DataQuery) backend.DataResponse {
13+
query := &models.CodeScanningQuery{}
14+
if err := UnmarshalQuery(q.JSON, query); err != nil {
15+
return *err
16+
}
17+
return dfutil.FrameResponseWithError(s.Datasource.HandleCodeScanningQuery(ctx, query, q))
18+
}
19+
20+
// HandleCodeScanning handles the plugin query for github code scanning
21+
func (s *QueryHandler) HandleCodeScanning(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) {
22+
return &backend.QueryDataResponse{
23+
Responses: processQueries(ctx, req, s.handleCodeScanningRequests),
24+
}, nil
25+
}

0 commit comments

Comments
 (0)