github.com/khulnasoft-lab/tunnel-db@v0.0.0-20231117205118-74e1113bd007/pkg/vulnsrc/suse-cvrf/suse-cvrf.go (about) 1 package susecvrf 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "io" 7 "log" 8 "path/filepath" 9 "strconv" 10 "strings" 11 12 bolt "go.etcd.io/bbolt" 13 "golang.org/x/xerrors" 14 15 "github.com/khulnasoft-lab/goversion/pkg/version" 16 "github.com/khulnasoft-lab/tunnel-db/pkg/db" 17 "github.com/khulnasoft-lab/tunnel-db/pkg/types" 18 "github.com/khulnasoft-lab/tunnel-db/pkg/utils" 19 "github.com/khulnasoft-lab/tunnel-db/pkg/vulnsrc/vulnerability" 20 ) 21 22 type Distribution int 23 24 const ( 25 SUSEEnterpriseLinux Distribution = iota 26 OpenSUSE 27 28 platformOpenSUSEFormat = "openSUSE Leap %s" 29 platformSUSELinuxFormat = "SUSE Linux Enterprise %s" 30 ) 31 32 var ( 33 suseDir = filepath.Join("cvrf", "suse") 34 35 source = types.DataSource{ 36 ID: vulnerability.SuseCVRF, 37 Name: "SUSE CVRF", 38 URL: "https://ftp.suse.com/pub/projects/security/cvrf/", 39 } 40 ) 41 42 type VulnSrc struct { 43 dist Distribution 44 dbc db.Operation 45 } 46 47 func NewVulnSrc(dist Distribution) VulnSrc { 48 return VulnSrc{ 49 dist: dist, 50 dbc: db.Config{}, 51 } 52 } 53 54 func (vs VulnSrc) Name() types.SourceID { 55 if vs.dist == OpenSUSE { 56 return "opensuse-cvrf" 57 } 58 return source.ID 59 } 60 61 func (vs VulnSrc) Update(dir string) error { 62 log.Println("Saving SUSE CVRF") 63 64 rootDir := filepath.Join(dir, "vuln-list", suseDir) 65 switch vs.dist { 66 case SUSEEnterpriseLinux: 67 rootDir = filepath.Join(rootDir, "suse") 68 case OpenSUSE: 69 rootDir = filepath.Join(rootDir, "opensuse") 70 default: 71 return xerrors.New("unknown distribution") 72 } 73 74 var cvrfs []SuseCvrf 75 err := utils.FileWalk(rootDir, func(r io.Reader, path string) error { 76 var cvrf SuseCvrf 77 if err := json.NewDecoder(r).Decode(&cvrf); err != nil { 78 return xerrors.Errorf("failed to decode SUSE CVRF JSON: %w %+v", err, cvrf) 79 } 80 cvrfs = append(cvrfs, cvrf) 81 return nil 82 }) 83 if err != nil { 84 return xerrors.Errorf("error in SUSE CVRF walk: %w", err) 85 } 86 87 if err = vs.save(cvrfs); err != nil { 88 return xerrors.Errorf("error in SUSE CVRF save: %w", err) 89 } 90 91 return nil 92 } 93 94 func (vs VulnSrc) save(cvrfs []SuseCvrf) error { 95 err := vs.dbc.BatchUpdate(func(tx *bolt.Tx) error { 96 return vs.commit(tx, cvrfs) 97 }) 98 if err != nil { 99 return xerrors.Errorf("error in batch update: %w", err) 100 } 101 return nil 102 } 103 104 func (vs VulnSrc) commit(tx *bolt.Tx, cvrfs []SuseCvrf) error { 105 for _, cvrf := range cvrfs { 106 affectedPkgs := getAffectedPackages(cvrf.ProductTree.Relationships) 107 if len(affectedPkgs) == 0 { 108 continue 109 } 110 111 for _, affectedPkg := range affectedPkgs { 112 advisory := types.Advisory{ 113 FixedVersion: affectedPkg.Package.FixedVersion, 114 } 115 116 if err := vs.dbc.PutDataSource(tx, affectedPkg.OSVer, source); err != nil { 117 return xerrors.Errorf("failed to put data source: %w", err) 118 } 119 120 if err := vs.dbc.PutAdvisoryDetail(tx, cvrf.Tracking.ID, affectedPkg.Package.Name, 121 []string{affectedPkg.OSVer}, advisory); err != nil { 122 return xerrors.Errorf("unable to save %s CVRF: %w", affectedPkg.OSVer, err) 123 } 124 } 125 126 var references []string 127 for _, ref := range cvrf.References { 128 references = append(references, ref.URL) 129 } 130 131 severity := types.SeverityUnknown 132 for _, cvuln := range cvrf.Vulnerabilities { 133 for _, threat := range cvuln.Threats { 134 sev := severityFromThreat(threat.Severity) 135 if severity < sev { 136 severity = sev 137 } 138 } 139 } 140 141 vuln := types.VulnerabilityDetail{ 142 References: references, 143 Title: cvrf.Title, 144 Description: getDetail(cvrf.Notes), 145 Severity: severity, 146 } 147 148 if err := vs.dbc.PutVulnerabilityDetail(tx, cvrf.Tracking.ID, source.ID, vuln); err != nil { 149 return xerrors.Errorf("failed to save SUSE CVRF vulnerability: %w", err) 150 } 151 152 // for optimization 153 if err := vs.dbc.PutVulnerabilityID(tx, cvrf.Tracking.ID); err != nil { 154 return xerrors.Errorf("failed to save the vulnerability ID: %w", err) 155 } 156 } 157 return nil 158 } 159 160 func getAffectedPackages(relationships []Relationship) []AffectedPackage { 161 var pkgs []AffectedPackage 162 for _, relationship := range relationships { 163 osVer := getOSVersion(relationship.RelatesToProductReference) 164 if osVer == "" { 165 continue 166 } 167 168 pkg := getPackage(relationship.ProductReference) 169 if pkg == nil { 170 log.Printf("invalid package name: %s", relationship.ProductReference) 171 continue 172 } 173 174 pkgs = append(pkgs, AffectedPackage{ 175 OSVer: osVer, 176 Package: *pkg, 177 }) 178 } 179 180 return pkgs 181 } 182 183 func getOSVersion(platformName string) string { 184 if strings.Contains(platformName, "SUSE Manager") { 185 // SUSE Linux Enterprise Module for SUSE Manager Server 4.0 186 return "" 187 } 188 if strings.HasPrefix(platformName, "openSUSE Leap") { 189 // openSUSE Leap 15.0 190 ss := strings.Split(platformName, " ") 191 if len(ss) < 3 { 192 log.Printf("invalid version: %s", platformName) 193 return "" 194 } 195 if _, err := version.Parse(ss[2]); err != nil { 196 log.Printf("invalid version: %s, err: %s", platformName, err) 197 return "" 198 } 199 return fmt.Sprintf(platformOpenSUSEFormat, ss[2]) 200 } 201 if strings.Contains(platformName, "SUSE Linux Enterprise") { 202 // e.g. SUSE Linux Enterprise Storage 7, SUSE Linux Enterprise Micro 5.1 203 if strings.HasPrefix(platformName, "SUSE Linux Enterprise Storage") || strings.HasPrefix(platformName, "SUSE Linux Enterprise Micro") { 204 return "" 205 } 206 207 ss := strings.Fields(strings.ReplaceAll(platformName, "-", " ")) 208 vs := make([]string, 0, 2) 209 for i := len(ss) - 1; i > 0; i-- { 210 v, err := strconv.Atoi(strings.TrimPrefix(ss[i], "SP")) 211 if err != nil { 212 continue 213 } 214 vs = append(vs, fmt.Sprintf("%d", v)) 215 if len(vs) == 2 { 216 break 217 } 218 } 219 switch len(vs) { 220 case 0: 221 log.Printf("failed to detect version: %s", platformName) 222 return "" 223 case 1: 224 return fmt.Sprintf(platformSUSELinuxFormat, vs[0]) 225 case 2: 226 return fmt.Sprintf(platformSUSELinuxFormat, fmt.Sprintf("%s.%s", vs[1], vs[0])) 227 } 228 } 229 230 return "" 231 } 232 233 func getDetail(notes []DocumentNote) string { 234 for _, n := range notes { 235 if n.Type == "General" && n.Title == "Details" { 236 return n.Text 237 } 238 } 239 return "" 240 } 241 242 func getPackage(packVer string) *Package { 243 name, version := splitPkgName(packVer) 244 return &Package{ 245 Name: name, 246 FixedVersion: version, 247 } 248 } 249 250 // reference: https://github.com/khulnasoft-lab/tunnel-db/blob/5c844be3ba6b9ef13df640857a10f8737e360feb/pkg/vulnsrc/redhat/redhat.go#L196-L217 251 func splitPkgName(pkgName string) (string, string) { 252 var version string 253 254 // Trim release 255 index := strings.LastIndex(pkgName, "-") 256 if index == -1 { 257 return "", "" 258 } 259 version = pkgName[index:] 260 pkgName = pkgName[:index] 261 262 // Trim version 263 index = strings.LastIndex(pkgName, "-") 264 if index == -1 { 265 return "", "" 266 } 267 version = pkgName[index+1:] + version 268 pkgName = pkgName[:index] 269 270 return pkgName, version 271 } 272 273 func (vs VulnSrc) Get(version string, pkgName string) ([]types.Advisory, error) { 274 var bucket string 275 switch vs.dist { 276 case SUSEEnterpriseLinux: 277 bucket = fmt.Sprintf(platformSUSELinuxFormat, version) 278 case OpenSUSE: 279 bucket = fmt.Sprintf(platformOpenSUSEFormat, version) 280 default: 281 return nil, xerrors.New("unknown distribution") 282 } 283 284 advisories, err := vs.dbc.GetAdvisories(bucket, pkgName) 285 if err != nil { 286 return nil, xerrors.Errorf("failed to get SUSE advisories: %w", err) 287 } 288 return advisories, nil 289 } 290 291 func severityFromThreat(sev string) types.Severity { 292 switch sev { 293 case "low": 294 return types.SeverityLow 295 case "moderate": 296 return types.SeverityMedium 297 case "important": 298 return types.SeverityHigh 299 case "critical": 300 return types.SeverityCritical 301 } 302 return types.SeverityUnknown 303 }