Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] feat!(detector): use vuls2 for redhat/alma/rocky #2070

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ type Config struct {
Metasploit MetasploitConf `json:"metasploit,omitempty"`
KEVuln KEVulnConf `json:"kevuln,omitempty"`
Cti CtiConf `json:"cti,omitempty"`
Vuls2 Vuls2DictConf `json:"vuls2Dict,omitempty"`
shino marked this conversation as resolved.
Show resolved Hide resolved

Slack SlackConf `json:"-"`
EMail SMTPConf `json:"-"`
Expand Down
6 changes: 6 additions & 0 deletions config/vulnDictConf.go
Original file line number Diff line number Diff line change
Expand Up @@ -328,3 +328,9 @@ func (cnf *CtiConf) Init() {
cnf.setDefault("go-cti.sqlite3")
cnf.DebugSQL = Conf.DebugSQL
}

type Vuls2DictConf struct {
Repository string
Path string
SkipUpdate bool
}
13 changes: 11 additions & 2 deletions detector/detector.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"github.com/future-architect/vuls/constant"
"github.com/future-architect/vuls/contrib/owasp-dependency-check/parser"
"github.com/future-architect/vuls/cwe"
"github.com/future-architect/vuls/detector/vuls2"
"github.com/future-architect/vuls/gost"
"github.com/future-architect/vuls/logging"
"github.com/future-architect/vuls/models"
Expand Down Expand Up @@ -49,7 +50,7 @@ func Detect(rs []models.ScanResult, dir string) ([]models.ScanResult, error) {
return nil, xerrors.Errorf("Failed to fill with Library dependency: %w", err)
}

if err := DetectPkgCves(&r, config.Conf.OvalDict, config.Conf.Gost, config.Conf.LogOpts); err != nil {
if err := DetectPkgCves(&r, config.Conf.OvalDict, config.Conf.Gost, config.Conf.Vuls2, config.Conf.LogOpts, config.Conf.NoProgress); err != nil {
return nil, xerrors.Errorf("Failed to detect Pkg CVE: %w", err)
}

Expand Down Expand Up @@ -317,14 +318,19 @@ func Detect(rs []models.ScanResult, dir string) ([]models.ScanResult, error) {

// DetectPkgCves detects OS pkg cves
// pass 2 configs
func DetectPkgCves(r *models.ScanResult, ovalCnf config.GovalDictConf, gostCnf config.GostConf, logOpts logging.LogOpts) error {
func DetectPkgCves(r *models.ScanResult, ovalCnf config.GovalDictConf, gostCnf config.GostConf, vuls2Cnf config.Vuls2DictConf, logOpts logging.LogOpts, noProgress bool) error {
// Pkg Scan
if isPkgCvesDetactable(r) {
// OVAL, gost(Debian Security Tracker) does not support Package for Raspbian, so skip it.
if r.Family == constant.Raspbian {
r = r.RemoveRaspbianPackFromResult()
}

// Vuls2
if err := vuls2.Detect(r, vuls2Cnf, noProgress); err != nil {
return xerrors.Errorf("Failed to detect CVE with Vuls2: %w", err)
}

// OVAL
if err := detectPkgsCvesWithOval(ovalCnf, r, logOpts); err != nil {
return xerrors.Errorf("Failed to detect CVE with OVAL: %w", err)
Expand Down Expand Up @@ -537,6 +543,9 @@ func detectPkgsCvesWithOval(cnf config.GovalDictConf, r *models.ScanResult, logO
logging.Log.Infof("Skip OVAL and Scan with gost alone.")
logging.Log.Infof("%s: %d CVEs are detected with OVAL", r.FormatServerName(), 0)
return nil
case constant.RedHat, constant.CentOS, constant.Alma, constant.Rocky:
logging.Log.Debugf("Skip OVAL and Scan by Vuls2")
return nil
case constant.Windows, constant.MacOSX, constant.MacOSXServer, constant.MacOS, constant.MacOSServer, constant.FreeBSD, constant.ServerTypePseudo:
return nil
default:
Expand Down
195 changes: 195 additions & 0 deletions detector/vuls2/db.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
package vuls2

import (
"context"
"encoding/json"
"fmt"
"io"
"os"
"path/filepath"
"time"

"github.com/future-architect/vuls/logging"
"github.com/klauspost/compress/zstd"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors"
progressbar "github.com/schollz/progressbar/v3"
"golang.org/x/xerrors"
oras "oras.land/oras-go/v2"
"oras.land/oras-go/v2/content"
"oras.land/oras-go/v2/content/memory"
"oras.land/oras-go/v2/registry/remote"

db "github.com/MaineK00n/vuls2/pkg/db/common"
)

const (
SchemaVersion = 0
dbMediaType = "application/vnd.vulsio.vuls.db.layer.v1+zstd"
)

var (
// DefaultGHCRRepository is GitHub Container Registry for vuls2 db for vuls0
DefaultGHCRRepository = fmt.Sprintf("%s:%d", "ghcr.io/vulsio/vuls-nightly-db", SchemaVersion)

// DefaultPath is the path for vuls2 db file
DefaultPath = func() string {
wd, _ := os.Getwd()
return filepath.Join(wd, "vuls.db")
}()
)

type Config struct {
Repository string
Path string
SkipUpdate bool
Quiet bool
}

func (c Config) Refresh() error {

lastModified, fileExists, err := c.loadLastModified()
if err != nil {
return xerrors.Errorf("Failed to load vuls2 db metadata. err: %w", err)
}

if fileExists && time.Now().Before(lastModified.Add(6*time.Hour)) {
return nil
}

if c.SkipUpdate {
if !fileExists {
return xerrors.New("Vuls2 db not found, cannot skip update")
}
return nil
}

logging.Log.Infof("Downloading vuls2 db. repository: %s", c.Repository)
if err := c.fetch(c.Repository); err != nil {
return xerrors.Errorf("Failed to fetch vuls2 db. repository: %s, err: %w", c.Repository, err)
}

return nil
}

func (c Config) New() (db.DB, error) {
vuls2Config := db.Config{
Type: "boltdb",
Path: c.Path,
}

dbc, err := vuls2Config.New()
if err != nil {
return nil, xerrors.Errorf("Failed to new vuls2 db. err: %w", err)
}

return dbc, nil
}

func (c Config) fetch(repoPath string) error {
logging.Log.Infof("Fetch vuls.db from %s", repoPath)

ctx := context.TODO()

ms := memory.New()

repo, err := remote.NewRepository(repoPath)
if err != nil {
return xerrors.Errorf("Failed to create repository client. repository: %s, err: %w", repoPath, err)
}

manifestDescriptor, err := oras.Copy(ctx, repo, "latest", ms, "latest", oras.DefaultCopyOptions)
if err != nil {
return xerrors.Errorf("Failed to copy. repository: %s, err: %w", repoPath, err)
}

r, err := ms.Fetch(ctx, manifestDescriptor)
if err != nil {
return xerrors.Errorf("Failed to fetch manifest. err: %w", err)
}
defer r.Close()

var manifest ocispec.Manifest
if err := json.NewDecoder(content.NewVerifyReader(r, manifestDescriptor)).Decode(&manifest); err != nil {
return xerrors.Errorf("Failed to decode manifest. err: %w", err)
}

l := func() *ocispec.Descriptor {
for _, l := range manifest.Layers {
if l.MediaType == dbMediaType {
return &l
}
}
return nil
}()
if l == nil {
return xerrors.Errorf("Failed to find digest and filename from layers. actual layers: %#v", manifest.Layers)
}

r, err = repo.Fetch(ctx, *l)
if err != nil {
return xerrors.Errorf("Failed to fetch content. err: %w", err)
}
defer r.Close()

d, err := zstd.NewReader(content.NewVerifyReader(r, *l))
if err != nil {
return errors.Wrap(err, "new zstd reader")
}
defer d.Close()

if err := os.MkdirAll(filepath.Dir(c.Path), 0755); err != nil {
return errors.Wrapf(err, "mkdir %s", filepath.Dir(c.Path))
}

f, err := os.Create(c.Path)
if err != nil {
return errors.Wrapf(err, "create %s", c.Path)
}
defer f.Close()

var pb *progressbar.ProgressBar
pb = progressbar.DefaultBytesSilent(-1)
if !c.Quiet {
pb = progressbar.DefaultBytes(-1, "downloading")
}
if _, err := d.WriteTo(io.MultiWriter(f, pb)); err != nil {
return xerrors.Errorf("Failed to write. filename: %s. err: %w", f.Name(), err)
}
_ = pb.Finish()

return nil
}

func (c Config) loadLastModified() (time.Time, bool, error) {
if _, err := os.Stat(c.Path); errors.Is(err, os.ErrNotExist) {
return time.Time{}, false, nil
}

conf := db.Config{
Type: "boltdb",
Path: c.Path,
}

dbc, err := conf.New()
if err != nil {
return time.Time{}, false, xerrors.Errorf("Failed to new vuls2 db. path: %s, err: %w", c.Path, err)
}

if err := dbc.Open(); err != nil {
return time.Time{}, false, xerrors.Errorf("Failed to open vuls2 db. path: %s, err: %w", c.Path, err)
}
defer func() {
_ = dbc.Close()
}()

metadata, err := dbc.GetMetadata()
if err != nil {
return time.Time{}, false, xerrors.Errorf("Failed to get vuls2 db metadata. path: %s, err: %w", c.Path, err)
}
if metadata == nil {
return time.Time{}, false, xerrors.Errorf("vuls2 db metadata is nil. path: %s", c.Path)
}

return metadata.LastModified, true, nil
}
124 changes: 124 additions & 0 deletions detector/vuls2/vendor.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
package vuls2

import (
"fmt"
"slices"
"strings"

dataTypes "github.com/MaineK00n/vuls-data-update/pkg/extract/types/data"
"github.com/MaineK00n/vuls-data-update/pkg/extract/types/data/detection/condition/criteria/criterion"
"github.com/MaineK00n/vuls-data-update/pkg/extract/types/data/detection/condition/criteria/criterion/versioncriterion/fixstatus"
referenceTypes "github.com/MaineK00n/vuls-data-update/pkg/extract/types/data/reference"
vulnerabilityTypes "github.com/MaineK00n/vuls-data-update/pkg/extract/types/data/vulnerability"
"github.com/future-architect/vuls/constant"
"github.com/future-architect/vuls/models"
rpm "github.com/knqyf263/go-rpm-version"
)

func resolveCveContentList(family string, rootIDBase, rootID1 dataTypes.RootID, ccListBase, ccList1 []models.CveContent) []models.CveContent {
switch family {
// This logic is from old vuls's oval resolution. Maybe more widely applicable than these four distros.
case constant.RedHat, constant.CentOS, constant.Alma, constant.Rocky:
if rootIDBase < rootID1 {
return ccList1
}
return ccListBase
default:
return ccListBase
}
}

func resolveAffectedPackage(family string, rootIDBase, rootID1 dataTypes.RootID, statusBase, status1 models.PackageFixStatus) models.PackageFixStatus {
switch family {
// This logic is from old vuls's oval resolution. Maybe more widely applicable than these four distros.
case constant.RedHat, constant.CentOS, constant.Alma, constant.Rocky:
former, latter := func() (models.PackageFixStatus, models.PackageFixStatus) {
if rootIDBase < rootID1 {
return statusBase, status1
}
return status1, statusBase
}()

if latter.NotFixedYet {
former.NotFixedYet = true
return former
}
return latter
default:
return statusBase
}
}

func ignoresCriterion(family string, cn criterion.FilteredCriterion) bool {
switch family {
case constant.RedHat, constant.CentOS, constant.Alma, constant.Rocky:
if cn.Criterion.Version.FixStatus != nil && cn.Criterion.Version.FixStatus.Class == fixstatus.ClassUnfixed {
switch cn.Criterion.Version.FixStatus.Vendor {
case "Will not fix", "Under investigation":
return true
}
}
return false
default:
return false
}
}

func ignoresWholeCriteria(family string, cn criterion.FilteredCriterion) bool {
switch family {
case constant.RedHat, constant.CentOS, constant.Alma, constant.Rocky:
// Ignore whole criteria from root if kpatch-patch-* package is included.
return strings.HasPrefix(cn.Criterion.Version.Package.Name, "kpatch-patch-")
default:
return false
}
}

func selectFixedIn(family string, fixed []string) string {
if len(fixed) == 0 {
return ""
}

switch family {
case constant.RedHat, constant.CentOS, constant.Alma, constant.Rocky:
return slices.MaxFunc(fixed, func(x, y string) int {
return rpm.NewVersion(x).Compare(rpm.NewVersion(y))
})
default:
return fixed[0]
}
}

func advisoryReferenceSource(family string, r referenceTypes.Reference) string {
switch family {
case constant.RedHat:
return "RHSA"
default:
return r.Source
}
}

func cveContentSourceLink(ccType models.CveContentType, v vulnerabilityTypes.Vulnerability) string {
switch ccType {
case models.RedHat:
return fmt.Sprintf("https://access.redhat.com/security/cve/%s", v.Content.ID)
default:
return ""
}
}

func discardsNewVulnInfoBySourceID(family, baseSourceID, newSourceID string) bool {
switch family {
case constant.RedHat, constant.CentOS:
switch newSourceID {
case "redhat-csaf":
return false
case "redhat-ovalv2":
return baseSourceID == "redhat-ovalv1"
default:
return true
}
default:
return true
}
}
Loading
Loading