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