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  }