github.com/google/osv-scalibr@v0.4.1/guidedremediation/internal/resolution/resolve.go (about) 1 // Copyright 2025 Google LLC 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 // Package resolution provides dependency graph resolution and vulnerability findings 16 // for guided remediation. 17 package resolution 18 19 import ( 20 "context" 21 "errors" 22 "fmt" 23 "slices" 24 25 "deps.dev/util/resolve" 26 "deps.dev/util/resolve/dep" 27 mavenresolve "deps.dev/util/resolve/maven" 28 npmresolve "deps.dev/util/resolve/npm" 29 pypiresolve "deps.dev/util/resolve/pypi" 30 client "github.com/google/osv-scalibr/clients/resolution" 31 "github.com/google/osv-scalibr/guidedremediation/internal/manifest" 32 "github.com/google/osv-scalibr/guidedremediation/internal/manifest/maven" 33 "github.com/google/osv-scalibr/guidedremediation/options" 34 "github.com/google/osv-scalibr/internal/mavenutil" 35 ) 36 37 // Resolve resolves the dependencies in the manifest using the provided client. 38 func Resolve(ctx context.Context, c resolve.Client, m manifest.Manifest, opts options.ResolutionOptions) (*resolve.Graph, error) { 39 // Insert the manifest dependency into the client 40 cl := client.NewOverrideClient(c) 41 cl.AddVersion(m.Root(), m.Requirements()) 42 for _, lm := range m.LocalManifests() { 43 cl.AddVersion(lm.Root(), lm.Requirements()) 44 } 45 46 var r resolve.Resolver 47 sys := m.System() 48 switch sys { 49 case resolve.Maven: 50 r = mavenresolve.NewResolver(cl) 51 case resolve.NPM: 52 r = npmresolve.NewResolver(cl) 53 case resolve.PyPI: 54 r = pypiresolve.NewResolver(cl) 55 case resolve.UnknownSystem: 56 fallthrough 57 default: 58 return nil, fmt.Errorf("no resolver for ecosystem %v", sys) 59 } 60 61 graph, err := r.Resolve(ctx, m.Root().VersionKey) 62 if err != nil { 63 return nil, fmt.Errorf("error resolving manifest dependencies: %w", err) 64 } 65 66 if graph.Error != "" { 67 return nil, fmt.Errorf("manifest resolved with error: %s", graph.Error) 68 } 69 70 return resolvePostProcess(ctx, cl, m, opts, graph) 71 } 72 73 func resolvePostProcess(ctx context.Context, cl resolve.Client, m manifest.Manifest, opts options.ResolutionOptions, graph *resolve.Graph) (*resolve.Graph, error) { 74 if m.System() == resolve.Maven && opts.MavenManagement { 75 // Add a node & edge for each dependency in dependencyManagement that doesn't already appear in the resolved graph 76 manifestSpecific, ok := m.EcosystemSpecific().(maven.ManifestSpecific) 77 if !ok { 78 return graph, errors.New("invalid maven ManifestSpecific data") 79 } 80 81 // Search through OriginalRequirements management dependencies in this pom only (not parents). 82 for _, req := range manifestSpecific.OriginalRequirements { 83 if req.Origin != mavenutil.OriginManagement { 84 // TODO(#463): also check management in activated profiles and dependencies in inactive profiles. 85 continue 86 } 87 88 // Unique identifier for this package. 89 reqKey := MakeRequirementKey(resolve.RequirementVersion{ 90 VersionKey: resolve.VersionKey{ 91 PackageKey: resolve.PackageKey{ 92 System: resolve.Maven, 93 Name: req.Name(), 94 }, 95 VersionType: resolve.Requirement, 96 Version: string(req.Version), 97 }, 98 Type: resolve.MavenDepType(req.Dependency, req.Origin), 99 }) 100 101 // Find the current version of the dependencyManagement dependency, after property interpolation & changes from remediation. 102 requirements := m.Requirements() 103 idx := slices.IndexFunc(requirements, func(rv resolve.RequirementVersion) bool { 104 if origin, _ := rv.Type.GetAttr(dep.MavenDependencyOrigin); origin != mavenutil.OriginManagement { 105 return false 106 } 107 108 return reqKey == MakeRequirementKey(rv) 109 }) 110 111 if idx == -1 { 112 // Ideally, this would be an error, but there a few known instances where this lookup fails: 113 // 1. The artifact name contain a property (properties aren't substituted in OriginalRequirements, but are in Manifest.Requirements) 114 // 2. Missing properties (due to e.g. un-activated profiles) cause the dependency to be invalid, and therefore excluded from Manifest.Requirements. 115 // Ignore these dependencies in these cases so that we can still remediation vulns in the other packages. 116 continue 117 } 118 119 rv := requirements[idx] 120 121 // See if the package is already in the resolved graph. 122 // Check the edges so we can make sure the ArtifactTypes and Classifiers match. 123 if !slices.ContainsFunc(graph.Edges, func(e resolve.Edge) bool { 124 return reqKey == MakeRequirementKey(resolve.RequirementVersion{ 125 VersionKey: graph.Nodes[e.To].Version, 126 Type: e.Type, 127 }) 128 }) { 129 // Management dependency not in graph - create the node. 130 // Find the version the management requirement would resolve to. 131 // First assume it's a soft requirement. 132 vk := rv.VersionKey 133 vk.VersionType = resolve.Concrete 134 if _, err := cl.Version(ctx, vk); err != nil { 135 // Not a soft requirement - try find a match. 136 vk.VersionType = resolve.Requirement 137 vks, err := cl.MatchingVersions(ctx, vk) 138 if err != nil || len(vks) == 0 { 139 err = graph.AddError(0, vk, fmt.Sprintf("could not find a version that satisfies requirement %s for package %s", vk.Version, vk.Name)) 140 if err != nil { 141 return nil, err 142 } 143 144 continue 145 } 146 vk = vks[len(vks)-1].VersionKey 147 } 148 // Add the node & and edge from the root. 149 nID := graph.AddNode(vk) 150 if err := graph.AddEdge(0, nID, rv.Version, rv.Type.Clone()); err != nil { 151 return nil, err 152 } 153 } 154 } 155 } 156 157 return graph, nil 158 }