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

feat(os): support for Arch Linux #2010

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
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
4 changes: 4 additions & 0 deletions config/os.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@ func GetEOL(family, release string) (eol EOL, found bool) {
"2027": {StandardSupportUntil: time.Date(2031, 6, 30, 23, 59, 59, 0, time.UTC)},
"2029": {StandardSupportUntil: time.Date(2033, 6, 30, 23, 59, 59, 0, time.UTC)},
}[getAmazonLinuxVersion(release)]
case constant.Arch:
// Arch Linux uses a rolling release model.
// https://wiki.archlinux.org/title/Arch_Linux
eol, found = EOL{Ended: false}, true
case constant.RedHat:
// https://access.redhat.com/support/policy/updates/errata
eol, found = map[string]EOL{
Expand Down
3 changes: 3 additions & 0 deletions constant/constant.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ package constant
// Define them in the each package.

const (
// Arch is
Arch = "arch"

// RedHat is
RedHat = "redhat"

Expand Down
8 changes: 5 additions & 3 deletions detector/detector.go
Original file line number Diff line number Diff line change
Expand Up @@ -373,6 +373,8 @@ func isPkgCvesDetactable(r *models.ScanResult) bool {
case constant.FreeBSD, constant.MacOSX, constant.MacOSXServer, constant.MacOS, constant.MacOSServer, constant.ServerTypePseudo:
logging.Log.Infof("%s type. Skip OVAL and gost detection", r.Family)
return false
case constant.Arch:
return true
case constant.Windows:
return true
default:
Expand Down Expand Up @@ -534,7 +536,7 @@ func detectPkgsCvesWithOval(cnf config.GovalDictConf, r *models.ScanResult, logO
}()

switch r.Family {
case constant.Debian, constant.Raspbian, constant.Ubuntu:
case constant.Arch, constant.Debian, constant.Raspbian, constant.Ubuntu:
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
Expand Down Expand Up @@ -581,15 +583,15 @@ func detectPkgsCvesWithGost(cnf config.GostConf, r *models.ScanResult, logOpts l
nCVEs, err := client.DetectCVEs(r, true)
if err != nil {
switch r.Family {
case constant.Debian, constant.Raspbian, constant.Ubuntu, constant.Windows:
case constant.Arch, constant.Debian, constant.Raspbian, constant.Ubuntu, constant.Windows:
return xerrors.Errorf("Failed to detect CVEs with gost: %w", err)
default:
return xerrors.Errorf("Failed to detect unfixed CVEs with gost: %w", err)
}
}

switch r.Family {
case constant.Debian, constant.Raspbian, constant.Ubuntu, constant.Windows:
case constant.Arch, constant.Debian, constant.Raspbian, constant.Ubuntu, constant.Windows:
logging.Log.Infof("%s: %d CVEs are detected with gost", r.FormatServerName(), nCVEs)
default:
logging.Log.Infof("%s: %d unfixed CVEs are detected with gost", r.FormatServerName(), nCVEs)
Expand Down
164 changes: 164 additions & 0 deletions gost/arch.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
//go:build !scanner
// +build !scanner

package gost

import (
"encoding/json"
"io"
"net/http"
"time"

"github.com/future-architect/vuls/logging"
"github.com/future-architect/vuls/models"
"github.com/hashicorp/go-version"
"golang.org/x/exp/maps"
"golang.org/x/xerrors"
)

// Arch is Gost client
type Arch struct {
Base
}

// ArchIssue is struct of Arch Linux security issue
type ArchIssue struct {
Name string

// Contains list of package names
Packages []string
Status string
Severity string
Type string

// Vulnerable version.
Affected string

// Fixed version. May be empty.
Fixed string
Ticket string

// Contains list of CVEs
Issues []string
Advisories []string
}

func (arch Arch) FetchAllIssues() ([]ArchIssue, error) {
client := &http.Client{Timeout: 2 * 60 * time.Second}
r, err := client.Get("https://security.archlinux.org/issues/all.json")
if err != nil {
return nil, xerrors.Errorf("Failed to fetch files. err: %w", err)
}
defer r.Body.Close()

body, err := io.ReadAll(r.Body)
if err != nil {
return nil, xerrors.Errorf("Failed to read response body. err: %w", err)
}

var archIssues []ArchIssue
if err := json.Unmarshal(body, &archIssues); err != nil {
return nil, xerrors.Errorf("Failed to unmarshal. err: %w", err)
}

return archIssues, nil
}

func (arch Arch) DetectCVEs(r *models.ScanResult, _ bool) (nCVEs int, err error) {
detects := map[string]cveContent{}

archIssues, _ := arch.FetchAllIssues()
for _, issue := range archIssues {
for _, pkgName := range issue.Packages {
if _, ok := r.Packages[pkgName]; ok {
pkgVer := r.Packages[pkgName].Version + "-" + r.Packages[pkgName].Release
for _, content := range arch.detect(issue, pkgName, pkgVer) {
c, ok := detects[content.cveContent.CveID]
if ok {
content.fixStatuses = append(content.fixStatuses, c.fixStatuses...)
}
detects[content.cveContent.CveID] = content
}
}
}
}

for _, content := range detects {
v, ok := r.ScannedCves[content.cveContent.CveID]
if ok {
if v.CveContents == nil {
v.CveContents = models.NewCveContents(content.cveContent)
} else {
v.CveContents[models.ArchLinuxSecurityTracker] = []models.CveContent{content.cveContent}
}
v.Confidences.AppendIfMissing(models.ArchLinuxSecurityTrackerMatch)
} else {
v = models.VulnInfo{
CveID: content.cveContent.CveID,
CveContents: models.NewCveContents(content.cveContent),
Confidences: models.Confidences{models.ArchLinuxSecurityTrackerMatch},
}
}

for _, s := range content.fixStatuses {
v.AffectedPackages = v.AffectedPackages.Store(s)
}
r.ScannedCves[content.cveContent.CveID] = v
}

return len(unique(maps.Keys(detects))), nil
}

func (arch Arch) detect(issue ArchIssue, pkgName, verStr string) []cveContent {
var contents []cveContent

for _, cveId := range issue.Issues {
c := cveContent{
cveContent: models.CveContent{
Type: models.ArchLinuxSecurityTracker,
CveID: cveId,
},
}

vera, err := version.NewVersion(verStr)
if err != nil {
logging.Log.Debugf("Failed to parse version. version: %s, err: %v", verStr, err)
continue
}

if issue.Fixed != "" {
verb, err := version.NewVersion(issue.Fixed)
if err != nil {
logging.Log.Debugf("Failed to parse version. version: %s, err: %v", issue.Fixed, err)
continue
}

if vera.LessThan(verb) {
c.fixStatuses = append(c.fixStatuses,
models.PackageFixStatus{
Name: pkgName,
FixedIn: issue.Fixed,
})
}
} else {
verb, err := version.NewVersion(issue.Affected)
if err != nil {
logging.Log.Debugf("Failed to parse version. version: %s, err: %v", issue.Affected, err)
continue
}

if vera.LessThanOrEqual(verb) {
c.fixStatuses = append(c.fixStatuses,
models.PackageFixStatus{
Name: pkgName,
})
}
}

if len(c.fixStatuses) > 0 {
contents = append(contents, c)
}
}

return contents
}
2 changes: 2 additions & 0 deletions gost/gost.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ func NewGostClient(cnf config.GostConf, family string, o logging.LogOpts) (Clien

base := Base{driver: db, baseURL: cnf.GetURL()}
switch family {
case constant.Arch:
return Arch{base}, nil
case constant.Debian, constant.Raspbian:
return Debian{base}, nil
case constant.Ubuntu:
Expand Down
3 changes: 3 additions & 0 deletions models/cvecontents.go
Original file line number Diff line number Diff line change
Expand Up @@ -442,6 +442,9 @@ const (
// RedHatAPI is RedHat
RedHatAPI CveContentType = "redhat_api"

// ArchLinuxSecurityTracker is Arch Linux security tracker
ArchLinuxSecurityTracker CveContentType = "arch_linux_security_tracker"

// DebianSecurityTracker is Debian Security tracker
DebianSecurityTracker CveContentType = "debian_security_tracker"

Expand Down
6 changes: 6 additions & 0 deletions models/vulninfos.go
Original file line number Diff line number Diff line change
Expand Up @@ -1003,6 +1003,9 @@ const (
// RedHatAPIStr is :
RedHatAPIStr = "RedHatAPIMatch"

// ArchLinuxSecurityTrackerMatchStr :
ArchLinuxSecurityTrackerMatchStr = "ArchLinuxSecurityTrackerMatch"

// DebianSecurityTrackerMatchStr :
DebianSecurityTrackerMatchStr = "DebianSecurityTrackerMatch"

Expand Down Expand Up @@ -1044,6 +1047,9 @@ var (
// RedHatAPIMatch ranking how confident the CVE-ID was detected correctly
RedHatAPIMatch = Confidence{100, RedHatAPIStr, 0}

// DebianSecurityTrackerMatch ranking how confident the CVE-ID was detected correctly
ArchLinuxSecurityTrackerMatch = Confidence{100, ArchLinuxSecurityTrackerMatchStr, 0}

// DebianSecurityTrackerMatch ranking how confident the CVE-ID was detected correctly
DebianSecurityTrackerMatch = Confidence{100, DebianSecurityTrackerMatchStr, 0}

Expand Down
31 changes: 31 additions & 0 deletions oval/arch.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
//go:build !scanner
// +build !scanner

package oval

import (
"github.com/future-architect/vuls/constant"
"github.com/future-architect/vuls/models"
ovaldb "github.com/vulsio/goval-dictionary/db"
)

// Arch is the interface for Arch OVAL.
type Arch struct {
Base
}

// NewArch creates OVAL client for Arch
func NewArch(driver ovaldb.DB, baseURL string) Arch {
return Arch{
Base{
driver: driver,
baseURL: baseURL,
family: constant.Arch,
},
}
}

// FillWithOval returns scan result after updating CVE info by OVAL
func (o Arch) FillWithOval(_ *models.ScanResult) (nCVEs int, err error) {
return 0, nil
}
4 changes: 4 additions & 0 deletions oval/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -603,6 +603,8 @@ func NewOVALClient(family string, cnf config.GovalDictConf, o logging.LogOpts) (
}

switch family {
case constant.Arch:
return NewArch(driver, cnf.GetURL()), nil
case constant.Debian, constant.Raspbian:
return NewDebian(driver, cnf.GetURL()), nil
case constant.Ubuntu:
Expand Down Expand Up @@ -647,6 +649,8 @@ func NewOVALClient(family string, cnf config.GovalDictConf, o logging.LogOpts) (
// For example, CentOS/Alma/Rocky uses Red Hat's OVAL, so return 'redhat'
func GetFamilyInOval(familyInScanResult string) (string, error) {
switch familyInScanResult {
case constant.Arch:
return constant.Arch, nil
case constant.Debian, constant.Raspbian:
return constant.Debian, nil
case constant.Ubuntu:
Expand Down
Loading