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

     1  package glad
     2  
     3  import (
     4  	"encoding/json"
     5  	"io"
     6  	"log"
     7  	"path/filepath"
     8  	"strings"
     9  
    10  	bolt "go.etcd.io/bbolt"
    11  	"golang.org/x/text/cases"
    12  	"golang.org/x/text/language"
    13  	"golang.org/x/xerrors"
    14  
    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/bucket"
    19  	"github.com/khulnasoft-lab/tunnel-db/pkg/vulnsrc/vulnerability"
    20  )
    21  
    22  const (
    23  	// GitLab Advisory Database
    24  	gladDir = "glad"
    25  
    26  	// cf. https://gitlab.com/gitlab-org/security-products/gemnasium-db/-/tree/e4176fff52c027165ae5a79f5b1193090e2fbef0#package-slug-and-package-name
    27  	Conan packageType = "conan"
    28  )
    29  
    30  var (
    31  	supportedIDPrefixes = []string{
    32  		"CVE",
    33  		"GHSA",
    34  		"GMS",
    35  	}
    36  
    37  	// Mapping between GLAD slug and Tunnel ecosystem
    38  	ecosystems = map[packageType]types.Ecosystem{
    39  		Conan: vulnerability.Conan,
    40  	}
    41  
    42  	source = types.DataSource{
    43  		ID:   vulnerability.GLAD,
    44  		Name: "GitLab Advisory Database Community",
    45  		URL:  "https://gitlab.com/gitlab-org/advisories-community",
    46  	}
    47  )
    48  
    49  type packageType string
    50  
    51  type VulnSrc struct {
    52  	dbc db.Operation
    53  }
    54  
    55  func NewVulnSrc() VulnSrc {
    56  	return VulnSrc{
    57  		dbc: db.Config{},
    58  	}
    59  }
    60  
    61  func (vs VulnSrc) Name() types.SourceID {
    62  	return source.ID
    63  }
    64  
    65  func (vs VulnSrc) Update(dir string) error {
    66  	for t := range ecosystems {
    67  		log.Printf("    Updating GitLab Advisory Database %s...", cases.Title(language.English).String(string(t)))
    68  		rootDir := filepath.Join(dir, "vuln-list", gladDir, string(t))
    69  		if err := vs.update(t, rootDir); err != nil {
    70  			return xerrors.Errorf("update error: %w", err)
    71  		}
    72  	}
    73  	return nil
    74  }
    75  
    76  func (vs VulnSrc) update(pkgType packageType, rootDir string) error {
    77  	var glads []Advisory
    78  	err := utils.FileWalk(rootDir, func(r io.Reader, path string) error {
    79  		if !supportedIDs(filepath.Base(path)) {
    80  			return nil
    81  		}
    82  
    83  		var glad Advisory
    84  		if err := json.NewDecoder(r).Decode(&glad); err != nil {
    85  			return xerrors.Errorf("failed to decode GLAD: %w", err)
    86  		}
    87  
    88  		glads = append(glads, glad)
    89  		return nil
    90  	})
    91  	if err != nil {
    92  		return xerrors.Errorf("walk error: %w", err)
    93  	}
    94  
    95  	if err = vs.save(pkgType, glads); err != nil {
    96  		return xerrors.Errorf("save error: %w", err)
    97  	}
    98  
    99  	return nil
   100  }
   101  
   102  func (vs VulnSrc) save(pkgType packageType, glads []Advisory) error {
   103  	err := vs.dbc.BatchUpdate(func(tx *bolt.Tx) error {
   104  		return vs.commit(tx, pkgType, glads)
   105  	})
   106  	if err != nil {
   107  		return xerrors.Errorf("batch update error: %w", err)
   108  	}
   109  	return nil
   110  }
   111  
   112  func (vs VulnSrc) commit(tx *bolt.Tx, pkgType packageType, glads []Advisory) error {
   113  	for _, glad := range glads {
   114  		a := types.Advisory{
   115  			VulnerableVersions: []string{glad.AffectedRange},
   116  			PatchedVersions:    glad.FixedVersions,
   117  		}
   118  
   119  		// e.g. "go/github.com/go-ldap/ldap" => "go", "github.com/go-ldap/ldap"
   120  		ss := strings.SplitN(glad.PackageSlug, "/", 2)
   121  		if len(ss) < 2 {
   122  			return xerrors.Errorf("failed to parse package slug: %s", glad.PackageSlug)
   123  		}
   124  
   125  		pkgName := ss[1]
   126  		ecosystem, ok := ecosystems[pkgType]
   127  		if !ok {
   128  			return xerrors.Errorf("failed to get ecosystem: %s", pkgType)
   129  		}
   130  		bucketName := bucket.Name(ecosystem, source.Name)
   131  		if err := vs.dbc.PutDataSource(tx, bucketName, source); err != nil {
   132  			return xerrors.Errorf("failed to put data source: %w", err)
   133  		}
   134  
   135  		if err := vs.dbc.PutAdvisoryDetail(tx, glad.Identifier, pkgName, []string{bucketName}, a); err != nil {
   136  			return xerrors.Errorf("failed to save GLAD advisory detail: %w", err)
   137  		}
   138  
   139  		// glad's cvss score is taken from NVD
   140  		vuln := types.VulnerabilityDetail{
   141  			ID:          glad.Identifier,
   142  			Severity:    types.SeverityUnknown,
   143  			References:  glad.Urls,
   144  			Title:       glad.Title,
   145  			Description: glad.Description,
   146  		}
   147  
   148  		if err := vs.dbc.PutVulnerabilityDetail(tx, glad.Identifier, source.ID, vuln); err != nil {
   149  			return xerrors.Errorf("failed to save GLAD vulnerability detail: %w", err)
   150  		}
   151  
   152  		// for optimization
   153  		if err := vs.dbc.PutVulnerabilityID(tx, glad.Identifier); err != nil {
   154  			return xerrors.Errorf("failed to save the vulnerability ID: %w", err)
   155  		}
   156  	}
   157  
   158  	return nil
   159  }
   160  
   161  func supportedIDs(fileName string) bool {
   162  	for _, prefix := range supportedIDPrefixes {
   163  		if strings.HasPrefix(fileName, prefix) {
   164  			return true
   165  		}
   166  	}
   167  	return false
   168  }