vuln-list-update/glad/glad.go
2022-04-21 12:39:18 +03:00

161 lines
4.1 KiB
Go

package glad
import (
"fmt"
"log"
"os"
"path/filepath"
"strings"
"github.com/spf13/afero"
"golang.org/x/xerrors"
"gopkg.in/yaml.v2"
"github.com/aquasecurity/vuln-list-update/git"
"github.com/aquasecurity/vuln-list-update/utils"
)
const (
repoURL = "https://gitlab.com/gitlab-org/advisories-community.git"
repoBranch = "main"
gladDir = "glad" // GitLab Advisory Database
)
var (
// https://gitlab.com/gitlab-org/advisories-community
supportedTypes = []string{"gem", "go", "maven", "npm", "nuget", "packagist", "pypi", "nuget", "conan"}
)
type Updater struct {
alternativeRepoBranch string
alternativeRepoURL string
vulnListDir string
cacheDir string
appFs afero.Fs
}
func NewUpdater(alternativeRepoURL string, alternativeRepoBranch string) Updater {
return Updater{
alternativeRepoBranch: alternativeRepoBranch,
alternativeRepoURL: alternativeRepoURL,
vulnListDir: utils.VulnListDir(),
cacheDir: utils.CacheDir(),
appFs: afero.NewOsFs(),
}
}
func (u Updater) Update() error {
log.Print("Fetching GitLab Advisory Database (advisories-community)")
gc := git.Config{}
dir := filepath.Join(u.cacheDir, gladDir)
defaultOrAlternativeRepoURL := repoURL
defaultOrAlternativeRepoBranch := repoBranch
if len(u.alternativeRepoURL) > 0 {
defaultOrAlternativeRepoURL = u.alternativeRepoURL
}
if len(u.alternativeRepoBranch) > 0 {
defaultOrAlternativeRepoBranch = u.alternativeRepoBranch
}
if _, err := gc.CloneOrPull(defaultOrAlternativeRepoURL, dir, defaultOrAlternativeRepoBranch, false); err != nil {
return xerrors.Errorf("failed to clone or pull: %w", err)
}
log.Println("Walking glad...")
for _, target := range supportedTypes {
targetDir := filepath.Join(dir, target)
if ok, _ := utils.Exists(targetDir); !ok {
continue
}
if err := u.walkDir(targetDir); err != nil {
return xerrors.Errorf("failed to walk %s: %w", targetDir, err)
}
}
return nil
}
func (u Updater) walkDir(root string) error {
var advisories []advisory
err := afero.Walk(u.appFs, root, func(path string, info os.FileInfo, err error) error {
if err != nil {
return xerrors.Errorf("file walk error: %w", err)
}
if info.IsDir() {
return nil
}
ext := filepath.Ext(path)
if ext != ".yaml" && ext != ".yml" {
return nil
}
f, err := u.appFs.Open(path)
if err != nil {
return xerrors.Errorf("file open error (%s): %w", path, err)
}
defer f.Close()
var adv advisory
if err = yaml.NewDecoder(f).Decode(&adv); err != nil {
return xerrors.Errorf("unable to decode YAML (%s): %w", path, err)
}
advisories = append(advisories, adv)
return nil
})
if err != nil {
return xerrors.Errorf("walk error: %w", err)
}
for _, adv := range advisories {
adv.PackageSlug = strings.TrimSuffix(adv.PackageSlug, "/")
// Update Identifier to upper case
// e.g. cvE-2014-3530 => CVE-2014-3530
// https://gitlab.com/gitlab-org/advisories-community/-/blob/74a18a7968c2bdd2dd901f6c98f06cb1d9684476/maven/org.picketlink/picketlink-common/cvE-2014-3530.yml
adv.Identifier = strings.ToUpper(adv.Identifier)
slug := u.searchPrefix(adv, advisories)
if slug != "" {
// Update the package_slug to flatten nested packages
// e.g. go/k8s.io/kubernetes => go/k8s.io/kubernetes
// go/k8s.io/kubernetes/pkg/kubelet/kuberuntime => go/k8s.io/kubernetes
adv.PackageSlug = slug
}
if err = u.save(adv); err != nil {
return xerrors.Errorf("save error: %w", err)
}
}
return nil
}
func (u Updater) searchPrefix(adv advisory, advisories []advisory) string {
for _, a := range advisories {
if a.PackageSlug == adv.PackageSlug {
continue
}
if strings.HasPrefix(adv.PackageSlug, a.PackageSlug) {
return a.PackageSlug
}
}
return ""
}
func (u Updater) save(adv advisory) error {
s := strings.Split(adv.PackageSlug, "/")
dir := filepath.Join(s...)
dir = filepath.Join(u.vulnListDir, gladDir, dir)
fileName := fmt.Sprintf("%s.json", adv.Identifier)
if err := utils.WriteJSON(u.appFs, dir, fileName, adv); err != nil {
return xerrors.Errorf("unable to write JSON (%s): %w", fileName, err)
}
return nil
}