-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
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
Deadlyelder
wants to merge
3
commits into
future-architect:master
Choose a base branch
from
del4cy:support_arch_linux
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Conversation
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
Removes obsolete comment Signed-off-by: Sankalp <[email protected]>
@Deadlyelder @ziyagenc |
Since the fork repository created by the organization does not allow the maintainer to add commits, I have written the following patch. Also, when sending PRs in the future, please write unit tests. commit 2ba1adb1557c3cbf1a81bfce14096d2358dd4e8b
Author: MaineK00n <[email protected]>
Date: Sat Aug 31 15:52:05 2024 +0900
refactor(arch): reviewer corrections
:100644 100644 1e5ccea 7a0d2e1 M detector/detector.go
:100644 100644 46ce7a3 ae75d8f M go.mod
:100644 100644 d1f77a1 ec676b3 M go.sum
:100644 100644 adef4d1 ed094cb M gost/arch.go
:000000 100644 0000000 27aca8f A gost/arch_test.go
:100644 100644 3bee991 3c33eca M models/vulninfos.go
:100644 000000 17e9f2a 0000000 D oval/arch.go
:100644 100644 5b6ae37 cded582 M oval/util.go
:100644 100644 62ae527 6a71cc0 M scanner/arch.go
:000000 100644 0000000 135b497 A scanner/arch_test.go
:100644 100644 56a5892 82ae7d9 M scanner/scanner.go
diff --git a/detector/detector.go b/detector/detector.go
index 1e5ccea..7a0d2e1 100644
--- a/detector/detector.go
+++ b/detector/detector.go
@@ -373,9 +373,7 @@ 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:
+ case constant.Arch, constant.Windows:
return true
default:
if r.ScannedVia == "trivy" {
@@ -536,11 +534,11 @@ func detectPkgsCvesWithOval(cnf config.GovalDictConf, r *models.ScanResult, logO
}()
switch r.Family {
- case constant.Arch, constant.Debian, constant.Raspbian, constant.Ubuntu:
+ case 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
- case constant.Windows, constant.MacOSX, constant.MacOSXServer, constant.MacOS, constant.MacOSServer, constant.FreeBSD, constant.ServerTypePseudo:
+ case constant.Arch, constant.Windows, constant.MacOSX, constant.MacOSXServer, constant.MacOS, constant.MacOSServer, constant.FreeBSD, constant.ServerTypePseudo:
return nil
default:
logging.Log.Debugf("Check if oval fetched: %s %s", r.Family, r.Release)
diff --git a/go.mod b/go.mod
index 46ce7a3..ae75d8f 100644
--- a/go.mod
+++ b/go.mod
@@ -9,6 +9,7 @@ require (
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.4.0
github.com/BurntSushi/toml v1.4.0
github.com/CycloneDX/cyclonedx-go v0.9.0
+ github.com/MaineK00n/go-pacman-version v0.0.0-20210916231937-19e87b7d7184
github.com/Ullaakut/nmap/v2 v2.2.2
github.com/aquasecurity/trivy v0.54.1
github.com/aquasecurity/trivy-db v0.0.0-20240718084044-d23a6ca8ba04
@@ -55,7 +56,7 @@ require (
github.com/vulsio/go-exploitdb v0.4.7-0.20240318122115-ccb3abc151a1
github.com/vulsio/go-kev v0.1.4-0.20240318121733-b3386e67d3fb
github.com/vulsio/go-msfdb v0.2.4-0.20240318121704-8bfc812656dc
- github.com/vulsio/gost v0.4.6-0.20240501065222-d47d2e716bfa
+ github.com/vulsio/gost v0.4.6-0.20240830085319-0786e102a856
github.com/vulsio/goval-dictionary v0.9.6-0.20240625074017-1da5dfb8b28a
go.etcd.io/bbolt v1.3.10
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8
@@ -130,7 +131,7 @@ require (
github.com/bitnami/go-version v0.0.0-20231130084017-bb00604d650c // indirect
github.com/blang/semver v3.5.1+incompatible // indirect
github.com/bmatcuk/doublestar/v4 v4.6.1 // indirect
- github.com/briandowns/spinner v1.23.0 // indirect
+ github.com/briandowns/spinner v1.23.1 // indirect
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/chai2010/gettext-go v1.0.2 // indirect
diff --git a/go.sum b/go.sum
index d1f77a1..ec676b3 100644
--- a/go.sum
+++ b/go.sum
@@ -255,6 +255,8 @@ github.com/Intevation/gval v1.3.0 h1:+Ze5sft5MmGbZrHj06NVUbcxCb67l9RaPTLMNr37mjw
github.com/Intevation/gval v1.3.0/go.mod h1:xmGyGpP5be12EL0P12h+dqiYG8qn2j3PJxIgkoOHO5o=
github.com/Intevation/jsonpath v0.2.1 h1:rINNQJ0Pts5XTFEG+zamtdL7l9uuE1z0FBA+r55Sw+A=
github.com/Intevation/jsonpath v0.2.1/go.mod h1:WnZ8weMmwAx/fAO3SutjYFU+v7DFreNYnibV7CiaYIw=
+github.com/MaineK00n/go-pacman-version v0.0.0-20210916231937-19e87b7d7184 h1:enu2psM1AcUsNx36T+X13lcy2kmFFV4kwCMmL7i4yiQ=
+github.com/MaineK00n/go-pacman-version v0.0.0-20210916231937-19e87b7d7184/go.mod h1:iMNOZ59Aouwx++SN7zGEi8yB9JTd+ZwYufdnC02mjd4=
github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ=
github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE=
github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
@@ -426,8 +428,8 @@ github.com/bmatcuk/doublestar/v4 v4.6.1 h1:FH9SifrbvJhnlQpztAx++wlkk70QBf0iBWDwN
github.com/bmatcuk/doublestar/v4 v4.6.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
github.com/bradleyjkemp/cupaloy/v2 v2.8.0 h1:any4BmKE+jGIaMpnU8YgH/I2LPiLBufr6oMMlVBbn9M=
github.com/bradleyjkemp/cupaloy/v2 v2.8.0/go.mod h1:bm7JXdkRd4BHJk9HpwqAI8BoAY1lps46Enkdqw6aRX0=
-github.com/briandowns/spinner v1.23.0 h1:alDF2guRWqa/FOZZYWjlMIx2L6H0wyewPxo/CH4Pt2A=
-github.com/briandowns/spinner v1.23.0/go.mod h1:rPG4gmXeN3wQV/TsAY4w8lPdIM6RX3yqeBQJSrbXjuE=
+github.com/briandowns/spinner v1.23.1 h1:t5fDPmScwUjozhDj4FA46p5acZWIPXYE30qW2Ptu650=
+github.com/briandowns/spinner v1.23.1/go.mod h1:LaZeM4wm2Ywy6vO571mvhQNRcWfRUnXOs0RcKV0wYKM=
github.com/bshuster-repo/logrus-logstash-hook v1.0.0 h1:e+C0SB5R1pu//O4MQ3f9cFuPGoOVeF2fE4Og9otCc70=
github.com/bshuster-repo/logrus-logstash-hook v1.0.0/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk=
github.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd h1:rFt+Y/IK1aEZkEHchZRSq9OQbsSzIT/OrI8YFFmRIng=
@@ -1382,8 +1384,8 @@ github.com/vulsio/go-kev v0.1.4-0.20240318121733-b3386e67d3fb h1:j03zKKkR+WWaPoP
github.com/vulsio/go-kev v0.1.4-0.20240318121733-b3386e67d3fb/go.mod h1:AjLUC5oGYi3dWakVE6WuuHoC+xL/f8YN8CFC45oTE9c=
github.com/vulsio/go-msfdb v0.2.4-0.20240318121704-8bfc812656dc h1:nf62vF8T3yAmmwu7xMycqIvTVincv/sH7FyeeWWodxs=
github.com/vulsio/go-msfdb v0.2.4-0.20240318121704-8bfc812656dc/go.mod h1:X7NqckQva6ok3GaWRYFAEvd72xzWFeGKOm9YOCWeIhc=
-github.com/vulsio/gost v0.4.6-0.20240501065222-d47d2e716bfa h1:AmXiFpp2kFuoCgGw/yBl+RGuanSbPg7cV78dvIrbJ/k=
-github.com/vulsio/gost v0.4.6-0.20240501065222-d47d2e716bfa/go.mod h1:fWe/YGX+XpPYIjrIvvl15/x/6GXj+pqbn8BHwnE3X/g=
+github.com/vulsio/gost v0.4.6-0.20240830085319-0786e102a856 h1:BHS1viDR2LyDK5zfntQYyCr4ZHzTHSa68976Ks6pvlA=
+github.com/vulsio/gost v0.4.6-0.20240830085319-0786e102a856/go.mod h1:CHZaikJjPZhNGtiVI5BUXbZ1WAD9o1QWujd3Ch+Wj/0=
github.com/vulsio/goval-dictionary v0.9.6-0.20240625074017-1da5dfb8b28a h1:8X9wH7AocxgrM52PYtjBZ2Xd/axrzCHonWwhQZSgQaM=
github.com/vulsio/goval-dictionary v0.9.6-0.20240625074017-1da5dfb8b28a/go.mod h1:Qkcs63pRa/ZuOrQO0xPIhR/M6WVKOQEV60fkRJFkM60=
github.com/xanzy/go-gitlab v0.102.0 h1:ExHuJ1OTQ2yt25zBMMj0G96ChBirGYv8U7HyUiYkZ+4=
diff --git a/gost/arch.go b/gost/arch.go
index adef4d1..ed094cb 100644
--- a/gost/arch.go
+++ b/gost/arch.go
@@ -5,15 +5,16 @@ package gost
import (
"encoding/json"
- "io"
- "net/http"
- "time"
+ "fmt"
- "github.com/future-architect/vuls/logging"
- "github.com/future-architect/vuls/models"
- "github.com/hashicorp/go-version"
+ version "github.com/MaineK00n/go-pacman-version"
"golang.org/x/exp/maps"
"golang.org/x/xerrors"
+
+ "github.com/future-architect/vuls/logging"
+ "github.com/future-architect/vuls/models"
+ "github.com/future-architect/vuls/util"
+ gostmodels "github.com/vulsio/gost/models"
)
// Arch is Gost client
@@ -21,144 +22,156 @@ 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")
+func (arch Arch) DetectCVEs(r *models.ScanResult, _ bool) (nCVEs int, err error) {
+ fixedCVEs, err := arch.detectCVEsWithFixState(r, true)
if err != nil {
- return nil, xerrors.Errorf("Failed to fetch files. err: %w", err)
+ return 0, xerrors.Errorf("Failed to detect fixed CVEs. err: %w", err)
}
- defer r.Body.Close()
- body, err := io.ReadAll(r.Body)
+ unfixedCVEs, err := arch.detectCVEsWithFixState(r, false)
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 0, xerrors.Errorf("Failed to detect unfixed CVEs. err: %w", err)
}
- return archIssues, nil
+ return len(unique(append(fixedCVEs, unfixedCVEs...))), 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
+func (arch Arch) detectCVEsWithFixState(r *models.ScanResult, fixed bool) ([]string, error) {
+ detects := map[string]gostmodels.ArchADV{}
+ if arch.driver == nil {
+ urlPrefix, err := util.URLPathJoin(arch.baseURL, "arch", "pkgs")
+ if err != nil {
+ return nil, xerrors.Errorf("Failed to join URLPath. err: %w", err)
+ }
+ s := "fixed-advs"
+ if !fixed {
+ s = "unfixed-advs"
+ }
+ responses, err := getCvesWithFixStateViaHTTP(r, urlPrefix, s)
+ if err != nil {
+ return nil, xerrors.Errorf("Failed to get Advisories via HTTP. err: %w", err)
+ }
+
+ for _, res := range responses {
+ if res.request.isSrcPack {
+ continue
+ }
+
+ var as map[string]gostmodels.ArchADV
+ if err := json.Unmarshal([]byte(res.json), &as); err != nil {
+ return nil, xerrors.Errorf("Failed to unmarshal json. err: %w", err)
+ }
+ for _, content := range arch.detect(as, r.Packages[res.request.packName]) {
+ if base, ok := detects[content.Name]; ok {
+ content.Packages = append(content.Packages, base.Packages...)
+ }
+ detects[content.Name] = content
+ }
+ }
+ } else {
+ for _, p := range r.Packages {
+ as, err := func() (map[string]gostmodels.ArchADV, error) {
+ if !fixed {
+ return arch.driver.GetUnfixedAdvsArch(p.Name)
+ }
+ return arch.driver.GetFixedAdvsArch(p.Name)
+ }()
+ if err != nil {
+ return nil, xerrors.Errorf("Failed to get Advisories. package: %s, err: %w", p.Name, err)
+ }
+ for _, content := range arch.detect(as, p) {
+ if base, ok := detects[content.Name]; ok {
+ content.Packages = append(content.Packages, base.Packages...)
}
+ detects[content.Name] = content
}
}
}
+ cs := make(map[string]struct{})
for _, content := range detects {
- v, ok := r.ScannedCves[content.cveContent.CveID]
- if ok {
- if v.CveContents == nil {
- v.CveContents = models.NewCveContents(content.cveContent)
+ for _, i := range content.Issues {
+ v, ok := r.ScannedCves[i.Issue]
+ if ok {
+ v.DistroAdvisories.AppendIfMissing(&models.DistroAdvisory{
+ AdvisoryID: content.Name,
+ Severity: content.Severity,
+ })
} else {
- v.CveContents[models.ArchLinuxSecurityTracker] = []models.CveContent{content.cveContent}
+ v = models.VulnInfo{
+ CveID: i.Issue,
+ CveContents: models.NewCveContents(models.CveContent{
+ Type: models.ArchLinuxSecurityTracker,
+ CveID: i.Issue,
+ SourceLink: fmt.Sprintf("https://security.archlinux.org/%s", i.Issue),
+ }),
+ Confidences: models.Confidences{models.ArchLinuxSecurityTrackerMatch},
+ DistroAdvisories: models.DistroAdvisories{{
+ AdvisoryID: content.Name,
+ Severity: content.Severity,
+ }},
+ }
}
- 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.Packages {
+ v.AffectedPackages = v.AffectedPackages.Store(models.PackageFixStatus{
+ Name: s.Name,
+ NotFixedYet: !fixed,
+ FixState: content.Status,
+ FixedIn: func() string {
+ if content.Fixed != nil {
+ return *content.Fixed
+ }
+ return ""
+ }(),
+ })
}
- }
- for _, s := range content.fixStatuses {
- v.AffectedPackages = v.AffectedPackages.Store(s)
+ r.ScannedCves[i.Issue] = v
+
+ cs[i.Issue] = struct{}{}
}
- r.ScannedCves[content.cveContent.CveID] = v
}
-
- return len(unique(maps.Keys(detects))), nil
+ return maps.Keys(cs), 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
+func (arch Arch) detect(advs map[string]gostmodels.ArchADV, pkg models.Package) []gostmodels.ArchADV {
+ var as []gostmodels.ArchADV
+ for _, a := range advs {
+ switch a.Status {
+ case "Fixed":
+ if a.Fixed == nil {
+ logging.Log.Debugf("Failed to get fixed version in %s", a.Name)
+ break
}
- if vera.LessThan(verb) {
- c.fixStatuses = append(c.fixStatuses,
- models.PackageFixStatus{
- Name: pkgName,
- FixedIn: issue.Fixed,
- })
- }
- } else {
- verb, err := version.NewVersion(issue.Affected)
+ affected, err := arch.isGostDefAffected(fmt.Sprintf("%s-%s", pkg.Version, pkg.Release), *a.Fixed)
if err != nil {
- logging.Log.Debugf("Failed to parse version. version: %s, err: %v", issue.Affected, err)
- continue
+ logging.Log.Debugf("Failed to parse versions: %s, Ver: %s, Gost: %s", err, fmt.Sprintf("%s-%s", pkg.Version, pkg.Release), a.Affected)
+ break
}
-
- if vera.LessThanOrEqual(verb) {
- c.fixStatuses = append(c.fixStatuses,
- models.PackageFixStatus{
- Name: pkgName,
- })
+ if affected {
+ a.Packages = []gostmodels.ArchPackage{{Name: pkg.Name}}
+ as = append(as, a)
}
- }
-
- if len(c.fixStatuses) > 0 {
- contents = append(contents, c)
+ case "Vulnerable":
+ a.Packages = []gostmodels.ArchPackage{{Name: pkg.Name}}
+ as = append(as, a)
+ default:
+ logging.Log.Debugf("Failed to check vulnerable Advisory. err: unknown status: %s", a.Status)
}
}
+ return as
+}
- return contents
+func (arch Arch) isGostDefAffected(installedVersion, gostVersion string) (bool, error) {
+ vera, err := version.NewVersion(installedVersion)
+ if err != nil {
+ return false, xerrors.Errorf("Failed to parse version. version: %s, err: %w", installedVersion, err)
+ }
+ verb, err := version.NewVersion(gostVersion)
+ if err != nil {
+ return false, xerrors.Errorf("Failed to parse version. version: %s, err: %w", gostVersion, err)
+ }
+ return vera.LessThan(verb), nil
}
diff --git a/gost/arch_test.go b/gost/arch_test.go
new file mode 100644
index 0000000..27aca8f
--- /dev/null
+++ b/gost/arch_test.go
@@ -0,0 +1,40 @@
+//go:build !scanner
+// +build !scanner
+
+package gost
+
+import (
+ "reflect"
+ "testing"
+
+ "github.com/future-architect/vuls/models"
+ gostmodels "github.com/vulsio/gost/models"
+)
+
+func TestArch_detect(t *testing.T) {
+ type fields struct {
+ Base Base
+ }
+ type args struct {
+ advs map[string]gostmodels.ArchADV
+ pkg models.Package
+ }
+ tests := []struct {
+ name string
+ fields fields
+ args args
+ want []gostmodels.ArchADV
+ }{
+ // TODO: Add test cases.
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ arch := Arch{
+ Base: tt.fields.Base,
+ }
+ if got := arch.detect(tt.args.advs, tt.args.pkg); !reflect.DeepEqual(got, tt.want) {
+ t.Errorf("Arch.detect() = %v, want %v", got, tt.want)
+ }
+ })
+ }
+}
diff --git a/models/vulninfos.go b/models/vulninfos.go
index 3bee991..3c33eca 100644
--- a/models/vulninfos.go
+++ b/models/vulninfos.go
@@ -1047,7 +1047,7 @@ 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 ranking how confident the CVE-ID was detected correctly
ArchLinuxSecurityTrackerMatch = Confidence{100, ArchLinuxSecurityTrackerMatchStr, 0}
// DebianSecurityTrackerMatch ranking how confident the CVE-ID was detected correctly
diff --git a/oval/arch.go b/oval/arch.go
deleted file mode 100644
index 17e9f2a..0000000
--- a/oval/arch.go
+++ /dev/null
@@ -1,31 +0,0 @@
-//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
-}
diff --git a/oval/util.go b/oval/util.go
index 5b6ae37..cded582 100644
--- a/oval/util.go
+++ b/oval/util.go
@@ -603,8 +603,6 @@ 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:
@@ -633,7 +631,7 @@ func NewOVALClient(family string, cnf config.GovalDictConf, o logging.LogOpts) (
return NewAmazon(driver, cnf.GetURL()), nil
case constant.Fedora:
return NewFedora(driver, cnf.GetURL()), nil
- case constant.FreeBSD, constant.Windows:
+ case constant.Arch, constant.FreeBSD, constant.Windows:
return NewPseudo(family), nil
case constant.ServerTypePseudo:
return NewPseudo(family), nil
@@ -649,8 +647,6 @@ 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:
@@ -673,7 +669,7 @@ func GetFamilyInOval(familyInScanResult string) (string, error) {
return constant.Alpine, nil
case constant.Amazon:
return constant.Amazon, nil
- case constant.FreeBSD, constant.Windows:
+ case constant.Arch, constant.FreeBSD, constant.Windows:
return "", nil
case constant.ServerTypePseudo:
return "", nil
diff --git a/scanner/arch.go b/scanner/arch.go
index 62ae527..6a71cc0 100644
--- a/scanner/arch.go
+++ b/scanner/arch.go
@@ -2,14 +2,17 @@ package scanner
import (
"bufio"
+ "fmt"
+ "net/textproto"
"strings"
+ "golang.org/x/xerrors"
+
"github.com/future-architect/vuls/config"
"github.com/future-architect/vuls/constant"
"github.com/future-architect/vuls/logging"
"github.com/future-architect/vuls/models"
"github.com/future-architect/vuls/util"
- "golang.org/x/xerrors"
)
// inherit OsTypeInterface
@@ -71,6 +74,12 @@ func (o *arch) checkIfSudoNoPasswd() error {
"stat /proc/1/exe",
"ls -l /proc/1/exe",
"cat /proc/1/maps",
+ "lsof -i -P -n",
+ "pacman -Qi",
+ "pacman -Qu",
+ }
+ if !o.getServerInfo().Mode.IsOffline() {
+ cmds = append(cmds, "pacman -Sy")
}
for _, cmd := range cmds {
@@ -87,28 +96,27 @@ func (o *arch) checkIfSudoNoPasswd() error {
}
func (o *arch) parseInstalledPackages(stdout string) (models.Packages, models.SrcPackages, error) {
- packs := models.Packages{}
- pkgInfos := strings.Split(stdout, "\n\n")
- for _, pkgInfo := range pkgInfos {
+ packs := make(models.Packages)
+ for _, pkgInfo := range strings.Split(stdout, "\n\n") {
if len(strings.TrimSpace(pkgInfo)) == 0 {
continue
}
- lines := strings.Split(pkgInfo, "\n")
+ header, err := textproto.NewReader(bufio.NewReader(strings.NewReader(fmt.Sprintf("%s\n\n", pkgInfo)))).ReadMIMEHeader()
+ if err != nil {
+ return nil, nil, xerrors.Errorf("Failed to read package info. err: %w", err)
+ }
+
var name, version, release, arch string
- for _, line := range lines {
- columns := strings.Split(line, ":")
- leftColumn := strings.TrimSpace(columns[0])
- rightColumn := strings.TrimSpace(strings.Join(columns[1:], ":"))
- switch leftColumn {
+ for k := range header {
+ switch strings.TrimSpace(k) {
case "Name":
- name = rightColumn
+ name = header.Get(k)
case "Version":
- values := strings.Split(rightColumn, "-")
- version = values[0]
- release = values[1]
+ version, release, _ = strings.Cut(header.Get(k), "-")
case "Architecture":
- arch = rightColumn
+ arch = header.Get(k)
+ default:
}
}
@@ -154,12 +162,21 @@ func (o *arch) scanPackages() error {
Version: version,
}
- installed, err := o.scanInstalledPackages()
+ o.Packages, err = o.scanInstalledPackages()
if err != nil {
o.log.Errorf("Failed to scan installed packages: %s", err)
return err
}
+ if !o.getServerInfo().Mode.IsOffline() {
+ if err := o.updatePackageDB(); err != nil {
+ err = xerrors.Errorf("Failed to update package DB: %w", err)
+ o.log.Warnf("err: %+v", err)
+ o.warns = append(o.warns, err)
+ // Only warning this error
+ }
+ }
+
updatable, err := o.scanUpdatablePackages()
if err != nil {
err = xerrors.Errorf("Failed to scan updatable packages: %w", err)
@@ -167,10 +184,8 @@ func (o *arch) scanPackages() error {
o.warns = append(o.warns, err)
// Only warning this error
} else {
- installed.MergeNewVersion(updatable)
+ o.Packages.MergeNewVersion(updatable)
}
-
- o.Packages = installed
return nil
}
@@ -180,60 +195,53 @@ func (o *arch) scanInstalledPackages() (models.Packages, error) {
if !r.isSuccess() {
return nil, xerrors.Errorf("Failed to SSH: %s", r)
}
- pkgs, _, _ := o.parseInstalledPackages(r.Stdout)
+ pkgs, _, err := o.parseInstalledPackages(r.Stdout)
+ if err != nil {
+ return nil, xerrors.Errorf("Failed to parse installed packages. err: %w", err)
+ }
return pkgs, nil
}
-func (o *arch) scanUpdatablePackages() (models.Packages, error) {
- listOutdateCmd := `TMPPATH="${TMPDIR:-/tmp}/vuls"
-DBPATH="$(pacman-conf DBPath)"
-
-mkdir -p "$TMPPATH"
-ln -s "$DBPATH/local" "$TMPPATH" &>/dev/null
-fakeroot -- pacman -Sy --dbpath "$TMPPATH" --logfile /dev/null &>/dev/null
-pacman -Qu --dbpath "$TMPPATH" 2>/dev/null
-`
- cmd := util.PrependProxyEnv(listOutdateCmd)
+func (o *arch) updatePackageDB() error {
+ cmd := util.PrependProxyEnv("pacman -Sy")
r := o.exec(cmd, noSudo)
if !r.isSuccess() {
- return nil, xerrors.Errorf("Failed to SSH: %s", r)
+ return xerrors.Errorf("Failed to SSH: %s", r)
}
- pkgs, _ := o.parseOutdatedPackages(r.Stdout)
-
- unlinkCmd := `TMPPATH="${TMPDIR:-/tmp}/vuls"
-rm -r "$TMPPATH"`
+ return nil
+}
- cmd = util.PrependProxyEnv(unlinkCmd)
- r = o.exec(cmd, noSudo)
+func (o *arch) scanUpdatablePackages() (models.Packages, error) {
+ cmd := util.PrependProxyEnv("pacman -Qu")
+ r := o.exec(cmd, noSudo)
if !r.isSuccess() {
- err := xerrors.Errorf("Failed to SSH: %s", r)
- o.log.Warnf("err: %+v", err)
- o.warns = append(o.warns, err)
- // Only warning this error
+ return nil, xerrors.Errorf("Failed to SSH: %s", r)
+ }
+ pkgs, err := o.parseOutdatedPackages(r.Stdout)
+ if err != nil {
+ return nil, xerrors.Errorf("Failed to parse outdated packages. err: %w", err)
}
return pkgs, nil
}
func (o *arch) parseOutdatedPackages(stdout string) (models.Packages, error) {
- packs := models.Packages{}
+ packs := make(models.Packages)
scanner := bufio.NewScanner(strings.NewReader(stdout))
for scanner.Scan() {
line := scanner.Text()
- if !strings.Contains(line, "->") {
- continue
+ lhs, rhs, ok := strings.Cut(line, "->")
+ if !ok {
+ return nil, xerrors.Errorf("unexpected `pacman -Qu` line format. expected: %q, actual: %q", "<package name> <install version> -> <updatable version>", line)
}
- ss := strings.Fields(line)
- name := ss[0]
- fullVersionInfo := ss[3]
-
- versionAndRelease := strings.Split(fullVersionInfo, "-")
+ name := strings.Fields(lhs)[0]
+ version, release, _ := strings.Cut(strings.TrimSpace(rhs), "-")
packs[name] = models.Package{
Name: name,
- NewVersion: versionAndRelease[0],
- NewRelease: versionAndRelease[1],
+ NewVersion: version,
+ NewRelease: release,
}
}
return packs, nil
diff --git a/scanner/arch_test.go b/scanner/arch_test.go
new file mode 100644
index 0000000..135b497
--- /dev/null
+++ b/scanner/arch_test.go
@@ -0,0 +1,184 @@
+package scanner
+
+import (
+ "reflect"
+ "testing"
+
+ "github.com/future-architect/vuls/config"
+ "github.com/future-architect/vuls/models"
+)
+
+func Test_arch_parseInstalledPackages(t *testing.T) {
+ type args struct {
+ stdout string
+ }
+ tests := []struct {
+ name string
+ args args
+ wantBins models.Packages
+ wantSrcs models.SrcPackages
+ wantErr bool
+ }{
+ {
+ name: "happy",
+ args: args{
+ stdout: `Name : acl
+Version : 2.3.2-1
+Description : Access control list utilities, libraries and headers
+Architecture : x86_64
+URL : https://savannah.nongnu.org/projects/acl
+Licenses : LGPL
+Groups : None
+Provides : xfsacl libacl.so=1-64
+Depends On : glibc
+Optional Deps : None
+Required By : coreutils gettext libarchive sed shadow systemd tar
+Optional For : None
+Conflicts With : xfsacl
+Replaces : xfsacl
+Installed Size : 329.98 KiB
+Packager : Christian Hesse <[email protected]>
+Build Date : Wed Jan 24 08:57:20 2024
+Install Date : Sun Aug 25 00:03:46 2024
+Install Reason : Installed as a dependency for another package
+Install Script : No
+Validated By : Signature
+
+Name : archlinux-keyring
+Version : 20240709-1
+Description : Arch Linux PGP keyring
+Architecture : any
+URL : https://gitlab.archlinux.org/archlinux/archlinux-keyring/
+Licenses : GPL-3.0-or-later
+Groups : None
+Provides : None
+Depends On : pacman
+Optional Deps : None
+Required By : base
+Optional For : None
+Conflicts With : None
+Replaces : None
+Installed Size : 1709.20 KiB
+Packager : Christian Hesse <[email protected]>
+Build Date : Tue Jul 9 21:31:37 2024
+Install Date : Sun Aug 25 00:03:47 2024
+Install Reason : Installed as a dependency for another package
+Install Script : Yes
+Validated By : Signature
+
+Name : openssl
+Version : 3.3.1-1
+Description : The Open Source toolkit for Secure Sockets Layer and Transport Layer Security
+Architecture : x86_64
+URL : https://www.openssl.org
+Licenses : Apache-2.0
+Groups : None
+Provides : libcrypto.so=3-64 libssl.so=3-64
+Depends On : glibc
+Optional Deps : ca-certificates [installed]
+ perl
+Required By : coreutils cryptsetup curl kmod krb5 libarchive libevent libsasl libssh2 openssh systemd tpm2-tss
+Optional For : None
+Conflicts With : None
+Replaces : openssl-perl openssl-doc
+Installed Size : 10.95 MiB
+Packager : Pierre Schmitz <[email protected]>
+Build Date : Tue Jun 4 19:29:08 2024
+Install Date : Sun Aug 25 00:03:46 2024
+Install Reason : Installed as a dependency for another package
+Install Script : No
+Validated By : Signature
+
+`,
+ },
+ wantBins: models.Packages{
+ "acl": {
+ Name: "acl",
+ Version: "2.3.2",
+ Release: "1",
+ Arch: "x86_64",
+ },
+ "archlinux-keyring": {
+ Name: "archlinux-keyring",
+ Version: "20240709",
+ Release: "1",
+ Arch: "any",
+ },
+ "openssl": {
+ Name: "openssl",
+ Version: "3.3.1",
+ Release: "1",
+ Arch: "x86_64",
+ },
+ },
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ o := newArch(config.ServerInfo{})
+ gotBins, gotSrcs, err := o.parseInstalledPackages(tt.args.stdout)
+ if (err != nil) != tt.wantErr {
+ t.Errorf("arch.parseInstalledPackages() error = %v, wantErr %v", err, tt.wantErr)
+ return
+ }
+ if !reflect.DeepEqual(gotBins, tt.wantBins) {
+ t.Errorf("arch.parseInstalledPackages() gotBins = %v, wantBins %v", gotBins, tt.wantBins)
+ }
+ if !reflect.DeepEqual(gotSrcs, tt.wantSrcs) {
+ t.Errorf("arch.parseInstalledPackages() gotSrcs = %v, wantSrcs %v", gotSrcs, tt.wantSrcs)
+ }
+ })
+ }
+}
+
+func Test_arch_parseOutdatedPackages(t *testing.T) {
+ type args struct {
+ stdout string
+ }
+ tests := []struct {
+ name string
+ args args
+ want models.Packages
+ wantErr bool
+ }{
+ {
+ name: "happy",
+ args: args{
+ stdout: `bash 5.2.032-1 -> 5.2.032-2
+ca-certificates-mozilla 3.103-1 -> 3.104-1
+device-mapper 2.03.25-2 -> 2.03.26-1
+`,
+ },
+ want: models.Packages{
+ "bash": {
+ Name: "bash",
+ NewVersion: "5.2.032",
+ NewRelease: "2",
+ },
+ "ca-certificates-mozilla": {
+ Name: "ca-certificates-mozilla",
+ NewVersion: "3.104",
+ NewRelease: "1",
+ },
+ "device-mapper": {
+ Name: "device-mapper",
+ NewVersion: "2.03.26",
+ NewRelease: "1",
+ },
+ },
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ o := newArch(config.ServerInfo{})
+ got, err := o.parseOutdatedPackages(tt.args.stdout)
+ if (err != nil) != tt.wantErr {
+ t.Errorf("arch.parseOutdatedPackages() error = %v, wantErr %v", err, tt.wantErr)
+ return
+ }
+ if !reflect.DeepEqual(got, tt.want) {
+ t.Errorf("arch.parseOutdatedPackages() = %v, want %v", got, tt.want)
+ }
+ })
+ }
+}
diff --git a/scanner/scanner.go b/scanner/scanner.go
index 56a5892..82ae7d9 100644
--- a/scanner/scanner.go
+++ b/scanner/scanner.go
@@ -763,11 +763,6 @@ func (s Scanner) detectOS(c config.ServerInfo) osTypeInterface {
}
}
- if itsMe, osType := detectArch(c); itsMe {
- logging.Log.Debugf("Arch based Linux. Host: %s:%s", c.Host, c.Port)
- return osType
- }
-
if itsMe, osType := detectWindows(c); itsMe {
logging.Log.Debugf("Windows. Host: %s:%s", c.Host, c.Port)
return osType
@@ -798,6 +793,11 @@ func (s Scanner) detectOS(c config.ServerInfo) osTypeInterface {
return osType
}
+ if itsMe, osType := detectArch(c); itsMe {
+ logging.Log.Debugf("Arch based Linux. Host: %s:%s", c.Host, c.Port)
+ return osType
+ }
+
if itsMe, osType := detectMacOS(c); itsMe {
logging.Log.Debugf("MacOS. Host: %s:%s", c.Host, c.Port)
return osType
FROM archlinux
RUN pacman -Syu --noconfirm && \
pacman -S --noconfirm openssh
RUN mkdir -p /var/run/sshd
RUN sed -i 's/#\?PermitRootLogin prohibit-password/PermitRootLogin yes/' /etc/ssh/sshd_config
RUN sed -i 's@session\s*required\s*pam_loginuid.so@session optional pam_loginuid.so@g' /etc/pam.d/sshd
ENV NOTVISIBLE "in users profile"
RUN echo "export VISIBLE=now" >> /etc/profile
COPY .ssh/id_rsa.pub /root/authorized_keys
RUN mkdir -p ~/.ssh && \
mv ~/authorized_keys ~/.ssh/authorized_keys && \
chmod 0600 ~/.ssh/authorized_keys
RUN ssh-keygen -A
EXPOSE 22
## AVG-2851
RUN pacman -U --noconfirm https://archive.archlinux.org/packages/x/xz/xz-5.6.0-1-x86_64.pkg.tar.zst
## AVG-2847
RUN pacman -S --noconfirm minizip
CMD ["/usr/sbin/sshd", "-D"] $ vuls scan
...
[Sep 4 02:18:41] INFO [localhost] Validating config...
[Sep 4 02:18:41] INFO [localhost] Detecting Server/Container OS...
[Sep 4 02:18:41] INFO [localhost] Detecting OS of servers...
[Sep 4 02:18:42] INFO [localhost] (1/1) Detected: docker: arch
[Sep 4 02:18:42] INFO [localhost] Detecting OS of containers...
[Sep 4 02:18:42] INFO [localhost] Checking Scan Modes...
[Sep 4 02:18:42] INFO [localhost] Detecting Platforms...
[Sep 4 02:18:44] INFO [localhost] (1/1) docker is running on other
[Sep 4 02:18:44] INFO [docker] Scanning OS pkg in fast mode
Scan Summary
================
docker arch 122 installed, 3 updatable
To view the detail, vuls tui is useful.
To send a report, run vuls report -h.
$ vuls report
...
[Sep 4 02:20:44] INFO [localhost] docker: 3 CVEs are detected with gost
[Sep 4 02:20:44] INFO [localhost] docker: 0 CVEs are detected with CPE
[Sep 4 02:20:44] INFO [localhost] docker: 0 PoC are detected
[Sep 4 02:20:44] INFO [localhost] docker: 0 exploits are detected
[Sep 4 02:20:44] INFO [localhost] docker: Known Exploited Vulnerabilities are detected for 0 CVEs
[Sep 4 02:20:44] INFO [localhost] docker: Cyber Threat Intelligences are detected for 0 CVEs
[Sep 4 02:20:44] INFO [localhost] docker: total 3 CVEs detected
[Sep 4 02:20:44] INFO [localhost] docker: 0 CVEs filtered by --confidence-over=80
docker (arch)
=============
Total: 3 (Critical:2 High:0 Medium:1 Low:0 ?:0)
1/3 Fixed, 0 poc, 0 exploits, cisa: 0, uscert: 0, jpcert: 0 alerts
122 installed
+----------------+------+--------+-----+-----------+---------+----------+
| CVE-ID | CVSS | ATTACK | POC | ALERT | FIXED | PACKAGES |
+----------------+------+--------+-----+-----------+---------+----------+
| CVE-2023-45853 | 10.0 | AV:L | | | unfixed | minizip |
+----------------+------+--------+-----+-----------+---------+----------+
| CVE-2024-3094 | 10.0 | AV:N | | | fixed | xz |
+----------------+------+--------+-----+-----------+---------+----------+
| CVE-2022-2068 | 6.9 | AV:L | | | unfixed | openssl |
+----------------+------+--------+-----+-----------+---------+----------+ |
MaineK00n
changed the title
[Feature] Support for Arch Linux
feat(os): support for Arch Linux
Sep 3, 2024
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
What did you implement:
Implements support for Arch Linux.
Fixes # (issue)
Addresses #527 and fixes #2009
Type of change
How Has This Been Tested?
Tested on an instance of Arch Linux hosted on network for similar functionalities as other supported OS on vuls.
Checklist:
You don't have to satisfy all of the following.
make fmt
make test
Is this ready for review?: YES