|
| 1 | +package remediation |
| 2 | + |
| 3 | +import ( |
| 4 | + "fmt" |
| 5 | + |
| 6 | + "github.com/CycloneDX/cyclonedx-go" |
| 7 | + |
| 8 | + "github.com/jfrog/jfrog-client-go/utils/log" |
| 9 | + "github.com/jfrog/jfrog-client-go/xray" |
| 10 | + "github.com/jfrog/jfrog-client-go/xray/services/utils" |
| 11 | + |
| 12 | + "github.com/jfrog/jfrog-cli-security/utils/formats/cdxutils" |
| 13 | +) |
| 14 | + |
| 15 | +func AttachFixedVersionsToVulnerabilities(xrayManager *xray.XrayServicesManager, bom *cyclonedx.BOM) error { |
| 16 | + if bom.Vulnerabilities == nil || len(*bom.Vulnerabilities) == 0 { |
| 17 | + log.Debug("No vulnerabilities found in the SBOM, skipping attaching fixed versions") |
| 18 | + return nil |
| 19 | + } |
| 20 | + // Remediate the CVE by forcing the transitive dependency to a specific fix-version |
| 21 | + remediationOptions, err := xrayManager.RemediationByCve(bom) |
| 22 | + if err != nil { |
| 23 | + return fmt.Errorf("failed to get remediation options from Xray: %w", err) |
| 24 | + } |
| 25 | + log.Verbose(fmt.Sprintf("Remediation options received from Xray: %+v", remediationOptions)) |
| 26 | + for _, vulnerability := range *bom.Vulnerabilities { |
| 27 | + matchVulnerabilityToRemediationOptions(bom, &vulnerability, remediationOptions) |
| 28 | + } |
| 29 | + return nil |
| 30 | +} |
| 31 | + |
| 32 | +func matchVulnerabilityToRemediationOptions(bom *cyclonedx.BOM, vulnerability *cyclonedx.Vulnerability, remediationOptions utils.CveRemediationResponse) { |
| 33 | + if vulnerability.Affects == nil || len(*vulnerability.Affects) == 0 { |
| 34 | + log.Debug("No affected components found for vulnerability " + vulnerability.ID + ", skipping attaching fixed versions") |
| 35 | + return |
| 36 | + } |
| 37 | + if cveRemediationOptions, found := remediationOptions[vulnerability.ID]; found { |
| 38 | + for i, affect := range *vulnerability.Affects { |
| 39 | + // Lets find the remediation for this specific component |
| 40 | + affectComponent := cdxutils.SearchComponentByRef(bom.Components, affect.Ref) |
| 41 | + if affectComponent == nil { |
| 42 | + log.Debug("Affected component " + affect.Ref + " not found in BOM components, skipping attaching fixed versions for vulnerability " + vulnerability.ID) |
| 43 | + continue |
| 44 | + } |
| 45 | + // Convert remediation steps to fixed versions affected versions |
| 46 | + for _, step := range getAffectComponentCveRemediationStepsByFixedVersion(vulnerability.ID, *affectComponent, cveRemediationOptions) { |
| 47 | + cdxutils.AppendAffectedVersionsIfNotExists(&affect, cyclonedx.AffectedVersions{ |
| 48 | + Version: step.UpgradeTo.Version, |
| 49 | + Status: cyclonedx.VulnerabilityStatusNotAffected, |
| 50 | + }) |
| 51 | + } |
| 52 | + (*vulnerability.Affects)[i] = affect |
| 53 | + } |
| 54 | + } else { |
| 55 | + log.Debug("No remediation options found for vulnerability " + vulnerability.ID) |
| 56 | + } |
| 57 | +} |
| 58 | + |
| 59 | +func getAffectComponentCveRemediationStepsByFixedVersion(cve string, component cyclonedx.Component, cveRemediationOptions []utils.Option) (steps []utils.OptionStep) { |
| 60 | + for _, cveRemediationOption := range cveRemediationOptions { |
| 61 | + if cveRemediationOption.Type != utils.InLock { |
| 62 | + // We only want InLock remediation type (forcing the actual component to a specific fix version) |
| 63 | + continue |
| 64 | + } |
| 65 | + for _, step := range cveRemediationOption.Steps { |
| 66 | + if step.StepType == utils.NoFixVersion { |
| 67 | + log.Debug(fmt.Sprintf("No fix version available for component '%s' in vulnerability '%s'", component.Name, cve)) |
| 68 | + continue |
| 69 | + } else if step.StepType == utils.PackageNotFound { |
| 70 | + log.Debug(fmt.Sprintf("Component '%s' not found in catalog for vulnerability '%s'", component.Name, cve)) |
| 71 | + continue |
| 72 | + } |
| 73 | + // We only want FixVersion step type |
| 74 | + if step.StepType == utils.FixVersion && step.PkgVersion.Name == component.Name && step.PkgVersion.Version == component.Version { |
| 75 | + steps = append(steps, step) |
| 76 | + } |
| 77 | + } |
| 78 | + } |
| 79 | + if len(steps) == 0 { |
| 80 | + log.Debug(fmt.Sprintf("No remediation steps by forcing fixed version found for component '%s' in vulnerability '%s'", component.Name, cve)) |
| 81 | + } |
| 82 | + return |
| 83 | +} |
0 commit comments