Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(guided remediation): add override strategy #511

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions internal/guidedremediation/lockfile/lockfile.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,13 @@ package lockfile
import (
"deps.dev/util/resolve"
scalibrfs "github.com/google/osv-scalibr/fs"
"github.com/google/osv-scalibr/internal/guidedremediation/remediation/strategy"
)

// ReadWriter is the interface for parsing and applying remediation patches to a lockfile.
type ReadWriter interface {
System() resolve.System
Read(path string, fsys scalibrfs.FS) (*resolve.Graph, error)
SupportedStrategies() []strategy.Strategy
// TODO(#454): Write()
}
6 changes: 6 additions & 0 deletions internal/guidedremediation/lockfile/npm/packagelockjson.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (
"github.com/google/osv-scalibr/internal/dependencyfile/packagelockjson"
"github.com/google/osv-scalibr/internal/guidedremediation/lockfile"
"github.com/google/osv-scalibr/internal/guidedremediation/manifest/npm"
"github.com/google/osv-scalibr/internal/guidedremediation/remediation/strategy"
"github.com/google/osv-scalibr/log"
)

Expand All @@ -42,6 +43,11 @@ func (r readWriter) System() resolve.System {
return resolve.NPM
}

// SupportedStrategies returns the remediation strategies supported for this lockfile.
func (r readWriter) SupportedStrategies() []strategy.Strategy {
return []strategy.Strategy{}
}

type dependencyVersionSpec struct {
Version string
DepType dep.Type
Expand Down
4 changes: 4 additions & 0 deletions internal/guidedremediation/manifest/manifest.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package manifest
import (
"deps.dev/util/resolve"
scalibrfs "github.com/google/osv-scalibr/fs"
"github.com/google/osv-scalibr/internal/guidedremediation/remediation/strategy"
)

// Manifest is the interface for the representation of a manifest file needed for dependency resolution.
Expand All @@ -30,6 +31,8 @@ type Manifest interface {
LocalManifests() []Manifest // Manifests of local packages
EcosystemSpecific() any // Any ecosystem-specific information needed

PatchRequirement(req resolve.RequirementVersion) error // Patch the requirements to use new requirement.

Clone() Manifest // Clone the manifest
}

Expand All @@ -41,5 +44,6 @@ type RequirementKey any
type ReadWriter interface {
System() resolve.System
Read(path string, fsys scalibrfs.FS) (Manifest, error)
SupportedStrategies() []strategy.Strategy
// TODO(#454): Write()
}
36 changes: 36 additions & 0 deletions internal/guidedremediation/manifest/maven/pomxml.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import (
"github.com/google/osv-scalibr/extractor/filesystem"
scalibrfs "github.com/google/osv-scalibr/fs"
"github.com/google/osv-scalibr/internal/guidedremediation/manifest"
"github.com/google/osv-scalibr/internal/guidedremediation/remediation/strategy"
"github.com/google/osv-scalibr/internal/mavenutil"
)

Expand Down Expand Up @@ -138,6 +139,36 @@ func (m *mavenManifest) Clone() manifest.Manifest {
return clone
}

// PatchRequirement modifies the manifest's requirements to include the new requirement version.
// If the package already is in the requirements, updates the version.
// Otherwise, adds req to the dependencyManagement of the root pom.xml.
func (m *mavenManifest) PatchRequirement(req resolve.RequirementVersion) error {
found := false
i := 0
for _, r := range m.requirements {
if r.PackageKey != req.PackageKey {
m.requirements[i] = r
i++

continue
}
origin, hasOrigin := r.Type.GetAttr(dep.MavenDependencyOrigin)
if !hasOrigin || origin == mavenutil.OriginManagement {
found = true
r.Version = req.Version
m.requirements[i] = r
i++
}
}
m.requirements = m.requirements[:i]
if !found {
req.Type.AddAttr(dep.MavenDependencyOrigin, mavenutil.OriginManagement)
m.requirements = append(m.requirements, req)
}

return nil
}

type readWriter struct {
*datasource.MavenRegistryAPIClient
}
Expand All @@ -156,6 +187,11 @@ func (r readWriter) System() resolve.System {
return resolve.Maven
}

// SupportedStrategies returns the remediation strategies supported for this manifest.
func (r readWriter) SupportedStrategies() []strategy.Strategy {
return []strategy.Strategy{strategy.StrategyOverride}
}

// Read parses the manifest from the given file.
func (r readWriter) Read(path string, fsys scalibrfs.FS) (manifest.Manifest, error) {
// TODO(#472): much of this logic is duplicated with the pomxmlnet extractor.
Expand Down
12 changes: 12 additions & 0 deletions internal/guidedremediation/manifest/npm/packagejson.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (
"deps.dev/util/resolve/dep"
scalibrfs "github.com/google/osv-scalibr/fs"
"github.com/google/osv-scalibr/internal/guidedremediation/manifest"
"github.com/google/osv-scalibr/internal/guidedremediation/remediation/strategy"
"github.com/google/osv-scalibr/log"
)

Expand Down Expand Up @@ -114,6 +115,12 @@ func (m *npmManifest) Clone() manifest.Manifest {
return clone
}

// PatchRequirement modifies the manifest's requirements to include the new requirement version.
func (m *npmManifest) PatchRequirement(req resolve.RequirementVersion) error {
// TODO(#454)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you add details about what needs to be done?

return nil
}

type readWriter struct{}

// GetReadWriter returns a ReadWriter for package.json manifest files.
Expand All @@ -127,6 +134,11 @@ func (r readWriter) System() resolve.System {
return resolve.NPM
}

// SupportedStrategies returns the remediation strategies supported for this manifest.
func (r readWriter) SupportedStrategies() []strategy.Strategy {
return []strategy.Strategy{}
}

// PackageJSON is the structure for the contents of a package.json file.
type PackageJSON struct {
Name string `json:"name"`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,10 @@ package matchertest
import (
"context"
"os"
"slices"
"testing"

"deps.dev/util/resolve"
"github.com/google/osv-scalibr/extractor"
"github.com/google/osv-scalibr/internal/guidedremediation/vulns"
"github.com/ossf/osv-schema/bindings/go/osvschema"
"gopkg.in/yaml.v3"
)
Expand All @@ -35,7 +34,7 @@ func (mvc MockVulnerabilityMatcher) MatchVulnerabilities(ctx context.Context, in
result := make([][]*osvschema.Vulnerability, len(invs))
for i, inv := range invs {
for _, vuln := range mvc {
if vulnAffectsInv(vuln, inv) {
if vulns.IsAffected(vuln, inv) {
result[i] = append(result[i], vuln)
}
}
Expand Down Expand Up @@ -64,72 +63,3 @@ func NewMockVulnerabilityMatcher(t *testing.T, vulnsYAML string) MockVulnerabili
}
return MockVulnerabilityMatcher(vulns.Vulns)
}

// TODO(#454): similar logic will need to be used elsewhere in guided remediation.
func vulnAffectsInv(vuln *osvschema.Vulnerability, inv *extractor.Inventory) bool {
resolveSys, ok := inv.Metadata.(resolve.System)
if !ok {
return false
}
sys := resolveSys.Semver()
for _, affected := range vuln.Affected {
if affected.Package.Ecosystem != inv.Ecosystem() ||
affected.Package.Name != inv.Name {
continue
}
if slices.Contains(affected.Versions, inv.Version) {
return true
}
for _, r := range affected.Ranges {
if r.Type != "ECOSYSTEM" &&
!(r.Type == "SEMVER" && affected.Package.Ecosystem == "npm") {
continue
}
events := slices.Clone(r.Events)
eventVersion := func(e osvschema.Event) string {
if e.Introduced != "" {
return e.Introduced
}
if e.Fixed != "" {
return e.Fixed
}
return e.LastAffected
}
slices.SortFunc(events, func(a, b osvschema.Event) int {
aVer := eventVersion(a)
bVer := eventVersion(b)
if aVer == "0" {
if bVer == "0" {
return 0
}
return -1
}
if bVer == "0" {
return 1
}
// sys.Compare on strings is expensive, should consider precomputing sys.Parse
return sys.Compare(aVer, bVer)
})
idx, exact := slices.BinarySearchFunc(events, inv.Version, func(e osvschema.Event, v string) int {
eVer := eventVersion(e)
if eVer == "0" {
return -1
}
return sys.Compare(eVer, v)
})
if exact {
e := events[idx]
// Version is exactly on a range-inclusive event
if e.Introduced != "" || e.LastAffected != "" {
return true
}
} else {
// Version is between events, only match if previous event is Introduced
if idx != 0 && events[idx-1].Introduced != "" {
return true
}
}
}
}
return false
}
Loading
Loading