Skip to content

Commit 6aab02b

Browse files
CalamarBicefalopaulrosca-snyk
authored andcommitted
wip
1 parent 40d59d1 commit 6aab02b

File tree

5 files changed

+680
-237
lines changed

5 files changed

+680
-237
lines changed

internal/remediation/model.go

Lines changed: 41 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,26 +2,36 @@ package remediation
22

33
// Input
44

5+
/**
6+
* Finding represents a vulnerability for a given package & version.
7+
*
8+
* If a finding is introduced through multiple paths, a single finding with several `DependencyPath` entries must be constructed.
9+
*/
510
type Finding struct {
6-
Package Package
7-
Severity Severity
8-
Title string
9-
DependencyPath DependencyPath
10-
11-
// Snyk vuln ID
12-
VulnId string
11+
Package Package
12+
Vulnerability Vulnerability
13+
DependencyPaths []DependencyPath
1314

1415
// All versions containing a fix for this problem
1516
InitiallyFixedInVersions []string
1617
Fix Fix
18+
PackageManager PackageManager
1719
}
20+
21+
type PackageManager string
22+
23+
type Outcome string
1824
type Fix interface {
1925
Outcome() Outcome
2026
}
2127

2228
type PinFix struct {
2329
outcome Outcome
24-
pinAction PinAction
30+
PinAction PinAction
31+
}
32+
33+
type PinAction struct {
34+
Package Package
2535
}
2636

2737
func (pf PinFix) Outcome() Outcome {
@@ -31,22 +41,37 @@ func (pf PinFix) Outcome() Outcome {
3141
func NewPinFix(outcome Outcome, action PinAction) Fix {
3242
return PinFix{
3343
outcome: outcome,
34-
pinAction: action,
44+
PinAction: action,
3545
}
3646
}
3747

38-
type Outcome string
48+
type UpgradeFix struct {
49+
outcome Outcome
50+
UpgradeAction UpgradeAction
51+
}
52+
53+
type UpgradeAction struct {
54+
PackageName string
55+
UpgradePaths []DependencyPath
56+
}
57+
58+
func (uf UpgradeFix) Outcome() Outcome {
59+
return uf.outcome
60+
}
61+
62+
func NewUpgradeFix(outcome Outcome, action UpgradeAction) Fix {
63+
return UpgradeFix{
64+
outcome: outcome,
65+
UpgradeAction: action,
66+
}
67+
}
3968

4069
const (
4170
FullyResolved Outcome = "fully-resolved"
4271
PartiallyResolved Outcome = "partially-resolved"
4372
Unresolved Outcome = "unresolved"
4473
)
4574

46-
type PinAction struct {
47-
Package Package
48-
}
49-
5075
// Output
5176

5277
/*
@@ -75,7 +100,7 @@ type Upgrade struct {
75100
From Package
76101
To Package
77102

78-
Fixes []*VulnerabilityInPackage
103+
Fixes []VulnerabilityInPackage
79104
}
80105

81106
type VulnerabilityInPackage struct {
@@ -89,6 +114,7 @@ type Package struct {
89114
Name string
90115
Version string
91116
}
117+
92118
type Vulnerability struct {
93119
ID string
94120
Name string
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
package remediation
2+
3+
import (
4+
"github.com/snyk/cli-extension-os-flows/internal/semver"
5+
)
6+
7+
func FixesToRemediationSummary(findings []Finding) (Summary, error) {
8+
pins, err := calculatePins(findings)
9+
if err != nil {
10+
return Summary{}, err
11+
}
12+
upgrades, err := calculateUpgrades(findings)
13+
if err != nil {
14+
return Summary{}, err
15+
}
16+
return Summary{Pins: pins, Upgrades: upgrades}, err
17+
}
18+
19+
func calculateUpgrades(findings []Finding) ([]Upgrade, error) {
20+
var output []Upgrade
21+
for _, finding := range findings {
22+
switch finding.Fix.(type) {
23+
case UpgradeFix:
24+
for _, depPath := range finding.DependencyPaths {
25+
for _, upath := range finding.Fix.(UpgradeFix).UpgradeAction.UpgradePaths {
26+
valid := true
27+
for i, uDep := range upath {
28+
if uDep.Name != depPath[i+1].Name {
29+
valid = false
30+
}
31+
}
32+
if valid {
33+
output = append(output, Upgrade{
34+
From: depPath[1],
35+
To: upath[0],
36+
Fixes: []VulnerabilityInPackage{
37+
{
38+
VulnerablePackage: finding.Package,
39+
Vulnerability: finding.Vulnerability,
40+
IntroducedThrough: finding.DependencyPaths,
41+
},
42+
},
43+
})
44+
}
45+
}
46+
}
47+
default:
48+
continue
49+
}
50+
}
51+
return output, nil
52+
}
53+
54+
func calculatePins(findings []Finding) ([]Upgrade, error) {
55+
var output []Upgrade
56+
var pinMap = make(map[string]*[]*Upgrade)
57+
for _, finding := range findings {
58+
switch finding.Fix.(type) {
59+
case PinFix:
60+
vulnerablePackage := finding.Package
61+
key := finding.Package.Name
62+
63+
// We'll be grouping by package name
64+
upgrade, upgradeExists := pinMap[key]
65+
66+
vulnerabilityInPackage := VulnerabilityInPackage{
67+
VulnerablePackage: vulnerablePackage,
68+
Vulnerability: finding.Vulnerability,
69+
IntroducedThrough: finding.DependencyPaths,
70+
}
71+
72+
if upgradeExists {
73+
highestVersion, err := getMaxVersion(finding.PackageManager, (*upgrade)[0].To.Version, finding.Fix.(PinFix).PinAction.Package.Version)
74+
if err != nil {
75+
return nil, err
76+
}
77+
78+
// As we process individual pins, we'll get the highest version of a given package across vulns & package version
79+
// Goal is to obtain a single pin that fixes as many problems as possible
80+
versionAlreadyExists := false
81+
for _, u := range *upgrade {
82+
u.To.Version = highestVersion
83+
if u.From.Version == finding.Package.Version {
84+
u.Fixes = append(u.Fixes, vulnerabilityInPackage)
85+
versionAlreadyExists = true
86+
break
87+
}
88+
}
89+
if !versionAlreadyExists {
90+
*upgrade = append(*upgrade, &Upgrade{
91+
From: vulnerablePackage,
92+
To: finding.Fix.(PinFix).PinAction.Package,
93+
Fixes: []VulnerabilityInPackage{vulnerabilityInPackage},
94+
})
95+
}
96+
} else {
97+
pinMap[key] = &[]*Upgrade{{
98+
From: vulnerablePackage,
99+
To: finding.Fix.(PinFix).PinAction.Package,
100+
Fixes: []VulnerabilityInPackage{vulnerabilityInPackage},
101+
}}
102+
}
103+
104+
default:
105+
continue
106+
}
107+
}
108+
109+
for _, versionGroupedPins := range pinMap {
110+
for _, pin := range *versionGroupedPins {
111+
output = append(output, *pin)
112+
}
113+
}
114+
return output, nil
115+
}
116+
117+
func getMaxVersion(packageManager PackageManager, v1 string, v2 string) (string, error) {
118+
semverResolver, err := semver.GetSemver(string(packageManager))
119+
if err != nil {
120+
return "", err
121+
}
122+
var version string
123+
compare, err := semverResolver.Compare(v1, v2)
124+
if err != nil {
125+
return "", err
126+
}
127+
if compare >= 0 {
128+
version = v1
129+
} else {
130+
version = v2
131+
}
132+
return version, nil
133+
}

0 commit comments

Comments
 (0)