-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat!(detector): use vuls2 for redhat/alma/rocky
Co-authored-by: MaineK00n <[email protected]>
- Loading branch information
Showing
10 changed files
with
997 additions
and
22 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} | ||
} |
Oops, something went wrong.