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  }