github.com/khulnasoft-lab/tunnel-db@v0.0.0-20231117205118-74e1113bd007/pkg/vulnsrc/bundler/bundler.go (about)

     1  package bundler
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  	"path/filepath"
     7  	"strings"
     8  
     9  	bolt "go.etcd.io/bbolt"
    10  	"golang.org/x/xerrors"
    11  	"gopkg.in/yaml.v2"
    12  
    13  	"github.com/khulnasoft-lab/tunnel-db/pkg/db"
    14  	"github.com/khulnasoft-lab/tunnel-db/pkg/types"
    15  	"github.com/khulnasoft-lab/tunnel-db/pkg/vulnsrc/bucket"
    16  	"github.com/khulnasoft-lab/tunnel-db/pkg/vulnsrc/vulnerability"
    17  )
    18  
    19  const bundlerDir = "ruby-advisory-db"
    20  
    21  var (
    22  	source = types.DataSource{
    23  		ID:   vulnerability.RubySec,
    24  		Name: "Ruby Advisory Database",
    25  		URL:  "https://github.com/rubysec/ruby-advisory-db",
    26  	}
    27  
    28  	bucketName = bucket.Name(vulnerability.RubyGems, source.Name)
    29  )
    30  
    31  type RawAdvisory struct {
    32  	Gem                string
    33  	Cve                string
    34  	Osvdb              string
    35  	Ghsa               string
    36  	Title              string
    37  	Url                string
    38  	Description        string
    39  	CvssV2             float64  `yaml:"cvss_v2"`
    40  	CvssV3             float64  `yaml:"cvss_v3"`
    41  	PatchedVersions    []string `yaml:"patched_versions"`
    42  	UnaffectedVersions []string `yaml:"unaffected_versions"`
    43  	Related            Related
    44  }
    45  
    46  type Advisory struct {
    47  	VulnerabilityID    string   `json:",omitempty"`
    48  	PatchedVersions    []string `json:",omitempty"`
    49  	UnaffectedVersions []string `json:",omitempty"`
    50  }
    51  
    52  type Related struct {
    53  	Cve []string
    54  	Url []string
    55  }
    56  
    57  type VulnSrc struct {
    58  	dbc db.Operation
    59  }
    60  
    61  func NewVulnSrc() VulnSrc {
    62  	return VulnSrc{
    63  		dbc: db.Config{},
    64  	}
    65  }
    66  
    67  func (vs VulnSrc) Name() types.SourceID {
    68  	return source.ID
    69  }
    70  
    71  func (vs VulnSrc) Update(dir string) error {
    72  	repoPath := filepath.Join(dir, bundlerDir)
    73  	if err := vs.update(repoPath); err != nil {
    74  		return xerrors.Errorf("failed to update bundler vulnerabilities: %w", err)
    75  	}
    76  	return nil
    77  }
    78  
    79  func (vs VulnSrc) update(repoPath string) error {
    80  	root := filepath.Join(repoPath, "gems")
    81  
    82  	err := vs.dbc.BatchUpdate(func(tx *bolt.Tx) error {
    83  		if err := vs.dbc.PutDataSource(tx, bucketName, source); err != nil {
    84  			return xerrors.Errorf("failed to put data source: %w", err)
    85  		}
    86  
    87  		if err := vs.walk(tx, root); err != nil {
    88  			return xerrors.Errorf("failed to walk ruby advisories: %w", err)
    89  		}
    90  		return nil
    91  	})
    92  	if err != nil {
    93  		return xerrors.Errorf("batch update failed: %w", err)
    94  	}
    95  	return nil
    96  }
    97  
    98  func (vs VulnSrc) walk(tx *bolt.Tx, root string) error {
    99  	return filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
   100  		return vs.walkFunc(err, info, path, tx)
   101  	})
   102  }
   103  
   104  func (vs VulnSrc) walkFunc(err error, info os.FileInfo, path string, tx *bolt.Tx) error {
   105  	if err != nil {
   106  		return err
   107  	}
   108  	if info.IsDir() {
   109  		return nil
   110  	}
   111  	if strings.HasPrefix(strings.ToUpper(info.Name()), "OSVDB") {
   112  		return nil
   113  	}
   114  
   115  	buf, err := os.ReadFile(path)
   116  	if err != nil {
   117  		return xerrors.Errorf("failed to read a file: %w", err)
   118  	}
   119  
   120  	advisory := RawAdvisory{}
   121  	err = yaml.Unmarshal(buf, &advisory)
   122  	if err != nil {
   123  		return xerrors.Errorf("failed to unmarshal YAML: %w", err)
   124  	}
   125  	if strings.Contains(strings.ToLower(advisory.Url), "osvdb.org") {
   126  		advisory.Url = ""
   127  	}
   128  
   129  	var vulnerabilityID string
   130  	if advisory.Cve != "" {
   131  		vulnerabilityID = fmt.Sprintf("CVE-%s", advisory.Cve)
   132  	} else if advisory.Ghsa != "" {
   133  		vulnerabilityID = fmt.Sprintf("GHSA-%s", advisory.Ghsa)
   134  	} else {
   135  		return nil
   136  	}
   137  
   138  	// for detecting vulnerabilities
   139  	a := types.Advisory{
   140  		PatchedVersions:    advisory.PatchedVersions,
   141  		UnaffectedVersions: advisory.UnaffectedVersions,
   142  	}
   143  
   144  	err = vs.dbc.PutAdvisoryDetail(tx, vulnerabilityID, advisory.Gem, []string{bucketName}, a)
   145  	if err != nil {
   146  		return xerrors.Errorf("failed to save ruby advisory: %w", err)
   147  	}
   148  
   149  	// for displaying vulnerability detail
   150  	vuln := types.VulnerabilityDetail{
   151  		CvssScore:   advisory.CvssV2,
   152  		CvssScoreV3: advisory.CvssV3,
   153  		References:  append([]string{advisory.Url}, advisory.Related.Url...),
   154  		Title:       advisory.Title,
   155  		Description: advisory.Description,
   156  	}
   157  
   158  	if err = vs.dbc.PutVulnerabilityDetail(tx, vulnerabilityID, source.ID, vuln); err != nil {
   159  		return xerrors.Errorf("failed to save ruby vulnerability detail: %w", err)
   160  	}
   161  
   162  	// for optimization
   163  	if err = vs.dbc.PutVulnerabilityID(tx, vulnerabilityID); err != nil {
   164  		return xerrors.Errorf("failed to save the vulnerability ID: %w", err)
   165  	}
   166  	return nil
   167  }