github.com/khulnasoft-lab/tunnel-db@v0.0.0-20231117205118-74e1113bd007/pkg/vulnsrc/alma/alma.go (about) 1 package alma 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "io" 7 "log" 8 "path/filepath" 9 "strings" 10 11 bolt "go.etcd.io/bbolt" 12 "golang.org/x/xerrors" 13 14 version "github.com/khulnasoft-lab/go-rpm-version" 15 "github.com/khulnasoft-lab/tunnel-db/pkg/db" 16 "github.com/khulnasoft-lab/tunnel-db/pkg/types" 17 "github.com/khulnasoft-lab/tunnel-db/pkg/utils" 18 "github.com/khulnasoft-lab/tunnel-db/pkg/vulnsrc/vulnerability" 19 ) 20 21 const ( 22 almaDir = "alma" 23 ) 24 25 var ( 26 platformFormat = "alma %s" 27 28 source = types.DataSource{ 29 ID: vulnerability.Alma, 30 Name: "AlmaLinux Product Errata", 31 URL: "https://errata.almalinux.org/", 32 } 33 ) 34 35 type PutInput struct { 36 PlatformName string 37 CveID string 38 Vuln types.VulnerabilityDetail 39 Advisories map[string]types.Advisory 40 Erratum Erratum // for extensibility, not used in tunnel-db 41 } 42 43 type DB interface { 44 db.Operation 45 Put(*bolt.Tx, PutInput) error 46 Get(release, pkgName string) ([]types.Advisory, error) 47 } 48 49 type VulnSrc struct { 50 DB // Those who want to customize Tunnel DB can override put/get methods. 51 } 52 53 // Alma implements the DB interface 54 type Alma struct { 55 db.Operation 56 } 57 58 func NewVulnSrc() *VulnSrc { 59 return &VulnSrc{ 60 DB: &Alma{Operation: db.Config{}}, 61 } 62 } 63 64 func (vs *VulnSrc) Name() types.SourceID { 65 return source.ID 66 } 67 68 func (vs *VulnSrc) Update(dir string) error { 69 rootDir := filepath.Join(dir, "vuln-list", almaDir) 70 errata, err := vs.parse(rootDir) 71 if err != nil { 72 return err 73 } 74 if err = vs.put(errata); err != nil { 75 return xerrors.Errorf("error in Alma save: %w", err) 76 } 77 78 return nil 79 } 80 81 // parse parses all the advisories from Alma Linux. 82 func (vs *VulnSrc) parse(rootDir string) (map[string][]Erratum, error) { 83 errata := map[string][]Erratum{} 84 err := utils.FileWalk(rootDir, func(r io.Reader, path string) error { 85 var erratum Erratum 86 if err := json.NewDecoder(r).Decode(&erratum); err != nil { 87 return xerrors.Errorf("failed to decode Alma erratum: %w", err) 88 } 89 90 dirs := strings.Split(path, string(filepath.Separator)) 91 if len(dirs) < 3 { 92 log.Printf("invalid path: %s\n", path) 93 return nil 94 } 95 96 majorVer := dirs[len(dirs)-3] 97 errata[majorVer] = append(errata[majorVer], erratum) 98 return nil 99 }) 100 if err != nil { 101 return nil, xerrors.Errorf("error in Alma walk: %w", err) 102 } 103 104 return errata, nil 105 } 106 107 func (vs *VulnSrc) put(errataVer map[string][]Erratum) error { 108 err := vs.BatchUpdate(func(tx *bolt.Tx) error { 109 for majorVer, errata := range errataVer { 110 platformName := fmt.Sprintf(platformFormat, majorVer) 111 if err := vs.PutDataSource(tx, platformName, source); err != nil { 112 return xerrors.Errorf("failed to put data source: %w", err) 113 } 114 115 if err := vs.commit(tx, platformName, errata); err != nil { 116 return xerrors.Errorf("Alma %s commit error: %w", majorVer, err) 117 } 118 } 119 return nil 120 }) 121 if err != nil { 122 return xerrors.Errorf("error in db batch update: %w", err) 123 } 124 return nil 125 } 126 127 func (vs *VulnSrc) commit(tx *bolt.Tx, platformName string, errata []Erratum) error { 128 for _, erratum := range errata { 129 var references []string 130 for _, ref := range erratum.References { 131 if ref.Type != "cve" { 132 references = append(references, ref.Href) 133 } 134 } 135 136 for _, ref := range erratum.References { 137 if ref.Type != "cve" { 138 continue 139 } 140 141 // We need to work around this issue for now. 142 // https://github.com/khulnasoft-lab/fanal/issues/186#issuecomment-931523102 143 advisories := map[string]types.Advisory{} 144 145 cveID := ref.ID 146 for _, pkg := range erratum.Pkglist.Packages { 147 if pkg.Arch != "noarch" && pkg.Arch != "x86_64" { 148 continue 149 } 150 151 pkgName := pkg.Name 152 if erratum.Pkglist.Module.Name != "" && erratum.Pkglist.Module.Stream != "" { 153 pkgName = fmt.Sprintf("%s:%s::%s", erratum.Pkglist.Module.Name, erratum.Pkglist.Module.Stream, pkg.Name) 154 } 155 156 advisory := types.Advisory{ 157 FixedVersion: utils.ConstructVersion(pkg.Epoch, pkg.Version, pkg.Release), 158 } 159 160 if adv, ok := advisories[pkgName]; ok { 161 if version.NewVersion(advisory.FixedVersion).LessThan(version.NewVersion(adv.FixedVersion)) { 162 advisories[pkgName] = advisory 163 } 164 } else { 165 advisories[pkgName] = advisory 166 } 167 } 168 169 vuln := types.VulnerabilityDetail{ 170 Severity: generalizeSeverity(erratum.Severity), 171 Title: erratum.Title, 172 Description: erratum.Description, 173 References: references, 174 } 175 176 err := vs.Put(tx, PutInput{ 177 PlatformName: platformName, 178 CveID: cveID, 179 Vuln: vuln, 180 Advisories: advisories, 181 Erratum: erratum, 182 }) 183 if err != nil { 184 return xerrors.Errorf("db put error: %w", err) 185 } 186 } 187 } 188 return nil 189 } 190 191 func (a *Alma) Put(tx *bolt.Tx, input PutInput) error { 192 if err := a.PutVulnerabilityDetail(tx, input.CveID, source.ID, input.Vuln); err != nil { 193 return xerrors.Errorf("failed to save Alma vulnerability: %w", err) 194 } 195 196 // for optimization 197 if err := a.PutVulnerabilityID(tx, input.CveID); err != nil { 198 return xerrors.Errorf("failed to save the vulnerability ID: %w", err) 199 } 200 201 for pkgName, advisory := range input.Advisories { 202 if err := a.PutAdvisoryDetail(tx, input.CveID, pkgName, []string{input.PlatformName}, advisory); err != nil { 203 return xerrors.Errorf("failed to save Alma advisory: %w", err) 204 } 205 } 206 return nil 207 } 208 209 func (a *Alma) Get(release, pkgName string) ([]types.Advisory, error) { 210 bucket := fmt.Sprintf(platformFormat, release) 211 advisories, err := a.GetAdvisories(bucket, pkgName) 212 if err != nil { 213 return nil, xerrors.Errorf("failed to get Alma advisories: %w", err) 214 } 215 return advisories, nil 216 } 217 218 func generalizeSeverity(severity string) types.Severity { 219 switch strings.ToLower(severity) { 220 case "low": 221 return types.SeverityLow 222 case "moderate": 223 return types.SeverityMedium 224 case "important": 225 return types.SeverityHigh 226 case "critical": 227 return types.SeverityCritical 228 } 229 return types.SeverityUnknown 230 }