Skip to content

Commit 992216c

Browse files
author
sg
committed
reachability enricher
1 parent b9b9e06 commit 992216c

File tree

17 files changed

+1323
-0
lines changed

17 files changed

+1323
-0
lines changed
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# custom-annotation
2+
3+
This component implements an [enricher](https://github.com/smithy-security/smithy/blob/main/sdk/component/component.go)
4+
This enricher performs reachability analysis using [atom](https://github.com/AppThreat/atom).
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 as the following:
12+
13+
| Environment Variable | Type | Required | Default | Description |
14+
|----------------------------|--------|----------|---------|-------------------------------------------------------------------------|
15+
| ATOM\_FILE\_PATH | string | yes | - | Path to the file where atom has produced reachable slices. |
16+
17+
## How to run
18+
19+
Execute:
20+
21+
```shell
22+
docker-compose up --build --force-recreate --remove-orphans
23+
```
24+
25+
Then shutdown with:
26+
27+
```shell
28+
docker-compose down --rmi all
29+
```
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
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/sdk/component"
11+
12+
"github.com/smithy-security/smithy/new-components/enrichers/reachability/internal/annotation"
13+
)
14+
15+
func main() {
16+
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Minute)
17+
defer cancel()
18+
19+
if err := Main(ctx); err != nil {
20+
log.Fatalf("unexpected error: %v", err)
21+
}
22+
}
23+
24+
func Main(ctx context.Context, opts ...component.RunnerOption) error {
25+
opts = append(opts, component.RunnerWithComponentName("reachability"))
26+
27+
annotator := annotation.NewReachabilityAnnotator()
28+
29+
if err := component.RunEnricher(ctx, annotator, opts...); err != nil {
30+
return errors.Errorf("error enriching reachability annotation: %w", err)
31+
}
32+
33+
return nil
34+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
services:
2+
enricher:
3+
build:
4+
context: ../..
5+
args:
6+
- COMPONENT_PATH=enrichers/custom-annotation
7+
- COMPONENT_BINARY_SOURCE_PATH=cmd/main.go
8+
platform: linux/amd64
9+
env_file:
10+
- .env
Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
package annotation
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"log/slog"
7+
8+
"github.com/go-errors/errors"
9+
10+
"github.com/smithy-security/smithy/sdk/component"
11+
vf "github.com/smithy-security/smithy/sdk/component/vulnerability-finding"
12+
ocsf "github.com/smithy-security/smithy/sdk/gen/ocsf_schema/v1"
13+
14+
"github.com/smithy-security/smithy/new-components/enrichers/reachability/internal/atom"
15+
"github.com/smithy-security/smithy/new-components/enrichers/reachability/internal/atom/purl"
16+
"github.com/smithy-security/smithy/new-components/enrichers/reachability/internal/conf"
17+
"github.com/smithy-security/smithy/new-components/enrichers/reachability/internal/logging"
18+
"github.com/smithy-security/smithy/new-components/enrichers/reachability/internal/search"
19+
)
20+
21+
type (
22+
reachabilityAnnotator struct {
23+
cfg conf.Conf
24+
atomReader *atom.Reader
25+
annotationName string
26+
providerName string
27+
}
28+
)
29+
30+
// NewReachabilityAnnotator returns a new reachability enricher.
31+
func NewReachabilityAnnotator(cfg conf.Conf) *reachabilityAnnotator {
32+
return &reachabilityAnnotator{
33+
cfg: cfg,
34+
annotationName: "Reachable-Code",
35+
providerName: "reachability-enricher",
36+
}
37+
}
38+
39+
// Annotate adds annotated values to passed findings.
40+
func (ca *reachabilityAnnotator) Annotate(
41+
ctx context.Context,
42+
findings []*vf.VulnerabilityFinding,
43+
) ([]*vf.VulnerabilityFinding, error) {
44+
var (
45+
logger = component.LoggerFromContext(ctx).
46+
With(slog.Int("num_findings", len(findings))).
47+
With(slog.String("provider_name", ca.providerName))
48+
)
49+
50+
logger.Debug("preparing to annotate findings...")
51+
52+
purlParser, err := purl.NewParser()
53+
if err != nil {
54+
return nil, errors.Errorf("could not initialize purl parser: %w", err)
55+
}
56+
57+
ar, err := atom.NewReader(ca.cfg.ATOMFilePath, purlParser)
58+
if err != nil {
59+
return nil, errors.Errorf("could not initialize atom reader: %w", err)
60+
}
61+
ca.atomReader = ar
62+
findings, err = ca.Enrich(ctx, findings)
63+
if err != nil {
64+
return nil, errors.Errorf("could not enrich findings err: %w", err)
65+
}
66+
logger.Debug("findings annotated successfully!")
67+
return findings, nil
68+
}
69+
70+
// Enrich looks for untagged inputs and processes them outputting if any of them is reachable.
71+
// The reachability checks leverage atom - https://github.com/AppThreat/atom.
72+
func (ca *reachabilityAnnotator) Enrich(ctx context.Context, findings []*vf.VulnerabilityFinding) ([]*vf.VulnerabilityFinding, error) {
73+
var (
74+
logger = logging.FromContext(ctx).With(
75+
slog.String("atom_file_path", ca.cfg.ATOMFilePath),
76+
)
77+
)
78+
79+
logger.Debug("running enrichment step")
80+
logger.Debug("preparing to read response...")
81+
82+
logger = logger.With(slog.Int("num_tagged_resources", len(findings)))
83+
logger.Debug("preparing to read atom file...")
84+
85+
reachablesRes, err := ca.atomReader.Read(ctx)
86+
if err != nil {
87+
return nil, fmt.Errorf("could not read atom reachables from path %s: %w", ca.cfg.ATOMFilePath, err)
88+
}
89+
90+
logger = logger.With(slog.Int("num_atom_reachables", len(reachablesRes.Reachables)))
91+
logger.Debug("successfully read atom file!")
92+
logger.Debug("preparing to check for reachable purls...")
93+
94+
reachablePurls, err := ca.atomReader.ReachablePurls(ctx, reachablesRes)
95+
if err != nil {
96+
return nil, fmt.Errorf("could not get reachable purls: %w", err)
97+
}
98+
99+
logger = logger.With(slog.Int("num_reachable_purls", len(reachablePurls)))
100+
logger.Debug("successfully checked for reachable purls!")
101+
logger.Debug("preparing to create a new searcher...")
102+
103+
searcher, err := search.NewSearcher(reachablesRes.Reachables, reachablePurls)
104+
if err != nil {
105+
return nil, fmt.Errorf("could not create searcher: %w", err)
106+
}
107+
108+
logger.Debug("successfully created a new searcher!")
109+
logger.Debug("preparing to check for reachable targets...")
110+
numEnriched := 0
111+
numReachable := 0
112+
atomPurlParser, err := purl.NewParser()
113+
for idx, finding := range findings {
114+
logger := logger.With(
115+
slog.String("vendor", *finding.Finding.FindingInfo.ProductUid),
116+
slog.Any("scan_id", finding.ID),
117+
slog.Int("num_vulns", len(finding.Finding.Vulnerabilities)),
118+
)
119+
logger.Debug("preparing to enrich issues in target...")
120+
for _, vuln := range finding.Finding.Vulnerabilities {
121+
for _, pkg := range vuln.AffectedPackages {
122+
parsedPurls, err := atomPurlParser.ParsePurl(*pkg.Purl)
123+
if err != nil {
124+
logger.Error(
125+
"could not search affected package. Continuing...",
126+
slog.String("err", err.Error()))
127+
continue
128+
}
129+
var reachable bool
130+
var reachableEnrichment *ocsf.Enrichment
131+
for _, p := range parsedPurls {
132+
re, reached, err := ca.isReachable(searcher, p)
133+
if err != nil {
134+
logger.Error(
135+
"could not search affected package. Continuing...",
136+
slog.String("err", err.Error()),
137+
)
138+
continue
139+
}
140+
reachable = reached
141+
reachableEnrichment = re
142+
if reached {
143+
break
144+
}
145+
}
146+
if reachable {
147+
numReachable += 1
148+
}
149+
numEnriched += 1
150+
findings[idx].Finding.Enrichments = append(findings[idx].Finding.Enrichments, reachableEnrichment)
151+
}
152+
for _, code := range vuln.AffectedCode {
153+
reachableEnrichment, reachable, err := ca.isReachable(searcher, ca.makeCodeString(code))
154+
if err != nil {
155+
logger.Error(
156+
"could not search affected package. Continuing...",
157+
slog.String("err", err.Error()),
158+
)
159+
}
160+
if reachable {
161+
numReachable += 1
162+
}
163+
numEnriched += 1
164+
findings[idx].Finding.Enrichments = append(findings[idx].Finding.Enrichments, reachableEnrichment)
165+
}
166+
}
167+
168+
logger = logger.With(slog.Int("num_enriched_issues", numEnriched))
169+
logger = logger.With(slog.Int("num_reachable_issues", numReachable))
170+
logger.Debug("successfully enriched issues in target!")
171+
}
172+
logger.Debug("completed enrichment step!")
173+
return findings, nil
174+
}
175+
176+
func (ca *reachabilityAnnotator) makeCodeString(c *ocsf.AffectedCode) string {
177+
return fmt.Sprintf("%s:%d-%d", *c.File.Path, *c.StartLine, *c.EndLine)
178+
}
179+
180+
func (ca *reachabilityAnnotator) isReachable(searcher *search.Searcher, target string) (*ocsf.Enrichment, bool, error) {
181+
// Search.
182+
ok, err := searcher.Search(target)
183+
if err != nil {
184+
return nil, false, err
185+
}
186+
return &ocsf.Enrichment{
187+
Name: ca.annotationName,
188+
Value: fmt.Sprintf("%t", ok),
189+
Provider: &ca.providerName,
190+
}, ok, nil
191+
}

0 commit comments

Comments
 (0)