Skip to content

Commit 9609853

Browse files
Merge pull request #251 from cerberauth/link-issues-scans
Link scans to issues in reports
2 parents 3341eab + 0d79369 commit 9609853

File tree

33 files changed

+573
-199
lines changed

33 files changed

+573
-199
lines changed

internal/auth/security_scheme.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ type SecurityScheme struct {
1515
Type Type `json:"type" yaml:"type"`
1616
Scheme SchemeName `json:"scheme" yaml:"scheme"`
1717
In *SchemeIn `json:"in" yaml:"in"`
18-
TokenFormat *TokenFormat `json:"token_format" yaml:"token_format"`
18+
TokenFormat *TokenFormat `json:"tokenFormat" yaml:"tokenFormat"`
1919

2020
Name string `json:"name" yaml:"name"`
2121
Config interface{} `json:"config" yaml:"config"`

internal/request/request.go

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,13 @@ import (
66
"net/http"
77

88
"github.com/cerberauth/vulnapi/internal/auth"
9+
"github.com/google/uuid"
910
)
1011

1112
type Request struct {
12-
Body []byte
13+
ID string
14+
Body []byte
15+
1316
Client *Client
1417
HttpRequest *http.Request
1518
}
@@ -53,6 +56,7 @@ func NewRequest(method string, url string, body io.Reader, client *Client) (*Req
5356
}
5457

5558
return &Request{
59+
ID: uuid.New().String(),
5660
Body: bodyBuffer,
5761

5862
Client: client,
@@ -86,6 +90,10 @@ func (r *Request) WithSecurityScheme(securityScheme *auth.SecurityScheme) *Reque
8690
return r
8791
}
8892

93+
func (r *Request) GetID() string {
94+
return r.ID
95+
}
96+
8997
func (r *Request) GetMethod() string {
9098
return r.HttpRequest.Method
9199
}
@@ -138,7 +146,8 @@ func (r *Request) SetBody(body io.Reader) *Request {
138146
}
139147

140148
func (r *Request) Do() (*Response, error) {
141-
r.SetHeader("User-Agent", "vulnapi")
149+
r.SetHeader("user-agent", "vulnapi")
150+
r.SetHeader("x-vulnapi-request-id", r.GetID())
142151

143152
rl.Take()
144153
httpRes, err := r.Client.Do(r.HttpRequest)

internal/request/request_test.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ func TestNewRequest(t *testing.T) {
2020
request, err := request.NewRequest(method, url, body, nil)
2121

2222
assert.NoError(t, err)
23+
assert.NotEqual(t, "", request.ID)
2324
assert.Equal(t, method, request.HttpRequest.Method)
2425
assert.Equal(t, url, request.HttpRequest.URL.String())
2526
assert.Equal(t, []byte("test"), request.Body)
@@ -68,6 +69,17 @@ func TestWithSecurityScheme(t *testing.T) {
6869
assert.Equal(t, "Bearer "+token, request.HttpRequest.Header.Get("Authorization"))
6970
}
7071

72+
func TestGetID(t *testing.T) {
73+
method := http.MethodGet
74+
url := "http://localhost:8080/"
75+
76+
request, err := request.NewRequest(method, url, nil, nil)
77+
78+
assert.NoError(t, err)
79+
assert.NotEqual(t, "", request.GetID())
80+
assert.Equal(t, request.ID, request.GetID())
81+
}
82+
7183
func TestGetMethod(t *testing.T) {
7284
method := http.MethodGet
7385
url := "http://localhost:8080/"
@@ -241,6 +253,7 @@ func TestDo(t *testing.T) {
241253
assert.Equal(t, method, req.Method)
242254
assert.Equal(t, url, req.URL.String())
243255
assert.Equal(t, "vulnapi", req.Header.Get("User-Agent"))
256+
assert.Equal(t, request.GetID(), req.Header.Get("x-vulnapi-request-id"))
244257

245258
return httpmock.NewBytesResponse(http.StatusNoContent, nil), nil
246259
})
@@ -301,6 +314,7 @@ func TestDoWithClientHeaders(t *testing.T) {
301314
assert.Equal(t, method, req.Method)
302315
assert.Equal(t, url, req.URL.String())
303316
assert.Equal(t, "vulnapi", req.Header.Get("User-Agent"))
317+
assert.Equal(t, request.GetID(), req.Header.Get("x-vulnapi-request-id"))
304318
assert.Equal(t, header.Get("X-Test"), req.Header.Get("X-Test"))
305319

306320
return httpmock.NewBytesResponse(http.StatusNoContent, nil), nil
@@ -354,6 +368,7 @@ func TestDoWithSecuritySchemeHeaders(t *testing.T) {
354368
assert.Equal(t, method, req.Method)
355369
assert.Equal(t, url, req.URL.String())
356370
assert.Equal(t, "vulnapi", req.Header.Get("User-Agent"))
371+
assert.Equal(t, request.GetID(), req.Header.Get("x-vulnapi-request-id"))
357372
assert.Equal(t, "Bearer "+token, req.Header.Get("Authorization"))
358373

359374
return httpmock.NewBytesResponse(http.StatusNoContent, nil), nil
@@ -385,6 +400,7 @@ func TestDoWithHeadersSecuritySchemeHeaders(t *testing.T) {
385400
assert.Equal(t, method, req.Method)
386401
assert.Equal(t, url, req.URL.String())
387402
assert.Equal(t, "vulnapi", req.Header.Get("User-Agent"))
403+
assert.Equal(t, request.GetID(), req.Header.Get("x-vulnapi-request-id"))
388404
assert.Equal(t, header.Get("X-Test"), req.Header.Get("X-Test"))
389405
assert.Equal(t, "Bearer "+token, req.Header.Get("Authorization"))
390406

@@ -417,6 +433,7 @@ func TestDoWithCookiesSecuritySchemeHeaders(t *testing.T) {
417433
assert.Equal(t, method, req.Method)
418434
assert.Equal(t, url, req.URL.String())
419435
assert.Equal(t, "vulnapi", req.Header.Get("User-Agent"))
436+
assert.Equal(t, request.GetID(), req.Header.Get("x-vulnapi-request-id"))
420437
assert.Equal(t, cookies[0].Name, req.Cookies()[0].Name)
421438
assert.Equal(t, cookies[0].Value, req.Cookies()[0].Value)
422439
assert.Equal(t, "Bearer "+token, req.Header.Get("Authorization"))

internal/scan/attempt.go

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package scan
2+
3+
import (
4+
"github.com/cerberauth/vulnapi/internal/operation"
5+
"github.com/cerberauth/vulnapi/internal/request"
6+
)
7+
8+
type IssueScanAttemptStatus string
9+
10+
func (attemptStatus IssueScanAttemptStatus) String() string {
11+
return string(attemptStatus)
12+
}
13+
14+
const (
15+
IssueScanAttemptStatusPassed IssueScanAttemptStatus = "passed"
16+
IssueScanAttemptStatusFailed IssueScanAttemptStatus = "failed"
17+
IssueScanAttemptStatusNone IssueScanAttemptStatus = "none"
18+
)
19+
20+
type IssueScanAttempt struct {
21+
ID string
22+
Status IssueScanAttemptStatus
23+
Request *request.Request
24+
Response *request.Response
25+
Err error
26+
}
27+
28+
func NewIssueScanAttempt(operation *operation.Operation, req *request.Request, res *request.Response, err error) *IssueScanAttempt {
29+
return &IssueScanAttempt{
30+
ID: operation.GetID() + "-" + req.GetID(),
31+
Status: IssueScanAttemptStatusNone,
32+
33+
Request: req,
34+
Response: res,
35+
Err: err,
36+
}
37+
}
38+
39+
func (scanAttempt *IssueScanAttempt) WithBooleanStatus(status bool) *IssueScanAttempt {
40+
if status {
41+
return scanAttempt.Pass()
42+
}
43+
return scanAttempt.Fail()
44+
}
45+
46+
func (scanAttempt *IssueScanAttempt) Fail() *IssueScanAttempt {
47+
scanAttempt.Status = IssueScanAttemptStatusFailed
48+
return scanAttempt
49+
}
50+
51+
func (scanAttempt *IssueScanAttempt) Pass() *IssueScanAttempt {
52+
scanAttempt.Status = IssueScanAttemptStatusPassed
53+
return scanAttempt
54+
}
55+
56+
func (scanAttempt *IssueScanAttempt) HasPassed() bool {
57+
return scanAttempt.Status == IssueScanAttemptStatusPassed
58+
}
59+
60+
func (scanAttempt *IssueScanAttempt) HasFailed() bool {
61+
return scanAttempt.Status == IssueScanAttemptStatusFailed
62+
}

internal/scan/attempt_test.go

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
package scan
2+
3+
import (
4+
"net/http"
5+
"testing"
6+
7+
"github.com/cerberauth/vulnapi/internal/operation"
8+
"github.com/cerberauth/vulnapi/internal/request"
9+
"github.com/stretchr/testify/assert"
10+
)
11+
12+
func TestNewIssueScanAttempt(t *testing.T) {
13+
req, _ := request.NewRequest(http.MethodGet, "http://example.com", nil, nil)
14+
res := &request.Response{}
15+
err := error(nil)
16+
op, _ := operation.NewOperationFromRequest(req)
17+
18+
scanAttempt := NewIssueScanAttempt(op, req, res, err)
19+
20+
assert.NotNil(t, scanAttempt)
21+
assert.Equal(t, IssueScanAttemptStatusNone, scanAttempt.Status)
22+
assert.Equal(t, req, scanAttempt.Request)
23+
assert.Equal(t, res, scanAttempt.Response)
24+
assert.Equal(t, err, scanAttempt.Err)
25+
}
26+
27+
func TestIssueScanAttempt_WithBooleanStatus(t *testing.T) {
28+
req, _ := request.NewRequest(http.MethodGet, "http://example.com", nil, nil)
29+
res := &request.Response{}
30+
err := error(nil)
31+
op, _ := operation.NewOperationFromRequest(req)
32+
33+
scanAttempt := NewIssueScanAttempt(op, req, res, err)
34+
35+
scanAttempt.WithBooleanStatus(true)
36+
assert.Equal(t, IssueScanAttemptStatusPassed, scanAttempt.Status)
37+
38+
scanAttempt.WithBooleanStatus(false)
39+
assert.Equal(t, IssueScanAttemptStatusFailed, scanAttempt.Status)
40+
}
41+
42+
func TestIssueScanAttempt_Fail(t *testing.T) {
43+
req, _ := request.NewRequest(http.MethodGet, "http://example.com", nil, nil)
44+
res := &request.Response{}
45+
err := error(nil)
46+
op, _ := operation.NewOperationFromRequest(req)
47+
48+
scanAttempt := NewIssueScanAttempt(op, req, res, err)
49+
scanAttempt.Fail()
50+
51+
assert.Equal(t, IssueScanAttemptStatusFailed, scanAttempt.Status)
52+
}
53+
54+
func TestIssueScanAttempt_Pass(t *testing.T) {
55+
req, _ := request.NewRequest(http.MethodGet, "http://example.com", nil, nil)
56+
res := &request.Response{}
57+
err := error(nil)
58+
op, _ := operation.NewOperationFromRequest(req)
59+
60+
scanAttempt := NewIssueScanAttempt(op, req, res, err)
61+
scanAttempt.Pass()
62+
63+
assert.Equal(t, IssueScanAttemptStatusPassed, scanAttempt.Status)
64+
}
65+
66+
func TestIssueScanAttempt_HasPassed(t *testing.T) {
67+
req, _ := request.NewRequest(http.MethodGet, "http://example.com", nil, nil)
68+
res := &request.Response{}
69+
err := error(nil)
70+
op, _ := operation.NewOperationFromRequest(req)
71+
72+
scanAttempt := NewIssueScanAttempt(op, req, res, err)
73+
scanAttempt.Pass()
74+
75+
assert.True(t, scanAttempt.HasPassed())
76+
assert.False(t, scanAttempt.HasFailed())
77+
}
78+
79+
func TestIssueScanAttempt_HasFailed(t *testing.T) {
80+
req, _ := request.NewRequest(http.MethodGet, "http://example.com", nil, nil)
81+
res := &request.Response{}
82+
err := error(nil)
83+
op, _ := operation.NewOperationFromRequest(req)
84+
85+
scanAttempt := NewIssueScanAttempt(op, req, res, err)
86+
scanAttempt.Fail()
87+
88+
assert.True(t, scanAttempt.HasFailed())
89+
assert.False(t, scanAttempt.HasPassed())
90+
}

internal/scan/scan_url.go

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,8 @@ package scan
33
import (
44
"github.com/cerberauth/vulnapi/internal/auth"
55
"github.com/cerberauth/vulnapi/internal/operation"
6-
"github.com/cerberauth/vulnapi/internal/request"
76
)
87

9-
type IssueScanAttempt struct {
10-
Request *request.Request
11-
Response *request.Response
12-
Err error
13-
}
14-
158
func ScanURL(operation *operation.Operation, securityScheme *auth.SecurityScheme) (*IssueScanAttempt, error) {
169
req, err := operation.NewRequest()
1710
if err != nil {
@@ -25,9 +18,5 @@ func ScanURL(operation *operation.Operation, securityScheme *auth.SecurityScheme
2518
}
2619

2720
res, err := req.Do()
28-
return &IssueScanAttempt{
29-
Request: req,
30-
Response: res,
31-
Err: err,
32-
}, err
21+
return NewIssueScanAttempt(operation, req, res, err), err
3322
}

report/issue_report.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66

77
"github.com/cerberauth/vulnapi/internal/auth"
88
"github.com/cerberauth/vulnapi/internal/operation"
9+
"github.com/cerberauth/vulnapi/internal/scan"
910
"go.opentelemetry.io/otel/attribute"
1011
"go.opentelemetry.io/otel/trace"
1112
)
@@ -30,10 +31,35 @@ var IssueReportStatuses = []IssueReportStatus{
3031
IssueReportStatusNone,
3132
}
3233

34+
type IssueScanReport struct {
35+
ID string `json:"id" yaml:"id"`
36+
Status *scan.IssueScanAttemptStatus `json:"status" yaml:"status"`
37+
}
38+
39+
func NewIssueScanReport(id string, status *scan.IssueScanAttemptStatus) *IssueScanReport {
40+
return &IssueScanReport{
41+
ID: id,
42+
Status: status,
43+
}
44+
}
45+
46+
func (issueScanReport *IssueScanReport) GetStatus() scan.IssueScanAttemptStatus {
47+
return *issueScanReport.Status
48+
}
49+
50+
func (issueScanReport *IssueScanReport) HasFailed() bool {
51+
return *issueScanReport.Status == scan.IssueScanAttemptStatusFailed
52+
}
53+
54+
func (issueScanReport *IssueScanReport) HasPassed() bool {
55+
return *issueScanReport.Status == scan.IssueScanAttemptStatusPassed
56+
}
57+
3358
type IssueReport struct {
3459
Issue `json:",inline" yaml:",inline"`
3560
Status IssueReportStatus `json:"status" yaml:"status"`
3661

62+
Scans []*IssueScanReport `json:"scans" yaml:"scans"`
3763
Operation *operation.Operation `json:"-" yaml:"-"`
3864
SecurityScheme *auth.SecurityScheme `json:"-" yaml:"-"`
3965
}
@@ -42,6 +68,7 @@ func NewIssueReport(issue Issue) *IssueReport {
4268
return &IssueReport{
4369
Issue: issue,
4470
Status: IssueReportStatusNone,
71+
Scans: []*IssueScanReport{},
4572
}
4673
}
4774

@@ -122,6 +149,15 @@ func (vr *IssueReport) IsCriticalRiskSeverity() bool {
122149
return vr.CVSS.Score > 9
123150
}
124151

152+
func (vr *IssueReport) WithScanAttempt(attempt *scan.IssueScanAttempt) *IssueReport {
153+
return vr.AddScanAttempt(attempt)
154+
}
155+
156+
func (vr *IssueReport) AddScanAttempt(attempt *scan.IssueScanAttempt) *IssueReport {
157+
vr.Scans = append(vr.Scans, NewIssueScanReport(attempt.ID, &attempt.Status))
158+
return vr
159+
}
160+
125161
func (vr *IssueReport) String() string {
126162
return fmt.Sprintf("[%s] %s", vr.SeverityLevelString(), vr.Name)
127163
}

0 commit comments

Comments
 (0)