Skip to content

Commit 98ad5a3

Browse files
committed
osv-scanner for sca + examples
1 parent 8529622 commit 98ad5a3

File tree

11 files changed

+453
-0
lines changed

11 files changed

+453
-0
lines changed
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# osv-scanner
2+
3+
This component implements a [scanner](https://github.com/smithy-security/smithy/blob/main/sdk/component/component.go)
4+
that parses json reports output by [osv-scan](https://google.github.io/osv-scanner/) into [ocsf](https://github.com/ocsf) format.
5+
6+
## Environment variables
7+
8+
The component uses environment variables for configuration.
9+
10+
It requires the component
11+
environment variables defined [here](https://github.com/smithy-security/smithy/blob/main/sdk/README.md#component) as well
12+
as the following:
13+
14+
| Environment Variable | Type | Required | Default | Description |
15+
|--------------------------|--------|----------|------------|---------------------------------------------------------|
16+
| RAW\_OUT\_FILE\_PATH | string | yes | - | The path where to find the osv-scan report |
17+
| TARGET\_TYPE | string | false | repository | The type of target that was used to generate the report |
18+
19+
## Test data
20+
21+
The `results.json` file used in tests was generated with the following steps:
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"log"
6+
"time"
7+
8+
"github.com/go-errors/errors"
9+
10+
"github.com/smithy-security/smithy/new-components/scanners/osv-scanner/internal/transformer"
11+
"github.com/smithy-security/smithy/sdk/component"
12+
)
13+
14+
func main() {
15+
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Minute)
16+
defer cancel()
17+
18+
if err := Main(ctx); err != nil {
19+
log.Fatalf("unexpected error: %v", err)
20+
}
21+
}
22+
23+
// Main is the main entrypoint of this component
24+
func Main(ctx context.Context, opts ...component.RunnerOption) error {
25+
opts = append(opts, component.RunnerWithComponentName("bandit"))
26+
27+
ocsfTransformer, err := transformer.New()
28+
if err != nil {
29+
return errors.Errorf("could not create transformer: %w", err)
30+
}
31+
32+
if err := component.RunScanner(ctx, ocsfTransformer, opts...); err != nil {
33+
return errors.Errorf("could not run scanner: %w", err)
34+
}
35+
36+
return nil
37+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
name: osv-scanner
2+
description: "Scans projects with google's osv-scanner, parses sarif findings into OCSF format"
3+
type: scanner
4+
steps:
5+
- name: scanner
6+
image: components/scanners/osv-scanner/scanner
7+
executable: /bin/bash
8+
env_vars:
9+
RAW_OUT_FILE: "{{ scratchWorkspace }}/output.json"
10+
args:
11+
- -c
12+
- /entrypoint.sh scan source -r --format=sarif --call-analysis=true {{ sourceCodeWorkspace }}
13+
- name: parser
14+
image: components/scanners/osv-scanner
15+
env_vars:
16+
RAW_OUT_FILE: "{{ scratchWorkspace }}/output.json"
17+
SCANNED_PROJECT_ROOT: "{{ sourceCodeWorkspace }}"
18+
executable: /bin/app
Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
package transformer
2+
3+
import (
4+
"context"
5+
"log/slog"
6+
"os"
7+
8+
"github.com/go-errors/errors"
9+
"github.com/jonboulle/clockwork"
10+
"github.com/smithy-security/pkg/env"
11+
"github.com/smithy-security/pkg/sarif"
12+
sarifschemav210 "github.com/smithy-security/pkg/sarif/spec/gen/sarif-schema/v2-1-0"
13+
14+
"github.com/smithy-security/smithy/sdk/component"
15+
ocsffindinginfo "github.com/smithy-security/smithy/sdk/gen/ocsf_ext/finding_info/v1"
16+
ocsf "github.com/smithy-security/smithy/sdk/gen/ocsf_schema/v1"
17+
)
18+
19+
type (
20+
21+
// OSVScannerTransformerOption allows customising the transformer.
22+
OSVScannerTransformerOption func(b *OSVScannerTransformer) error
23+
24+
// OSVScannerTransformer represents the osv-scanner output parser
25+
OSVScannerTransformer struct {
26+
targetType ocsffindinginfo.DataSource_TargetType
27+
clock clockwork.Clock
28+
rawOutFile string
29+
fileContents []byte
30+
projectRoot string
31+
}
32+
)
33+
34+
var (
35+
36+
// Generic errors
37+
38+
// ErrNilClock is thrown when the option setclock is called with empty clock
39+
ErrNilClock = errors.Errorf("invalid nil clock")
40+
// ErrEmptyTarget is thrown when the option set target is called with empty target
41+
ErrEmptyTarget = errors.Errorf("invalid empty target")
42+
// ErrEmptyRawOutfilePath is thrown when the option raw outfile path is called with empty path
43+
ErrEmptyRawOutfilePath = errors.Errorf("invalid raw out file path")
44+
// ErrEmptyRawOutfileContents is thrown when the option raw outfile contents is called with empty contents
45+
ErrEmptyRawOutfileContents = errors.Errorf("empty raw out file contents")
46+
// ErrBadTargetType is thrown when the option set target type is called with an unspecified or empty target type
47+
ErrBadTargetType = errors.New("invalid empty target type")
48+
49+
// OSVScanner Parser Specific Errors
50+
// ErrEmptyPath is thrown when called with an empty project root
51+
ErrEmptyPath = errors.Errorf("called with an empty project root")
52+
// ErrNoLineRange is thrown when osv-scanner produces a finding without a line range
53+
ErrNoLineRange = errors.Errorf("osv-scanner result does not contain a line range")
54+
// ErrBadDataSource is thrown when osv-scanner produces a finding that cannot have a datasource (e.g. no filename)
55+
ErrBadDataSource = errors.Errorf("failed to marshal data source to JSON")
56+
// ErrCouldNotFindPackage is thrown when nancy cannot find the dependency in any go.mod files
57+
ErrCouldNotFindPackage = errors.Errorf("could not find package")
58+
)
59+
60+
// OSVScanneryTransformerWithClock allows customising the underlying clock.
61+
func OSVScannerTransformerWithClock(clock clockwork.Clock) OSVScannerTransformerOption {
62+
return func(g *OSVScannerTransformer) error {
63+
if clock == nil {
64+
return ErrNilClock
65+
}
66+
g.clock = clock
67+
return nil
68+
}
69+
}
70+
71+
// OSVScannerRawOutFileGlob allows customising the underlying raw out file path.
72+
func OSVScannerRawOutFileGlob(path string) OSVScannerTransformerOption {
73+
return func(g *OSVScannerTransformer) error {
74+
if path == "" {
75+
return ErrEmptyRawOutfilePath
76+
}
77+
g.rawOutFile = path
78+
return nil
79+
}
80+
}
81+
82+
// OSVScannerRawOutFileContents allows customising the underlying raw out file contents.
83+
func OSVScannerRawOutFileContents(contents []byte) OSVScannerTransformerOption {
84+
return func(g *OSVScannerTransformer) error {
85+
if contents == nil {
86+
return ErrEmptyRawOutfileContents
87+
}
88+
g.fileContents = contents
89+
return nil
90+
}
91+
}
92+
93+
// OSVScannerTransformerWithProjectRoot allows customising the path of the target project root
94+
func OSVScannerTransformerWithProjectRoot(path string) OSVScannerTransformerOption {
95+
return func(g *OSVScannerTransformer) error {
96+
if path == "" {
97+
return ErrEmptyPath
98+
}
99+
g.projectRoot = path
100+
return nil
101+
}
102+
}
103+
104+
// New returns a new osv-scanner transformer.
105+
func New(opts ...OSVScannerTransformerOption) (*OSVScannerTransformer, error) {
106+
rawOutFile, err := env.GetOrDefault(
107+
"RAW_OUT_FILE",
108+
"",
109+
env.WithDefaultOnError(false),
110+
)
111+
if err != nil {
112+
return nil, err
113+
}
114+
115+
t := OSVScannerTransformer{
116+
clock: clockwork.NewRealClock(),
117+
targetType: ocsffindinginfo.DataSource_TARGET_TYPE_REPOSITORY,
118+
rawOutFile: rawOutFile,
119+
}
120+
121+
for _, opt := range opts {
122+
if err := opt(&t); err != nil {
123+
return nil, errors.Errorf("failed to apply option: %w", err)
124+
}
125+
}
126+
return &t, nil
127+
}
128+
129+
// Transform transforms raw sarif findings into ocsf vulnerability findings.
130+
func (b *OSVScannerTransformer) Transform(ctx context.Context) ([]*ocsf.VulnerabilityFinding, error) {
131+
logger := component.LoggerFromContext(ctx)
132+
133+
logger.Debug("preparing to parse raw osv-scanner output...")
134+
fileContents, err := os.ReadFile(b.rawOutFile)
135+
if err != nil {
136+
return nil, errors.Errorf("could not read file %s", b.rawOutFile)
137+
}
138+
var report sarifschemav210.SchemaJson
139+
if err := report.UnmarshalJSON(fileContents); err != nil {
140+
return nil, errors.Errorf("failed to parse raw semgrep output: %w", err)
141+
}
142+
143+
transformer, err := sarif.NewTransformer(&report,
144+
"",
145+
sarif.TargetTypeRepository,
146+
b.clock, sarif.RealUUIDProvider{})
147+
if err != nil {
148+
return nil, err
149+
}
150+
vulns, err := transformer.ToOCSF(ctx)
151+
if err != nil {
152+
return nil, err
153+
}
154+
155+
logger.Debug(
156+
"successfully parsed raw osv-scanner findings to ocsf vulnerability findings!",
157+
slog.Int("num_parsed_findings", len(vulns)),
158+
)
159+
return vulns, nil
160+
}

0 commit comments

Comments
 (0)