feat: init nightly vuls for blackhat
This commit is contained in:
		
							
								
								
									
										22
									
								
								pkg/cmd/config/config.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								pkg/cmd/config/config.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,22 @@
 | 
			
		||||
package config
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"github.com/MakeNowJust/heredoc"
 | 
			
		||||
	"github.com/spf13/cobra"
 | 
			
		||||
 | 
			
		||||
	configInitCmd "github.com/future-architect/vuls/pkg/cmd/config/init"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func NewCmdConfig() *cobra.Command {
 | 
			
		||||
	cmd := &cobra.Command{
 | 
			
		||||
		Use:   "config <subcommand>",
 | 
			
		||||
		Short: "Vuls Config Operation",
 | 
			
		||||
		Example: heredoc.Doc(`
 | 
			
		||||
			$ vuls config init > config.json
 | 
			
		||||
		`),
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	cmd.AddCommand(configInitCmd.NewCmdInit())
 | 
			
		||||
 | 
			
		||||
	return cmd
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										101
									
								
								pkg/cmd/config/init/init.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										101
									
								
								pkg/cmd/config/init/init.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,101 @@
 | 
			
		||||
package init
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"os"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"text/template"
 | 
			
		||||
 | 
			
		||||
	"github.com/MakeNowJust/heredoc"
 | 
			
		||||
	"github.com/pkg/errors"
 | 
			
		||||
	"github.com/spf13/cobra"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func NewCmdInit() *cobra.Command {
 | 
			
		||||
	cmd := &cobra.Command{
 | 
			
		||||
		Use:   "init",
 | 
			
		||||
		Short: "generate vuls config template",
 | 
			
		||||
		Args:  cobra.NoArgs,
 | 
			
		||||
		RunE: func(_ *cobra.Command, _ []string) error {
 | 
			
		||||
			return generateConfigTemplate()
 | 
			
		||||
		},
 | 
			
		||||
		Example: heredoc.Doc(`
 | 
			
		||||
			$ vuls config init > config.json
 | 
			
		||||
		`),
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return cmd
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func generateConfigTemplate() error {
 | 
			
		||||
	pwd, err := os.Getwd()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		pwd = os.TempDir()
 | 
			
		||||
	}
 | 
			
		||||
	home, err := os.UserHomeDir()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		home = "/home/vuls"
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	create := func(name, t string) *template.Template {
 | 
			
		||||
		return template.Must(template.New(name).Parse(t))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	t := create("config template",
 | 
			
		||||
		`{
 | 
			
		||||
	"server": {
 | 
			
		||||
		"listen": "127.0.0.1:5515",
 | 
			
		||||
		"path": "{{.dbpath}}"
 | 
			
		||||
	},
 | 
			
		||||
	"hosts": {
 | 
			
		||||
		"local": {
 | 
			
		||||
			"type": "local",
 | 
			
		||||
			"scan": {
 | 
			
		||||
				"ospkg": {
 | 
			
		||||
					"root": false
 | 
			
		||||
				}
 | 
			
		||||
			},
 | 
			
		||||
			"detect": {
 | 
			
		||||
				"path": "{{.dbpath}}",
 | 
			
		||||
				"result_dir": "{{.results}}"
 | 
			
		||||
			}
 | 
			
		||||
		},
 | 
			
		||||
		"remote": {
 | 
			
		||||
			"type": "remote",
 | 
			
		||||
			"host": "127.0.0.1",
 | 
			
		||||
			"port": "22",
 | 
			
		||||
			"user": "vuls",
 | 
			
		||||
			"ssh_config": "{{.sshconfig}}",
 | 
			
		||||
			"ssh_key": "{{.sshkey}}",
 | 
			
		||||
			"scan": {
 | 
			
		||||
				"ospkg": {
 | 
			
		||||
					"root": false
 | 
			
		||||
				}
 | 
			
		||||
			},
 | 
			
		||||
			"detect": {
 | 
			
		||||
				"path": "{{.dbpath}}",
 | 
			
		||||
				"result_dir": "{{.results}}"
 | 
			
		||||
			}
 | 
			
		||||
		},
 | 
			
		||||
		"cpe": {
 | 
			
		||||
			"type": "local",
 | 
			
		||||
			"scan": {
 | 
			
		||||
				"cpe": [
 | 
			
		||||
					{
 | 
			
		||||
						"cpe": "cpe:2.3:a:apache:log4j:2.3:*:*:*:*:*:*:*"
 | 
			
		||||
					}
 | 
			
		||||
				]
 | 
			
		||||
			},
 | 
			
		||||
			"detect": {
 | 
			
		||||
				"path": "{{.dbpath}}",
 | 
			
		||||
				"result_dir": "{{.results}}"
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
`)
 | 
			
		||||
 | 
			
		||||
	if err := t.Execute(os.Stdout, map[string]string{"dbpath": filepath.Join(pwd, "vuls.db"), "results": filepath.Join(pwd, "results"), "sshconfig": filepath.Join(home, ".ssh", "config"), "sshkey": filepath.Join(home, ".ssh", "id_rsa")}); err != nil {
 | 
			
		||||
		return errors.Wrap(err, "output config template")
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										413
									
								
								pkg/cmd/db/create/create.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										413
									
								
								pkg/cmd/db/create/create.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,413 @@
 | 
			
		||||
package create
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io/fs"
 | 
			
		||||
	"net/url"
 | 
			
		||||
	"os"
 | 
			
		||||
	"os/exec"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/MakeNowJust/heredoc"
 | 
			
		||||
	"github.com/pkg/errors"
 | 
			
		||||
	"github.com/spf13/cobra"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/pkg/cmd/db/create/vulnsrc"
 | 
			
		||||
	"github.com/future-architect/vuls/pkg/db"
 | 
			
		||||
	"github.com/future-architect/vuls/pkg/util"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type DBCreateOption struct {
 | 
			
		||||
	Path string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewCmdCreate() *cobra.Command {
 | 
			
		||||
	opts := &DBCreateOption{
 | 
			
		||||
		Path: "vuls.db",
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	cmd := &cobra.Command{
 | 
			
		||||
		Use:   "create",
 | 
			
		||||
		Short: "Create Vuls DB",
 | 
			
		||||
		Args:  cobra.ExactArgs(1),
 | 
			
		||||
		RunE: func(_ *cobra.Command, args []string) error {
 | 
			
		||||
			return create(args[0], opts.Path)
 | 
			
		||||
		},
 | 
			
		||||
		Example: heredoc.Doc(`
 | 
			
		||||
			$ vuls db create https://github.com/vulsio/vuls-data.git
 | 
			
		||||
			$ vuls db create /home/MaineK00n/.cache/vuls
 | 
			
		||||
		`),
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	cmd.Flags().StringVarP(&opts.Path, "path", "p", "vuls.db", "path to create Vuls DB")
 | 
			
		||||
 | 
			
		||||
	return cmd
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func create(src, dbpath string) error {
 | 
			
		||||
	datapath := src
 | 
			
		||||
	if u, err := url.Parse(src); err == nil && u.Scheme != "" {
 | 
			
		||||
		cloneDir := filepath.Join(util.CacheDir(), "clone")
 | 
			
		||||
		if err := exec.Command("git", "clone", "--depth", "1", src, cloneDir).Run(); err != nil {
 | 
			
		||||
			return errors.Wrapf(err, "git clone --depth 1 %s %s", src, cloneDir)
 | 
			
		||||
		}
 | 
			
		||||
		datapath = cloneDir
 | 
			
		||||
	}
 | 
			
		||||
	if _, err := os.Stat(datapath); err != nil {
 | 
			
		||||
		return errors.Wrapf(err, "%s not found", datapath)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	db, err := db.Open("boltdb", dbpath, false)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return errors.Wrap(err, "open db")
 | 
			
		||||
	}
 | 
			
		||||
	defer db.Close()
 | 
			
		||||
 | 
			
		||||
	if err := filepath.WalkDir(datapath, func(path string, d fs.DirEntry, err error) error {
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if d.IsDir() {
 | 
			
		||||
			return nil
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		p := strings.TrimPrefix(strings.TrimPrefix(path, datapath), string(os.PathSeparator))
 | 
			
		||||
		srcType, p, found := strings.Cut(p, string(os.PathSeparator))
 | 
			
		||||
		if !found {
 | 
			
		||||
			return nil
 | 
			
		||||
		}
 | 
			
		||||
		advType, p, found := strings.Cut(p, string(os.PathSeparator))
 | 
			
		||||
		if !found {
 | 
			
		||||
			return errors.Errorf(`unexpected filepath. expected: "%s/["official", ...]/["vulnerability", "os", "library", "cpe"]/...", actual: "%s"`, datapath, path)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		bs, err := util.Read(path)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return errors.Wrapf(err, "read %s", path)
 | 
			
		||||
		}
 | 
			
		||||
		if len(bs) == 0 {
 | 
			
		||||
			return nil
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		switch advType {
 | 
			
		||||
		case "vulnerability":
 | 
			
		||||
			var src vulnsrc.Vulnerability
 | 
			
		||||
			if err := json.Unmarshal(bs, &src); err != nil {
 | 
			
		||||
				return errors.Wrapf(err, "unmarshal json. path: %s", path)
 | 
			
		||||
			}
 | 
			
		||||
			if err := db.PutVulnerability(srcType, fmt.Sprintf("vulnerability:%s", src.ID), vulnsrc.ToVulsVulnerability(src)); err != nil {
 | 
			
		||||
				return errors.Wrap(err, "put vulnerability")
 | 
			
		||||
			}
 | 
			
		||||
		case "os":
 | 
			
		||||
			advType, err := toAdvType(p)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return errors.Wrap(err, "path to adv type")
 | 
			
		||||
			}
 | 
			
		||||
			bucket, err := toAdvBucket(p)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return errors.Wrap(err, "path to adv bucket")
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			switch advType {
 | 
			
		||||
			case "redhat_oval":
 | 
			
		||||
				if strings.Contains(p, "repository_to_cpe.json") {
 | 
			
		||||
					var src vulnsrc.RepositoryToCPE
 | 
			
		||||
					if err := json.Unmarshal(bs, &src); err != nil {
 | 
			
		||||
						return errors.Wrapf(err, "unmarshal json. path: %s", path)
 | 
			
		||||
					}
 | 
			
		||||
					if err := db.PutRedHatRepoToCPE(srcType, bucket, vulnsrc.ToVulsRepositoryToCPE(src)); err != nil {
 | 
			
		||||
						return errors.Wrap(err, "put repository to cpe")
 | 
			
		||||
					}
 | 
			
		||||
					break
 | 
			
		||||
				}
 | 
			
		||||
				var src vulnsrc.DetectPackage
 | 
			
		||||
				if err := json.Unmarshal(bs, &src); err != nil {
 | 
			
		||||
					return errors.Wrapf(err, "unmarshal json. path: %s", path)
 | 
			
		||||
				}
 | 
			
		||||
				pkgs, err := vulnsrc.ToVulsPackage(src, advType)
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					return errors.Wrap(err, "to vuls package")
 | 
			
		||||
				}
 | 
			
		||||
				if err := db.PutPackage(srcType, bucket, pkgs); err != nil {
 | 
			
		||||
					return errors.Wrap(err, "put package")
 | 
			
		||||
				}
 | 
			
		||||
			case "windows":
 | 
			
		||||
				if strings.Contains(p, "supercedence.json") {
 | 
			
		||||
					var supercedences []vulnsrc.Supercedence
 | 
			
		||||
					if err := json.Unmarshal(bs, &supercedences); err != nil {
 | 
			
		||||
						return errors.Wrapf(err, "unnmarshal json. path: %s", path)
 | 
			
		||||
					}
 | 
			
		||||
					if err := db.PutWindowsSupercedence(srcType, bucket, vulnsrc.ToVulsSupercedences(supercedences)); err != nil {
 | 
			
		||||
						return errors.Wrap(err, "put supercedence")
 | 
			
		||||
					}
 | 
			
		||||
					break
 | 
			
		||||
				}
 | 
			
		||||
				var src vulnsrc.DetectPackage
 | 
			
		||||
				if err := json.Unmarshal(bs, &src); err != nil {
 | 
			
		||||
					return errors.Wrapf(err, "unmarshal json. path: %s", path)
 | 
			
		||||
				}
 | 
			
		||||
				pkgs, err := vulnsrc.ToVulsPackage(src, advType)
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					return errors.Wrap(err, "to vuls package")
 | 
			
		||||
				}
 | 
			
		||||
				if err := db.PutPackage(srcType, bucket, pkgs); err != nil {
 | 
			
		||||
					return errors.Wrap(err, "put package")
 | 
			
		||||
				}
 | 
			
		||||
			default:
 | 
			
		||||
				var src vulnsrc.DetectPackage
 | 
			
		||||
				if err := json.Unmarshal(bs, &src); err != nil {
 | 
			
		||||
					return errors.Wrapf(err, "unmarshal json. path: %s", path)
 | 
			
		||||
				}
 | 
			
		||||
				pkgs, err := vulnsrc.ToVulsPackage(src, advType)
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					return errors.Wrap(err, "to vuls package")
 | 
			
		||||
				}
 | 
			
		||||
				if err := db.PutPackage(srcType, bucket, pkgs); err != nil {
 | 
			
		||||
					return errors.Wrap(err, "put package")
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		case "library":
 | 
			
		||||
		case "cpe":
 | 
			
		||||
			var src vulnsrc.DetectCPE
 | 
			
		||||
			if err := json.Unmarshal(bs, &src); err != nil {
 | 
			
		||||
				return errors.Wrapf(err, "unmarshal json. path: %s", path)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			advType, err := toAdvType(p)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return errors.Wrap(err, "path to adv type")
 | 
			
		||||
			}
 | 
			
		||||
			cs, err := vulnsrc.ToVulsCPEConfiguration(src, advType)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return errors.Wrap(err, "to vuls cpe configuration")
 | 
			
		||||
			}
 | 
			
		||||
			bucket, err := toAdvBucket(p)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return errors.Wrap(err, "path to adv bucket")
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if err := db.PutCPEConfiguration(srcType, bucket, cs); err != nil {
 | 
			
		||||
				return errors.Wrap(err, "put cpe configuration")
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return nil
 | 
			
		||||
	}); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func toAdvType(path string) (string, error) {
 | 
			
		||||
	ss := strings.Split(path, string(os.PathSeparator))
 | 
			
		||||
	if len(ss) < 3 && ss[0] != "windows" {
 | 
			
		||||
		return "", errors.Errorf(`unexpected path. accepts: "[<os name>, <library name>, "nvd", "jvn"]/**/*.json*", received: "%s"`, path)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	switch ss[0] {
 | 
			
		||||
	case "alma", "alpine", "amazon", "epel", "fedora", "oracle", "rocky":
 | 
			
		||||
		return fmt.Sprintf("%s:%s", ss[0], ss[1]), nil
 | 
			
		||||
	case "arch", "freebsd", "gentoo", "windows", "conan", "erlang", "nvd", "jvn":
 | 
			
		||||
		return ss[0], nil
 | 
			
		||||
	case "debian":
 | 
			
		||||
		switch ss[1] {
 | 
			
		||||
		case "oval":
 | 
			
		||||
			return "debian_oval", nil
 | 
			
		||||
		case "tracker":
 | 
			
		||||
			return "debian_security_tracker", nil
 | 
			
		||||
		default:
 | 
			
		||||
			return "", errors.Errorf(`unexpected debian advisory type. accepts: ["oval", "tracker"], received: "%s"`, ss[1])
 | 
			
		||||
		}
 | 
			
		||||
	case "redhat":
 | 
			
		||||
		switch ss[1] {
 | 
			
		||||
		case "api":
 | 
			
		||||
			return "redhat_security_api", nil
 | 
			
		||||
		case "oval":
 | 
			
		||||
			return "redhat_oval", nil
 | 
			
		||||
		default:
 | 
			
		||||
			return "", errors.Errorf(`unexpected redhat advisory type. accepts: ["api", "oval"], received: "%s"`, ss[1])
 | 
			
		||||
		}
 | 
			
		||||
	case "suse":
 | 
			
		||||
		switch ss[1] {
 | 
			
		||||
		case "cvrf":
 | 
			
		||||
			return "suse_cvrf", nil
 | 
			
		||||
		case "oval":
 | 
			
		||||
			return "suse_oval", nil
 | 
			
		||||
		default:
 | 
			
		||||
			return "", errors.Errorf(`unexpected suse advisory type. accepts: ["cvrf", "oval"], received: "%s"`, ss[1])
 | 
			
		||||
		}
 | 
			
		||||
	case "ubuntu":
 | 
			
		||||
		switch ss[1] {
 | 
			
		||||
		case "oval":
 | 
			
		||||
			return "ubuntu_oval", nil
 | 
			
		||||
		case "tracker":
 | 
			
		||||
			return "ubuntu_security_tracker", nil
 | 
			
		||||
		default:
 | 
			
		||||
			return "", errors.Errorf(`unexpected debian advisory type. accepts: ["oval", "tracker"], received: "%s"`, ss[1])
 | 
			
		||||
		}
 | 
			
		||||
	case "cargo":
 | 
			
		||||
		switch ss[1] {
 | 
			
		||||
		case "db":
 | 
			
		||||
			return "cargo_db", nil
 | 
			
		||||
		case "ghsa":
 | 
			
		||||
			return "cargo_ghsa", nil
 | 
			
		||||
		case "osv":
 | 
			
		||||
			return "cargo_osv", nil
 | 
			
		||||
		default:
 | 
			
		||||
			return "", errors.Errorf(`unexpected cargo advisory type. accepts: ["db", "ghsa", "osv"], received: "%s"`, ss[1])
 | 
			
		||||
		}
 | 
			
		||||
	case "composer":
 | 
			
		||||
		switch ss[1] {
 | 
			
		||||
		case "db":
 | 
			
		||||
			return "composer_db", nil
 | 
			
		||||
		case "ghsa":
 | 
			
		||||
			return "composer_ghsa", nil
 | 
			
		||||
		case "glsa":
 | 
			
		||||
			return "composer_glsa", nil
 | 
			
		||||
		default:
 | 
			
		||||
			return "", errors.Errorf(`unexpected composer advisory type. accepts: ["db", "ghsa", "glsa"], received: "%s"`, ss[1])
 | 
			
		||||
		}
 | 
			
		||||
	case "golang":
 | 
			
		||||
		switch ss[1] {
 | 
			
		||||
		case "db":
 | 
			
		||||
			return "golang_db", nil
 | 
			
		||||
		case "ghsa":
 | 
			
		||||
			return "golang_ghsa", nil
 | 
			
		||||
		case "glsa":
 | 
			
		||||
			return "golang_glsa", nil
 | 
			
		||||
		case "govulndb":
 | 
			
		||||
			return "golang_govulndb", nil
 | 
			
		||||
		case "osv":
 | 
			
		||||
			return "golang_osv", nil
 | 
			
		||||
		default:
 | 
			
		||||
			return "", errors.Errorf(`unexpected golang advisory type. accepts: ["db", "ghsa", "glsa", "govulndb", "osv"], received: "%s"`, ss[1])
 | 
			
		||||
		}
 | 
			
		||||
	case "maven":
 | 
			
		||||
		switch ss[1] {
 | 
			
		||||
		case "ghsa":
 | 
			
		||||
			return "maven_ghsa", nil
 | 
			
		||||
		case "glsa":
 | 
			
		||||
			return "maven_glsa", nil
 | 
			
		||||
		default:
 | 
			
		||||
			return "", errors.Errorf(`unexpected maven advisory type. accepts: ["ghsa", "glsa"], received: "%s"`, ss[1])
 | 
			
		||||
		}
 | 
			
		||||
	case "npm":
 | 
			
		||||
		switch ss[1] {
 | 
			
		||||
		case "db":
 | 
			
		||||
			return "npm_db", nil
 | 
			
		||||
		case "ghsa":
 | 
			
		||||
			return "npm_ghsa", nil
 | 
			
		||||
		case "glsa":
 | 
			
		||||
			return "npm_glsa", nil
 | 
			
		||||
		case "osv":
 | 
			
		||||
			return "npm_osv", nil
 | 
			
		||||
		default:
 | 
			
		||||
			return "", errors.Errorf(`unexpected npm advisory type. accepts: ["db", "ghsa", "glsa", "osv"], received: "%s"`, ss[1])
 | 
			
		||||
		}
 | 
			
		||||
	case "nuget":
 | 
			
		||||
		switch ss[1] {
 | 
			
		||||
		case "ghsa":
 | 
			
		||||
			return "nuget_ghsa", nil
 | 
			
		||||
		case "glsa":
 | 
			
		||||
			return "nuget_glsa", nil
 | 
			
		||||
		case "osv":
 | 
			
		||||
			return "nuget_osv", nil
 | 
			
		||||
		default:
 | 
			
		||||
			return "", errors.Errorf(`unexpected nuget advisory type. accepts: ["ghsa", "glsa", "osv"], received: "%s"`, ss[1])
 | 
			
		||||
		}
 | 
			
		||||
	case "pip":
 | 
			
		||||
		switch ss[1] {
 | 
			
		||||
		case "db":
 | 
			
		||||
			return "pip_db", nil
 | 
			
		||||
		case "ghsa":
 | 
			
		||||
			return "pip_ghsa", nil
 | 
			
		||||
		case "glsa":
 | 
			
		||||
			return "pip_glsa", nil
 | 
			
		||||
		case "osv":
 | 
			
		||||
			return "pip_osv", nil
 | 
			
		||||
		default:
 | 
			
		||||
			return "", errors.Errorf(`unexpected pip advisory type. accepts: ["db", "ghsa", "glsa", "osv"], received: "%s"`, ss[1])
 | 
			
		||||
		}
 | 
			
		||||
	case "rubygems":
 | 
			
		||||
		switch ss[1] {
 | 
			
		||||
		case "db":
 | 
			
		||||
			return "rubygems_db", nil
 | 
			
		||||
		case "ghsa":
 | 
			
		||||
			return "rubygems_ghsa", nil
 | 
			
		||||
		case "glsa":
 | 
			
		||||
			return "rubygems_glsa", nil
 | 
			
		||||
		case "osv":
 | 
			
		||||
			return "rubygems_osv", nil
 | 
			
		||||
		default:
 | 
			
		||||
			return "", errors.Errorf(`unexpected rubygems advisory type. accepts: ["db", "ghsa", "glsa", "osv"], received: "%s"`, ss[1])
 | 
			
		||||
		}
 | 
			
		||||
	default:
 | 
			
		||||
		return "", errors.Errorf(`unexpected os or library or cpe. accepts: ["alma", "alpine", "amazon", "arch", "debian", "epel", "fedora", "freebsd", "gentoo", "oracle", "redhat", "rocky", "suse", "ubuntu", "windows", "cargo", "composer", "conan", "erlang", "golang", "maven", "npm", "nuget", "pip", "rubygems", "nvd", "jvn"], received: "%s"`, ss[0])
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func toAdvBucket(path string) (string, error) {
 | 
			
		||||
	ss := strings.Split(path, string(os.PathSeparator))
 | 
			
		||||
	if len(ss) < 3 && ss[0] != "windows" {
 | 
			
		||||
		return "", errors.Errorf(`unexpected path. accepts: "[<os name>, <library name>, "nvd", "jvn"]/**/*.json*", received: "%s"`, path)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	switch ss[0] {
 | 
			
		||||
	case "alma", "alpine", "amazon", "epel", "fedora", "oracle", "rocky":
 | 
			
		||||
		return fmt.Sprintf("%s:%s", ss[0], ss[1]), nil
 | 
			
		||||
	case "arch", "freebsd", "gentoo", "cargo", "composer", "conan", "erlang", "golang", "maven", "npm", "nuget", "pip", "rubygems":
 | 
			
		||||
		return ss[0], nil
 | 
			
		||||
	case "debian":
 | 
			
		||||
		switch ss[1] {
 | 
			
		||||
		case "oval", "tracker":
 | 
			
		||||
			return fmt.Sprintf("%s:%s", ss[0], ss[2]), nil
 | 
			
		||||
		default:
 | 
			
		||||
			return "", errors.Errorf(`unexpected debian advisory type. accepts: ["oval", "tracker"], received: "%s"`, ss[1])
 | 
			
		||||
		}
 | 
			
		||||
	case "redhat":
 | 
			
		||||
		switch ss[1] {
 | 
			
		||||
		case "api":
 | 
			
		||||
			return fmt.Sprintf("%s:%s", ss[0], ss[2]), nil
 | 
			
		||||
		case "oval":
 | 
			
		||||
			if len(ss) < 4 {
 | 
			
		||||
				return "", errors.Errorf(`unexpected path. accepts: "redhat/oval/<os version>/<stream>/yyyy/*.json*", received: "%s"`, path)
 | 
			
		||||
			}
 | 
			
		||||
			if strings.Contains(path, "repository_to_cpe.json") {
 | 
			
		||||
				return fmt.Sprintf("%s_cpe:%s", ss[0], ss[3]), nil
 | 
			
		||||
			}
 | 
			
		||||
			return fmt.Sprintf("%s:%s", ss[0], ss[3]), nil
 | 
			
		||||
		default:
 | 
			
		||||
			return "", errors.Errorf(`unexpected redhat advisory type. accepts: ["api", "oval"], received: "%s"`, ss[1])
 | 
			
		||||
		}
 | 
			
		||||
	case "suse":
 | 
			
		||||
		switch ss[1] {
 | 
			
		||||
		case "cvrf", "oval":
 | 
			
		||||
			if len(ss) < 4 {
 | 
			
		||||
				return "", errors.Errorf(`unexpected path. accepts: "suse/[cvrf, oval]/<os>/<version>/yyyy/*.json*", received: "%s"`, path)
 | 
			
		||||
			}
 | 
			
		||||
			return fmt.Sprintf("%s:%s", ss[2], ss[3]), nil
 | 
			
		||||
		default:
 | 
			
		||||
			return "", errors.Errorf(`unexpected suse advisory type. accepts: ["cvrf", "oval"], received: "%s"`, ss[1])
 | 
			
		||||
		}
 | 
			
		||||
	case "ubuntu":
 | 
			
		||||
		switch ss[1] {
 | 
			
		||||
		case "oval", "tracker":
 | 
			
		||||
			return fmt.Sprintf("%s:%s", ss[0], ss[2]), nil
 | 
			
		||||
		default:
 | 
			
		||||
			return "", errors.Errorf(`unexpected debian advisory type. accepts: ["oval", "tracker"], received: "%s"`, ss[1])
 | 
			
		||||
		}
 | 
			
		||||
	case "windows":
 | 
			
		||||
		if strings.Contains(path, "supercedence.json") {
 | 
			
		||||
			return "windows_supercedence", nil
 | 
			
		||||
		}
 | 
			
		||||
		return fmt.Sprintf("%s:%s", ss[0], ss[1]), nil
 | 
			
		||||
	case "nvd", "jvn":
 | 
			
		||||
		return "cpe", nil
 | 
			
		||||
	default:
 | 
			
		||||
		return "", errors.Errorf(`unexpected os or library or cpe. accepts: ["alma", "alpine", "amazon", "arch", "debian", "epel", "fedora", "freebsd", "gentoo", "oracle", "redhat", "rocky", "suse", "ubuntu", "windows", "cargo", "composer", "conan", "erlang", "golang", "maven", "npm", "nuget", "pip", "rubygems", "nvd", "jvn"], received: "%s"`, ss[0])
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										269
									
								
								pkg/cmd/db/create/vulnsrc/types.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										269
									
								
								pkg/cmd/db/create/vulnsrc/types.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,269 @@
 | 
			
		||||
package vulnsrc
 | 
			
		||||
 | 
			
		||||
// https://github.com/MaineK00n/vuls-data-update/blob/main/pkg/build/types.go
 | 
			
		||||
 | 
			
		||||
import "time"
 | 
			
		||||
 | 
			
		||||
type Vulnerability struct {
 | 
			
		||||
	ID          string        `json:"id,omitempty"`
 | 
			
		||||
	Advisory    *Advisories   `json:"advisory,omitempty"`
 | 
			
		||||
	Title       *Titles       `json:"title,omitempty"`
 | 
			
		||||
	Description *Descriptions `json:"description,omitempty"`
 | 
			
		||||
	CVSS        *CVSSes       `json:"cvss,omitempty"`
 | 
			
		||||
	EPSS        *EPSS         `json:"epss,omitempty"`
 | 
			
		||||
	CWE         *CWEs         `json:"cwe,omitempty"`
 | 
			
		||||
	Metasploit  []Metasploit  `json:"metasploit,omitempty"`
 | 
			
		||||
	Exploit     *Exploit      `json:"exploit,omitempty"`
 | 
			
		||||
	KEV         *KEV          `json:"kev,omitempty"`
 | 
			
		||||
	Mitigation  *Mitigation   `json:"mitigation,omitempty"`
 | 
			
		||||
	Published   *Publisheds   `json:"published,omitempty"`
 | 
			
		||||
	Modified    *Modifieds    `json:"modified,omitempty"`
 | 
			
		||||
	References  *References   `json:"references,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Advisories struct {
 | 
			
		||||
	MITRE                 *Advisory             `json:"mitre,omitempty"`
 | 
			
		||||
	NVD                   *Advisory             `json:"nvd,omitempty"`
 | 
			
		||||
	JVN                   []Advisory            `json:"jvn,omitempty"`
 | 
			
		||||
	Alma                  map[string][]Advisory `json:"alma,omitempty"`
 | 
			
		||||
	Alpine                map[string]Advisory   `json:"alpine,omitempty"`
 | 
			
		||||
	Amazon                map[string][]Advisory `json:"amazon,omitempty"`
 | 
			
		||||
	Arch                  []Advisory            `json:"arch,omitempty"`
 | 
			
		||||
	DebianOVAL            map[string][]Advisory `json:"debian_oval,omitempty"`
 | 
			
		||||
	DebianSecurityTracker map[string]Advisory   `json:"debian_security_tracker,omitempty"`
 | 
			
		||||
	FreeBSD               []Advisory            `json:"freebsd,omitempty"`
 | 
			
		||||
	Oracle                map[string][]Advisory `json:"oracle,omitempty"`
 | 
			
		||||
	RedHatOVAL            map[string][]Advisory `json:"redhat_oval,omitempty"`
 | 
			
		||||
	SUSEOVAL              map[string][]Advisory `json:"suse_oval,omitempty"`
 | 
			
		||||
	SUSECVRF              *Advisory             `json:"suse_cvrf,omitempty"`
 | 
			
		||||
	UbuntuOVAL            map[string][]Advisory `json:"ubuntu_oval,omitempty"`
 | 
			
		||||
	UbuntuSecurityTracker *Advisory             `json:"ubuntu_security_tracker,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
type Advisory struct {
 | 
			
		||||
	ID  string `json:"id,omitempty"`
 | 
			
		||||
	URL string `json:"url,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Titles struct {
 | 
			
		||||
	MITRE                 string                       `json:"mitre,omitempty"`
 | 
			
		||||
	NVD                   string                       `json:"nvd,omitempty"`
 | 
			
		||||
	JVN                   map[string]string            `json:"jvn,omitempty"`
 | 
			
		||||
	Alma                  map[string]map[string]string `json:"alma,omitempty"`
 | 
			
		||||
	Alpine                map[string]string            `json:"alpine,omitempty"`
 | 
			
		||||
	Amazon                map[string]map[string]string `json:"amazon,omitempty"`
 | 
			
		||||
	Arch                  map[string]string            `json:"arch,omitempty"`
 | 
			
		||||
	DebianOVAL            map[string]map[string]string `json:"debian_oval,omitempty"`
 | 
			
		||||
	DebianSecurityTracker map[string]string            `json:"debian_security_tracker,omitempty"`
 | 
			
		||||
	FreeBSD               map[string]string            `json:"freebsd,omitempty"`
 | 
			
		||||
	Oracle                map[string]map[string]string `json:"oracle,omitempty"`
 | 
			
		||||
	RedHatOVAL            map[string]map[string]string `json:"redhat_oval,omitempty"`
 | 
			
		||||
	SUSEOVAL              map[string]map[string]string `json:"suse_oval,omitempty"`
 | 
			
		||||
	SUSECVRF              string                       `json:"suse_cvrf,omitempty"`
 | 
			
		||||
	UbuntuOVAL            map[string]map[string]string `json:"ubuntu_oval,omitempty"`
 | 
			
		||||
	UbuntuSecurityTracker string                       `json:"ubuntu_security_tracker,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Descriptions struct {
 | 
			
		||||
	MITRE                 string                       `json:"mitre,omitempty"`
 | 
			
		||||
	NVD                   string                       `json:"nvd,omitempty"`
 | 
			
		||||
	JVN                   map[string]string            `json:"jvn,omitempty"`
 | 
			
		||||
	Alma                  map[string]map[string]string `json:"alma,omitempty"`
 | 
			
		||||
	Amazon                map[string]map[string]string `json:"amazon,omitempty"`
 | 
			
		||||
	DebianOVAL            map[string]map[string]string `json:"debian_oval,omitempty"`
 | 
			
		||||
	DebianSecurityTracker map[string]string            `json:"debian_security_tracker,omitempty"`
 | 
			
		||||
	FreeBSD               map[string]string            `json:"freebsd,omitempty"`
 | 
			
		||||
	Oracle                map[string]map[string]string `json:"oracle,omitempty"`
 | 
			
		||||
	RedHatOVAL            map[string]map[string]string `json:"redhat_oval,omitempty"`
 | 
			
		||||
	SUSEOVAL              map[string]map[string]string `json:"suse_oval,omitempty"`
 | 
			
		||||
	SUSECVRF              string                       `json:"suse_cvrf,omitempty"`
 | 
			
		||||
	UbuntuOVAL            map[string]map[string]string `json:"ubuntu_oval,omitempty"`
 | 
			
		||||
	UbuntuSecurityTracker string                       `json:"ubuntu_security_tracker,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type CVSSes struct {
 | 
			
		||||
	NVD                   []CVSS                       `json:"nvd,omitempty"`
 | 
			
		||||
	JVN                   map[string][]CVSS            `json:"jvn,omitempty"`
 | 
			
		||||
	Alma                  map[string]map[string][]CVSS `json:"alma,omitempty"`
 | 
			
		||||
	Amazon                map[string]map[string][]CVSS `json:"amazon,omitempty"`
 | 
			
		||||
	Arch                  map[string][]CVSS            `json:"arch,omitempty"`
 | 
			
		||||
	Oracle                map[string]map[string][]CVSS `json:"oracle,omitempty"`
 | 
			
		||||
	RedHatOVAL            map[string]map[string][]CVSS `json:"redhat_oval,omitempty"`
 | 
			
		||||
	SUSEOVAL              map[string]map[string][]CVSS `json:"suse_oval,omitempty"`
 | 
			
		||||
	SUSECVRF              []CVSS                       `json:"suse_cvrf,omitempty"`
 | 
			
		||||
	UbuntuOVAL            map[string]map[string][]CVSS `json:"ubuntu_oval,omitempty"`
 | 
			
		||||
	UbuntuSecurityTracker []CVSS                       `json:"ubuntu_security_tracker,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type CVSS struct {
 | 
			
		||||
	Version  string   `json:"version,omitempty"`
 | 
			
		||||
	Source   string   `json:"source,omitempty"`
 | 
			
		||||
	Vector   string   `json:"vector,omitempty"`
 | 
			
		||||
	Score    *float64 `json:"score,omitempty"`
 | 
			
		||||
	Severity string   `json:"severity,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type EPSS struct {
 | 
			
		||||
	EPSS       *float64 `json:"epss,omitempty"`
 | 
			
		||||
	Percentile *float64 `json:"percentile,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type CWEs struct {
 | 
			
		||||
	NVD        []string                       `json:"nvd,omitempty"`
 | 
			
		||||
	JVN        map[string][]string            `json:"jvn,omitempty"`
 | 
			
		||||
	RedHatOVAL map[string]map[string][]string `json:"redhat_oval,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Metasploit struct {
 | 
			
		||||
	Name        string   `json:"name,omitempty"`
 | 
			
		||||
	Title       string   `json:"title,omitempty"`
 | 
			
		||||
	Description string   `json:"description,omitempty"`
 | 
			
		||||
	URLs        []string `json:"urls,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Exploit struct {
 | 
			
		||||
	NVD       []string    `json:"nvd,omitempty"`
 | 
			
		||||
	ExploitDB []ExploitDB `json:"exploit_db,omitempty"`
 | 
			
		||||
	GitHub    []GitHub    `json:"github,omitempty"`
 | 
			
		||||
	InTheWild []InTheWild `json:"inthewild,omitempty"`
 | 
			
		||||
	Trickest  *Trickest   `json:"trickest,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type ExploitDB struct {
 | 
			
		||||
	ID          string `json:"name,omitempty"`
 | 
			
		||||
	Type        string `json:"type,omitempty"`
 | 
			
		||||
	Description string `json:"description,omitempty"`
 | 
			
		||||
	URL         string `json:"url,omitempty"`
 | 
			
		||||
	FileURL     string `json:"file_url,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type GitHub struct {
 | 
			
		||||
	Name    string `json:"name,omitempty"`
 | 
			
		||||
	Stars   int    `json:"stars"`
 | 
			
		||||
	Forks   int    `json:"forks"`
 | 
			
		||||
	Watches int    `json:"watches"`
 | 
			
		||||
	URL     string `json:"url,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type InTheWild struct {
 | 
			
		||||
	Source string `json:"source,omitempty"`
 | 
			
		||||
	URL    string `json:"url,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Trickest struct {
 | 
			
		||||
	Description string       `json:"description,omitempty"`
 | 
			
		||||
	PoC         *TrickestPoc `json:"poc,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type TrickestPoc struct {
 | 
			
		||||
	Reference []string `json:"reference,omitempty"`
 | 
			
		||||
	GitHub    []string `json:"github,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type KEV struct {
 | 
			
		||||
	Title          string     `json:"title,omitempty"`
 | 
			
		||||
	Description    string     `json:"description,omitempty"`
 | 
			
		||||
	RequiredAction string     `json:"required_action,omitempty"`
 | 
			
		||||
	DueDate        *time.Time `json:"due_date,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Mitigation struct {
 | 
			
		||||
	NVD                   []string `json:"nvd,omitempty"`
 | 
			
		||||
	UbuntuSecurityTracker string   `json:"ubuntu_security_tracker,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Publisheds struct {
 | 
			
		||||
	MITRE                 *time.Time                       `json:"mitre,omitempty"`
 | 
			
		||||
	NVD                   *time.Time                       `json:"nvd,omitempty"`
 | 
			
		||||
	JVN                   map[string]*time.Time            `json:"jvn,omitempty"`
 | 
			
		||||
	Alma                  map[string]map[string]*time.Time `json:"alma,omitempty"`
 | 
			
		||||
	Amazon                map[string]map[string]*time.Time `json:"amazon,omitempty"`
 | 
			
		||||
	FreeBSD               map[string]*time.Time            `json:"freebsd,omitempty"`
 | 
			
		||||
	Oracle                map[string]map[string]*time.Time `json:"oracle,omitempty"`
 | 
			
		||||
	RedHatOVAL            map[string]map[string]*time.Time `json:"redhat_oval,omitempty"`
 | 
			
		||||
	SUSEOVAL              map[string]map[string]*time.Time `json:"suse_oval,omitempty"`
 | 
			
		||||
	SUSECVRF              *time.Time                       `json:"suse_cvrf,omitempty"`
 | 
			
		||||
	UbuntuOVAL            map[string]map[string]*time.Time `json:"ubuntu_oval,omitempty"`
 | 
			
		||||
	UbuntuSecurityTracker *time.Time                       `json:"ubuntu_security_tracker,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Modifieds struct {
 | 
			
		||||
	MITRE      *time.Time                       `json:"mitre,omitempty"`
 | 
			
		||||
	NVD        *time.Time                       `json:"nvd,omitempty"`
 | 
			
		||||
	JVN        map[string]*time.Time            `json:"jvn,omitempty"`
 | 
			
		||||
	Alma       map[string]map[string]*time.Time `json:"alma,omitempty"`
 | 
			
		||||
	Amazon     map[string]map[string]*time.Time `json:"amazon,omitempty"`
 | 
			
		||||
	FreeBSD    map[string]*time.Time            `json:"freebsd,omitempty"`
 | 
			
		||||
	RedHatOVAL map[string]map[string]*time.Time `json:"redhat_oval,omitempty"`
 | 
			
		||||
	SUSEOVAL   map[string]map[string]*time.Time `json:"suse_oval,omitempty"`
 | 
			
		||||
	SUSECVRF   *time.Time                       `json:"suse_cvrf,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type References struct {
 | 
			
		||||
	MITRE                 []Reference                       `json:"mitre,omitempty"`
 | 
			
		||||
	NVD                   []Reference                       `json:"nvd,omitempty"`
 | 
			
		||||
	JVN                   map[string][]Reference            `json:"jvn,omitempty"`
 | 
			
		||||
	Alma                  map[string]map[string][]Reference `json:"alma,omitempty"`
 | 
			
		||||
	Amazon                map[string]map[string][]Reference `json:"amazon,omitempty"`
 | 
			
		||||
	Arch                  map[string][]Reference            `json:"arch,omitempty"`
 | 
			
		||||
	DebianOVAL            map[string]map[string][]Reference `json:"debian_oval,omitempty"`
 | 
			
		||||
	DebianSecurityTracker map[string][]Reference            `json:"debian_security_tracker,omitempty"`
 | 
			
		||||
	FreeBSD               map[string][]Reference            `json:"freebsd,omitempty"`
 | 
			
		||||
	Oracle                map[string]map[string][]Reference `json:"oracle,omitempty"`
 | 
			
		||||
	RedHatOVAL            map[string]map[string][]Reference `json:"redhat_oval,omitempty"`
 | 
			
		||||
	SUSEOVAL              map[string]map[string][]Reference `json:"suse_oval,omitempty"`
 | 
			
		||||
	SUSECVRF              []Reference                       `json:"suse_cvrf,omitempty"`
 | 
			
		||||
	UbuntuOVAL            map[string]map[string][]Reference `json:"ubuntu_oval,omitempty"`
 | 
			
		||||
	UbuntuSecurityTracker []Reference                       `json:"ubuntu_security_tracker,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Reference struct {
 | 
			
		||||
	Source string   `json:"source,omitempty"`
 | 
			
		||||
	Name   string   `json:"name,omitempty"`
 | 
			
		||||
	Tags   []string `json:"tags,omitempty"`
 | 
			
		||||
	URL    string   `json:"url,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type DetectCPE struct {
 | 
			
		||||
	ID             string                        `json:"id,omitempty"`
 | 
			
		||||
	Configurations map[string][]CPEConfiguration `json:"configurations,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type CPEConfiguration struct {
 | 
			
		||||
	Vulnerable []CPE `json:"vulnerable,omitempty"`
 | 
			
		||||
	RunningOn  []CPE `json:"running_on,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type CPE struct {
 | 
			
		||||
	CPEVersion string    `json:"cpe_version,omitempty"`
 | 
			
		||||
	CPE        string    `json:"cpe,omitempty"`
 | 
			
		||||
	Version    []Version `json:"version,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type DetectPackage struct {
 | 
			
		||||
	ID       string               `json:"id,omitempty"`
 | 
			
		||||
	Packages map[string][]Package `json:"packages,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Package struct {
 | 
			
		||||
	Name            string      `json:"name,omitempty"`
 | 
			
		||||
	Status          string      `json:"status,omitempty"`
 | 
			
		||||
	Version         [][]Version `json:"version,omitempty"`
 | 
			
		||||
	ModularityLabel string      `json:"modularity_label,omitempty"`
 | 
			
		||||
	Arch            []string    `json:"arch,omitempty"`
 | 
			
		||||
	Repository      string      `json:"repository,omitempty"`
 | 
			
		||||
	CPE             []string    `json:"cpe,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Version struct {
 | 
			
		||||
	Operator string `json:"operator,omitempty"`
 | 
			
		||||
	Version  string `json:"version,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type RepositoryToCPE map[string][]string
 | 
			
		||||
 | 
			
		||||
type Supercedence struct {
 | 
			
		||||
	KBID         string `json:"KBID,omitempty"`
 | 
			
		||||
	Supersededby struct {
 | 
			
		||||
		KBIDs []string `json:"KBIDs,omitempty"`
 | 
			
		||||
	} `json:"Supersededby,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										619
									
								
								pkg/cmd/db/create/vulnsrc/vulnsrc.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										619
									
								
								pkg/cmd/db/create/vulnsrc/vulnsrc.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,619 @@
 | 
			
		||||
package vulnsrc
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"golang.org/x/exp/maps"
 | 
			
		||||
 | 
			
		||||
	"github.com/knqyf263/go-cpe/common"
 | 
			
		||||
	"github.com/knqyf263/go-cpe/naming"
 | 
			
		||||
	"github.com/pkg/errors"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/pkg/db/types"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func ToVulsVulnerability(src Vulnerability) types.Vulnerability {
 | 
			
		||||
	return types.Vulnerability{
 | 
			
		||||
		ID:          src.ID,
 | 
			
		||||
		Advisory:    toVulsAdvisory(src.Advisory),
 | 
			
		||||
		Title:       toVulsTitle(src.Title),
 | 
			
		||||
		Description: toVulsDescription(src.Description),
 | 
			
		||||
		CVSS:        toVulsCVSS(src.CVSS),
 | 
			
		||||
		EPSS:        toVulsEPSS(src.EPSS),
 | 
			
		||||
		CWE:         toVulsCWE(src.CWE),
 | 
			
		||||
		Metasploit:  toVulsMetasploit(src.Metasploit),
 | 
			
		||||
		Exploit:     toVulsExploit(src.Exploit),
 | 
			
		||||
		KEV:         src.KEV != nil,
 | 
			
		||||
		Published:   toVulsPublished(src.Published),
 | 
			
		||||
		Modified:    toVulsModified(src.Modified),
 | 
			
		||||
		Reference:   toVulsReference(src.References),
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func toVulsAdvisory(src *Advisories) []string {
 | 
			
		||||
	if src == nil {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var advs []string
 | 
			
		||||
	if src.MITRE != nil {
 | 
			
		||||
		advs = append(advs, "mitre")
 | 
			
		||||
	}
 | 
			
		||||
	if src.NVD != nil {
 | 
			
		||||
		advs = append(advs, "nvd")
 | 
			
		||||
	}
 | 
			
		||||
	for _, a := range src.JVN {
 | 
			
		||||
		advs = append(advs, fmt.Sprintf("jvn:%s", a.ID))
 | 
			
		||||
	}
 | 
			
		||||
	for v, as := range src.Alma {
 | 
			
		||||
		for _, a := range as {
 | 
			
		||||
			advs = append(advs, fmt.Sprintf("alma:%s:%s", v, a.ID))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	for v, a := range src.Alpine {
 | 
			
		||||
		advs = append(advs, fmt.Sprintf("alpine:%s:%s", v, a.ID))
 | 
			
		||||
	}
 | 
			
		||||
	for v, as := range src.Amazon {
 | 
			
		||||
		for _, a := range as {
 | 
			
		||||
			advs = append(advs, fmt.Sprintf("amazon:%s:%s", v, a.ID))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	for _, a := range src.Arch {
 | 
			
		||||
		advs = append(advs, fmt.Sprintf("arch:%s", a.ID))
 | 
			
		||||
	}
 | 
			
		||||
	for v, as := range src.DebianOVAL {
 | 
			
		||||
		for _, a := range as {
 | 
			
		||||
			advs = append(advs, fmt.Sprintf("debian_oval:%s:%s", v, a.ID))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	for v, a := range src.DebianSecurityTracker {
 | 
			
		||||
		advs = append(advs, fmt.Sprintf("debian_security_tracker:%s:%s", v, a.ID))
 | 
			
		||||
	}
 | 
			
		||||
	for _, a := range src.FreeBSD {
 | 
			
		||||
		advs = append(advs, fmt.Sprintf("freebsd:%s", a.ID))
 | 
			
		||||
	}
 | 
			
		||||
	for v, as := range src.Oracle {
 | 
			
		||||
		for _, a := range as {
 | 
			
		||||
			advs = append(advs, fmt.Sprintf("oracle:%s:%s", v, a.ID))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	for v, as := range src.RedHatOVAL {
 | 
			
		||||
		for _, a := range as {
 | 
			
		||||
			advs = append(advs, fmt.Sprintf("redhat_oval:%s:%s", v, a.ID))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	for v, as := range src.SUSEOVAL {
 | 
			
		||||
		for _, a := range as {
 | 
			
		||||
			advs = append(advs, fmt.Sprintf("suse_oval:%s:%s", v, a.ID))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if src.SUSECVRF != nil {
 | 
			
		||||
		advs = append(advs, "suse_cvrf")
 | 
			
		||||
	}
 | 
			
		||||
	for v, as := range src.UbuntuOVAL {
 | 
			
		||||
		for _, a := range as {
 | 
			
		||||
			advs = append(advs, fmt.Sprintf("ubuntu_oval:%s:%s", v, a.ID))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if src.UbuntuSecurityTracker != nil {
 | 
			
		||||
		advs = append(advs, "ubuntu_security_tracker")
 | 
			
		||||
	}
 | 
			
		||||
	return advs
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func toVulsTitle(src *Titles) string {
 | 
			
		||||
	if src == nil {
 | 
			
		||||
		return ""
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if src.NVD != "" {
 | 
			
		||||
		return src.NVD
 | 
			
		||||
	}
 | 
			
		||||
	if src.MITRE != "" {
 | 
			
		||||
		return src.MITRE
 | 
			
		||||
	}
 | 
			
		||||
	return ""
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func toVulsDescription(src *Descriptions) string {
 | 
			
		||||
	if src == nil {
 | 
			
		||||
		return ""
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if src.NVD != "" {
 | 
			
		||||
		return src.NVD
 | 
			
		||||
	}
 | 
			
		||||
	if src.MITRE != "" {
 | 
			
		||||
		return src.MITRE
 | 
			
		||||
	}
 | 
			
		||||
	return ""
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func toVulsCVSS(src *CVSSes) []types.CVSS {
 | 
			
		||||
	if src == nil {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var cvsses []types.CVSS
 | 
			
		||||
	for _, c := range src.NVD {
 | 
			
		||||
		cvsses = append(cvsses, types.CVSS{
 | 
			
		||||
			Source:   "nvd",
 | 
			
		||||
			Version:  c.Version,
 | 
			
		||||
			Vector:   c.Vector,
 | 
			
		||||
			Score:    c.Score,
 | 
			
		||||
			Severity: c.Severity,
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
	for id, cs := range src.JVN {
 | 
			
		||||
		for _, c := range cs {
 | 
			
		||||
			cvsses = append(cvsses, types.CVSS{
 | 
			
		||||
				Source:   fmt.Sprintf("jvn:%s", id),
 | 
			
		||||
				Version:  c.Version,
 | 
			
		||||
				Vector:   c.Vector,
 | 
			
		||||
				Score:    c.Score,
 | 
			
		||||
				Severity: c.Severity,
 | 
			
		||||
			})
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	for v, idcs := range src.Alma {
 | 
			
		||||
		for id, cs := range idcs {
 | 
			
		||||
			for _, c := range cs {
 | 
			
		||||
				cvsses = append(cvsses, types.CVSS{
 | 
			
		||||
					Source:   fmt.Sprintf("alma:%s:%s", v, id),
 | 
			
		||||
					Version:  c.Version,
 | 
			
		||||
					Vector:   c.Vector,
 | 
			
		||||
					Score:    c.Score,
 | 
			
		||||
					Severity: c.Severity,
 | 
			
		||||
				})
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	for v, idcs := range src.Amazon {
 | 
			
		||||
		for id, cs := range idcs {
 | 
			
		||||
			for _, c := range cs {
 | 
			
		||||
				cvsses = append(cvsses, types.CVSS{
 | 
			
		||||
					Source:   fmt.Sprintf("amazon:%s:%s", v, id),
 | 
			
		||||
					Version:  c.Version,
 | 
			
		||||
					Vector:   c.Vector,
 | 
			
		||||
					Score:    c.Score,
 | 
			
		||||
					Severity: c.Severity,
 | 
			
		||||
				})
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	for id, cs := range src.Arch {
 | 
			
		||||
		for _, c := range cs {
 | 
			
		||||
			cvsses = append(cvsses, types.CVSS{
 | 
			
		||||
				Source:   fmt.Sprintf("arch:%s", id),
 | 
			
		||||
				Version:  c.Version,
 | 
			
		||||
				Vector:   c.Vector,
 | 
			
		||||
				Score:    c.Score,
 | 
			
		||||
				Severity: c.Severity,
 | 
			
		||||
			})
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	for v, idcs := range src.Oracle {
 | 
			
		||||
		for id, cs := range idcs {
 | 
			
		||||
			for _, c := range cs {
 | 
			
		||||
				cvsses = append(cvsses, types.CVSS{
 | 
			
		||||
					Source:   fmt.Sprintf("oracle:%s:%s", v, id),
 | 
			
		||||
					Version:  c.Version,
 | 
			
		||||
					Vector:   c.Vector,
 | 
			
		||||
					Score:    c.Score,
 | 
			
		||||
					Severity: c.Severity,
 | 
			
		||||
				})
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	for v, idcs := range src.RedHatOVAL {
 | 
			
		||||
		for id, cs := range idcs {
 | 
			
		||||
			for _, c := range cs {
 | 
			
		||||
				cvsses = append(cvsses, types.CVSS{
 | 
			
		||||
					Source:   fmt.Sprintf("redhat_oval:%s:%s", v, id),
 | 
			
		||||
					Version:  c.Version,
 | 
			
		||||
					Vector:   c.Vector,
 | 
			
		||||
					Score:    c.Score,
 | 
			
		||||
					Severity: c.Severity,
 | 
			
		||||
				})
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	for v, idcs := range src.SUSEOVAL {
 | 
			
		||||
		for id, cs := range idcs {
 | 
			
		||||
			for _, c := range cs {
 | 
			
		||||
				cvsses = append(cvsses, types.CVSS{
 | 
			
		||||
					Source:   fmt.Sprintf("suse_oval:%s:%s", v, id),
 | 
			
		||||
					Version:  c.Version,
 | 
			
		||||
					Vector:   c.Vector,
 | 
			
		||||
					Score:    c.Score,
 | 
			
		||||
					Severity: c.Severity,
 | 
			
		||||
				})
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	for _, c := range src.SUSECVRF {
 | 
			
		||||
		cvsses = append(cvsses, types.CVSS{
 | 
			
		||||
			Source:   "suse_cvrf",
 | 
			
		||||
			Version:  c.Version,
 | 
			
		||||
			Vector:   c.Vector,
 | 
			
		||||
			Score:    c.Score,
 | 
			
		||||
			Severity: c.Severity,
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
	for v, idcs := range src.UbuntuOVAL {
 | 
			
		||||
		for id, cs := range idcs {
 | 
			
		||||
			for _, c := range cs {
 | 
			
		||||
				cvsses = append(cvsses, types.CVSS{
 | 
			
		||||
					Source:   fmt.Sprintf("ubuntu_oval:%s:%s", v, id),
 | 
			
		||||
					Version:  c.Version,
 | 
			
		||||
					Vector:   c.Vector,
 | 
			
		||||
					Score:    c.Score,
 | 
			
		||||
					Severity: c.Severity,
 | 
			
		||||
				})
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	for _, c := range src.UbuntuSecurityTracker {
 | 
			
		||||
		cvsses = append(cvsses, types.CVSS{
 | 
			
		||||
			Source:   "ubuntu_security_tracker",
 | 
			
		||||
			Version:  c.Version,
 | 
			
		||||
			Vector:   c.Vector,
 | 
			
		||||
			Score:    c.Score,
 | 
			
		||||
			Severity: c.Severity,
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return cvsses
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func toVulsEPSS(src *EPSS) *types.EPSS {
 | 
			
		||||
	if src == nil {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	return &types.EPSS{EPSS: src.EPSS, Percentile: src.Percentile}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func toVulsCWE(src *CWEs) []types.CWE {
 | 
			
		||||
	if src == nil {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	m := map[string][]string{}
 | 
			
		||||
	for _, c := range src.NVD {
 | 
			
		||||
		m[c] = append(m[c], "nvd")
 | 
			
		||||
	}
 | 
			
		||||
	for id, cs := range src.JVN {
 | 
			
		||||
		for _, c := range cs {
 | 
			
		||||
			m[c] = append(m[c], fmt.Sprintf("jvn:%s", id))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	for v, idcs := range src.RedHatOVAL {
 | 
			
		||||
		for id, cs := range idcs {
 | 
			
		||||
			for _, c := range cs {
 | 
			
		||||
				m[c] = append(m[c], fmt.Sprintf("redhat_oval:%s:%s", v, id))
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var cwes []types.CWE
 | 
			
		||||
	for id, srcs := range m {
 | 
			
		||||
		cwes = append(cwes, types.CWE{
 | 
			
		||||
			Source: srcs,
 | 
			
		||||
			ID:     id,
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
	return cwes
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func toVulsMetasploit(src []Metasploit) []types.Metasploit {
 | 
			
		||||
	ms := make([]types.Metasploit, 0, len(src))
 | 
			
		||||
	for _, m := range src {
 | 
			
		||||
		ms = append(ms, types.Metasploit{
 | 
			
		||||
			Title: m.Title,
 | 
			
		||||
			URL:   m.URLs[0],
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
	return ms
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func toVulsExploit(src *Exploit) []types.Exploit {
 | 
			
		||||
	if src == nil {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	m := map[string][]string{}
 | 
			
		||||
	for _, e := range src.NVD {
 | 
			
		||||
		m[e] = append(m[e], "nvd")
 | 
			
		||||
	}
 | 
			
		||||
	for _, e := range src.ExploitDB {
 | 
			
		||||
		m[e.URL] = append(m[e.URL], "exploit-db")
 | 
			
		||||
	}
 | 
			
		||||
	for _, e := range src.GitHub {
 | 
			
		||||
		m[e.URL] = append(m[e.URL], "github")
 | 
			
		||||
	}
 | 
			
		||||
	for _, e := range src.InTheWild {
 | 
			
		||||
		m[e.URL] = append(m[e.URL], "inthewild")
 | 
			
		||||
	}
 | 
			
		||||
	if src.Trickest != nil {
 | 
			
		||||
		if src.Trickest.PoC != nil {
 | 
			
		||||
			for _, e := range src.Trickest.PoC.Reference {
 | 
			
		||||
				m[e] = append(m[e], "trickest")
 | 
			
		||||
			}
 | 
			
		||||
			for _, e := range src.Trickest.PoC.GitHub {
 | 
			
		||||
				m[e] = append(m[e], "trickest")
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var es []types.Exploit
 | 
			
		||||
	for u, srcs := range m {
 | 
			
		||||
		es = append(es, types.Exploit{
 | 
			
		||||
			Source: srcs,
 | 
			
		||||
			URL:    u,
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
	return es
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func toVulsPublished(src *Publisheds) *time.Time {
 | 
			
		||||
	if src == nil {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if src.NVD != nil {
 | 
			
		||||
		return src.NVD
 | 
			
		||||
	}
 | 
			
		||||
	if src.MITRE != nil {
 | 
			
		||||
		return src.MITRE
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func toVulsModified(src *Modifieds) *time.Time {
 | 
			
		||||
	if src == nil {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if src.NVD != nil {
 | 
			
		||||
		return src.NVD
 | 
			
		||||
	}
 | 
			
		||||
	if src.MITRE != nil {
 | 
			
		||||
		return src.MITRE
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func toVulsReference(src *References) []string {
 | 
			
		||||
	if src == nil {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	m := map[string]struct{}{}
 | 
			
		||||
	for _, r := range src.MITRE {
 | 
			
		||||
		m[r.URL] = struct{}{}
 | 
			
		||||
	}
 | 
			
		||||
	for _, r := range src.NVD {
 | 
			
		||||
		m[r.URL] = struct{}{}
 | 
			
		||||
	}
 | 
			
		||||
	for _, rs := range src.JVN {
 | 
			
		||||
		for _, r := range rs {
 | 
			
		||||
			m[r.URL] = struct{}{}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	for _, idrs := range src.Alma {
 | 
			
		||||
		for _, rs := range idrs {
 | 
			
		||||
			for _, r := range rs {
 | 
			
		||||
				m[r.URL] = struct{}{}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	for _, idrs := range src.Amazon {
 | 
			
		||||
		for _, rs := range idrs {
 | 
			
		||||
			for _, r := range rs {
 | 
			
		||||
				m[r.URL] = struct{}{}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	for _, rs := range src.Arch {
 | 
			
		||||
		for _, r := range rs {
 | 
			
		||||
			m[r.URL] = struct{}{}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	for _, idrs := range src.DebianOVAL {
 | 
			
		||||
		for _, rs := range idrs {
 | 
			
		||||
			for _, r := range rs {
 | 
			
		||||
				m[r.URL] = struct{}{}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	for _, rs := range src.DebianSecurityTracker {
 | 
			
		||||
		for _, r := range rs {
 | 
			
		||||
			m[r.URL] = struct{}{}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	for _, rs := range src.FreeBSD {
 | 
			
		||||
		for _, r := range rs {
 | 
			
		||||
			m[r.URL] = struct{}{}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	for _, idrs := range src.Oracle {
 | 
			
		||||
		for _, rs := range idrs {
 | 
			
		||||
			for _, r := range rs {
 | 
			
		||||
				m[r.URL] = struct{}{}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	for _, idrs := range src.RedHatOVAL {
 | 
			
		||||
		for _, rs := range idrs {
 | 
			
		||||
			for _, r := range rs {
 | 
			
		||||
				m[r.URL] = struct{}{}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	for _, idrs := range src.SUSEOVAL {
 | 
			
		||||
		for _, rs := range idrs {
 | 
			
		||||
			for _, r := range rs {
 | 
			
		||||
				m[r.URL] = struct{}{}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	for _, r := range src.SUSECVRF {
 | 
			
		||||
		m[r.URL] = struct{}{}
 | 
			
		||||
	}
 | 
			
		||||
	for _, idrs := range src.UbuntuOVAL {
 | 
			
		||||
		for _, rs := range idrs {
 | 
			
		||||
			for _, r := range rs {
 | 
			
		||||
				m[r.URL] = struct{}{}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	for _, r := range src.UbuntuSecurityTracker {
 | 
			
		||||
		m[r.URL] = struct{}{}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return maps.Keys(m)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func ToVulsPackage(src DetectPackage, advType string) (map[string]types.Packages, error) {
 | 
			
		||||
	m := map[string]types.Packages{}
 | 
			
		||||
	for id, ps := range src.Packages {
 | 
			
		||||
		id = fmt.Sprintf("%s:%s", advType, id)
 | 
			
		||||
		for _, p := range ps {
 | 
			
		||||
			name, err := toVulsPackageName(p.Name, p.ModularityLabel)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return nil, errors.Wrap(err, "to vuls package name")
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			base, ok := m[name]
 | 
			
		||||
			if !ok {
 | 
			
		||||
				base = types.Packages{
 | 
			
		||||
					ID:      src.ID,
 | 
			
		||||
					Package: map[string]types.Package{},
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			vers := make([][]types.Version, 0, len(p.Version))
 | 
			
		||||
			for _, vs := range p.Version {
 | 
			
		||||
				vss := make([]types.Version, 0, len(vs))
 | 
			
		||||
				for _, v := range vs {
 | 
			
		||||
					vss = append(vss, types.Version{
 | 
			
		||||
						Operator: v.Operator,
 | 
			
		||||
						Version:  v.Version,
 | 
			
		||||
					})
 | 
			
		||||
				}
 | 
			
		||||
				vers = append(vers, vss)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			base.Package[id] = types.Package{
 | 
			
		||||
				Status:     p.Status,
 | 
			
		||||
				Version:    vers,
 | 
			
		||||
				Arch:       p.Arch,
 | 
			
		||||
				Repository: p.Repository,
 | 
			
		||||
				CPE:        p.CPE,
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			m[name] = base
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return m, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func toVulsPackageName(name, modularitylabel string) (string, error) {
 | 
			
		||||
	if modularitylabel == "" {
 | 
			
		||||
		return name, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ss := strings.Split(modularitylabel, ":")
 | 
			
		||||
	if len(ss) < 2 {
 | 
			
		||||
		return name, errors.Errorf(`[WARN] unexpected modularitylabel. accepts: "<module name>:<stream>(:<version>:<context>:<arch>)", received: "%s"`, modularitylabel)
 | 
			
		||||
	}
 | 
			
		||||
	return fmt.Sprintf("%s:%s::%s", ss[0], ss[1], name), nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func ToVulsCPEConfiguration(src DetectCPE, advType string) (map[string]types.CPEConfigurations, error) {
 | 
			
		||||
	m := map[string][]types.CPEConfiguration{}
 | 
			
		||||
	for id, cs := range src.Configurations {
 | 
			
		||||
		id = fmt.Sprintf("%s:%s", advType, id)
 | 
			
		||||
		for _, c := range cs {
 | 
			
		||||
			rs := make([]types.CPE, 0, len(c.RunningOn))
 | 
			
		||||
			for _, r := range c.RunningOn {
 | 
			
		||||
				rs = append(rs, toVulnsrcCPEtoVulsCPE(r))
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			for _, v := range c.Vulnerable {
 | 
			
		||||
				m[id] = append(m[id], types.CPEConfiguration{
 | 
			
		||||
					Vulnerable: toVulnsrcCPEtoVulsCPE(v),
 | 
			
		||||
					RunningOn:  rs,
 | 
			
		||||
				})
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	m2 := map[string]types.CPEConfigurations{}
 | 
			
		||||
	for id, cs := range m {
 | 
			
		||||
		for _, c := range cs {
 | 
			
		||||
			pvp, err := toVulsCPEConfigurationName(c.Vulnerable.CPEVersion, c.Vulnerable.CPE)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return nil, errors.Wrap(err, "to vuls cpe configuration name")
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			base, ok := m2[pvp]
 | 
			
		||||
			if !ok {
 | 
			
		||||
				base = types.CPEConfigurations{
 | 
			
		||||
					ID:            src.ID,
 | 
			
		||||
					Configuration: map[string][]types.CPEConfiguration{},
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			base.Configuration[id] = append(base.Configuration[id], c)
 | 
			
		||||
 | 
			
		||||
			m2[pvp] = base
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return m2, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func toVulsCPEConfigurationName(version string, cpe string) (string, error) {
 | 
			
		||||
	var (
 | 
			
		||||
		wfn common.WellFormedName
 | 
			
		||||
		err error
 | 
			
		||||
	)
 | 
			
		||||
	switch version {
 | 
			
		||||
	case "2.3":
 | 
			
		||||
		wfn, err = naming.UnbindFS(cpe)
 | 
			
		||||
	default:
 | 
			
		||||
		wfn, err = naming.UnbindURI(cpe)
 | 
			
		||||
	}
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "", errors.Wrapf(err, "unbind %s", cpe)
 | 
			
		||||
	}
 | 
			
		||||
	return fmt.Sprintf("%s:%s:%s", wfn.GetString(common.AttributePart), wfn.GetString(common.AttributeVendor), wfn.GetString(common.AttributeProduct)), nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func toVulnsrcCPEtoVulsCPE(s CPE) types.CPE {
 | 
			
		||||
	d := types.CPE{
 | 
			
		||||
		CPEVersion: s.CPEVersion,
 | 
			
		||||
		CPE:        s.CPE,
 | 
			
		||||
	}
 | 
			
		||||
	for _, v := range s.Version {
 | 
			
		||||
		d.Version = append(d.Version, types.Version{
 | 
			
		||||
			Operator: v.Operator,
 | 
			
		||||
			Version:  v.Version,
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
	return d
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func ToVulsRepositoryToCPE(src RepositoryToCPE) types.RepositoryToCPE {
 | 
			
		||||
	return types.RepositoryToCPE(src)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func ToVulsSupercedences(src []Supercedence) types.Supercedence {
 | 
			
		||||
	ss := types.Supercedence{}
 | 
			
		||||
	for _, s := range src {
 | 
			
		||||
		ss[s.KBID] = s.Supersededby.KBIDs
 | 
			
		||||
	}
 | 
			
		||||
	return ss
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										37
									
								
								pkg/cmd/db/db.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								pkg/cmd/db/db.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,37 @@
 | 
			
		||||
package db
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"github.com/MakeNowJust/heredoc"
 | 
			
		||||
	"github.com/spf13/cobra"
 | 
			
		||||
 | 
			
		||||
	dbCreateCmd "github.com/future-architect/vuls/pkg/cmd/db/create"
 | 
			
		||||
	dbEditCmd "github.com/future-architect/vuls/pkg/cmd/db/edit"
 | 
			
		||||
	dbFetchCmd "github.com/future-architect/vuls/pkg/cmd/db/fetch"
 | 
			
		||||
	dbSearchCmd "github.com/future-architect/vuls/pkg/cmd/db/search"
 | 
			
		||||
	dbUploadCmd "github.com/future-architect/vuls/pkg/cmd/db/upload"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func NewCmdDB() *cobra.Command {
 | 
			
		||||
	cmd := &cobra.Command{
 | 
			
		||||
		Use:   "db <subcommand>",
 | 
			
		||||
		Short: "Vuls DB Operation",
 | 
			
		||||
		Example: heredoc.Doc(`
 | 
			
		||||
			$ vuls db create https://github.com/vulsio/vuls-data.git
 | 
			
		||||
			$ vuls db create /home/MaineK00n/.cache/vuls
 | 
			
		||||
			$ vuls db edit ubuntu 22.04 openssl
 | 
			
		||||
			$ vuls db edit vulnerability CVE-2022-3602
 | 
			
		||||
			$ vuls db fetch
 | 
			
		||||
			$ vuls db fetch ghcr.io/vuls/db
 | 
			
		||||
			$ vuls db search ubuntu 22.04 openssl
 | 
			
		||||
			$ vuls db search vulnerability CVE-2022-3602
 | 
			
		||||
		`),
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	cmd.AddCommand(dbCreateCmd.NewCmdCreate())
 | 
			
		||||
	cmd.AddCommand(dbEditCmd.NewCmdEdit())
 | 
			
		||||
	cmd.AddCommand(dbFetchCmd.NewCmdFetch())
 | 
			
		||||
	cmd.AddCommand(dbSearchCmd.NewCmdSearch())
 | 
			
		||||
	cmd.AddCommand(dbUploadCmd.NewCmdUpload())
 | 
			
		||||
 | 
			
		||||
	return cmd
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										23
									
								
								pkg/cmd/db/edit/edit.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								pkg/cmd/db/edit/edit.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,23 @@
 | 
			
		||||
package edit
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"github.com/MakeNowJust/heredoc"
 | 
			
		||||
	"github.com/spf13/cobra"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func NewCmdEdit() *cobra.Command {
 | 
			
		||||
	cmd := &cobra.Command{
 | 
			
		||||
		Use:   "edit",
 | 
			
		||||
		Short: "Edit Data in Vuls DB",
 | 
			
		||||
		Args:  cobra.RangeArgs(2, 3),
 | 
			
		||||
		RunE: func(_ *cobra.Command, _ []string) error {
 | 
			
		||||
			return nil
 | 
			
		||||
		},
 | 
			
		||||
		Example: heredoc.Doc(`
 | 
			
		||||
			$ vuls db edit ubuntu 22.04 openssl
 | 
			
		||||
			$ vuls db edit vulnerability CVE-2022-3602
 | 
			
		||||
		`),
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return cmd
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										152
									
								
								pkg/cmd/db/fetch/fetch.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										152
									
								
								pkg/cmd/db/fetch/fetch.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,152 @@
 | 
			
		||||
package fetch
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"archive/tar"
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"compress/gzip"
 | 
			
		||||
	"context"
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"io"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
 | 
			
		||||
	"github.com/MakeNowJust/heredoc"
 | 
			
		||||
	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
 | 
			
		||||
	"github.com/pkg/errors"
 | 
			
		||||
	"github.com/spf13/cobra"
 | 
			
		||||
	"oras.land/oras-go/v2/content"
 | 
			
		||||
	"oras.land/oras-go/v2/registry/remote"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type DBFetchOption struct {
 | 
			
		||||
	Path      string
 | 
			
		||||
	PlainHTTP bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	defaultVulsDBRepository = "ghcr.io/mainek00n/vuls-data/vuls-db"
 | 
			
		||||
	defaultTag              = "latest"
 | 
			
		||||
	vulsDBConfigMediaType   = "application/vnd.vuls.vuls.db"
 | 
			
		||||
	vulsDBLayerMediaType    = "application/vnd.vuls.vuls.db.layer.v1.tar+gzip"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func NewCmdFetch() *cobra.Command {
 | 
			
		||||
	opts := &DBFetchOption{
 | 
			
		||||
		Path: "vuls.db",
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	cmd := &cobra.Command{
 | 
			
		||||
		Use:   "fetch",
 | 
			
		||||
		Short: "Fetch Vuls DB",
 | 
			
		||||
		Args:  cobra.MaximumNArgs(1),
 | 
			
		||||
		RunE: func(_ *cobra.Command, args []string) error {
 | 
			
		||||
			p := defaultVulsDBRepository
 | 
			
		||||
			if len(args) > 0 {
 | 
			
		||||
				p = args[0]
 | 
			
		||||
			}
 | 
			
		||||
			return fetch(context.Background(), p, opts.Path, opts.PlainHTTP)
 | 
			
		||||
		},
 | 
			
		||||
		Example: heredoc.Doc(`
 | 
			
		||||
			$ vuls db fetch
 | 
			
		||||
			$ vuls db fetch ghcr.io/vuls/db
 | 
			
		||||
		`),
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	cmd.Flags().StringVarP(&opts.Path, "path", "p", "vuls.db", "path to fetch Vuls DB")
 | 
			
		||||
	cmd.Flags().BoolVarP(&opts.PlainHTTP, "plain-http", "", false, "container registry is provided with plain http")
 | 
			
		||||
 | 
			
		||||
	return cmd
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func fetch(ctx context.Context, ref, dbpath string, plainHTTP bool) error {
 | 
			
		||||
	repo, err := remote.NewRepository(ref)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return errors.WithStack(err)
 | 
			
		||||
	}
 | 
			
		||||
	if plainHTTP {
 | 
			
		||||
		repo.PlainHTTP = true
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	desc, err := repo.Resolve(ctx, defaultTag)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return errors.WithStack(err)
 | 
			
		||||
	}
 | 
			
		||||
	pulledBlob, err := content.FetchAll(ctx, repo, desc)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return errors.WithStack(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var manifest ocispec.Manifest
 | 
			
		||||
	if err := json.Unmarshal(pulledBlob, &manifest); err != nil {
 | 
			
		||||
		return errors.WithStack(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if manifest.Config.MediaType != vulsDBConfigMediaType {
 | 
			
		||||
		return errors.New("not vuls repository")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, l := range manifest.Layers {
 | 
			
		||||
		if l.MediaType != vulsDBLayerMediaType {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		desc, err := repo.Blobs().Resolve(ctx, l.Digest.String())
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return errors.WithStack(err)
 | 
			
		||||
		}
 | 
			
		||||
		rc, err := repo.Fetch(ctx, desc)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return errors.WithStack(err)
 | 
			
		||||
		}
 | 
			
		||||
		defer rc.Close()
 | 
			
		||||
 | 
			
		||||
		bs, err := content.ReadAll(rc, desc)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return errors.WithStack(err)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		gr, err := gzip.NewReader(bytes.NewReader(bs))
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return errors.WithStack(err)
 | 
			
		||||
		}
 | 
			
		||||
		defer gr.Close()
 | 
			
		||||
 | 
			
		||||
		tr := tar.NewReader(gr)
 | 
			
		||||
		for {
 | 
			
		||||
			header, err := tr.Next()
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				if err == io.EOF {
 | 
			
		||||
					break
 | 
			
		||||
				}
 | 
			
		||||
				return errors.Wrap(err, "Next()")
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			switch header.Typeflag {
 | 
			
		||||
			case tar.TypeDir:
 | 
			
		||||
				if err := os.MkdirAll(filepath.Join(dbpath, header.Name), header.FileInfo().Mode()); err != nil {
 | 
			
		||||
					return errors.WithStack(err)
 | 
			
		||||
				}
 | 
			
		||||
			case tar.TypeReg:
 | 
			
		||||
				if err := func() error {
 | 
			
		||||
					f, err := os.OpenFile(dbpath, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, os.FileMode(header.Mode))
 | 
			
		||||
					if err != nil {
 | 
			
		||||
						return errors.WithStack(err)
 | 
			
		||||
					}
 | 
			
		||||
					defer f.Close()
 | 
			
		||||
 | 
			
		||||
					if _, err := io.Copy(f, tr); err != nil {
 | 
			
		||||
						return errors.WithStack(err)
 | 
			
		||||
					}
 | 
			
		||||
 | 
			
		||||
					return nil
 | 
			
		||||
				}(); err != nil {
 | 
			
		||||
					return err
 | 
			
		||||
				}
 | 
			
		||||
			default:
 | 
			
		||||
				return errors.Errorf("unknown type: %s in %s", header.Typeflag, header.Name)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										23
									
								
								pkg/cmd/db/search/search.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								pkg/cmd/db/search/search.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,23 @@
 | 
			
		||||
package search
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"github.com/MakeNowJust/heredoc"
 | 
			
		||||
	"github.com/spf13/cobra"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func NewCmdSearch() *cobra.Command {
 | 
			
		||||
	cmd := &cobra.Command{
 | 
			
		||||
		Use:   "search",
 | 
			
		||||
		Short: "Search Vulnerabilty/Package in Vuls DB",
 | 
			
		||||
		Args:  cobra.RangeArgs(2, 3),
 | 
			
		||||
		RunE: func(_ *cobra.Command, _ []string) error {
 | 
			
		||||
			return nil
 | 
			
		||||
		},
 | 
			
		||||
		Example: heredoc.Doc(`
 | 
			
		||||
			$ vuls db search ubuntu 22.04 openssl
 | 
			
		||||
			$ vuls db search vulnerability CVE-2022-3602
 | 
			
		||||
		`),
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return cmd
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										23
									
								
								pkg/cmd/db/upload/upload.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								pkg/cmd/db/upload/upload.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,23 @@
 | 
			
		||||
package upload
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"github.com/MakeNowJust/heredoc"
 | 
			
		||||
	"github.com/spf13/cobra"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func NewCmdUpload() *cobra.Command {
 | 
			
		||||
	cmd := &cobra.Command{
 | 
			
		||||
		Use:   "upload",
 | 
			
		||||
		Short: "Upload Vuls DB",
 | 
			
		||||
		Args:  cobra.MaximumNArgs(1),
 | 
			
		||||
		RunE: func(_ *cobra.Command, _ []string) error {
 | 
			
		||||
			return nil
 | 
			
		||||
		},
 | 
			
		||||
		Example: heredoc.Doc(`
 | 
			
		||||
			$ vuls db upload
 | 
			
		||||
			$ vuls db upload ghcr.io/vuls/db
 | 
			
		||||
		`),
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return cmd
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										183
									
								
								pkg/cmd/detect/detect.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										183
									
								
								pkg/cmd/detect/detect.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,183 @@
 | 
			
		||||
package detect
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io/fs"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/MakeNowJust/heredoc"
 | 
			
		||||
	"github.com/pkg/errors"
 | 
			
		||||
	"github.com/spf13/cobra"
 | 
			
		||||
	"go.uber.org/zap"
 | 
			
		||||
	"golang.org/x/exp/slices"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/pkg/config"
 | 
			
		||||
	"github.com/future-architect/vuls/pkg/detect"
 | 
			
		||||
	"github.com/future-architect/vuls/pkg/log"
 | 
			
		||||
	"github.com/future-architect/vuls/pkg/types"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type DetectOptions struct {
 | 
			
		||||
	Config string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewCmdDetect() *cobra.Command {
 | 
			
		||||
	opts := &DetectOptions{
 | 
			
		||||
		Config: "config.json",
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	cmd := &cobra.Command{
 | 
			
		||||
		Use:   "detect ([\"host\"])",
 | 
			
		||||
		Short: "Vuls detect vulnerabilities",
 | 
			
		||||
		RunE: func(_ *cobra.Command, args []string) error {
 | 
			
		||||
			if err := exec(context.Background(), opts.Config, args); err != nil {
 | 
			
		||||
				return errors.Wrap(err, "failed to detect")
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			return nil
 | 
			
		||||
		},
 | 
			
		||||
		Example: heredoc.Doc(`
 | 
			
		||||
			$ vuls detect
 | 
			
		||||
			$ vuls detect results/**/host.json
 | 
			
		||||
		`),
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	cmd.Flags().StringVarP(&opts.Config, "config", "c", "config.json", "vuls config file path")
 | 
			
		||||
 | 
			
		||||
	return cmd
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func exec(ctx context.Context, path string, args []string) error {
 | 
			
		||||
	logger, err := zap.NewProduction()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return errors.Wrap(err, "create logger")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ctx = log.ContextWithLogger(ctx, logger)
 | 
			
		||||
 | 
			
		||||
	c, err := config.Open(path)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return errors.Wrapf(err, "open %s as config", path)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(args) == 0 {
 | 
			
		||||
		pwd, err := os.Getwd()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return errors.Wrap(err, "get working direcotry")
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		fs, err := os.ReadDir(filepath.Join(pwd, "results"))
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return errors.Wrapf(err, "read %s", filepath.Join(pwd, "results"))
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		var ds []time.Time
 | 
			
		||||
		for _, f := range fs {
 | 
			
		||||
			if !f.IsDir() {
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			t, err := time.Parse("2006-01-02T150405-0700", f.Name())
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
			ds = append(ds, t)
 | 
			
		||||
		}
 | 
			
		||||
		if len(ds) == 0 {
 | 
			
		||||
			return errors.Wrapf(err, "result dir not found")
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		slices.SortFunc(ds, func(e1, e2 time.Time) bool {
 | 
			
		||||
			return e1.After(e2)
 | 
			
		||||
		})
 | 
			
		||||
 | 
			
		||||
		args = append(args, filepath.Join(pwd, "results", ds[0].Format("2006-01-02T150405-0700")))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	type result struct {
 | 
			
		||||
		error string
 | 
			
		||||
		nCVEs int
 | 
			
		||||
	}
 | 
			
		||||
	detectCVEs := map[string]result{}
 | 
			
		||||
	for _, arg := range args {
 | 
			
		||||
		if err := filepath.WalkDir(arg, func(p string, d fs.DirEntry, err error) error {
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if d.IsDir() {
 | 
			
		||||
				return nil
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			f, err := os.OpenFile(p, os.O_RDWR, 0644)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return errors.Wrapf(err, "open %s", p)
 | 
			
		||||
			}
 | 
			
		||||
			defer f.Close()
 | 
			
		||||
 | 
			
		||||
			var host types.Host
 | 
			
		||||
			if err := json.NewDecoder(f).Decode(&host); err != nil {
 | 
			
		||||
				return errors.Wrapf(err, "decode %s", p)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			hc, ok := c.Hosts[host.Name]
 | 
			
		||||
			if !ok {
 | 
			
		||||
				return errors.Wrapf(err, "not found %s in %s", host.Name, path)
 | 
			
		||||
			}
 | 
			
		||||
			host.Config.Detect = &hc.Detect
 | 
			
		||||
 | 
			
		||||
			host.ScannedCves = nil
 | 
			
		||||
			host.DetectError = ""
 | 
			
		||||
 | 
			
		||||
			if err := detect.Detect(ctx, &host); err != nil {
 | 
			
		||||
				host.DetectError = err.Error()
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			name := host.Name
 | 
			
		||||
			if host.Family != "" && host.Release != "" {
 | 
			
		||||
				name = fmt.Sprintf("%s (%s %s)", host.Name, host.Family, host.Release)
 | 
			
		||||
			}
 | 
			
		||||
			errstr := host.DetectError
 | 
			
		||||
			if host.ScanError != "" {
 | 
			
		||||
				errstr = fmt.Sprintf("scan error: %s", host.ScanError)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			detectCVEs[name] = result{
 | 
			
		||||
				error: errstr,
 | 
			
		||||
				nCVEs: len(host.ScannedCves),
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if err := f.Truncate(0); err != nil {
 | 
			
		||||
				return errors.Wrap(err, "truncate file")
 | 
			
		||||
			}
 | 
			
		||||
			if _, err := f.Seek(0, 0); err != nil {
 | 
			
		||||
				return errors.Wrap(err, "set offset")
 | 
			
		||||
			}
 | 
			
		||||
			enc := json.NewEncoder(f)
 | 
			
		||||
			enc.SetIndent("", "  ")
 | 
			
		||||
			if err := enc.Encode(host); err != nil {
 | 
			
		||||
				return errors.Wrap(err, "encode json")
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			return nil
 | 
			
		||||
		}); err != nil {
 | 
			
		||||
			return errors.Wrapf(err, "walk %s", arg)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	fmt.Println("Detect Summary")
 | 
			
		||||
	fmt.Println("==============")
 | 
			
		||||
	for name, r := range detectCVEs {
 | 
			
		||||
		if r.error != "" {
 | 
			
		||||
			fmt.Printf("%s : error msg: %s\n", name, r.error)
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		fmt.Printf("%s : success %d CVEs detected\n", name, r.nCVEs)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										245
									
								
								pkg/cmd/report/report.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										245
									
								
								pkg/cmd/report/report.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,245 @@
 | 
			
		||||
package report
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io/fs"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/MakeNowJust/heredoc"
 | 
			
		||||
	"github.com/olekukonko/tablewriter"
 | 
			
		||||
	"github.com/pkg/errors"
 | 
			
		||||
	"github.com/spf13/cobra"
 | 
			
		||||
	"go.uber.org/zap"
 | 
			
		||||
	"golang.org/x/exp/slices"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/pkg/log"
 | 
			
		||||
	"github.com/future-architect/vuls/pkg/types"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type ReportOptions struct {
 | 
			
		||||
	Format string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewCmdReport() *cobra.Command {
 | 
			
		||||
	opts := &ReportOptions{
 | 
			
		||||
		Format: "oneline",
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	cmd := &cobra.Command{
 | 
			
		||||
		Use:   "report (<result path>)",
 | 
			
		||||
		Short: "Vuls report vulnerabilities",
 | 
			
		||||
		RunE: func(_ *cobra.Command, args []string) error {
 | 
			
		||||
			if err := exec(context.Background(), opts.Format, args); err != nil {
 | 
			
		||||
				return errors.Wrap(err, "failed to report")
 | 
			
		||||
			}
 | 
			
		||||
			return nil
 | 
			
		||||
		},
 | 
			
		||||
		Example: heredoc.Doc(`
 | 
			
		||||
			$ vuls report
 | 
			
		||||
			$ vuls report results
 | 
			
		||||
			$ vuls report resutls/2022-11-05T01:08:44+09:00/local/localhost.json
 | 
			
		||||
		`),
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	cmd.Flags().StringVarP(&opts.Format, "format", "f", "oneline", "stdout format")
 | 
			
		||||
 | 
			
		||||
	return cmd
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type affectedPackage struct {
 | 
			
		||||
	name   string
 | 
			
		||||
	status string
 | 
			
		||||
	source string
 | 
			
		||||
}
 | 
			
		||||
type result struct {
 | 
			
		||||
	cveid      string
 | 
			
		||||
	cvssVector string
 | 
			
		||||
	cvssScore  *float64
 | 
			
		||||
	epss       *float64
 | 
			
		||||
	kev        bool
 | 
			
		||||
	packages   []affectedPackage
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func exec(ctx context.Context, format string, args []string) error {
 | 
			
		||||
	logger, err := zap.NewProduction()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return errors.Wrap(err, "create logger")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ctx = log.ContextWithLogger(ctx, logger)
 | 
			
		||||
 | 
			
		||||
	if len(args) == 0 {
 | 
			
		||||
		pwd, err := os.Getwd()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return errors.Wrap(err, "get working direcotry")
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		fs, err := os.ReadDir(filepath.Join(pwd, "results"))
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return errors.Wrapf(err, "read %s", filepath.Join(pwd, "results"))
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		var ds []time.Time
 | 
			
		||||
		for _, f := range fs {
 | 
			
		||||
			if !f.IsDir() {
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			t, err := time.Parse("2006-01-02T150405-0700", f.Name())
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
			ds = append(ds, t)
 | 
			
		||||
		}
 | 
			
		||||
		if len(ds) == 0 {
 | 
			
		||||
			return errors.Wrapf(err, "result dir not found")
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		slices.SortFunc(ds, func(e1, e2 time.Time) bool {
 | 
			
		||||
			return e1.After(e2)
 | 
			
		||||
		})
 | 
			
		||||
 | 
			
		||||
		args = append(args, filepath.Join(pwd, "results", ds[0].Format("2006-01-02T150405-0700")))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	rs := map[string][]result{}
 | 
			
		||||
	for _, arg := range args {
 | 
			
		||||
		if err := filepath.WalkDir(arg, func(path string, d fs.DirEntry, err error) error {
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if d.IsDir() {
 | 
			
		||||
				return nil
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			f, err := os.Open(path)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return errors.Wrapf(err, "open %s", path)
 | 
			
		||||
			}
 | 
			
		||||
			defer f.Close()
 | 
			
		||||
 | 
			
		||||
			var host types.Host
 | 
			
		||||
			if err := json.NewDecoder(f).Decode(&host); err != nil {
 | 
			
		||||
				return errors.Wrapf(err, "decode %s", path)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			name := host.Name
 | 
			
		||||
			if host.Family != "" && host.Release != "" {
 | 
			
		||||
				name = fmt.Sprintf("%s (%s %s)", host.Name, host.Family, host.Release)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			for id, vinfo := range host.ScannedCves {
 | 
			
		||||
				r := result{
 | 
			
		||||
					cveid: id,
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				if officialCont, ok := vinfo.Content["official"]; ok {
 | 
			
		||||
					for _, c := range officialCont.CVSS {
 | 
			
		||||
						if c.Source != "nvd" || strings.HasPrefix(c.Version, "3") {
 | 
			
		||||
							continue
 | 
			
		||||
						}
 | 
			
		||||
						r.cvssVector = c.Vector
 | 
			
		||||
						r.cvssScore = c.Score
 | 
			
		||||
					}
 | 
			
		||||
					if officialCont.EPSS != nil {
 | 
			
		||||
						r.epss = officialCont.EPSS.EPSS
 | 
			
		||||
					}
 | 
			
		||||
					r.kev = officialCont.KEV
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				for _, p := range vinfo.AffectedPackages {
 | 
			
		||||
					r.packages = append(r.packages, affectedPackage{
 | 
			
		||||
						name:   p.Name,
 | 
			
		||||
						status: p.Status,
 | 
			
		||||
						source: p.Source,
 | 
			
		||||
					})
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				rs[name] = append(rs[name], r)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			return nil
 | 
			
		||||
		}); err != nil {
 | 
			
		||||
			return errors.Wrapf(err, "walk %s", arg)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	switch format {
 | 
			
		||||
	case "oneline":
 | 
			
		||||
		formatOneline(rs)
 | 
			
		||||
	case "list":
 | 
			
		||||
		formatList(rs)
 | 
			
		||||
	default:
 | 
			
		||||
		return errors.Errorf("%s is not implemented format", format)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func formatOneline(rs map[string][]result) {
 | 
			
		||||
	for name, lines := range rs {
 | 
			
		||||
		fmt.Println(name)
 | 
			
		||||
		fmt.Println(strings.Repeat("=", len(name)))
 | 
			
		||||
 | 
			
		||||
		status := map[string]int{}
 | 
			
		||||
		for _, l := range lines {
 | 
			
		||||
			for _, p := range l.packages {
 | 
			
		||||
				s := p.status
 | 
			
		||||
				if p.status == "" {
 | 
			
		||||
					s = "(none)"
 | 
			
		||||
				}
 | 
			
		||||
				status[s]++
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		var ss []string
 | 
			
		||||
		for s, num := range status {
 | 
			
		||||
			ss = append(ss, fmt.Sprintf("%s: %d", s, num))
 | 
			
		||||
		}
 | 
			
		||||
		fmt.Printf("%d CVEs detected. package status: %s\n\n", len(lines), strings.Join(ss, ", "))
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func formatList(rs map[string][]result) {
 | 
			
		||||
	for name, lines := range rs {
 | 
			
		||||
		slices.SortFunc(lines, func(l1, l2 result) bool {
 | 
			
		||||
			s1, s2 := 0.0, 0.0
 | 
			
		||||
			if l1.cvssScore != nil {
 | 
			
		||||
				s1 = *l1.cvssScore
 | 
			
		||||
			}
 | 
			
		||||
			if l2.cvssScore != nil {
 | 
			
		||||
				s2 = *l2.cvssScore
 | 
			
		||||
			}
 | 
			
		||||
			return s1 > s2
 | 
			
		||||
		})
 | 
			
		||||
 | 
			
		||||
		fmt.Println(name)
 | 
			
		||||
		fmt.Println(strings.Repeat("=", len(name)))
 | 
			
		||||
		table := tablewriter.NewWriter(os.Stdout)
 | 
			
		||||
		table.SetHeader([]string{"CVEID", "Vector", "CVSS", "EPSS", "KEV", "Package", "Status", "Source"})
 | 
			
		||||
		table.SetAutoMergeCells(true)
 | 
			
		||||
		table.SetRowLine(true)
 | 
			
		||||
		for _, l := range lines {
 | 
			
		||||
			for _, p := range l.packages {
 | 
			
		||||
				var score string
 | 
			
		||||
				if l.cvssScore != nil {
 | 
			
		||||
					score = fmt.Sprintf("%.1f", *l.cvssScore)
 | 
			
		||||
				}
 | 
			
		||||
				var epss string
 | 
			
		||||
				if l.epss != nil {
 | 
			
		||||
					epss = fmt.Sprintf("%f", *l.epss)
 | 
			
		||||
				}
 | 
			
		||||
				source, _, _ := strings.Cut(p.source, ":")
 | 
			
		||||
				table.Append([]string{l.cveid, l.cvssVector, score, epss, fmt.Sprintf("%v", l.kev), p.name, p.status, source})
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		table.Render()
 | 
			
		||||
		fmt.Println()
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										44
									
								
								pkg/cmd/root/root.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								pkg/cmd/root/root.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,44 @@
 | 
			
		||||
package root
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"github.com/MakeNowJust/heredoc"
 | 
			
		||||
	"github.com/spf13/cobra"
 | 
			
		||||
 | 
			
		||||
	configCmd "github.com/future-architect/vuls/pkg/cmd/config"
 | 
			
		||||
	dbCmd "github.com/future-architect/vuls/pkg/cmd/db"
 | 
			
		||||
	detectCmd "github.com/future-architect/vuls/pkg/cmd/detect"
 | 
			
		||||
	reportCmd "github.com/future-architect/vuls/pkg/cmd/report"
 | 
			
		||||
	scanCmd "github.com/future-architect/vuls/pkg/cmd/scan"
 | 
			
		||||
	serverCmd "github.com/future-architect/vuls/pkg/cmd/server"
 | 
			
		||||
	tuiCmd "github.com/future-architect/vuls/pkg/cmd/tui"
 | 
			
		||||
	versionCmd "github.com/future-architect/vuls/pkg/cmd/version"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func NewCmdRoot() *cobra.Command {
 | 
			
		||||
	cmd := &cobra.Command{
 | 
			
		||||
		Use:           "vuls <command>",
 | 
			
		||||
		Short:         "Vuls",
 | 
			
		||||
		Long:          "Vulnerability Scanner: Vuls",
 | 
			
		||||
		SilenceErrors: true,
 | 
			
		||||
		SilenceUsage:  true,
 | 
			
		||||
		Example: heredoc.Doc(`
 | 
			
		||||
			$ vuls config init
 | 
			
		||||
			$ vuls db fetch
 | 
			
		||||
			$ vuls scan
 | 
			
		||||
			$ vuls detect
 | 
			
		||||
			$ vuls report
 | 
			
		||||
			$ vuls tui
 | 
			
		||||
		`),
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	cmd.AddCommand(configCmd.NewCmdConfig())
 | 
			
		||||
	cmd.AddCommand(dbCmd.NewCmdDB())
 | 
			
		||||
	cmd.AddCommand(detectCmd.NewCmdDetect())
 | 
			
		||||
	cmd.AddCommand(reportCmd.NewCmdReport())
 | 
			
		||||
	cmd.AddCommand(scanCmd.NewCmdScan())
 | 
			
		||||
	cmd.AddCommand(serverCmd.NewCmdServer())
 | 
			
		||||
	cmd.AddCommand(tuiCmd.NewCmdTUI())
 | 
			
		||||
	cmd.AddCommand(versionCmd.NewCmdVersion())
 | 
			
		||||
 | 
			
		||||
	return cmd
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										136
									
								
								pkg/cmd/scan/scan.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										136
									
								
								pkg/cmd/scan/scan.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,136 @@
 | 
			
		||||
package scan
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/MakeNowJust/heredoc"
 | 
			
		||||
	"github.com/pkg/errors"
 | 
			
		||||
	"github.com/spf13/cobra"
 | 
			
		||||
	"go.uber.org/zap"
 | 
			
		||||
	"golang.org/x/exp/maps"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/pkg/config"
 | 
			
		||||
	"github.com/future-architect/vuls/pkg/log"
 | 
			
		||||
	"github.com/future-architect/vuls/pkg/scan"
 | 
			
		||||
	"github.com/future-architect/vuls/pkg/types"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type ScanOptions struct {
 | 
			
		||||
	Config string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewCmdScan() *cobra.Command {
 | 
			
		||||
	opts := &ScanOptions{
 | 
			
		||||
		Config: "config.json",
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	cmd := &cobra.Command{
 | 
			
		||||
		Use:   "scan ([\"host\"])",
 | 
			
		||||
		Short: "Vuls scan your machine information",
 | 
			
		||||
		RunE: func(_ *cobra.Command, args []string) error {
 | 
			
		||||
			if err := exec(context.Background(), opts.Config, args); err != nil {
 | 
			
		||||
				return errors.Wrap(err, "failed to scan")
 | 
			
		||||
			}
 | 
			
		||||
			return nil
 | 
			
		||||
		},
 | 
			
		||||
		Example: heredoc.Doc(`
 | 
			
		||||
			$ vuls scan
 | 
			
		||||
			$ vuls scan host
 | 
			
		||||
		`),
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	cmd.Flags().StringVarP(&opts.Config, "config", "c", "config.json", "vuls config file path")
 | 
			
		||||
 | 
			
		||||
	return cmd
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func exec(ctx context.Context, path string, args []string) error {
 | 
			
		||||
	logger, err := zap.NewProduction()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return errors.Wrap(err, "create logger")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ctx = log.ContextWithLogger(ctx, logger)
 | 
			
		||||
 | 
			
		||||
	c, err := config.Open(path)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return errors.Wrapf(err, "open %s as config", path)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	hosts := []types.Host{}
 | 
			
		||||
	targets := args
 | 
			
		||||
	if len(args) == 0 {
 | 
			
		||||
		targets = maps.Keys(c.Hosts)
 | 
			
		||||
	}
 | 
			
		||||
	for _, t := range targets {
 | 
			
		||||
		h, ok := c.Hosts[t]
 | 
			
		||||
		if !ok {
 | 
			
		||||
			return errors.Errorf("host %s is not defined in config %s", t, path)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		hosts = append(hosts, types.Host{
 | 
			
		||||
			Name: t,
 | 
			
		||||
			Config: types.Config{
 | 
			
		||||
				Type:      h.Type,
 | 
			
		||||
				Host:      h.Host,
 | 
			
		||||
				Port:      h.Port,
 | 
			
		||||
				User:      h.User,
 | 
			
		||||
				SSHConfig: h.SSHConfig,
 | 
			
		||||
				SSHKey:    h.SSHKey,
 | 
			
		||||
				Scan:      &h.Scan,
 | 
			
		||||
			},
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for i := range hosts {
 | 
			
		||||
		if err := scan.Scan(ctx, &hosts[i]); err != nil {
 | 
			
		||||
			hosts[i].ScanError = err.Error()
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	now := time.Now()
 | 
			
		||||
	for _, h := range hosts {
 | 
			
		||||
		if err := func() error {
 | 
			
		||||
			resultDir := filepath.Join(h.Config.Scan.ResultDir, now.Format("2006-01-02T150405-0700"))
 | 
			
		||||
			if err := os.MkdirAll(resultDir, os.ModePerm); err != nil {
 | 
			
		||||
				return errors.Wrapf(err, "mkdir %s", resultDir)
 | 
			
		||||
			}
 | 
			
		||||
			f, err := os.Create(filepath.Join(resultDir, fmt.Sprintf("%s.json", h.Name)))
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return errors.Wrapf(err, "create %s", filepath.Join(resultDir, fmt.Sprintf("%s.json", h.Name)))
 | 
			
		||||
			}
 | 
			
		||||
			defer f.Close()
 | 
			
		||||
 | 
			
		||||
			enc := json.NewEncoder(f)
 | 
			
		||||
			enc.SetIndent("", "  ")
 | 
			
		||||
			if err := enc.Encode(h); err != nil {
 | 
			
		||||
				return errors.Wrapf(err, "encode %s result", h.Name)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			return nil
 | 
			
		||||
		}(); err != nil {
 | 
			
		||||
			return errors.Wrapf(err, "write %s result", h.Name)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	fmt.Println("Scan Summary")
 | 
			
		||||
	fmt.Println("============")
 | 
			
		||||
	for _, h := range hosts {
 | 
			
		||||
		name := h.Name
 | 
			
		||||
		if h.Family != "" && h.Release != "" {
 | 
			
		||||
			name = fmt.Sprintf("%s (%s %s)", h.Name, h.Family, h.Release)
 | 
			
		||||
		}
 | 
			
		||||
		if h.ScanError != "" {
 | 
			
		||||
			fmt.Printf("%s : error msg: %s\n", name, h.ScanError)
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		fmt.Printf("%s: success ospkg: %d, cpe: %d, KB %d installed\n", name, len(h.Packages.OSPkg), len(h.Packages.CPE), len(h.Packages.KB))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										78
									
								
								pkg/cmd/server/server.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										78
									
								
								pkg/cmd/server/server.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,78 @@
 | 
			
		||||
package server
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
 | 
			
		||||
	"github.com/MakeNowJust/heredoc"
 | 
			
		||||
	"github.com/labstack/echo/v4"
 | 
			
		||||
	"github.com/pkg/errors"
 | 
			
		||||
	"github.com/spf13/cobra"
 | 
			
		||||
	"go.uber.org/zap"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/pkg/config"
 | 
			
		||||
	"github.com/future-architect/vuls/pkg/log"
 | 
			
		||||
	"github.com/future-architect/vuls/pkg/server"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type Serveroptions struct {
 | 
			
		||||
	Config string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewCmdServer() *cobra.Command {
 | 
			
		||||
	opts := &Serveroptions{
 | 
			
		||||
		Config: "config.json",
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	cmd := &cobra.Command{
 | 
			
		||||
		Use:   "server",
 | 
			
		||||
		Short: "Vuls start server mode",
 | 
			
		||||
		Args:  cobra.NoArgs,
 | 
			
		||||
		RunE: func(_ *cobra.Command, _ []string) error {
 | 
			
		||||
			if err := exec(context.Background(), opts.Config); err != nil {
 | 
			
		||||
				return errors.Wrap(err, "failed to server")
 | 
			
		||||
			}
 | 
			
		||||
			return nil
 | 
			
		||||
		},
 | 
			
		||||
		Example: heredoc.Doc(`
 | 
			
		||||
			$ vuls server
 | 
			
		||||
		`),
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	cmd.Flags().StringVarP(&opts.Config, "config", "c", "config.json", "vuls config file path")
 | 
			
		||||
 | 
			
		||||
	return cmd
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func exec(ctx context.Context, path string) error {
 | 
			
		||||
	logger, err := zap.NewProduction()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return errors.Wrap(err, "create logger")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ctx = log.ContextWithLogger(ctx, logger)
 | 
			
		||||
 | 
			
		||||
	c, err := config.Open(path)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return errors.Wrapf(err, "open %s as config", path)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if c.Server == nil {
 | 
			
		||||
		pwd, err := os.Getwd()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return errors.Wrap(err, "get working directory")
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		c.Server = &config.Server{
 | 
			
		||||
			Listen: "127.0.0.1:5515",
 | 
			
		||||
			Path:   filepath.Join(pwd, "vuls.db"),
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	e := echo.New()
 | 
			
		||||
	e.POST("/scan", server.Scan())
 | 
			
		||||
	e.POST("/detect", server.Detect(c.Server.Path))
 | 
			
		||||
 | 
			
		||||
	return e.Start(c.Server.Listen)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										33
									
								
								pkg/cmd/tui/tui.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								pkg/cmd/tui/tui.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,33 @@
 | 
			
		||||
package tui
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"github.com/MakeNowJust/heredoc"
 | 
			
		||||
	"github.com/spf13/cobra"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type TUIOptions struct {
 | 
			
		||||
	Config string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewCmdTUI() *cobra.Command {
 | 
			
		||||
	opts := &TUIOptions{
 | 
			
		||||
		Config: "config.json",
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	cmd := &cobra.Command{
 | 
			
		||||
		Use:   "tui (<result path>)",
 | 
			
		||||
		Short: "View vulnerabilities detected by TUI",
 | 
			
		||||
		RunE: func(_ *cobra.Command, _ []string) error {
 | 
			
		||||
			return nil
 | 
			
		||||
		},
 | 
			
		||||
		Example: heredoc.Doc(`
 | 
			
		||||
			$ vuls tui
 | 
			
		||||
			$ vuls tui results
 | 
			
		||||
			$ vuls tui resutls/2022-11-05T01:08:44+09:00/local/localhost.json
 | 
			
		||||
		`),
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	cmd.Flags().StringVarP(&opts.Config, "config", "c", "config.json", "vuls config file path")
 | 
			
		||||
 | 
			
		||||
	return cmd
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										25
									
								
								pkg/cmd/version/version.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								pkg/cmd/version/version.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,25 @@
 | 
			
		||||
package version
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"os"
 | 
			
		||||
 | 
			
		||||
	"github.com/spf13/cobra"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
	Version  string
 | 
			
		||||
	Revision string
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func NewCmdVersion() *cobra.Command {
 | 
			
		||||
	cmd := &cobra.Command{
 | 
			
		||||
		Use:   "version",
 | 
			
		||||
		Short: "Print the version",
 | 
			
		||||
		Args:  cobra.NoArgs,
 | 
			
		||||
		Run: func(_ *cobra.Command, _ []string) {
 | 
			
		||||
			fmt.Fprintf(os.Stdout, "vuls %s %s\n", Version, Revision)
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	return cmd
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										59
									
								
								pkg/config/config.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								pkg/config/config.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,59 @@
 | 
			
		||||
package config
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"os"
 | 
			
		||||
	"os/user"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
 | 
			
		||||
	"github.com/pkg/errors"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func Open(path string) (Config, error) {
 | 
			
		||||
	f, err := os.Open(path)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return Config{}, errors.Wrapf(err, "open %s", path)
 | 
			
		||||
	}
 | 
			
		||||
	defer f.Close()
 | 
			
		||||
 | 
			
		||||
	var src Config
 | 
			
		||||
	if err := json.NewDecoder(f).Decode(&src); err != nil {
 | 
			
		||||
		return Config{}, errors.Wrap(err, "decode json")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	u, err := user.Current()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return Config{}, errors.Wrap(err, "get current user")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	pwd, err := os.Getwd()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return Config{}, errors.Wrap(err, "get working directory")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	config := Config{Server: src.Server, Hosts: map[string]Host{}}
 | 
			
		||||
	for n, h := range src.Hosts {
 | 
			
		||||
		c := Host{
 | 
			
		||||
			Type:      h.Type,
 | 
			
		||||
			Host:      h.Host,
 | 
			
		||||
			Port:      h.Port,
 | 
			
		||||
			User:      h.User,
 | 
			
		||||
			SSHConfig: h.SSHConfig,
 | 
			
		||||
			SSHKey:    h.SSHKey,
 | 
			
		||||
			Scan:      h.Scan,
 | 
			
		||||
			Detect:    h.Detect,
 | 
			
		||||
		}
 | 
			
		||||
		if c.User == nil {
 | 
			
		||||
			c.User = &u.Name
 | 
			
		||||
		}
 | 
			
		||||
		if c.Scan.ResultDir == "" {
 | 
			
		||||
			c.Scan.ResultDir = filepath.Join(pwd, "results")
 | 
			
		||||
		}
 | 
			
		||||
		if c.Detect.ResultDir == "" {
 | 
			
		||||
			c.Detect.ResultDir = filepath.Join(pwd, "results")
 | 
			
		||||
		}
 | 
			
		||||
		config.Hosts[n] = c
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return config, nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										41
									
								
								pkg/config/types.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								pkg/config/types.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,41 @@
 | 
			
		||||
package config
 | 
			
		||||
 | 
			
		||||
type Config struct {
 | 
			
		||||
	Server *Server         `json:"server"`
 | 
			
		||||
	Hosts  map[string]Host `json:"hosts"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Scan struct {
 | 
			
		||||
	OSPkg     *scanOSPkg `json:"ospkg,omitempty"`
 | 
			
		||||
	CPE       []scanCPE  `json:"cpe,omitempty"`
 | 
			
		||||
	ResultDir string     `json:"result_dir,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type scanOSPkg struct {
 | 
			
		||||
	Root bool `json:"root"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type scanCPE struct {
 | 
			
		||||
	CPE       string `json:"cpe,omitempty"`
 | 
			
		||||
	RunningOn string `json:"running_on,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Detect struct {
 | 
			
		||||
	Path      string `json:"path"`
 | 
			
		||||
	ResultDir string `json:"result_dir"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Server struct {
 | 
			
		||||
	Listen string `json:"listen"`
 | 
			
		||||
	Path   string `json:"path"`
 | 
			
		||||
}
 | 
			
		||||
type Host struct {
 | 
			
		||||
	Type      string  `json:"type"`
 | 
			
		||||
	Host      *string `json:"host"`
 | 
			
		||||
	Port      *string `json:"port"`
 | 
			
		||||
	User      *string `json:"user"`
 | 
			
		||||
	SSHConfig *string `json:"ssh_config"`
 | 
			
		||||
	SSHKey    *string `json:"ssh_key"`
 | 
			
		||||
	Scan      Scan    `json:"scan"`
 | 
			
		||||
	Detect    Detect  `json:"detect"`
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										490
									
								
								pkg/db/boltdb/boltdb.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										490
									
								
								pkg/db/boltdb/boltdb.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,490 @@
 | 
			
		||||
package boltdb
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/pkg/errors"
 | 
			
		||||
	bolt "go.etcd.io/bbolt"
 | 
			
		||||
	"golang.org/x/exp/maps"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/pkg/db/types"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type options struct {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Option interface {
 | 
			
		||||
	apply(*options)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type DB struct {
 | 
			
		||||
	conn *bolt.DB
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func Open(dbPath string, debug bool, opts ...Option) (*DB, error) {
 | 
			
		||||
	db, err := bolt.Open(dbPath, 0666, nil)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, errors.Wrap(err, "open boltdb")
 | 
			
		||||
	}
 | 
			
		||||
	return &DB{conn: db}, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (db *DB) Close() error {
 | 
			
		||||
	if db.conn == nil {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	if err := db.conn.Close(); err != nil {
 | 
			
		||||
		return errors.Wrap(err, "close boltdb")
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (db *DB) PutVulnerability(src, key string, value types.Vulnerability) error {
 | 
			
		||||
	bucket, id, found := strings.Cut(key, ":")
 | 
			
		||||
	if !found {
 | 
			
		||||
		return errors.Errorf(`unexpected key. accepts: "vulnerability:<Vulnerability ID>, received: "%s"`, key)
 | 
			
		||||
	}
 | 
			
		||||
	if err := db.conn.Update(func(tx *bolt.Tx) error {
 | 
			
		||||
		b, err := tx.CreateBucketIfNotExists([]byte(bucket))
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return errors.Wrapf(err, "create %s bucket", bucket)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		vb, err := b.CreateBucketIfNotExists([]byte(id))
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return errors.Wrapf(err, "create %s/%s bucket", bucket, id)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		bs, err := json.MarshalIndent(value, "", "  ")
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return errors.Wrap(err, "marshal json")
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if err := vb.Put([]byte(src), bs); err != nil {
 | 
			
		||||
			return errors.Wrapf(err, "put %%s/%s/%s", bucket, id, src)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return nil
 | 
			
		||||
	}); err != nil {
 | 
			
		||||
		return errors.Wrap(err, "update db")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (db *DB) PutPackage(src, key string, value map[string]types.Packages) error {
 | 
			
		||||
	if err := db.conn.Update(func(tx *bolt.Tx) error {
 | 
			
		||||
		name, version, found := strings.Cut(key, ":")
 | 
			
		||||
		if !found && name == "" {
 | 
			
		||||
			return errors.Errorf(`unexpected key. accepts: "<osname>(:<version>)", received: "%s"`, key)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		bucket := name
 | 
			
		||||
		b, err := tx.CreateBucketIfNotExists([]byte(name))
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return errors.Wrapf(err, "create %s bucket", name)
 | 
			
		||||
		}
 | 
			
		||||
		switch name {
 | 
			
		||||
		case "arch", "freebsd", "gentoo":
 | 
			
		||||
		case "redhat":
 | 
			
		||||
			if version == "" {
 | 
			
		||||
				return errors.Errorf(`unexpected key. accepts: "<osname>:<version>", received: "%s"`, key)
 | 
			
		||||
			}
 | 
			
		||||
			b, err = b.CreateBucketIfNotExists([]byte(version[:1]))
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return errors.Wrapf(err, "create %s/%s bucket", name, version[:1])
 | 
			
		||||
			}
 | 
			
		||||
			b, err = b.CreateBucketIfNotExists([]byte(version))
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return errors.Wrapf(err, "create %s/%s/%s bucket", name, version[:1], version)
 | 
			
		||||
			}
 | 
			
		||||
			bucket = fmt.Sprintf("%s/%s/%s", name, version[:1], version)
 | 
			
		||||
		default:
 | 
			
		||||
			if version == "" {
 | 
			
		||||
				return errors.Errorf(`unexpected key. accepts: "<osname>:<version>", received: "%s"`, key)
 | 
			
		||||
			}
 | 
			
		||||
			b, err = b.CreateBucketIfNotExists([]byte(version))
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return errors.Wrapf(err, "crate %s/%s bucket", name, version)
 | 
			
		||||
			}
 | 
			
		||||
			bucket = fmt.Sprintf("%s/%s", name, version)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		for n, v := range value {
 | 
			
		||||
			pb, err := b.CreateBucketIfNotExists([]byte(n))
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return errors.Wrapf(err, "create %s/%s bucket", bucket, n)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			vb, err := pb.CreateBucketIfNotExists([]byte(v.ID))
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return errors.Wrapf(err, "create %s/%s/%s bucket", bucket, n, v.ID)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			var p map[string]types.Package
 | 
			
		||||
			bs := vb.Get([]byte(src))
 | 
			
		||||
			if len(bs) > 0 {
 | 
			
		||||
				if err := json.Unmarshal(bs, &p); err != nil {
 | 
			
		||||
					return errors.Wrap(err, "unmarshal json")
 | 
			
		||||
				}
 | 
			
		||||
			} else {
 | 
			
		||||
				p = map[string]types.Package{}
 | 
			
		||||
			}
 | 
			
		||||
			maps.Copy(p, v.Package)
 | 
			
		||||
			bs, err = json.MarshalIndent(p, "", "  ")
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return errors.Wrap(err, "marshal json")
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if err := vb.Put([]byte(src), bs); err != nil {
 | 
			
		||||
				return errors.Wrapf(err, "put %s", key)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if name == "windows" {
 | 
			
		||||
			kbToProduct := map[string][]string{}
 | 
			
		||||
			for n, ps := range value {
 | 
			
		||||
				for _, p := range ps.Package {
 | 
			
		||||
					for _, v := range p.Version {
 | 
			
		||||
						if _, err := strconv.Atoi(v[0].Version); err != nil {
 | 
			
		||||
							continue
 | 
			
		||||
						}
 | 
			
		||||
						kbToProduct[v[0].Version] = append(kbToProduct[v[0].Version], n)
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			b, err := tx.CreateBucketIfNotExists([]byte("windows_kb_to_product"))
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return errors.Wrap(err, "create windows_kb_to_product bucket")
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if version == "" {
 | 
			
		||||
				return errors.Errorf(`unexpected key. accepts: "<osname>:<version>", received: "%s"`, key)
 | 
			
		||||
			}
 | 
			
		||||
			b, err = b.CreateBucketIfNotExists([]byte(version))
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return errors.Wrapf(err, "create %s/%s bucket", name, version)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			for kb, ps := range kbToProduct {
 | 
			
		||||
				bs, err := json.Marshal(ps)
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					return errors.Wrap(err, "marshal json")
 | 
			
		||||
				}
 | 
			
		||||
				b.Put([]byte(kb), bs)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return nil
 | 
			
		||||
	}); err != nil {
 | 
			
		||||
		return errors.Wrap(err, "update db")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (db *DB) PutCPEConfiguration(src, key string, value map[string]types.CPEConfigurations) error {
 | 
			
		||||
	if err := db.conn.Update(func(tx *bolt.Tx) error {
 | 
			
		||||
		b, err := tx.CreateBucketIfNotExists([]byte(key))
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return errors.Wrapf(err, "create %s bucket", key)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		for pvp, c := range value {
 | 
			
		||||
			pvpb, err := b.CreateBucketIfNotExists([]byte(pvp))
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return errors.Wrapf(err, "create %s/%s bucket", key, pvp)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			vb, err := pvpb.CreateBucketIfNotExists([]byte(c.ID))
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return errors.Wrapf(err, "create %s/%s/%s bucket", key, pvp, c.ID)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			var v map[string][]types.CPEConfiguration
 | 
			
		||||
			bs := vb.Get([]byte(src))
 | 
			
		||||
			if len(bs) > 0 {
 | 
			
		||||
				if err := json.Unmarshal(bs, &v); err != nil {
 | 
			
		||||
					return errors.Wrap(err, "unmarshal json")
 | 
			
		||||
				}
 | 
			
		||||
			} else {
 | 
			
		||||
				v = map[string][]types.CPEConfiguration{}
 | 
			
		||||
			}
 | 
			
		||||
			maps.Copy(v, c.Configuration)
 | 
			
		||||
			bs, err = json.MarshalIndent(v, "", "  ")
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return errors.Wrap(err, "marshal json")
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if err := vb.Put([]byte(src), bs); err != nil {
 | 
			
		||||
				return errors.Wrapf(err, "put %s", key)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return nil
 | 
			
		||||
	}); err != nil {
 | 
			
		||||
		return errors.Wrap(err, "update db")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (db *DB) PutRedHatRepoToCPE(src, key string, value types.RepositoryToCPE) error {
 | 
			
		||||
	if err := db.conn.Update(func(tx *bolt.Tx) error {
 | 
			
		||||
		name, version, found := strings.Cut(key, ":")
 | 
			
		||||
		if !found && name == "" {
 | 
			
		||||
			return errors.Errorf(`unexpected key. accepts: "redhat_cpe:<version>", received: "%s"`, key)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		b, err := tx.CreateBucketIfNotExists([]byte(name))
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return errors.Wrapf(err, "create %s bucket", name)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		b, err = b.CreateBucketIfNotExists([]byte(version[:1]))
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return errors.Wrapf(err, "create %s/%s bucket", name, version[:1])
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		b, err = b.CreateBucketIfNotExists([]byte(version))
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return errors.Wrapf(err, "create %s/%s/%s bucket", name, version[:1], version)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		for repo, cpes := range value {
 | 
			
		||||
			rb, err := b.CreateBucketIfNotExists([]byte(repo))
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return errors.Wrapf(err, "create %s/%s/%s/%s bucket", name, version[:1], version, repo)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			bs, err := json.MarshalIndent(cpes, "", "  ")
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return errors.Wrap(err, "marshal json")
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if err := rb.Put([]byte(src), bs); err != nil {
 | 
			
		||||
				return errors.Wrapf(err, "put %s", key)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return nil
 | 
			
		||||
	}); err != nil {
 | 
			
		||||
		return errors.Wrap(err, "update db")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (db *DB) PutWindowsSupercedence(src, key string, value types.Supercedence) error {
 | 
			
		||||
	if err := db.conn.Update(func(tx *bolt.Tx) error {
 | 
			
		||||
		b, err := tx.CreateBucketIfNotExists([]byte(key))
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return errors.Wrapf(err, "create %s bucket", key)
 | 
			
		||||
		}
 | 
			
		||||
		for kb, supercedences := range value {
 | 
			
		||||
			kbb, err := b.CreateBucketIfNotExists([]byte(kb))
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return errors.Wrapf(err, "create %s/%s bucket", key, kb)
 | 
			
		||||
			}
 | 
			
		||||
			bs, err := json.Marshal(supercedences)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return errors.Wrap(err, "marshal json")
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if err := kbb.Put([]byte(src), bs); err != nil {
 | 
			
		||||
				return errors.Wrapf(err, "put %s", key)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return nil
 | 
			
		||||
	}); err != nil {
 | 
			
		||||
		return errors.Wrap(err, "update db")
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (db *DB) GetVulnerability(ids []string) (map[string]map[string]types.Vulnerability, error) {
 | 
			
		||||
	r := map[string]map[string]types.Vulnerability{}
 | 
			
		||||
	if err := db.conn.View(func(tx *bolt.Tx) error {
 | 
			
		||||
		b := tx.Bucket([]byte("vulnerability"))
 | 
			
		||||
		if b == nil {
 | 
			
		||||
			return nil
 | 
			
		||||
		}
 | 
			
		||||
		for _, id := range ids {
 | 
			
		||||
			vb := b.Bucket([]byte(id))
 | 
			
		||||
			if vb == nil {
 | 
			
		||||
				return nil
 | 
			
		||||
			}
 | 
			
		||||
			r[string(id)] = map[string]types.Vulnerability{}
 | 
			
		||||
			if err := vb.ForEach(func(src, bs []byte) error {
 | 
			
		||||
				var v types.Vulnerability
 | 
			
		||||
				if err := json.Unmarshal(bs, &v); err != nil {
 | 
			
		||||
					return errors.Wrapf(err, "decode %s/%s", string(id), string(src))
 | 
			
		||||
				}
 | 
			
		||||
				r[string(id)][string(src)] = v
 | 
			
		||||
 | 
			
		||||
				return nil
 | 
			
		||||
			}); err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return nil
 | 
			
		||||
	}); err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return r, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (db *DB) GetPackage(family, release string, name string) (map[string]map[string]map[string]types.Package, error) {
 | 
			
		||||
	r := map[string]map[string]map[string]types.Package{}
 | 
			
		||||
	if err := db.conn.View(func(tx *bolt.Tx) error {
 | 
			
		||||
		b := tx.Bucket([]byte(family))
 | 
			
		||||
		if b == nil {
 | 
			
		||||
			return nil
 | 
			
		||||
		}
 | 
			
		||||
		switch family {
 | 
			
		||||
		case "debian", "ubuntu", "windows":
 | 
			
		||||
			b = b.Bucket([]byte(release))
 | 
			
		||||
			if b == nil {
 | 
			
		||||
				return nil
 | 
			
		||||
			}
 | 
			
		||||
			b = b.Bucket([]byte(name))
 | 
			
		||||
			if b == nil {
 | 
			
		||||
				return nil
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if err := b.ForEach(func(cveid, _ []byte) error {
 | 
			
		||||
				vb := b.Bucket(cveid)
 | 
			
		||||
				r[string(cveid)] = map[string]map[string]types.Package{}
 | 
			
		||||
				if err := vb.ForEach(func(src, bs []byte) error {
 | 
			
		||||
					var v map[string]types.Package
 | 
			
		||||
					if err := json.Unmarshal(bs, &v); err != nil {
 | 
			
		||||
						return errors.Wrapf(err, "decode %s/%s", string(cveid), string(src))
 | 
			
		||||
					}
 | 
			
		||||
					r[string(cveid)][string(src)] = v
 | 
			
		||||
 | 
			
		||||
					return nil
 | 
			
		||||
				}); err != nil {
 | 
			
		||||
					return err
 | 
			
		||||
				}
 | 
			
		||||
				return nil
 | 
			
		||||
			}); err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
		default:
 | 
			
		||||
			return errors.New("not implemented")
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return nil
 | 
			
		||||
	}); err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return r, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (db *DB) GetCPEConfiguration(partvendorproduct string) (map[string]map[string]map[string][]types.CPEConfiguration, error) {
 | 
			
		||||
	r := map[string]map[string]map[string][]types.CPEConfiguration{}
 | 
			
		||||
	if err := db.conn.View(func(tx *bolt.Tx) error {
 | 
			
		||||
		b := tx.Bucket([]byte("cpe"))
 | 
			
		||||
		if b == nil {
 | 
			
		||||
			return nil
 | 
			
		||||
		}
 | 
			
		||||
		b = b.Bucket([]byte(partvendorproduct))
 | 
			
		||||
		if b == nil {
 | 
			
		||||
			return nil
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if err := b.ForEach(func(cveid, _ []byte) error {
 | 
			
		||||
			vb := b.Bucket(cveid)
 | 
			
		||||
			r[string(cveid)] = map[string]map[string][]types.CPEConfiguration{}
 | 
			
		||||
			if err := vb.ForEach(func(src, bs []byte) error {
 | 
			
		||||
				var v map[string][]types.CPEConfiguration
 | 
			
		||||
				if err := json.Unmarshal(bs, &v); err != nil {
 | 
			
		||||
					return errors.Wrapf(err, "decode cpe/%s/%s/%s", partvendorproduct, string(cveid), string(src))
 | 
			
		||||
				}
 | 
			
		||||
				r[string(cveid)][string(src)] = v
 | 
			
		||||
 | 
			
		||||
				return nil
 | 
			
		||||
			}); err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			return nil
 | 
			
		||||
		}); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return nil
 | 
			
		||||
	}); err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return r, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (db *DB) GetSupercedence(kbs []string) (map[string][]string, error) {
 | 
			
		||||
	r := map[string][]string{}
 | 
			
		||||
	if err := db.conn.View(func(tx *bolt.Tx) error {
 | 
			
		||||
		b := tx.Bucket([]byte("windows_supercedence"))
 | 
			
		||||
		if b == nil {
 | 
			
		||||
			return nil
 | 
			
		||||
		}
 | 
			
		||||
		for _, kb := range kbs {
 | 
			
		||||
			kbb := b.Bucket([]byte(kb))
 | 
			
		||||
			if kbb == nil {
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
			if err := kbb.ForEach(func(_, v []byte) error {
 | 
			
		||||
				var ss []string
 | 
			
		||||
				if err := json.Unmarshal(v, &ss); err != nil {
 | 
			
		||||
					return errors.Wrapf(err, "decode windows_supercedence/%s", kb)
 | 
			
		||||
				}
 | 
			
		||||
				r[kb] = append(r[kb], ss...)
 | 
			
		||||
				return nil
 | 
			
		||||
			}); err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return nil
 | 
			
		||||
	}); err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return r, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (db *DB) GetKBtoProduct(release string, kbs []string) ([]string, error) {
 | 
			
		||||
	var r []string
 | 
			
		||||
	if err := db.conn.View(func(tx *bolt.Tx) error {
 | 
			
		||||
		b := tx.Bucket([]byte("windows_kb_to_product"))
 | 
			
		||||
		if b == nil {
 | 
			
		||||
			return nil
 | 
			
		||||
		}
 | 
			
		||||
		b = b.Bucket([]byte(release))
 | 
			
		||||
		if b == nil {
 | 
			
		||||
			return nil
 | 
			
		||||
		}
 | 
			
		||||
		for _, kb := range kbs {
 | 
			
		||||
			if bs := b.Get([]byte(kb)); len(bs) > 0 {
 | 
			
		||||
				var ps []string
 | 
			
		||||
				if err := json.Unmarshal(bs, &ps); err != nil {
 | 
			
		||||
					return errors.Wrapf(err, "decode windows_kb_to_product/%s/%s", release, kb)
 | 
			
		||||
				}
 | 
			
		||||
				r = append(r, ps...)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		return nil
 | 
			
		||||
	}); err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return r, nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										149
									
								
								pkg/db/db.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										149
									
								
								pkg/db/db.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,149 @@
 | 
			
		||||
package db
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"github.com/pkg/errors"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/pkg/db/boltdb"
 | 
			
		||||
	"github.com/future-architect/vuls/pkg/db/rdb"
 | 
			
		||||
	"github.com/future-architect/vuls/pkg/db/redis"
 | 
			
		||||
	"github.com/future-architect/vuls/pkg/db/types"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type options struct {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Option interface {
 | 
			
		||||
	apply(*options)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type DB struct {
 | 
			
		||||
	name   string
 | 
			
		||||
	driver Driver
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Driver interface {
 | 
			
		||||
	Close() error
 | 
			
		||||
 | 
			
		||||
	PutVulnerability(string, string, types.Vulnerability) error
 | 
			
		||||
	PutPackage(string, string, map[string]types.Packages) error
 | 
			
		||||
	PutCPEConfiguration(string, string, map[string]types.CPEConfigurations) error
 | 
			
		||||
	PutRedHatRepoToCPE(string, string, types.RepositoryToCPE) error
 | 
			
		||||
	PutWindowsSupercedence(string, string, types.Supercedence) error
 | 
			
		||||
 | 
			
		||||
	GetVulnerability([]string) (map[string]map[string]types.Vulnerability, error)
 | 
			
		||||
	GetPackage(string, string, string) (map[string]map[string]map[string]types.Package, error)
 | 
			
		||||
	GetCPEConfiguration(string) (map[string]map[string]map[string][]types.CPEConfiguration, error)
 | 
			
		||||
	GetSupercedence([]string) (map[string][]string, error)
 | 
			
		||||
	GetKBtoProduct(string, []string) ([]string, error)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (db *DB) Name() string {
 | 
			
		||||
	return db.name
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func Open(dbType, dbPath string, debug bool, opts ...Option) (*DB, error) {
 | 
			
		||||
	switch dbType {
 | 
			
		||||
	case "boltdb":
 | 
			
		||||
		d, err := boltdb.Open(dbPath, debug)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, errors.Wrap(err, "open boltdb")
 | 
			
		||||
		}
 | 
			
		||||
		return &DB{name: dbType, driver: d}, nil
 | 
			
		||||
	case "sqlite3", "mysql", "postgres":
 | 
			
		||||
		d, err := rdb.Open(dbType, dbPath, debug)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, errors.Wrap(err, "open rdb")
 | 
			
		||||
		}
 | 
			
		||||
		return &DB{name: dbType, driver: d}, nil
 | 
			
		||||
	case "redis":
 | 
			
		||||
		d, err := redis.Open(dbPath, debug)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, errors.Wrap(err, "open rdb")
 | 
			
		||||
		}
 | 
			
		||||
		return &DB{name: dbType, driver: d}, nil
 | 
			
		||||
	default:
 | 
			
		||||
		return nil, errors.Errorf(`unexpected dbType. accepts: ["boltdb", "sqlite3", "mysql", "postgres", "redis"], received: "%s"`, dbType)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (db *DB) Close() error {
 | 
			
		||||
	if err := db.driver.Close(); err != nil {
 | 
			
		||||
		return errors.Wrapf(err, "close %s", db.name)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (db *DB) PutVulnerability(src, key string, value types.Vulnerability) error {
 | 
			
		||||
	if err := db.driver.PutVulnerability(src, key, value); err != nil {
 | 
			
		||||
		return errors.Wrapf(err, "put vulnerability")
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (db *DB) PutPackage(src, key string, value map[string]types.Packages) error {
 | 
			
		||||
	if err := db.driver.PutPackage(src, key, value); err != nil {
 | 
			
		||||
		return errors.Wrapf(err, "put package")
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (db *DB) PutCPEConfiguration(src, key string, value map[string]types.CPEConfigurations) error {
 | 
			
		||||
	if err := db.driver.PutCPEConfiguration(src, key, value); err != nil {
 | 
			
		||||
		return errors.Wrapf(err, "put cpe configuration")
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (db *DB) PutRedHatRepoToCPE(src, key string, value types.RepositoryToCPE) error {
 | 
			
		||||
	if err := db.driver.PutRedHatRepoToCPE(src, key, value); err != nil {
 | 
			
		||||
		return errors.Wrap(err, "put repository to cpe")
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (db *DB) PutWindowsSupercedence(src, key string, value types.Supercedence) error {
 | 
			
		||||
	if err := db.driver.PutWindowsSupercedence(src, key, value); err != nil {
 | 
			
		||||
		return errors.Wrap(err, "put supercedence")
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (db *DB) GetVulnerability(ids []string) (map[string]map[string]types.Vulnerability, error) {
 | 
			
		||||
	rs, err := db.driver.GetVulnerability(ids)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, errors.Wrapf(err, "get vulnerability")
 | 
			
		||||
	}
 | 
			
		||||
	return rs, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (db *DB) GetPackage(family, release string, name string) (map[string]map[string]map[string]types.Package, error) {
 | 
			
		||||
	rs, err := db.driver.GetPackage(family, release, name)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, errors.Wrapf(err, "get package")
 | 
			
		||||
	}
 | 
			
		||||
	return rs, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (db *DB) GetCPEConfiguration(partvendorproduct string) (map[string]map[string]map[string][]types.CPEConfiguration, error) {
 | 
			
		||||
	rs, err := db.driver.GetCPEConfiguration(partvendorproduct)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, errors.Wrapf(err, "get cpe configuration")
 | 
			
		||||
	}
 | 
			
		||||
	return rs, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (db *DB) GetSupercedence(kb []string) (map[string][]string, error) {
 | 
			
		||||
	rs, err := db.driver.GetSupercedence(kb)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, errors.Wrap(err, "get supercedence")
 | 
			
		||||
	}
 | 
			
		||||
	return rs, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (db *DB) GetKBtoProduct(release string, kb []string) ([]string, error) {
 | 
			
		||||
	rs, err := db.driver.GetKBtoProduct(release, kb)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, errors.Wrap(err, "get product from kb")
 | 
			
		||||
	}
 | 
			
		||||
	return rs, nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										110
									
								
								pkg/db/rdb/rdb.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										110
									
								
								pkg/db/rdb/rdb.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,110 @@
 | 
			
		||||
package rdb
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"database/sql"
 | 
			
		||||
 | 
			
		||||
	"github.com/pkg/errors"
 | 
			
		||||
	"gorm.io/driver/mysql"
 | 
			
		||||
	"gorm.io/driver/postgres"
 | 
			
		||||
	"gorm.io/gorm"
 | 
			
		||||
	_ "modernc.org/sqlite"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/pkg/db/types"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type options struct {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Option interface {
 | 
			
		||||
	apply(*options)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type DB struct {
 | 
			
		||||
	conn *gorm.DB
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func Open(dbType, dbPath string, debug bool, opts ...Option) (*DB, error) {
 | 
			
		||||
	switch dbType {
 | 
			
		||||
	case "sqlite3":
 | 
			
		||||
		// db, err := gorm.Open(sqlite.Open(dbPath))
 | 
			
		||||
		db := &gorm.DB{}
 | 
			
		||||
		conn, err := sql.Open("sqlite", dbPath)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, errors.Wrap(err, "open sqlite3")
 | 
			
		||||
		}
 | 
			
		||||
		db.ConnPool = conn
 | 
			
		||||
		return &DB{conn: db}, nil
 | 
			
		||||
	case "mysql":
 | 
			
		||||
		db, err := gorm.Open(mysql.Open(dbPath))
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, errors.Wrap(err, "open mysql")
 | 
			
		||||
		}
 | 
			
		||||
		return &DB{conn: db}, nil
 | 
			
		||||
	case "postgres":
 | 
			
		||||
		db, err := gorm.Open(postgres.Open(dbPath))
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, errors.Wrap(err, "open postgres")
 | 
			
		||||
		}
 | 
			
		||||
		return &DB{conn: db}, nil
 | 
			
		||||
	default:
 | 
			
		||||
		return nil, errors.Errorf(`unexpected dbType. accepts: ["sqlite3", "mysql", "postgres"], received: "%s"`, dbType)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (db *DB) Close() error {
 | 
			
		||||
	if db.conn == nil {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var (
 | 
			
		||||
		sqlDB *sql.DB
 | 
			
		||||
		err   error
 | 
			
		||||
	)
 | 
			
		||||
	if sqlDB, err = db.conn.DB(); err != nil {
 | 
			
		||||
		return errors.Wrap(err, "get *sql.DB")
 | 
			
		||||
	}
 | 
			
		||||
	if err := sqlDB.Close(); err != nil {
 | 
			
		||||
		return errors.Wrap(err, "close *sql.DB")
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (db *DB) PutVulnerability(src, key string, value types.Vulnerability) error {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (db *DB) PutPackage(src, key string, value map[string]types.Packages) error {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (db *DB) PutCPEConfiguration(src, key string, value map[string]types.CPEConfigurations) error {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (db *DB) PutRedHatRepoToCPE(src, key string, value types.RepositoryToCPE) error {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (db *DB) PutWindowsSupercedence(src, key string, value types.Supercedence) error {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (db *DB) GetVulnerability(ids []string) (map[string]map[string]types.Vulnerability, error) {
 | 
			
		||||
	return nil, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (db *DB) GetPackage(family, release string, name string) (map[string]map[string]map[string]types.Package, error) {
 | 
			
		||||
	return nil, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (db *DB) GetCPEConfiguration(partvendorproduct string) (map[string]map[string]map[string][]types.CPEConfiguration, error) {
 | 
			
		||||
	return nil, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (db *DB) GetSupercedence(kb []string) (map[string][]string, error) {
 | 
			
		||||
	return nil, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (db *DB) GetKBtoProduct(elease string, kb []string) ([]string, error) {
 | 
			
		||||
	return nil, nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										77
									
								
								pkg/db/redis/redis.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										77
									
								
								pkg/db/redis/redis.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,77 @@
 | 
			
		||||
package redis
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"github.com/go-redis/redis/v9"
 | 
			
		||||
	"github.com/pkg/errors"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/pkg/db/types"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type options struct {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Option interface {
 | 
			
		||||
	apply(*options)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type DB struct {
 | 
			
		||||
	conn *redis.Client
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func Open(dbPath string, debug bool, opts ...Option) (*DB, error) {
 | 
			
		||||
	redisOpts, err := redis.ParseURL(dbPath)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, errors.Wrap(err, "parse redis URL")
 | 
			
		||||
	}
 | 
			
		||||
	return &DB{conn: redis.NewClient(redisOpts)}, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (db *DB) Close() error {
 | 
			
		||||
	if db.conn == nil {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	if err := db.conn.Close(); err != nil {
 | 
			
		||||
		return errors.Wrap(err, "close redis")
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (db *DB) PutVulnerability(src, key string, value types.Vulnerability) error {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (db *DB) PutPackage(src, key string, value map[string]types.Packages) error {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (db *DB) PutCPEConfiguration(src, key string, value map[string]types.CPEConfigurations) error {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (db *DB) PutRedHatRepoToCPE(src, key string, value types.RepositoryToCPE) error {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (db *DB) PutWindowsSupercedence(src, key string, value types.Supercedence) error {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (db *DB) GetVulnerability(ids []string) (map[string]map[string]types.Vulnerability, error) {
 | 
			
		||||
	return nil, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (db *DB) GetPackage(family, release string, name string) (map[string]map[string]map[string]types.Package, error) {
 | 
			
		||||
	return nil, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (db *DB) GetCPEConfiguration(partvendorproduct string) (map[string]map[string]map[string][]types.CPEConfiguration, error) {
 | 
			
		||||
	return nil, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (db *DB) GetSupercedence(kb []string) (map[string][]string, error) {
 | 
			
		||||
	return nil, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (db *DB) GetKBtoProduct(release string, kb []string) ([]string, error) {
 | 
			
		||||
	return nil, nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										85
									
								
								pkg/db/types/types.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										85
									
								
								pkg/db/types/types.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,85 @@
 | 
			
		||||
package types
 | 
			
		||||
 | 
			
		||||
import "time"
 | 
			
		||||
 | 
			
		||||
type Vulnerability struct {
 | 
			
		||||
	ID          string       `json:"id,omitempty"`
 | 
			
		||||
	Advisory    []string     `json:"advisory,omitempty"`
 | 
			
		||||
	Title       string       `json:"title,omitempty"`
 | 
			
		||||
	Description string       `json:"description,omitempty"`
 | 
			
		||||
	CVSS        []CVSS       `json:"cvss,omitempty"`
 | 
			
		||||
	EPSS        *EPSS        `json:"epss,omitempty"`
 | 
			
		||||
	CWE         []CWE        `json:"cwe,omitempty"`
 | 
			
		||||
	Metasploit  []Metasploit `json:"metasploit,omitempty"`
 | 
			
		||||
	Exploit     []Exploit    `json:"exploit,omitempty"`
 | 
			
		||||
	KEV         bool         `json:"kev,omitempty"`
 | 
			
		||||
	Published   *time.Time   `json:"published,omitempty"`
 | 
			
		||||
	Modified    *time.Time   `json:"modified,omitempty"`
 | 
			
		||||
	Reference   []string     `json:"reference,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type CVSS struct {
 | 
			
		||||
	Source   string   `json:"source,omitempty"`
 | 
			
		||||
	Version  string   `json:"version,omitempty"`
 | 
			
		||||
	Vector   string   `json:"vector,omitempty"`
 | 
			
		||||
	Score    *float64 `json:"score,omitempty"`
 | 
			
		||||
	Severity string   `json:"severity,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type EPSS struct {
 | 
			
		||||
	EPSS       *float64 `json:"epss,omitempty"`
 | 
			
		||||
	Percentile *float64 `json:"percentile,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type CWE struct {
 | 
			
		||||
	Source []string `json:"source,omitempty"`
 | 
			
		||||
	ID     string   `json:"id,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Metasploit struct {
 | 
			
		||||
	Title string `json:"title,omitempty"`
 | 
			
		||||
	URL   string `json:"url,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Exploit struct {
 | 
			
		||||
	Source []string `json:"source,omitempty"`
 | 
			
		||||
	URL    string   `json:"url,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type CPEConfigurations struct {
 | 
			
		||||
	ID            string                        `json:"-,omitempty"`
 | 
			
		||||
	Configuration map[string][]CPEConfiguration `json:"configuration,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type CPEConfiguration struct {
 | 
			
		||||
	Vulnerable CPE   `json:"vulnerable,omitempty"`
 | 
			
		||||
	RunningOn  []CPE `json:"running_on,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type CPE struct {
 | 
			
		||||
	CPEVersion string    `json:"cpe_version,omitempty"`
 | 
			
		||||
	CPE        string    `json:"cpe,omitempty"`
 | 
			
		||||
	Version    []Version `json:"version,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Packages struct {
 | 
			
		||||
	ID      string             `json:"-,omitempty"`
 | 
			
		||||
	Package map[string]Package `json:"package,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Package struct {
 | 
			
		||||
	Status     string      `json:"status,omitempty"`
 | 
			
		||||
	Version    [][]Version `json:"version,omitempty"`
 | 
			
		||||
	Arch       []string    `json:"arch,omitempty"`
 | 
			
		||||
	Repository string      `json:"repository,omitempty"`
 | 
			
		||||
	CPE        []string    `json:"cpe,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Version struct {
 | 
			
		||||
	Operator string `json:"operator,omitempty"`
 | 
			
		||||
	Version  string `json:"version,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type RepositoryToCPE map[string][]string
 | 
			
		||||
 | 
			
		||||
type Supercedence map[string][]string
 | 
			
		||||
							
								
								
									
										1
									
								
								pkg/db/util/util.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								pkg/db/util/util.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
			
		||||
package util
 | 
			
		||||
							
								
								
									
										179
									
								
								pkg/detect/cpe/cpe.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										179
									
								
								pkg/detect/cpe/cpe.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,179 @@
 | 
			
		||||
package cpe
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/hashicorp/go-version"
 | 
			
		||||
	"github.com/knqyf263/go-cpe/common"
 | 
			
		||||
	"github.com/knqyf263/go-cpe/matching"
 | 
			
		||||
	"github.com/knqyf263/go-cpe/naming"
 | 
			
		||||
	"github.com/pkg/errors"
 | 
			
		||||
	"golang.org/x/exp/maps"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/pkg/db"
 | 
			
		||||
	dbTypes "github.com/future-architect/vuls/pkg/db/types"
 | 
			
		||||
	"github.com/future-architect/vuls/pkg/types"
 | 
			
		||||
	"github.com/future-architect/vuls/pkg/util"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type Detector struct{}
 | 
			
		||||
 | 
			
		||||
func (d Detector) Name() string {
 | 
			
		||||
	return "cpe detector"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d Detector) Detect(ctx context.Context, host *types.Host) error {
 | 
			
		||||
	if host.ScannedCves == nil {
 | 
			
		||||
		host.ScannedCves = map[string]types.VulnInfo{}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	vulndb, err := db.Open("boltdb", host.Config.Detect.Path, false)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return errors.Wrapf(err, "open %s", host.Config.Detect.Path)
 | 
			
		||||
	}
 | 
			
		||||
	defer vulndb.Close()
 | 
			
		||||
 | 
			
		||||
	for key, cpe := range host.Packages.CPE {
 | 
			
		||||
		installed, err := naming.UnbindFS(cpe.CPE)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return errors.Wrapf(err, "unbind %s", cpe.CPE)
 | 
			
		||||
		}
 | 
			
		||||
		var runningOn common.WellFormedName
 | 
			
		||||
		if cpe.RunningOn != "" {
 | 
			
		||||
			runningOn, err = naming.UnbindFS(cpe.RunningOn)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return errors.Wrapf(err, "unbind %s", cpe.RunningOn)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		cpes, err := vulndb.GetCPEConfiguration(fmt.Sprintf("%s:%s:%s", installed.GetString(common.AttributePart), installed.GetString(common.AttributeVendor), installed.GetString(common.AttributeProduct)))
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return errors.Wrap(err, "get cpe configuration")
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		for cveid, datasrcs := range cpes {
 | 
			
		||||
			for datasrc, orcs := range datasrcs {
 | 
			
		||||
				for id, andcs := range orcs {
 | 
			
		||||
					for _, c := range andcs {
 | 
			
		||||
						affected, err := compare(installed, &runningOn, c)
 | 
			
		||||
						if err != nil {
 | 
			
		||||
							return errors.Wrap(err, "compare")
 | 
			
		||||
						}
 | 
			
		||||
						if affected {
 | 
			
		||||
							vinfo, ok := host.ScannedCves[cveid]
 | 
			
		||||
							if !ok {
 | 
			
		||||
								host.ScannedCves[cveid] = types.VulnInfo{ID: cveid}
 | 
			
		||||
							}
 | 
			
		||||
							vinfo.AffectedPackages = append(vinfo.AffectedPackages, types.AffectedPackage{
 | 
			
		||||
								Name:   key,
 | 
			
		||||
								Source: fmt.Sprintf("%s:%s", datasrc, id),
 | 
			
		||||
							})
 | 
			
		||||
							vinfo.AffectedPackages = util.Unique(vinfo.AffectedPackages)
 | 
			
		||||
							host.ScannedCves[cveid] = vinfo
 | 
			
		||||
							break
 | 
			
		||||
						}
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	vulns, err := vulndb.GetVulnerability(maps.Keys(host.ScannedCves))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return errors.Wrap(err, "get vulnerability")
 | 
			
		||||
	}
 | 
			
		||||
	for cveid, datasrcs := range vulns {
 | 
			
		||||
		vinfo := host.ScannedCves[cveid]
 | 
			
		||||
		vinfo.Content = map[string]dbTypes.Vulnerability{}
 | 
			
		||||
		for src, v := range datasrcs {
 | 
			
		||||
			vinfo.Content[src] = v
 | 
			
		||||
		}
 | 
			
		||||
		host.ScannedCves[cveid] = vinfo
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func compare(installedCPE common.WellFormedName, installedRunningOn *common.WellFormedName, target dbTypes.CPEConfiguration) (bool, error) {
 | 
			
		||||
	var (
 | 
			
		||||
		wfn common.WellFormedName
 | 
			
		||||
		err error
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	if target.Vulnerable.CPEVersion == "2.3" {
 | 
			
		||||
		wfn, err = naming.UnbindFS(target.Vulnerable.CPE)
 | 
			
		||||
	} else {
 | 
			
		||||
		wfn, err = naming.UnbindURI(target.Vulnerable.CPE)
 | 
			
		||||
	}
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return false, errors.Wrapf(err, "unbind %s", target.Vulnerable.CPE)
 | 
			
		||||
	}
 | 
			
		||||
	if !matching.IsEqual(installedCPE, wfn) && !matching.IsSubset(installedCPE, wfn) {
 | 
			
		||||
		return false, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, runningOn := range target.RunningOn {
 | 
			
		||||
		if runningOn.CPEVersion == "2.3" {
 | 
			
		||||
			wfn, err = naming.UnbindFS(runningOn.CPE)
 | 
			
		||||
		} else {
 | 
			
		||||
			wfn, err = naming.UnbindURI(runningOn.CPE)
 | 
			
		||||
		}
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return false, errors.Wrapf(err, "unbind %s", runningOn.CPE)
 | 
			
		||||
		}
 | 
			
		||||
		if !matching.IsEqual(*installedRunningOn, wfn) && !matching.IsSubset(*installedRunningOn, wfn) {
 | 
			
		||||
			return false, nil
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(target.Vulnerable.Version) == 0 {
 | 
			
		||||
		return true, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	attrver := installedCPE.GetString(common.AttributeVersion)
 | 
			
		||||
	switch attrver {
 | 
			
		||||
	case "ANY":
 | 
			
		||||
		return true, nil
 | 
			
		||||
	case "NA":
 | 
			
		||||
		return false, nil
 | 
			
		||||
	default:
 | 
			
		||||
		v, err := version.NewVersion(strings.ReplaceAll(attrver, "\\", ""))
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return false, errors.Wrapf(err, "parse version in %s", installedCPE.GetString(common.AttributeVersion))
 | 
			
		||||
		}
 | 
			
		||||
		for _, vconf := range target.Vulnerable.Version {
 | 
			
		||||
			vconfv, err := version.NewVersion(vconf.Version)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return false, errors.Wrapf(err, "parse version in %s", vconf.Version)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			switch vconf.Operator {
 | 
			
		||||
			case "eq":
 | 
			
		||||
				if !v.Equal(vconfv) {
 | 
			
		||||
					return false, nil
 | 
			
		||||
				}
 | 
			
		||||
			case "lt":
 | 
			
		||||
				if !v.LessThan(vconfv) {
 | 
			
		||||
					return false, nil
 | 
			
		||||
				}
 | 
			
		||||
			case "le":
 | 
			
		||||
				if !v.LessThanOrEqual(vconfv) {
 | 
			
		||||
					return false, nil
 | 
			
		||||
				}
 | 
			
		||||
			case "gt":
 | 
			
		||||
				if !v.GreaterThan(vconfv) {
 | 
			
		||||
					return false, nil
 | 
			
		||||
				}
 | 
			
		||||
			case "ge":
 | 
			
		||||
				if !v.GreaterThanOrEqual(vconfv) {
 | 
			
		||||
					return false, nil
 | 
			
		||||
				}
 | 
			
		||||
			default:
 | 
			
		||||
				return false, errors.New("not supported operator")
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		return true, nil
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										151
									
								
								pkg/detect/debian/debian.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										151
									
								
								pkg/detect/debian/debian.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,151 @@
 | 
			
		||||
package debian
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"fmt"
 | 
			
		||||
 | 
			
		||||
	version "github.com/knqyf263/go-deb-version"
 | 
			
		||||
	"github.com/pkg/errors"
 | 
			
		||||
	"golang.org/x/exp/maps"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/pkg/db"
 | 
			
		||||
	dbTypes "github.com/future-architect/vuls/pkg/db/types"
 | 
			
		||||
	"github.com/future-architect/vuls/pkg/types"
 | 
			
		||||
	"github.com/future-architect/vuls/pkg/util"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type Detector struct{}
 | 
			
		||||
 | 
			
		||||
func (d Detector) Name() string {
 | 
			
		||||
	return "debian detector"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d Detector) Detect(ctx context.Context, host *types.Host) error {
 | 
			
		||||
	if host.ScannedCves == nil {
 | 
			
		||||
		host.ScannedCves = map[string]types.VulnInfo{}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	vulndb, err := db.Open("boltdb", host.Config.Detect.Path, false)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return errors.Wrapf(err, "open %s", host.Config.Detect.Path)
 | 
			
		||||
	}
 | 
			
		||||
	defer vulndb.Close()
 | 
			
		||||
 | 
			
		||||
	srcpkgs := map[string]string{}
 | 
			
		||||
	for _, p := range host.Packages.OSPkg {
 | 
			
		||||
		srcpkgs[p.SrcName] = p.SrcVersion
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for srcname, srcver := range srcpkgs {
 | 
			
		||||
		pkgs, err := vulndb.GetPackage(host.Family, host.Release, srcname)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return errors.Wrap(err, "get package")
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		for cveid, datasrcs := range pkgs {
 | 
			
		||||
			for datasrc, ps := range datasrcs {
 | 
			
		||||
				for id, p := range ps {
 | 
			
		||||
					switch p.Status {
 | 
			
		||||
					case "fixed":
 | 
			
		||||
						for _, andVs := range p.Version {
 | 
			
		||||
							affected := true
 | 
			
		||||
							for _, v := range andVs {
 | 
			
		||||
								r, err := compare(v.Operator, srcver, v.Version)
 | 
			
		||||
								if err != nil {
 | 
			
		||||
									return errors.Wrap(err, "compare")
 | 
			
		||||
								}
 | 
			
		||||
								if !r {
 | 
			
		||||
									affected = false
 | 
			
		||||
									break
 | 
			
		||||
								}
 | 
			
		||||
							}
 | 
			
		||||
							if affected {
 | 
			
		||||
								vinfo, ok := host.ScannedCves[cveid]
 | 
			
		||||
								if !ok {
 | 
			
		||||
									host.ScannedCves[cveid] = types.VulnInfo{ID: cveid}
 | 
			
		||||
								}
 | 
			
		||||
								vinfo.AffectedPackages = append(vinfo.AffectedPackages, types.AffectedPackage{
 | 
			
		||||
									Name:   srcname,
 | 
			
		||||
									Source: fmt.Sprintf("%s:%s", datasrc, id),
 | 
			
		||||
									Status: p.Status,
 | 
			
		||||
								})
 | 
			
		||||
								vinfo.AffectedPackages = util.Unique(vinfo.AffectedPackages)
 | 
			
		||||
								host.ScannedCves[cveid] = vinfo
 | 
			
		||||
							}
 | 
			
		||||
						}
 | 
			
		||||
					case "open":
 | 
			
		||||
						vinfo, ok := host.ScannedCves[cveid]
 | 
			
		||||
						if !ok {
 | 
			
		||||
							host.ScannedCves[cveid] = types.VulnInfo{ID: cveid}
 | 
			
		||||
						}
 | 
			
		||||
						vinfo.AffectedPackages = append(vinfo.AffectedPackages, types.AffectedPackage{
 | 
			
		||||
							Name:   srcname,
 | 
			
		||||
							Source: fmt.Sprintf("%s:%s", datasrc, id),
 | 
			
		||||
							Status: p.Status,
 | 
			
		||||
						})
 | 
			
		||||
						vinfo.AffectedPackages = util.Unique(vinfo.AffectedPackages)
 | 
			
		||||
						host.ScannedCves[cveid] = vinfo
 | 
			
		||||
					case "not affected":
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	vulns, err := vulndb.GetVulnerability(maps.Keys(host.ScannedCves))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return errors.Wrap(err, "get vulnerability")
 | 
			
		||||
	}
 | 
			
		||||
	for cveid, datasrcs := range vulns {
 | 
			
		||||
		vinfo := host.ScannedCves[cveid]
 | 
			
		||||
		vinfo.Content = map[string]dbTypes.Vulnerability{}
 | 
			
		||||
		for src, v := range datasrcs {
 | 
			
		||||
			vinfo.Content[src] = v
 | 
			
		||||
		}
 | 
			
		||||
		host.ScannedCves[cveid] = vinfo
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func compare(operator, srcver, ver string) (bool, error) {
 | 
			
		||||
	v1, err := version.NewVersion(srcver)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return false, errors.Wrap(err, "parse version")
 | 
			
		||||
	}
 | 
			
		||||
	v2, err := version.NewVersion(ver)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return false, errors.Wrap(err, "parse version")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	r := v1.Compare(v2)
 | 
			
		||||
	switch operator {
 | 
			
		||||
	case "eq":
 | 
			
		||||
		if r == 0 {
 | 
			
		||||
			return true, nil
 | 
			
		||||
		}
 | 
			
		||||
		return false, nil
 | 
			
		||||
	case "lt":
 | 
			
		||||
		if r < 0 {
 | 
			
		||||
			return true, nil
 | 
			
		||||
		}
 | 
			
		||||
		return false, nil
 | 
			
		||||
	case "le":
 | 
			
		||||
		if r <= 0 {
 | 
			
		||||
			return true, nil
 | 
			
		||||
		}
 | 
			
		||||
		return false, nil
 | 
			
		||||
	case "gt":
 | 
			
		||||
		if r > 0 {
 | 
			
		||||
			return true, nil
 | 
			
		||||
		}
 | 
			
		||||
		return false, nil
 | 
			
		||||
	case "ge":
 | 
			
		||||
		if r >= 0 {
 | 
			
		||||
			return true, nil
 | 
			
		||||
		}
 | 
			
		||||
		return false, nil
 | 
			
		||||
	default:
 | 
			
		||||
		return false, errors.New("not supported operator")
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										63
									
								
								pkg/detect/detect.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								pkg/detect/detect.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,63 @@
 | 
			
		||||
package detect
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/pkg/errors"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/pkg/cmd/version"
 | 
			
		||||
	"github.com/future-architect/vuls/pkg/detect/cpe"
 | 
			
		||||
	"github.com/future-architect/vuls/pkg/detect/debian"
 | 
			
		||||
	detectTypes "github.com/future-architect/vuls/pkg/detect/types"
 | 
			
		||||
	"github.com/future-architect/vuls/pkg/detect/ubuntu"
 | 
			
		||||
	"github.com/future-architect/vuls/pkg/detect/windows"
 | 
			
		||||
	"github.com/future-architect/vuls/pkg/types"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func Detect(ctx context.Context, host *types.Host) error {
 | 
			
		||||
	if host.ScanError != "" {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var detectors []detectTypes.Detector
 | 
			
		||||
	if len(host.Packages.OSPkg) > 0 {
 | 
			
		||||
		switch host.Family {
 | 
			
		||||
		case "debian":
 | 
			
		||||
			detectors = append(detectors, debian.Detector{})
 | 
			
		||||
		case "ubuntu":
 | 
			
		||||
			detectors = append(detectors, ubuntu.Detector{})
 | 
			
		||||
		default:
 | 
			
		||||
			return errors.New("not implemented")
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if len(host.Packages.KB) > 0 {
 | 
			
		||||
		detectors = append(detectors, windows.Detector{})
 | 
			
		||||
	}
 | 
			
		||||
	if len(host.Packages.CPE) > 0 {
 | 
			
		||||
		detectors = append(detectors, cpe.Detector{})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var err error
 | 
			
		||||
	for {
 | 
			
		||||
		if len(detectors) == 0 {
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
		d := detectors[0]
 | 
			
		||||
		if err = d.Detect(ctx, host); err != nil {
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
		detectors = detectors[1:]
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	t := time.Now()
 | 
			
		||||
	host.DetecteddAt = &t
 | 
			
		||||
	host.DetectedVersion = version.Version
 | 
			
		||||
	host.DetectedRevision = version.Revision
 | 
			
		||||
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return errors.Wrapf(err, "detect %s", host.Name)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										12
									
								
								pkg/detect/types/types.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								pkg/detect/types/types.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,12 @@
 | 
			
		||||
package types
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/pkg/types"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type Detector interface {
 | 
			
		||||
	Name() string
 | 
			
		||||
	Detect(context.Context, *types.Host) error
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										151
									
								
								pkg/detect/ubuntu/ubuntu.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										151
									
								
								pkg/detect/ubuntu/ubuntu.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,151 @@
 | 
			
		||||
package ubuntu
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"fmt"
 | 
			
		||||
 | 
			
		||||
	version "github.com/knqyf263/go-deb-version"
 | 
			
		||||
	"github.com/pkg/errors"
 | 
			
		||||
	"golang.org/x/exp/maps"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/pkg/db"
 | 
			
		||||
	dbTypes "github.com/future-architect/vuls/pkg/db/types"
 | 
			
		||||
	"github.com/future-architect/vuls/pkg/types"
 | 
			
		||||
	"github.com/future-architect/vuls/pkg/util"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type Detector struct{}
 | 
			
		||||
 | 
			
		||||
func (d Detector) Name() string {
 | 
			
		||||
	return "ubuntu detector"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d Detector) Detect(ctx context.Context, host *types.Host) error {
 | 
			
		||||
	if host.ScannedCves == nil {
 | 
			
		||||
		host.ScannedCves = map[string]types.VulnInfo{}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	vulndb, err := db.Open("boltdb", host.Config.Detect.Path, false)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return errors.Wrapf(err, "open %s", host.Config.Detect.Path)
 | 
			
		||||
	}
 | 
			
		||||
	defer vulndb.Close()
 | 
			
		||||
 | 
			
		||||
	srcpkgs := map[string]string{}
 | 
			
		||||
	for _, p := range host.Packages.OSPkg {
 | 
			
		||||
		srcpkgs[p.SrcName] = p.SrcVersion
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for srcname, srcver := range srcpkgs {
 | 
			
		||||
		pkgs, err := vulndb.GetPackage(host.Family, host.Release, srcname)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return errors.Wrap(err, "get package")
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		for cveid, datasrcs := range pkgs {
 | 
			
		||||
			for datasrc, ps := range datasrcs {
 | 
			
		||||
				for id, p := range ps {
 | 
			
		||||
					switch p.Status {
 | 
			
		||||
					case "released":
 | 
			
		||||
						for _, andVs := range p.Version {
 | 
			
		||||
							affected := true
 | 
			
		||||
							for _, v := range andVs {
 | 
			
		||||
								r, err := compare(v.Operator, srcver, v.Version)
 | 
			
		||||
								if err != nil {
 | 
			
		||||
									return errors.Wrap(err, "compare")
 | 
			
		||||
								}
 | 
			
		||||
								if !r {
 | 
			
		||||
									affected = false
 | 
			
		||||
									break
 | 
			
		||||
								}
 | 
			
		||||
							}
 | 
			
		||||
							if affected {
 | 
			
		||||
								vinfo, ok := host.ScannedCves[cveid]
 | 
			
		||||
								if !ok {
 | 
			
		||||
									host.ScannedCves[cveid] = types.VulnInfo{ID: cveid}
 | 
			
		||||
								}
 | 
			
		||||
								vinfo.AffectedPackages = append(vinfo.AffectedPackages, types.AffectedPackage{
 | 
			
		||||
									Name:   srcname,
 | 
			
		||||
									Source: fmt.Sprintf("%s:%s", datasrc, id),
 | 
			
		||||
									Status: p.Status,
 | 
			
		||||
								})
 | 
			
		||||
								vinfo.AffectedPackages = util.Unique(vinfo.AffectedPackages)
 | 
			
		||||
								host.ScannedCves[cveid] = vinfo
 | 
			
		||||
							}
 | 
			
		||||
						}
 | 
			
		||||
					case "needed", "deferred", "pending":
 | 
			
		||||
						vinfo, ok := host.ScannedCves[cveid]
 | 
			
		||||
						if !ok {
 | 
			
		||||
							host.ScannedCves[cveid] = types.VulnInfo{ID: cveid}
 | 
			
		||||
						}
 | 
			
		||||
						vinfo.AffectedPackages = append(vinfo.AffectedPackages, types.AffectedPackage{
 | 
			
		||||
							Name:   srcname,
 | 
			
		||||
							Source: fmt.Sprintf("%s:%s", datasrc, id),
 | 
			
		||||
							Status: p.Status,
 | 
			
		||||
						})
 | 
			
		||||
						vinfo.AffectedPackages = util.Unique(vinfo.AffectedPackages)
 | 
			
		||||
						host.ScannedCves[cveid] = vinfo
 | 
			
		||||
					case "not-affected", "DNE":
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	vulns, err := vulndb.GetVulnerability(maps.Keys(host.ScannedCves))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return errors.Wrap(err, "get vulnerability")
 | 
			
		||||
	}
 | 
			
		||||
	for cveid, datasrcs := range vulns {
 | 
			
		||||
		vinfo := host.ScannedCves[cveid]
 | 
			
		||||
		vinfo.Content = map[string]dbTypes.Vulnerability{}
 | 
			
		||||
		for src, v := range datasrcs {
 | 
			
		||||
			vinfo.Content[src] = v
 | 
			
		||||
		}
 | 
			
		||||
		host.ScannedCves[cveid] = vinfo
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func compare(operator, srcver, ver string) (bool, error) {
 | 
			
		||||
	v1, err := version.NewVersion(srcver)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return false, errors.Wrap(err, "parse version")
 | 
			
		||||
	}
 | 
			
		||||
	v2, err := version.NewVersion(ver)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return false, errors.Wrap(err, "parse version")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	r := v1.Compare(v2)
 | 
			
		||||
	switch operator {
 | 
			
		||||
	case "eq":
 | 
			
		||||
		if r == 0 {
 | 
			
		||||
			return true, nil
 | 
			
		||||
		}
 | 
			
		||||
		return false, nil
 | 
			
		||||
	case "lt":
 | 
			
		||||
		if r < 0 {
 | 
			
		||||
			return true, nil
 | 
			
		||||
		}
 | 
			
		||||
		return false, nil
 | 
			
		||||
	case "le":
 | 
			
		||||
		if r <= 0 {
 | 
			
		||||
			return true, nil
 | 
			
		||||
		}
 | 
			
		||||
		return false, nil
 | 
			
		||||
	case "gt":
 | 
			
		||||
		if r > 0 {
 | 
			
		||||
			return true, nil
 | 
			
		||||
		}
 | 
			
		||||
		return false, nil
 | 
			
		||||
	case "ge":
 | 
			
		||||
		if r >= 0 {
 | 
			
		||||
			return true, nil
 | 
			
		||||
		}
 | 
			
		||||
		return false, nil
 | 
			
		||||
	default:
 | 
			
		||||
		return false, errors.New("not supported operator")
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										120
									
								
								pkg/detect/windows/windows.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										120
									
								
								pkg/detect/windows/windows.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,120 @@
 | 
			
		||||
package windows
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"fmt"
 | 
			
		||||
 | 
			
		||||
	"github.com/pkg/errors"
 | 
			
		||||
	"golang.org/x/exp/maps"
 | 
			
		||||
	"golang.org/x/exp/slices"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/pkg/db"
 | 
			
		||||
	dbTypes "github.com/future-architect/vuls/pkg/db/types"
 | 
			
		||||
	"github.com/future-architect/vuls/pkg/types"
 | 
			
		||||
	"github.com/future-architect/vuls/pkg/util"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type Detector struct{}
 | 
			
		||||
 | 
			
		||||
func (d Detector) Name() string {
 | 
			
		||||
	return "windows detector"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d Detector) Detect(ctx context.Context, host *types.Host) error {
 | 
			
		||||
	if host.ScannedCves == nil {
 | 
			
		||||
		host.ScannedCves = map[string]types.VulnInfo{}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	vulndb, err := db.Open("boltdb", host.Config.Detect.Path, false)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return errors.Wrapf(err, "open %s", host.Config.Detect.Path)
 | 
			
		||||
	}
 | 
			
		||||
	defer vulndb.Close()
 | 
			
		||||
 | 
			
		||||
	supercedences, err := vulndb.GetSupercedence(host.Packages.KB)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return errors.Wrap(err, "get supercedence")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var unapplied []string
 | 
			
		||||
	for _, kbs := range supercedences {
 | 
			
		||||
		var applied bool
 | 
			
		||||
		for _, kb := range kbs {
 | 
			
		||||
			if slices.Contains(host.Packages.KB, kb) {
 | 
			
		||||
				applied = true
 | 
			
		||||
				break
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		if !applied {
 | 
			
		||||
			unapplied = append(unapplied, kbs...)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	unapplied = util.Unique(unapplied)
 | 
			
		||||
 | 
			
		||||
	products, err := vulndb.GetKBtoProduct(host.Release, append(host.Packages.KB, unapplied...))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return errors.Wrap(err, "get product from kb")
 | 
			
		||||
	}
 | 
			
		||||
	if !slices.Contains(products, host.Release) {
 | 
			
		||||
		products = append(products, host.Release)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, product := range util.Unique(products) {
 | 
			
		||||
		pkgs, err := vulndb.GetPackage(host.Family, host.Release, product)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return errors.Wrap(err, "get package")
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		for cveid, datasrcs := range pkgs {
 | 
			
		||||
			for datasrc, ps := range datasrcs {
 | 
			
		||||
				for id, p := range ps {
 | 
			
		||||
					switch p.Status {
 | 
			
		||||
					case "fixed":
 | 
			
		||||
						for _, v := range p.Version {
 | 
			
		||||
							if slices.Contains(unapplied, v[0].Version) {
 | 
			
		||||
								vinfo, ok := host.ScannedCves[cveid]
 | 
			
		||||
								if !ok {
 | 
			
		||||
									host.ScannedCves[cveid] = types.VulnInfo{ID: cveid}
 | 
			
		||||
								}
 | 
			
		||||
								vinfo.AffectedPackages = append(vinfo.AffectedPackages, types.AffectedPackage{
 | 
			
		||||
									Name:   fmt.Sprintf("%s: KB%s", product, v[0].Version),
 | 
			
		||||
									Source: fmt.Sprintf("%s:%s", datasrc, id),
 | 
			
		||||
									Status: p.Status,
 | 
			
		||||
								})
 | 
			
		||||
								vinfo.AffectedPackages = util.Unique(vinfo.AffectedPackages)
 | 
			
		||||
								host.ScannedCves[cveid] = vinfo
 | 
			
		||||
							}
 | 
			
		||||
						}
 | 
			
		||||
					case "unfixed":
 | 
			
		||||
						vinfo, ok := host.ScannedCves[cveid]
 | 
			
		||||
						if !ok {
 | 
			
		||||
							host.ScannedCves[cveid] = types.VulnInfo{ID: cveid}
 | 
			
		||||
						}
 | 
			
		||||
						vinfo.AffectedPackages = append(vinfo.AffectedPackages, types.AffectedPackage{
 | 
			
		||||
							Name:   product,
 | 
			
		||||
							Source: fmt.Sprintf("%s:%s", datasrc, id),
 | 
			
		||||
							Status: p.Status,
 | 
			
		||||
						})
 | 
			
		||||
						vinfo.AffectedPackages = util.Unique(vinfo.AffectedPackages)
 | 
			
		||||
						host.ScannedCves[cveid] = vinfo
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	vulns, err := vulndb.GetVulnerability(maps.Keys(host.ScannedCves))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return errors.Wrap(err, "get vulnerability")
 | 
			
		||||
	}
 | 
			
		||||
	for cveid, datasrcs := range vulns {
 | 
			
		||||
		vinfo := host.ScannedCves[cveid]
 | 
			
		||||
		vinfo.Content = map[string]dbTypes.Vulnerability{}
 | 
			
		||||
		for src, v := range datasrcs {
 | 
			
		||||
			vinfo.Content[src] = v
 | 
			
		||||
		}
 | 
			
		||||
		host.ScannedCves[cveid] = vinfo
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										22
									
								
								pkg/log/log.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								pkg/log/log.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,22 @@
 | 
			
		||||
package log
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
 | 
			
		||||
	"go.uber.org/zap"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type ctxLogger struct{}
 | 
			
		||||
 | 
			
		||||
// ContextWithLogger adds logger to context
 | 
			
		||||
func ContextWithLogger(ctx context.Context, l *zap.Logger) context.Context {
 | 
			
		||||
	return context.WithValue(ctx, ctxLogger{}, l)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// LoggerFromContext returns logger from context
 | 
			
		||||
func LoggerFromContext(ctx context.Context) *zap.Logger {
 | 
			
		||||
	if l, ok := ctx.Value(ctxLogger{}).(*zap.Logger); ok {
 | 
			
		||||
		return l
 | 
			
		||||
	}
 | 
			
		||||
	return zap.L()
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										44
									
								
								pkg/scan/cpe/cpe.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								pkg/scan/cpe/cpe.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,44 @@
 | 
			
		||||
package cpe
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"fmt"
 | 
			
		||||
 | 
			
		||||
	"github.com/knqyf263/go-cpe/naming"
 | 
			
		||||
	"github.com/pkg/errors"
 | 
			
		||||
 | 
			
		||||
	scanTypes "github.com/future-architect/vuls/pkg/scan/types"
 | 
			
		||||
	"github.com/future-architect/vuls/pkg/types"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type Analyzer struct {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a Analyzer) Name() string {
 | 
			
		||||
	return "cpe analyzer"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a Analyzer) Analyze(ctx context.Context, ah *scanTypes.AnalyzerHost) error {
 | 
			
		||||
	ah.Host.Packages.CPE = map[string]types.CPE{}
 | 
			
		||||
	for _, c := range ah.Host.Config.Scan.CPE {
 | 
			
		||||
		if _, err := naming.UnbindFS(c.CPE); err != nil {
 | 
			
		||||
			return errors.Wrapf(err, "unbind %s", c.CPE)
 | 
			
		||||
		}
 | 
			
		||||
		key := c.CPE
 | 
			
		||||
 | 
			
		||||
		if c.RunningOn != "" {
 | 
			
		||||
			if _, err := naming.UnbindFS(c.RunningOn); err != nil {
 | 
			
		||||
				return errors.Wrapf(err, "unbind %s", c.RunningOn)
 | 
			
		||||
			}
 | 
			
		||||
			key = fmt.Sprintf("%s_on_%s", c.CPE, c.RunningOn)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		ah.Host.Packages.CPE[key] = types.CPE{
 | 
			
		||||
			CPE:       c.CPE,
 | 
			
		||||
			RunningOn: c.RunningOn,
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										93
									
								
								pkg/scan/os/os.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										93
									
								
								pkg/scan/os/os.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,93 @@
 | 
			
		||||
package os
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bufio"
 | 
			
		||||
	"context"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/pkg/errors"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/pkg/scan/ospkg/apk"
 | 
			
		||||
	"github.com/future-architect/vuls/pkg/scan/ospkg/dpkg"
 | 
			
		||||
	"github.com/future-architect/vuls/pkg/scan/ospkg/rpm"
 | 
			
		||||
	"github.com/future-architect/vuls/pkg/scan/types"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type Analyzer struct {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a Analyzer) Name() string {
 | 
			
		||||
	return "os analyzer"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a Analyzer) Analyze(ctx context.Context, ah *types.AnalyzerHost) error {
 | 
			
		||||
	status, stdout, stderr, err := ah.Host.Exec(ctx, "cat /etc/os-release", false)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return errors.Wrap(err, `exec "cat /etc/os-release"`)
 | 
			
		||||
	}
 | 
			
		||||
	if stderr != "" {
 | 
			
		||||
		return errors.New(stderr)
 | 
			
		||||
	}
 | 
			
		||||
	if status != 0 {
 | 
			
		||||
		return errors.Errorf("exit status is %d", status)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ah.Host.Family, ah.Host.Release, err = ParseOSRelease(stdout)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return errors.Wrap(err, "parse /etc/os-release")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	switch ah.Host.Family {
 | 
			
		||||
	case "debian", "ubuntu":
 | 
			
		||||
		ah.Analyzers = append(ah.Analyzers, dpkg.Analyzer{})
 | 
			
		||||
	case "redhat", "centos", "alma", "rocky", "fedora", "opensuse", "opensuse.tumbleweed", "opensuse.leap", "suse.linux.enterprise.server", "suse.linux.enterprise.desktop":
 | 
			
		||||
		ah.Analyzers = append(ah.Analyzers, rpm.Analyzer{})
 | 
			
		||||
	case "alpine":
 | 
			
		||||
		ah.Analyzers = append(ah.Analyzers, apk.Analyzer{})
 | 
			
		||||
	case "":
 | 
			
		||||
		return errors.New("family is unknown")
 | 
			
		||||
	default:
 | 
			
		||||
		return errors.New("not supported OS")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func ParseOSRelease(stdout string) (string, string, error) {
 | 
			
		||||
	var family, versionID string
 | 
			
		||||
	scanner := bufio.NewScanner(strings.NewReader(stdout))
 | 
			
		||||
	for scanner.Scan() {
 | 
			
		||||
		line := scanner.Text()
 | 
			
		||||
 | 
			
		||||
		ss := strings.SplitN(line, "=", 2)
 | 
			
		||||
		if len(ss) != 2 {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		key, value := strings.TrimSpace(ss[0]), strings.TrimSpace(ss[1])
 | 
			
		||||
 | 
			
		||||
		switch key {
 | 
			
		||||
		case "ID":
 | 
			
		||||
			switch id := strings.Trim(value, `"'`); id {
 | 
			
		||||
			case "almalinux":
 | 
			
		||||
				family = "alma"
 | 
			
		||||
			case "opensuse-leap", "opensuse-tumbleweed":
 | 
			
		||||
				family = strings.ReplaceAll(id, "-", ".")
 | 
			
		||||
			case "sles":
 | 
			
		||||
				family = "suse.linux.enterprise.server"
 | 
			
		||||
			case "sled":
 | 
			
		||||
				family = "suse.linux.enterprise.desktop"
 | 
			
		||||
			default:
 | 
			
		||||
				family = strings.ToLower(id)
 | 
			
		||||
			}
 | 
			
		||||
		case "VERSION_ID":
 | 
			
		||||
			versionID = strings.Trim(value, `"'`)
 | 
			
		||||
		default:
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if family == "" {
 | 
			
		||||
		return "", "", errors.New("family is unknown")
 | 
			
		||||
	}
 | 
			
		||||
	return family, versionID, nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										71
									
								
								pkg/scan/ospkg/apk/apk.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										71
									
								
								pkg/scan/ospkg/apk/apk.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,71 @@
 | 
			
		||||
package apk
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bufio"
 | 
			
		||||
	"context"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/pkg/errors"
 | 
			
		||||
 | 
			
		||||
	scanTypes "github.com/future-architect/vuls/pkg/scan/types"
 | 
			
		||||
	"github.com/future-architect/vuls/pkg/types"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type Analyzer struct {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a Analyzer) Name() string {
 | 
			
		||||
	return "apk analyzer"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a Analyzer) Analyze(ctx context.Context, ah *scanTypes.AnalyzerHost) error {
 | 
			
		||||
	status, stdout, stderr, err := ah.Host.Exec(ctx, "apk info -v", false)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return errors.Wrap(err, `exec "apk info -v"`)
 | 
			
		||||
	}
 | 
			
		||||
	if stderr != "" {
 | 
			
		||||
		return errors.New(stderr)
 | 
			
		||||
	}
 | 
			
		||||
	if status != 0 {
 | 
			
		||||
		return errors.Errorf("exit status is %d", status)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ah.Host.Packages.OSPkg, err = ParseInstalledPackage(stdout)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return errors.Wrap(err, "parse installed package")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func ParseInstalledPackage(stdout string) (map[string]types.Package, error) {
 | 
			
		||||
	pkgs := map[string]types.Package{}
 | 
			
		||||
 | 
			
		||||
	scanner := bufio.NewScanner(strings.NewReader(stdout))
 | 
			
		||||
	for scanner.Scan() {
 | 
			
		||||
		name, version, err := parseApkInfo(scanner.Text())
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, errors.Wrap(err, "parse apk info line")
 | 
			
		||||
		}
 | 
			
		||||
		if name == "" || version == "" {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		pkgs[name] = types.Package{
 | 
			
		||||
			Name:    name,
 | 
			
		||||
			Version: version,
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return pkgs, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func parseApkInfo(line string) (string, string, error) {
 | 
			
		||||
	ss := strings.Split(line, "-")
 | 
			
		||||
	if len(ss) < 3 {
 | 
			
		||||
		if strings.Contains(ss[0], "WARNING") {
 | 
			
		||||
			return "", "", nil
 | 
			
		||||
		}
 | 
			
		||||
		return "", "", errors.Errorf(`unexpected package line format. accepts: "<package name>-<version>-<release>", received: "%s"`, line)
 | 
			
		||||
	}
 | 
			
		||||
	return strings.Join(ss[:len(ss)-2], "-"), strings.Join(ss[len(ss)-2:], "-"), nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										95
									
								
								pkg/scan/ospkg/dpkg/dpkg.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										95
									
								
								pkg/scan/ospkg/dpkg/dpkg.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,95 @@
 | 
			
		||||
package dpkg
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bufio"
 | 
			
		||||
	"context"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/pkg/errors"
 | 
			
		||||
 | 
			
		||||
	scanTypes "github.com/future-architect/vuls/pkg/scan/types"
 | 
			
		||||
	"github.com/future-architect/vuls/pkg/types"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type Analyzer struct {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a Analyzer) Name() string {
 | 
			
		||||
	return "dpkg analyzer"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a Analyzer) Analyze(ctx context.Context, ah *scanTypes.AnalyzerHost) error {
 | 
			
		||||
	status, stdout, stderr, err := ah.Host.Exec(ctx, `dpkg-query -W -f="\${binary:Package},\${db:Status-Abbrev},\${Version},\${Architecture},\${source:Package},\${source:Version}\n"`, false)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return errors.Wrap(err, `exec "dpkg-query -W -f="\${binary:Package},\${db:Status-Abbrev},\${Version},\${Architecture},\${source:Package},\${source:Version}\n"`)
 | 
			
		||||
	}
 | 
			
		||||
	if stderr != "" {
 | 
			
		||||
		return errors.New(stderr)
 | 
			
		||||
	}
 | 
			
		||||
	if status != 0 {
 | 
			
		||||
		return errors.Errorf("exit status is %d", status)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ah.Host.Packages.OSPkg, err = ParseInstalledPackage(stdout)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return errors.Wrap(err, "parse installed package")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func ParseInstalledPackage(stdout string) (map[string]types.Package, error) {
 | 
			
		||||
	pkgs := map[string]types.Package{}
 | 
			
		||||
 | 
			
		||||
	scanner := bufio.NewScanner(strings.NewReader(stdout))
 | 
			
		||||
	for scanner.Scan() {
 | 
			
		||||
		line := scanner.Text()
 | 
			
		||||
		if trimmed := strings.TrimSpace(line); len(trimmed) != 0 {
 | 
			
		||||
			name, status, version, arch, srcName, srcVersion, err := parseDPKGQueryLine(trimmed)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return nil, errors.Wrap(err, "parse dpkq query line")
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			packageStatus := status[1]
 | 
			
		||||
			// Package status:
 | 
			
		||||
			//     n = Not-installed
 | 
			
		||||
			//     c = Config-files
 | 
			
		||||
			//     H = Half-installed
 | 
			
		||||
			//     U = Unpacked
 | 
			
		||||
			//     F = Half-configured
 | 
			
		||||
			//     W = Triggers-awaiting
 | 
			
		||||
			//     t = Triggers-pending
 | 
			
		||||
			//     i = Installed
 | 
			
		||||
			if packageStatus != 'i' {
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
			pkgs[name] = types.Package{
 | 
			
		||||
				Name:       name,
 | 
			
		||||
				Version:    version,
 | 
			
		||||
				Arch:       arch,
 | 
			
		||||
				SrcName:    srcName,
 | 
			
		||||
				SrcVersion: srcVersion,
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return pkgs, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func parseDPKGQueryLine(line string) (string, string, string, string, string, string, error) {
 | 
			
		||||
	ss := strings.Split(line, ",")
 | 
			
		||||
	if len(ss) == 6 {
 | 
			
		||||
		// remove :amd64, i386...
 | 
			
		||||
		name, _, _ := strings.Cut(ss[0], ":")
 | 
			
		||||
		status := strings.TrimSpace(ss[1])
 | 
			
		||||
		if len(status) < 2 {
 | 
			
		||||
			return "", "", "", "", "", "", errors.Errorf(`unexpected db:Status-Abbrev format. accepts: "ii", received: "%s"`, status)
 | 
			
		||||
		}
 | 
			
		||||
		version := ss[2]
 | 
			
		||||
		arch := ss[3]
 | 
			
		||||
		srcName, _, _ := strings.Cut(ss[4], " ")
 | 
			
		||||
		srcVersion := ss[5]
 | 
			
		||||
		return name, status, version, arch, srcName, srcVersion, nil
 | 
			
		||||
	}
 | 
			
		||||
	return "", "", "", "", "", "", errors.Errorf(`unexpected package line format. accepts: "<bin name>,<status>,<bin version>,<arch>,<src name>,<src version>", received: "%s"`, line)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										114
									
								
								pkg/scan/ospkg/rpm/rpm.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										114
									
								
								pkg/scan/ospkg/rpm/rpm.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,114 @@
 | 
			
		||||
package rpm
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bufio"
 | 
			
		||||
	"context"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/hashicorp/go-version"
 | 
			
		||||
	"github.com/pkg/errors"
 | 
			
		||||
 | 
			
		||||
	scanTypes "github.com/future-architect/vuls/pkg/scan/types"
 | 
			
		||||
	"github.com/future-architect/vuls/pkg/types"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type Analyzer struct {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a Analyzer) Name() string {
 | 
			
		||||
	return "rpm analyzer"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a Analyzer) Analyze(ctx context.Context, ah *scanTypes.AnalyzerHost) error {
 | 
			
		||||
	status, stdout, stderr, err := ah.Host.Exec(ctx, `rpm --version`, false)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return errors.Wrap(err, `exec "rpm --version"`)
 | 
			
		||||
	}
 | 
			
		||||
	if stderr != "" {
 | 
			
		||||
		return errors.New(stderr)
 | 
			
		||||
	}
 | 
			
		||||
	if status != 0 {
 | 
			
		||||
		return errors.Errorf("exit status is %d", status)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	cmd := `rpm -qa --queryformat "%{NAME} %{EPOCH} %{VERSION} %{RELEASE} %{ARCH} %{VENDOR}\n"`
 | 
			
		||||
	rpmver, err := version.NewVersion(strings.TrimPrefix(strings.TrimSpace(stdout), "RPM version "))
 | 
			
		||||
	rpmModukaritylabel, err := version.NewVersion("4.15.0")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return errors.Wrap(err, "parse rpm version for modularitylabel")
 | 
			
		||||
	}
 | 
			
		||||
	rpmEpochNum, err := version.NewVersion("4.8.0")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return errors.Wrap(err, "parse rpm version for epochnum")
 | 
			
		||||
	}
 | 
			
		||||
	if rpmver.GreaterThanOrEqual(rpmModukaritylabel) {
 | 
			
		||||
		cmd = `rpm -qa --queryformat "%{NAME} %{EPOCHNUM} %{VERSION} %{RELEASE} %{ARCH} %{VENDOR} %{MODULARITYLABEL}\n"`
 | 
			
		||||
	} else if rpmver.GreaterThanOrEqual(rpmEpochNum) {
 | 
			
		||||
		cmd = `rpm -qa --queryformat "%{NAME} %{EPOCHNUM} %{VERSION} %{RELEASE} %{ARCH} %{VENDOR}\n"`
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	status, stdout, stderr, err = ah.Host.Exec(ctx, cmd, false)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return errors.Wrapf(err, `exec "%s"`, cmd)
 | 
			
		||||
	}
 | 
			
		||||
	if stderr != "" {
 | 
			
		||||
		return errors.New(stderr)
 | 
			
		||||
	}
 | 
			
		||||
	if status != 0 {
 | 
			
		||||
		return errors.Errorf("exit status is %d", status)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ah.Host.Packages.OSPkg, err = ParseInstalledPackage(stdout)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return errors.Wrap(err, "parse installed package")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func ParseInstalledPackage(stdout string) (map[string]types.Package, error) {
 | 
			
		||||
	pkgs := map[string]types.Package{}
 | 
			
		||||
 | 
			
		||||
	scanner := bufio.NewScanner(strings.NewReader(stdout))
 | 
			
		||||
	for scanner.Scan() {
 | 
			
		||||
		line := scanner.Text()
 | 
			
		||||
		if trimmed := strings.TrimSpace(line); len(trimmed) != 0 {
 | 
			
		||||
			name, version, release, arch, vendor, modularitylabel, err := parseRpmQaLine(trimmed)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return nil, errors.Wrap(err, "parse rpm -qa line")
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			pkgs[name] = types.Package{
 | 
			
		||||
				Name:            name,
 | 
			
		||||
				Version:         version,
 | 
			
		||||
				Release:         release,
 | 
			
		||||
				Arch:            arch,
 | 
			
		||||
				Vendor:          vendor,
 | 
			
		||||
				ModularityLabel: modularitylabel,
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return pkgs, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func parseRpmQaLine(line string) (string, string, string, string, string, string, error) {
 | 
			
		||||
	ss := strings.Fields(line)
 | 
			
		||||
	if len(ss) < 6 {
 | 
			
		||||
		return "", "", "", "", "", "", errors.Errorf(`unexpected rpm -qa line format. accepts: "<name> <epoch> <version> <release> <arch> <vendor>( <modularitylabel>)", received: "%s"`, line)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ver := ss[2]
 | 
			
		||||
	epoch := ss[1]
 | 
			
		||||
	if epoch != "0" && epoch != "(none)" {
 | 
			
		||||
		ver = fmt.Sprintf("%s:%s", epoch, ss[2])
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var modularitylabel string
 | 
			
		||||
	if len(ss) == 7 {
 | 
			
		||||
		modularitylabel = ss[5]
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return ss[0], ver, ss[3], ss[4], ss[5], modularitylabel, nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										54
									
								
								pkg/scan/scan.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								pkg/scan/scan.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,54 @@
 | 
			
		||||
package scan
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"runtime"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/pkg/errors"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/pkg/cmd/version"
 | 
			
		||||
	"github.com/future-architect/vuls/pkg/scan/cpe"
 | 
			
		||||
	"github.com/future-architect/vuls/pkg/scan/os"
 | 
			
		||||
	"github.com/future-architect/vuls/pkg/scan/systeminfo"
 | 
			
		||||
	scanTypes "github.com/future-architect/vuls/pkg/scan/types"
 | 
			
		||||
	"github.com/future-architect/vuls/pkg/types"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func Scan(ctx context.Context, host *types.Host) error {
 | 
			
		||||
	ah := scanTypes.AnalyzerHost{Host: host}
 | 
			
		||||
	if ah.Host.Config.Scan.OSPkg != nil {
 | 
			
		||||
		if runtime.GOOS == "windows" {
 | 
			
		||||
			ah.Analyzers = append(ah.Analyzers, systeminfo.Analyzer{})
 | 
			
		||||
		} else {
 | 
			
		||||
			ah.Analyzers = append(ah.Analyzers, os.Analyzer{})
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if len(ah.Host.Config.Scan.CPE) > 0 {
 | 
			
		||||
		ah.Analyzers = append(ah.Analyzers, cpe.Analyzer{})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var (
 | 
			
		||||
		err error
 | 
			
		||||
	)
 | 
			
		||||
	for {
 | 
			
		||||
		if len(ah.Analyzers) == 0 {
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
		a := ah.Analyzers[0]
 | 
			
		||||
		if err = a.Analyze(ctx, &ah); err != nil {
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
		ah.Analyzers = ah.Analyzers[1:]
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	t := time.Now()
 | 
			
		||||
	ah.Host.ScannedAt = &t
 | 
			
		||||
	ah.Host.ScannedVersion = version.Version
 | 
			
		||||
	ah.Host.ScannedRevision = version.Revision
 | 
			
		||||
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return errors.Wrapf(err, "analyze %s", ah.Host.Name)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										480
									
								
								pkg/scan/systeminfo/systeminfo.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										480
									
								
								pkg/scan/systeminfo/systeminfo.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,480 @@
 | 
			
		||||
package systeminfo
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bufio"
 | 
			
		||||
	"context"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/pkg/errors"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/pkg/scan/types"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type Analyzer struct {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a Analyzer) Name() string {
 | 
			
		||||
	return "systeminfo analyzer"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a Analyzer) Analyze(ctx context.Context, ah *types.AnalyzerHost) error {
 | 
			
		||||
	status, stdout, stderr, err := ah.Host.Exec(ctx, "systeminfo", false)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return errors.Wrap(err, `exec "systeminfo"`)
 | 
			
		||||
	}
 | 
			
		||||
	if stderr != "" {
 | 
			
		||||
		return errors.New(stderr)
 | 
			
		||||
	}
 | 
			
		||||
	if status != 0 {
 | 
			
		||||
		return errors.Errorf("exit status is %d", status)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ah.Host.Family, ah.Host.Release, ah.Host.Packages.KB, err = ParseSysteminfo(stdout)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return errors.Wrap(err, "parse systeminfo")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if ah.Host.Family == "" {
 | 
			
		||||
		return errors.New("family is unknown")
 | 
			
		||||
	}
 | 
			
		||||
	if ah.Host.Release == "" {
 | 
			
		||||
		return errors.New("release is unknown")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func ParseSysteminfo(stdout string) (string, string, []string, error) {
 | 
			
		||||
	var (
 | 
			
		||||
		o   osInfo
 | 
			
		||||
		kbs []string
 | 
			
		||||
	)
 | 
			
		||||
	scanner := bufio.NewScanner(strings.NewReader(stdout))
 | 
			
		||||
	for scanner.Scan() {
 | 
			
		||||
		line := scanner.Text()
 | 
			
		||||
 | 
			
		||||
		switch {
 | 
			
		||||
		case strings.HasPrefix(line, "OS Name:"):
 | 
			
		||||
			o.productName = strings.TrimSpace(strings.TrimPrefix(line, "OS Name:"))
 | 
			
		||||
		case strings.HasPrefix(line, "OS Version:"):
 | 
			
		||||
			s := strings.TrimSpace(strings.TrimPrefix(line, "OS Version:"))
 | 
			
		||||
			lhs, build, _ := strings.Cut(s, " Build ")
 | 
			
		||||
			vb, sp, _ := strings.Cut(lhs, " ")
 | 
			
		||||
			o.version = strings.TrimSuffix(vb, fmt.Sprintf(".%s", build))
 | 
			
		||||
			o.build = build
 | 
			
		||||
			if sp != "N/A" {
 | 
			
		||||
				o.servicePack = sp
 | 
			
		||||
			}
 | 
			
		||||
		case strings.HasPrefix(line, "System Type:"):
 | 
			
		||||
			o.arch = strings.TrimSpace(strings.TrimSuffix(strings.TrimPrefix(line, "System Type:"), "PC"))
 | 
			
		||||
		case strings.HasPrefix(line, "OS Configuration:"):
 | 
			
		||||
			switch {
 | 
			
		||||
			case strings.Contains(line, "Server"):
 | 
			
		||||
				o.installationType = "Server"
 | 
			
		||||
			case strings.Contains(line, "Workstation"):
 | 
			
		||||
				o.installationType = "Client"
 | 
			
		||||
			default:
 | 
			
		||||
				return "", "", nil, errors.Errorf(`installation type not found from "%s"`, line)
 | 
			
		||||
			}
 | 
			
		||||
		case strings.HasPrefix(line, "Hotfix(s):"):
 | 
			
		||||
			nKB, err := strconv.Atoi(strings.TrimSpace(strings.TrimSuffix(strings.TrimPrefix(line, "Hotfix(s):"), " Hotfix(s) Installed.")))
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return "", "", nil, errors.Errorf(`number of installed hotfix from "%s"`, line)
 | 
			
		||||
			}
 | 
			
		||||
			for i := 0; i < nKB; i++ {
 | 
			
		||||
				scanner.Scan()
 | 
			
		||||
				line := scanner.Text()
 | 
			
		||||
				_, rhs, found := strings.Cut(line, ":")
 | 
			
		||||
				if !found {
 | 
			
		||||
					continue
 | 
			
		||||
				}
 | 
			
		||||
				s := strings.TrimSpace(rhs)
 | 
			
		||||
				if strings.HasPrefix(s, "KB") {
 | 
			
		||||
					kbs = append(kbs, strings.TrimPrefix(s, "KB"))
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		default:
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	release, err := detectOSName(o)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "", "", nil, errors.Wrap(err, "detect os name")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return "windows", release, kbs, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type osInfo struct {
 | 
			
		||||
	productName      string
 | 
			
		||||
	version          string
 | 
			
		||||
	build            string
 | 
			
		||||
	edition          string
 | 
			
		||||
	servicePack      string
 | 
			
		||||
	arch             string
 | 
			
		||||
	installationType string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func detectOSName(osInfo osInfo) (string, error) {
 | 
			
		||||
	osName, err := detectOSNameFromOSInfo(osInfo)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "", errors.Wrapf(err, "detect OS Name from OSInfo: %#v", osInfo)
 | 
			
		||||
	}
 | 
			
		||||
	return osName, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func detectOSNameFromOSInfo(osInfo osInfo) (string, error) {
 | 
			
		||||
	switch osInfo.version {
 | 
			
		||||
	case "5.0":
 | 
			
		||||
		switch osInfo.installationType {
 | 
			
		||||
		case "Client":
 | 
			
		||||
			if osInfo.servicePack != "" {
 | 
			
		||||
				return fmt.Sprintf("Microsoft Windows 2000 %s", osInfo.servicePack), nil
 | 
			
		||||
			}
 | 
			
		||||
			return "Microsoft Windows 2000", nil
 | 
			
		||||
		case "Server":
 | 
			
		||||
			if osInfo.servicePack != "" {
 | 
			
		||||
				return fmt.Sprintf("Microsoft Windows 2000 Server %s", osInfo.servicePack), nil
 | 
			
		||||
			}
 | 
			
		||||
			return "Microsoft Windows 2000 Server", nil
 | 
			
		||||
		}
 | 
			
		||||
	case "5.1":
 | 
			
		||||
		switch osInfo.installationType {
 | 
			
		||||
		case "Client":
 | 
			
		||||
			var n string
 | 
			
		||||
			switch osInfo.edition {
 | 
			
		||||
			case "Professional":
 | 
			
		||||
				n = "Microsoft Windows XP Professional"
 | 
			
		||||
			case "Media Center":
 | 
			
		||||
				n = "Microsoft Windows XP Media Center Edition 2005"
 | 
			
		||||
			case "Tablet PC":
 | 
			
		||||
				n = "Microsoft Windows XP Tablet PC Edition 2005"
 | 
			
		||||
			default:
 | 
			
		||||
				n = "Microsoft Windows XP"
 | 
			
		||||
			}
 | 
			
		||||
			switch osInfo.arch {
 | 
			
		||||
			case "x64":
 | 
			
		||||
				n = fmt.Sprintf("%s x64 Edition", n)
 | 
			
		||||
			}
 | 
			
		||||
			if osInfo.servicePack != "" {
 | 
			
		||||
				return fmt.Sprintf("%s %s", n, osInfo.servicePack), nil
 | 
			
		||||
			}
 | 
			
		||||
			return n, nil
 | 
			
		||||
		}
 | 
			
		||||
	case "5.2":
 | 
			
		||||
		switch osInfo.installationType {
 | 
			
		||||
		case "Client":
 | 
			
		||||
			var n string
 | 
			
		||||
			switch osInfo.edition {
 | 
			
		||||
			case "Professional":
 | 
			
		||||
				n = "Microsoft Windows XP Professional"
 | 
			
		||||
			case "Media Center":
 | 
			
		||||
				n = "Microsoft Windows XP Media Center Edition 2005"
 | 
			
		||||
			case "Tablet PC":
 | 
			
		||||
				n = "Microsoft Windows XP Tablet PC Edition 2005"
 | 
			
		||||
			default:
 | 
			
		||||
				n = "Microsoft Windows XP"
 | 
			
		||||
			}
 | 
			
		||||
			switch osInfo.arch {
 | 
			
		||||
			case "x64":
 | 
			
		||||
				n = fmt.Sprintf("%s x64 Edition", n)
 | 
			
		||||
			}
 | 
			
		||||
			if osInfo.servicePack != "" {
 | 
			
		||||
				return fmt.Sprintf("%s %s", n, osInfo.servicePack), nil
 | 
			
		||||
			}
 | 
			
		||||
			return n, nil
 | 
			
		||||
		case "Server":
 | 
			
		||||
			n := "Microsoft Windows Server 2003"
 | 
			
		||||
			if strings.Contains(osInfo.productName, "R2") {
 | 
			
		||||
				n = "Microsoft Windows Server 2003 R2"
 | 
			
		||||
			}
 | 
			
		||||
			switch osInfo.arch {
 | 
			
		||||
			case "x64":
 | 
			
		||||
				n = fmt.Sprintf("%s x64 Edition", n)
 | 
			
		||||
			case "IA64":
 | 
			
		||||
				if osInfo.edition == "Enterprise" {
 | 
			
		||||
					n = fmt.Sprintf("%s, Enterprise Edition for Itanium-based Systems", n)
 | 
			
		||||
				} else {
 | 
			
		||||
					n = fmt.Sprintf("%s for Itanium-based Systems", n)
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			if osInfo.servicePack != "" {
 | 
			
		||||
				return fmt.Sprintf("%s %s", n, osInfo.servicePack), nil
 | 
			
		||||
			}
 | 
			
		||||
			return n, nil
 | 
			
		||||
		}
 | 
			
		||||
	case "6.0":
 | 
			
		||||
		switch osInfo.installationType {
 | 
			
		||||
		case "Client":
 | 
			
		||||
			var n string
 | 
			
		||||
			switch osInfo.arch {
 | 
			
		||||
			case "x64":
 | 
			
		||||
				n = "Windows Vista x64 Editions"
 | 
			
		||||
			default:
 | 
			
		||||
				n = "Windows Vista"
 | 
			
		||||
			}
 | 
			
		||||
			if osInfo.servicePack != "" {
 | 
			
		||||
				return fmt.Sprintf("%s %s", n, osInfo.servicePack), nil
 | 
			
		||||
			}
 | 
			
		||||
			return n, nil
 | 
			
		||||
		case "Server":
 | 
			
		||||
			arch, err := formatArch(osInfo.arch)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return "", err
 | 
			
		||||
			}
 | 
			
		||||
			if osInfo.servicePack != "" {
 | 
			
		||||
				return fmt.Sprintf("Windows Server 2008 for %s Systems %s", arch, osInfo.servicePack), nil
 | 
			
		||||
			}
 | 
			
		||||
			return fmt.Sprintf("Windows Server 2008 for %s Systems", arch), nil
 | 
			
		||||
		case "Server Core":
 | 
			
		||||
			arch, err := formatArch(osInfo.arch)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return "", err
 | 
			
		||||
			}
 | 
			
		||||
			if osInfo.servicePack != "" {
 | 
			
		||||
				return fmt.Sprintf("Windows Server 2008 for %s Systems %s (Server Core installation)", arch, osInfo.servicePack), nil
 | 
			
		||||
			}
 | 
			
		||||
			return fmt.Sprintf("Windows Server 2008 for %s Systems (Server Core installation)", arch), nil
 | 
			
		||||
		}
 | 
			
		||||
	case "6.1":
 | 
			
		||||
		switch osInfo.installationType {
 | 
			
		||||
		case "Client":
 | 
			
		||||
			arch, err := formatArch(osInfo.arch)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return "", err
 | 
			
		||||
			}
 | 
			
		||||
			if osInfo.servicePack != "" {
 | 
			
		||||
				return fmt.Sprintf("Windows 7 for %s Systems %s", arch, osInfo.servicePack), nil
 | 
			
		||||
			}
 | 
			
		||||
			return fmt.Sprintf("Windows 7 for %s Systems", arch), nil
 | 
			
		||||
		case "Server":
 | 
			
		||||
			arch, err := formatArch(osInfo.arch)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return "", err
 | 
			
		||||
			}
 | 
			
		||||
			if osInfo.servicePack != "" {
 | 
			
		||||
				return fmt.Sprintf("Windows Server 2008 R2 for %s Systems %s", arch, osInfo.servicePack), nil
 | 
			
		||||
			}
 | 
			
		||||
			return fmt.Sprintf("Windows Server 2008 R2 for %s Systems", arch), nil
 | 
			
		||||
		case "Server Core":
 | 
			
		||||
			arch, err := formatArch(osInfo.arch)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return "", err
 | 
			
		||||
			}
 | 
			
		||||
			if osInfo.servicePack != "" {
 | 
			
		||||
				return fmt.Sprintf("Windows Server 2008 R2 for %s Systems %s (Server Core installation)", arch, osInfo.servicePack), nil
 | 
			
		||||
			}
 | 
			
		||||
			return fmt.Sprintf("Windows Server 2008 R2 for %s Systems (Server Core installation)", arch), nil
 | 
			
		||||
		}
 | 
			
		||||
	case "6.2":
 | 
			
		||||
		switch osInfo.installationType {
 | 
			
		||||
		case "Client":
 | 
			
		||||
			arch, err := formatArch(osInfo.arch)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return "", err
 | 
			
		||||
			}
 | 
			
		||||
			return fmt.Sprintf("Windows 8 for %s Systems", arch), nil
 | 
			
		||||
		case "Server":
 | 
			
		||||
			return "Windows Server 2012", nil
 | 
			
		||||
		case "Server Core":
 | 
			
		||||
			return "Windows Server 2012 (Server Core installation)", nil
 | 
			
		||||
		}
 | 
			
		||||
	case "6.3":
 | 
			
		||||
		switch osInfo.installationType {
 | 
			
		||||
		case "Client":
 | 
			
		||||
			arch, err := formatArch(osInfo.arch)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return "", err
 | 
			
		||||
			}
 | 
			
		||||
			return fmt.Sprintf("Windows 8.1 for %s Systems", arch), nil
 | 
			
		||||
		case "Server":
 | 
			
		||||
			return "Windows Server 2012 R2", nil
 | 
			
		||||
		case "Server Core":
 | 
			
		||||
			return "Windows Server 2012 R2 (Server Core installation)", nil
 | 
			
		||||
		}
 | 
			
		||||
	case "10.0":
 | 
			
		||||
		switch osInfo.installationType {
 | 
			
		||||
		case "Client":
 | 
			
		||||
			if strings.Contains(osInfo.productName, "Windows 10") {
 | 
			
		||||
				arch, err := formatArch(osInfo.arch)
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					return "", err
 | 
			
		||||
				}
 | 
			
		||||
				name, err := formatNamebyBuild("10", osInfo.build)
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					return "", err
 | 
			
		||||
				}
 | 
			
		||||
				return fmt.Sprintf("%s for %s Systems", name, arch), nil
 | 
			
		||||
			}
 | 
			
		||||
			if strings.Contains(osInfo.productName, "Windows 11") {
 | 
			
		||||
				arch, err := formatArch(osInfo.arch)
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					return "", err
 | 
			
		||||
				}
 | 
			
		||||
				name, err := formatNamebyBuild("11", osInfo.build)
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					return "", err
 | 
			
		||||
				}
 | 
			
		||||
				return fmt.Sprintf("%s for %s Systems", name, arch), nil
 | 
			
		||||
			}
 | 
			
		||||
		case "Server":
 | 
			
		||||
			return formatNamebyBuild("Server", osInfo.build)
 | 
			
		||||
		case "Server Core":
 | 
			
		||||
			name, err := formatNamebyBuild("Server", osInfo.build)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return "", err
 | 
			
		||||
			}
 | 
			
		||||
			return fmt.Sprintf("%s (Server Core installation)", name), nil
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return "", errors.New("OS Name not found")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func formatArch(arch string) (string, error) {
 | 
			
		||||
	switch arch {
 | 
			
		||||
	case "x64-based":
 | 
			
		||||
		return "x64-based", nil
 | 
			
		||||
	case "ARM64-based":
 | 
			
		||||
		return "ARM64-based", nil
 | 
			
		||||
	case "Itanium-based":
 | 
			
		||||
		return "Itanium-based", nil
 | 
			
		||||
	case "X86-based":
 | 
			
		||||
		return "32-bit", nil
 | 
			
		||||
	default:
 | 
			
		||||
		return "", errors.New("CPU Architecture not found")
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type buildNumber struct {
 | 
			
		||||
	build string
 | 
			
		||||
	name  string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
	winBuilds = map[string][]buildNumber{
 | 
			
		||||
		"10": {
 | 
			
		||||
			{
 | 
			
		||||
				build: "10240",
 | 
			
		||||
				name:  "Windows 10", // not "Windows 10 Version 1507"
 | 
			
		||||
			},
 | 
			
		||||
			{
 | 
			
		||||
				build: "10586",
 | 
			
		||||
				name:  "Windows 10 Version 1511",
 | 
			
		||||
			},
 | 
			
		||||
			{
 | 
			
		||||
				build: "14393",
 | 
			
		||||
				name:  "Windows 10 Version 1607",
 | 
			
		||||
			},
 | 
			
		||||
			{
 | 
			
		||||
				build: "15063",
 | 
			
		||||
				name:  "Windows 10 Version 1703",
 | 
			
		||||
			},
 | 
			
		||||
			{
 | 
			
		||||
				build: "16299",
 | 
			
		||||
				name:  "Windows 10 Version 1709",
 | 
			
		||||
			},
 | 
			
		||||
			{
 | 
			
		||||
				build: "17134",
 | 
			
		||||
				name:  "Windows 10 Version 1803",
 | 
			
		||||
			},
 | 
			
		||||
			{
 | 
			
		||||
				build: "17763",
 | 
			
		||||
				name:  "Windows 10 Version 1809",
 | 
			
		||||
			},
 | 
			
		||||
			{
 | 
			
		||||
				build: "18362",
 | 
			
		||||
				name:  "Windows 10 Version 1903",
 | 
			
		||||
			},
 | 
			
		||||
			{
 | 
			
		||||
				build: "18363",
 | 
			
		||||
				name:  "Windows 10 Version 1909",
 | 
			
		||||
			},
 | 
			
		||||
			{
 | 
			
		||||
				build: "19041",
 | 
			
		||||
				name:  "Windows 10 Version 2004",
 | 
			
		||||
			},
 | 
			
		||||
			{
 | 
			
		||||
				build: "19042",
 | 
			
		||||
				name:  "Windows 10 Version 20H2",
 | 
			
		||||
			},
 | 
			
		||||
			{
 | 
			
		||||
				build: "19043",
 | 
			
		||||
				name:  "Windows 10 Version 21H1",
 | 
			
		||||
			},
 | 
			
		||||
			{
 | 
			
		||||
				build: "19044",
 | 
			
		||||
				name:  "Windows 10 Version 21H2",
 | 
			
		||||
			},
 | 
			
		||||
			// It seems that there are cases where the Product Name is Windows 10 even though it is Windows 11
 | 
			
		||||
			// ref: https://docs.microsoft.com/en-us/answers/questions/586548/in-the-official-version-of-windows-11-why-the-key.html
 | 
			
		||||
			{
 | 
			
		||||
				build: "22000",
 | 
			
		||||
				name:  "Windows 11",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		"11": {
 | 
			
		||||
			{
 | 
			
		||||
				build: "22000",
 | 
			
		||||
				name:  "Windows 11", // not "Windows 11 Version 21H2"
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		"Server": {
 | 
			
		||||
			{
 | 
			
		||||
				build: "14393",
 | 
			
		||||
				name:  "Windows Server 2016",
 | 
			
		||||
			},
 | 
			
		||||
			{
 | 
			
		||||
				build: "16299",
 | 
			
		||||
				name:  "Windows Server, Version 1709",
 | 
			
		||||
			},
 | 
			
		||||
			{
 | 
			
		||||
				build: "17134",
 | 
			
		||||
				name:  "Windows Server, Version 1809",
 | 
			
		||||
			},
 | 
			
		||||
			{
 | 
			
		||||
				build: "17763",
 | 
			
		||||
				name:  "Windows Server 2019",
 | 
			
		||||
			},
 | 
			
		||||
			{
 | 
			
		||||
				build: "18362",
 | 
			
		||||
				name:  "Windows Server, Version 1903",
 | 
			
		||||
			},
 | 
			
		||||
			{
 | 
			
		||||
				build: "18363",
 | 
			
		||||
				name:  "Windows Server, Version 1909",
 | 
			
		||||
			},
 | 
			
		||||
			{
 | 
			
		||||
				build: "19041",
 | 
			
		||||
				name:  "Windows Server, Version 2004",
 | 
			
		||||
			},
 | 
			
		||||
			{
 | 
			
		||||
				build: "19042",
 | 
			
		||||
				name:  "Windows Server, Version 20H2",
 | 
			
		||||
			},
 | 
			
		||||
			{
 | 
			
		||||
				build: "20348",
 | 
			
		||||
				name:  "Windows Server 2022",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func formatNamebyBuild(osType string, mybuild string) (string, error) {
 | 
			
		||||
	builds, ok := winBuilds[osType]
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return "", errors.New("OS Type not found")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	v := builds[0].name
 | 
			
		||||
	for _, b := range builds {
 | 
			
		||||
		if mybuild == b.build {
 | 
			
		||||
			return b.name, nil
 | 
			
		||||
		}
 | 
			
		||||
		if mybuild < b.build {
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
		v = b.name
 | 
			
		||||
	}
 | 
			
		||||
	return v, nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										17
									
								
								pkg/scan/types/types.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								pkg/scan/types/types.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,17 @@
 | 
			
		||||
package types
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/pkg/types"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type Analyzer interface {
 | 
			
		||||
	Name() string
 | 
			
		||||
	Analyze(context.Context, *AnalyzerHost) error
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type AnalyzerHost struct {
 | 
			
		||||
	Host      *types.Host
 | 
			
		||||
	Analyzers []Analyzer
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										108
									
								
								pkg/server/server.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										108
									
								
								pkg/server/server.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,108 @@
 | 
			
		||||
package server
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/google/uuid"
 | 
			
		||||
	"github.com/labstack/echo/v4"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/pkg/cmd/version"
 | 
			
		||||
	"github.com/future-architect/vuls/pkg/config"
 | 
			
		||||
	"github.com/future-architect/vuls/pkg/detect"
 | 
			
		||||
	"github.com/future-architect/vuls/pkg/scan/os"
 | 
			
		||||
	"github.com/future-architect/vuls/pkg/scan/ospkg/apk"
 | 
			
		||||
	"github.com/future-architect/vuls/pkg/scan/ospkg/dpkg"
 | 
			
		||||
	"github.com/future-architect/vuls/pkg/scan/ospkg/rpm"
 | 
			
		||||
	"github.com/future-architect/vuls/pkg/scan/systeminfo"
 | 
			
		||||
	"github.com/future-architect/vuls/pkg/types"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type scanContents struct {
 | 
			
		||||
	Contents []struct {
 | 
			
		||||
		ContentType string `json:"type,omitempty"`
 | 
			
		||||
		Content     string `json:"content,omitempty"`
 | 
			
		||||
	} `json:"contents,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func Scan() echo.HandlerFunc {
 | 
			
		||||
	return func(c echo.Context) error {
 | 
			
		||||
		s := new(scanContents)
 | 
			
		||||
		if err := c.Bind(s); err != nil {
 | 
			
		||||
			return c.JSON(http.StatusBadRequest, "bad request")
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		h := types.Host{Name: uuid.NewString()}
 | 
			
		||||
 | 
			
		||||
		for _, cont := range s.Contents {
 | 
			
		||||
			switch cont.ContentType {
 | 
			
		||||
			case "os-release":
 | 
			
		||||
				family, release, err := os.ParseOSRelease(cont.Content)
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					h.ScanError = err.Error()
 | 
			
		||||
					return c.JSON(http.StatusInternalServerError, h)
 | 
			
		||||
				}
 | 
			
		||||
				h.Family = family
 | 
			
		||||
				h.Release = release
 | 
			
		||||
			case "systeminfo":
 | 
			
		||||
				family, release, kbs, err := systeminfo.ParseSysteminfo(cont.Content)
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					h.ScanError = err.Error()
 | 
			
		||||
					return c.JSON(http.StatusInternalServerError, h)
 | 
			
		||||
				}
 | 
			
		||||
				h.Family = family
 | 
			
		||||
				h.Release = release
 | 
			
		||||
				h.Packages.KB = kbs
 | 
			
		||||
			case "apk":
 | 
			
		||||
				pkgs, err := apk.ParseInstalledPackage(cont.Content)
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					h.ScanError = err.Error()
 | 
			
		||||
					return c.JSON(http.StatusInternalServerError, h)
 | 
			
		||||
				}
 | 
			
		||||
				h.Packages.OSPkg = pkgs
 | 
			
		||||
			case "dpkg":
 | 
			
		||||
				pkgs, err := dpkg.ParseInstalledPackage(cont.Content)
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					h.ScanError = err.Error()
 | 
			
		||||
					return c.JSON(http.StatusInternalServerError, h)
 | 
			
		||||
				}
 | 
			
		||||
				h.Packages.OSPkg = pkgs
 | 
			
		||||
			case "rpm":
 | 
			
		||||
				pkgs, err := rpm.ParseInstalledPackage(cont.Content)
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					h.ScanError = err.Error()
 | 
			
		||||
					return c.JSON(http.StatusInternalServerError, h)
 | 
			
		||||
				}
 | 
			
		||||
				h.Packages.OSPkg = pkgs
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		t := time.Now()
 | 
			
		||||
		h.ScannedAt = &t
 | 
			
		||||
		h.ScannedVersion = version.Version
 | 
			
		||||
		h.ScannedRevision = version.Revision
 | 
			
		||||
		return c.JSON(http.StatusOK, h)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func Detect(dbpath string) echo.HandlerFunc {
 | 
			
		||||
	return func(c echo.Context) error {
 | 
			
		||||
		h := new(types.Host)
 | 
			
		||||
		if err := c.Bind(h); err != nil {
 | 
			
		||||
			return c.JSON(http.StatusBadRequest, "bad request")
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if h.Config.Detect == nil {
 | 
			
		||||
			h.Config.Detect = &config.Detect{}
 | 
			
		||||
		}
 | 
			
		||||
		h.Config.Detect.Path = dbpath
 | 
			
		||||
 | 
			
		||||
		if err := detect.Detect(context.Background(), h); err != nil {
 | 
			
		||||
			h.DetectError = err.Error()
 | 
			
		||||
			return c.JSON(http.StatusInternalServerError, h)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return c.JSON(http.StatusOK, h)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										182
									
								
								pkg/types/types.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										182
									
								
								pkg/types/types.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,182 @@
 | 
			
		||||
package types
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"context"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"os"
 | 
			
		||||
	"os/exec"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"runtime"
 | 
			
		||||
	"syscall"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/pkg/errors"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/pkg/config"
 | 
			
		||||
	"github.com/future-architect/vuls/pkg/db/types"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type Host struct {
 | 
			
		||||
	Name    string `json:"name,omitempty"`
 | 
			
		||||
	Family  string `json:"family,omitempty"`
 | 
			
		||||
	Release string `json:"release,omitempty"`
 | 
			
		||||
 | 
			
		||||
	ScannedAt       *time.Time `json:"scanned_at,omitempty"`
 | 
			
		||||
	ScannedVersion  string     `json:"scanned_version,omitempty"`
 | 
			
		||||
	ScannedRevision string     `json:"scanned_revision,omitempty"`
 | 
			
		||||
	ScanError       string     `json:"scan_error,omitempty"`
 | 
			
		||||
 | 
			
		||||
	DetecteddAt      *time.Time `json:"detectedd_at,omitempty"`
 | 
			
		||||
	DetectedVersion  string     `json:"detected_version,omitempty"`
 | 
			
		||||
	DetectedRevision string     `json:"detected_revision,omitempty"`
 | 
			
		||||
	DetectError      string     `json:"detect_error,omitempty"`
 | 
			
		||||
 | 
			
		||||
	ReportedAt       *time.Time `json:"reported_at,omitempty"`
 | 
			
		||||
	ReportedVersion  string     `json:"reported_version,omitempty"`
 | 
			
		||||
	ReportedRevision string     `json:"reported_revision,omitempty"`
 | 
			
		||||
 | 
			
		||||
	Packages    Packages            `json:"packages,omitempty"`
 | 
			
		||||
	ScannedCves map[string]VulnInfo `json:"scanned_cves,omitempty"`
 | 
			
		||||
 | 
			
		||||
	Config Config `json:"config,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (h *Host) Exec(ctx context.Context, cmd string, sudo bool) (int, string, string, error) {
 | 
			
		||||
	if sudo {
 | 
			
		||||
		cmd = fmt.Sprintf("sudo -S %s", cmd)
 | 
			
		||||
	}
 | 
			
		||||
	switch h.Config.Type {
 | 
			
		||||
	case "local":
 | 
			
		||||
		execCmd := exec.CommandContext(ctx, "/bin/sh", "-c", cmd)
 | 
			
		||||
		if runtime.GOOS == "windows" {
 | 
			
		||||
			execCmd = exec.CommandContext(ctx, cmd)
 | 
			
		||||
		}
 | 
			
		||||
		var stdoutBuf, stderrBuf bytes.Buffer
 | 
			
		||||
		execCmd.Stdout = &stdoutBuf
 | 
			
		||||
		execCmd.Stderr = &stderrBuf
 | 
			
		||||
		if err := execCmd.Run(); err != nil {
 | 
			
		||||
			if e, ok := err.(*exec.ExitError); ok {
 | 
			
		||||
				if s, ok := e.Sys().(syscall.WaitStatus); ok {
 | 
			
		||||
					return s.ExitStatus(), stdoutBuf.String(), stderrBuf.String(), nil
 | 
			
		||||
				} else {
 | 
			
		||||
					return 998, stdoutBuf.String(), stderrBuf.String(), nil
 | 
			
		||||
				}
 | 
			
		||||
			} else {
 | 
			
		||||
				return 999, stdoutBuf.String(), stderrBuf.String(), nil
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			return 0, stdoutBuf.String(), stderrBuf.String(), nil
 | 
			
		||||
		}
 | 
			
		||||
	case "remote":
 | 
			
		||||
		sshBinPath, err := exec.LookPath("ssh")
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return 0, "", "", errors.Wrap(err, "look path to ssh")
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		args := []string{"-tt"}
 | 
			
		||||
 | 
			
		||||
		home, err := os.UserHomeDir()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return 0, "", "", errors.Wrap(err, "find %s home directory")
 | 
			
		||||
		}
 | 
			
		||||
		args = append(args,
 | 
			
		||||
			"-o", "StrictHostKeyChecking=yes",
 | 
			
		||||
			"-o", "LogLevel=quiet",
 | 
			
		||||
			"-o", "ConnectionAttempts=3",
 | 
			
		||||
			"-o", "ConnectTimeout=10",
 | 
			
		||||
			"-o", "ControlMaster=auto",
 | 
			
		||||
			"-o", fmt.Sprintf("ControlPath=%s", filepath.Join(home, ".vuls", fmt.Sprintf("controlmaster-%%r-%s.%%p", h.Name))),
 | 
			
		||||
			"-o", "Controlpersist=10m",
 | 
			
		||||
			"-l", *h.Config.User,
 | 
			
		||||
		)
 | 
			
		||||
		if h.Config.Port != nil {
 | 
			
		||||
			args = append(args, "-p", *h.Config.Port)
 | 
			
		||||
		}
 | 
			
		||||
		if h.Config.SSHKey != nil {
 | 
			
		||||
			args = append(args, "-i", *h.Config.SSHKey, "-o", "PasswordAuthentication=no")
 | 
			
		||||
		}
 | 
			
		||||
		if runtime.GOOS == "windows" {
 | 
			
		||||
			args = append(args, *h.Config.Host, cmd)
 | 
			
		||||
		} else {
 | 
			
		||||
			args = append(args, *h.Config.Host, fmt.Sprintf("stty cols 1000; %s", cmd))
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		execCmd := exec.CommandContext(ctx, sshBinPath, args...)
 | 
			
		||||
		var stdoutBuf, stderrBuf bytes.Buffer
 | 
			
		||||
		execCmd.Stdout = &stdoutBuf
 | 
			
		||||
		execCmd.Stderr = &stderrBuf
 | 
			
		||||
		if err := execCmd.Run(); err != nil {
 | 
			
		||||
			if e, ok := err.(*exec.ExitError); ok {
 | 
			
		||||
				if s, ok := e.Sys().(syscall.WaitStatus); ok {
 | 
			
		||||
					return s.ExitStatus(), stdoutBuf.String(), stderrBuf.String(), nil
 | 
			
		||||
				} else {
 | 
			
		||||
					return 998, stdoutBuf.String(), stderrBuf.String(), nil
 | 
			
		||||
				}
 | 
			
		||||
			} else {
 | 
			
		||||
				return 999, stdoutBuf.String(), stderrBuf.String(), nil
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			return 0, stdoutBuf.String(), stderrBuf.String(), nil
 | 
			
		||||
		}
 | 
			
		||||
	default:
 | 
			
		||||
		return 0, "", "", errors.Errorf("%s is not implemented", h.Config.Type)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Packages struct {
 | 
			
		||||
	Kernel Kernel             `json:"kernel,omitempty"`
 | 
			
		||||
	OSPkg  map[string]Package `json:"ospkg,omitempty"`
 | 
			
		||||
	CPE    map[string]CPE     `json:"cpe,omitempty"`
 | 
			
		||||
	KB     []string           `json:"kb,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Kernel struct {
 | 
			
		||||
	Version         string `json:"version,omitempty"`
 | 
			
		||||
	Release         string `json:"release,omitempty"`
 | 
			
		||||
	RebootRrequired bool   `json:"reboot_rrequired,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Package struct {
 | 
			
		||||
	Name            string `json:"name,omitempty"`
 | 
			
		||||
	Version         string `json:"version,omitempty"`
 | 
			
		||||
	Release         string `json:"release,omitempty"`
 | 
			
		||||
	NewVersion      string `json:"new_version,omitempty"`
 | 
			
		||||
	NewRelease      string `json:"new_release,omitempty"`
 | 
			
		||||
	Arch            string `json:"arch,omitempty"`
 | 
			
		||||
	Vendor          string `json:"vendor,omitempty"`
 | 
			
		||||
	Repository      string `json:"repository,omitempty"`
 | 
			
		||||
	ModularityLabel string `json:"modularity_label,omitempty"`
 | 
			
		||||
 | 
			
		||||
	SrcName    string `json:"src_name,omitempty"`
 | 
			
		||||
	SrcVersion string `json:"src_version,omitempty"`
 | 
			
		||||
	SrcArch    string `json:"src_arch,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type CPE struct {
 | 
			
		||||
	CPE       string `json:"cpe,omitempty"`
 | 
			
		||||
	RunningOn string `json:"running_on,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type VulnInfo struct {
 | 
			
		||||
	ID               string                         `json:"id,omitempty"`
 | 
			
		||||
	Content          map[string]types.Vulnerability `json:"content,omitempty"`
 | 
			
		||||
	AffectedPackages []AffectedPackage              `json:"affected_packages,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type AffectedPackage struct {
 | 
			
		||||
	Name   string `json:"name,omitempty"`
 | 
			
		||||
	Source string `json:"source,omitempty"`
 | 
			
		||||
	Status string `json:"status,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Config struct {
 | 
			
		||||
	Type      string         `json:"type,omitempty"`
 | 
			
		||||
	Host      *string        `json:"host,omitempty"`
 | 
			
		||||
	Port      *string        `json:"port,omitempty"`
 | 
			
		||||
	User      *string        `json:"user,omitempty"`
 | 
			
		||||
	SSHConfig *string        `json:"ssh_config,omitempty"`
 | 
			
		||||
	SSHKey    *string        `json:"ssh_key,omitempty"`
 | 
			
		||||
	Scan      *config.Scan   `json:"scan,omitempty"`
 | 
			
		||||
	Detect    *config.Detect `json:"detect,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										76
									
								
								pkg/util/util.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										76
									
								
								pkg/util/util.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,76 @@
 | 
			
		||||
package util
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"compress/bzip2"
 | 
			
		||||
	"compress/gzip"
 | 
			
		||||
	"io"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
 | 
			
		||||
	"github.com/pkg/errors"
 | 
			
		||||
	"github.com/ulikunitz/xz"
 | 
			
		||||
	"golang.org/x/exp/maps"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func CacheDir() string {
 | 
			
		||||
	cacheDir, err := os.UserCacheDir()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		cacheDir = os.TempDir()
 | 
			
		||||
	}
 | 
			
		||||
	dir := filepath.Join(cacheDir, "vuls")
 | 
			
		||||
	return dir
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func Unique[T comparable](s []T) []T {
 | 
			
		||||
	m := map[T]struct{}{}
 | 
			
		||||
	for _, v := range s {
 | 
			
		||||
		m[v] = struct{}{}
 | 
			
		||||
	}
 | 
			
		||||
	return maps.Keys(m)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func Read(path string) ([]byte, error) {
 | 
			
		||||
	f, err := os.Open(path)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, errors.Wrapf(err, "open %s", path)
 | 
			
		||||
	}
 | 
			
		||||
	defer f.Close()
 | 
			
		||||
 | 
			
		||||
	switch filepath.Ext(path) {
 | 
			
		||||
	case ".gz":
 | 
			
		||||
		gr, err := gzip.NewReader(f)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, errors.Wrap(err, "create gzip reader")
 | 
			
		||||
		}
 | 
			
		||||
		defer gr.Close()
 | 
			
		||||
 | 
			
		||||
		bs, err := io.ReadAll(gr)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, errors.Wrap(err, "read data")
 | 
			
		||||
		}
 | 
			
		||||
		return bs, nil
 | 
			
		||||
	case ".bz2":
 | 
			
		||||
		bs, err := io.ReadAll(bzip2.NewReader(f))
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, errors.Wrap(err, "read data")
 | 
			
		||||
		}
 | 
			
		||||
		return bs, nil
 | 
			
		||||
	case ".xz":
 | 
			
		||||
		xr, err := xz.NewReader(f)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, errors.Wrap(err, "create xz reader")
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		bs, err := io.ReadAll(xr)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, errors.Wrap(err, "read data")
 | 
			
		||||
		}
 | 
			
		||||
		return bs, nil
 | 
			
		||||
	default:
 | 
			
		||||
		bs, err := io.ReadAll(f)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, errors.Wrap(err, "read data")
 | 
			
		||||
		}
 | 
			
		||||
		return bs, nil
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user