diff --git a/README.md b/README.md index 31628e5a..4a3abcf5 100644 --- a/README.md +++ b/README.md @@ -50,7 +50,7 @@ Vuls is a tool created to solve the problems listed above. It has the following [Supports major Linux/FreeBSD](https://vuls.io/docs/en/supported-os.html) -- Alpine, Amazon Linux, CentOS, Rocky Linux, Debian, Oracle Linux, Raspbian, RHEL, SUSE Enterprise Linux, and Ubuntu +- Alpine, Amazon Linux, CentOS, Alma Linux, Rocky Linux, Debian, Oracle Linux, Raspbian, RHEL, SUSE Enterprise Linux, and Ubuntu - FreeBSD - Cloud, on-premise, Running Docker Container @@ -101,15 +101,15 @@ Vuls is a tool created to solve the problems listed above. It has the following - Scan without root privilege, no dependencies - Almost no load on the scan target server -- Offline mode scan with no internet access. (CentOS, Rocky Linux, Debian, Oracle Linux, Red Hat, and Ubuntu) +- Offline mode scan with no internet access. (CentOS, Alma Linux, Rocky Linux, Debian, Oracle Linux, Red Hat, and Ubuntu) [Fast Root Scan](https://vuls.io/docs/en/architecture-fast-root-scan.html) - Scan with root privilege - Almost no load on the scan target server -- Detect processes affected by update using yum-ps (Amazon Linux, CentOS, Rocky Linux, Oracle Linux, and RedHat) +- Detect processes affected by update using yum-ps (Amazon Linux, CentOS, Alma Linux, Rocky Linux, Oracle Linux, and RedHat) - Detect processes which updated before but not restarting yet using checkrestart of debian-goodies (Debian and Ubuntu) -- Offline mode scan with no internet access. (CentOS, Rocky Linux, Debian, Oracle Linux, Red Hat, and Ubuntu) +- Offline mode scan with no internet access. (CentOS, Alma Linux, Rocky Linux, Debian, Oracle Linux, Red Hat, and Ubuntu) ### [Remote, Local scan mode, Server mode](https://vuls.io/docs/en/architecture-remote-local.html) diff --git a/config/config.go b/config/config.go index 7907622b..df8f2299 100644 --- a/config/config.go +++ b/config/config.go @@ -230,7 +230,7 @@ type ServerInfo struct { GitHubRepos map[string]GitHubConf `toml:"githubs" json:"githubs,omitempty"` // key: owner/repo UUIDs map[string]string `toml:"uuids,omitempty" json:"uuids,omitempty"` Memo string `toml:"memo,omitempty" json:"memo,omitempty"` - Enablerepo []string `toml:"enablerepo,omitempty" json:"enablerepo,omitempty"` // For CentOS, Rocky, RHEL, Amazon + Enablerepo []string `toml:"enablerepo,omitempty" json:"enablerepo,omitempty"` // For CentOS, Alma, Rocky, RHEL, Amazon Optional map[string]interface{} `toml:"optional,omitempty" json:"optional,omitempty"` // Optional key-value set that will be outputted to JSON Lockfiles []string `toml:"lockfiles,omitempty" json:"lockfiles,omitempty"` // ie) path/to/package-lock.json FindLock bool `toml:"findLock,omitempty" json:"findLock,omitempty"` diff --git a/config/os.go b/config/os.go index 25d3dc75..d26d7fcd 100644 --- a/config/os.go +++ b/config/os.go @@ -75,6 +75,10 @@ func GetEOL(family, release string) (eol EOL, found bool) { "7": {StandardSupportUntil: time.Date(2024, 6, 30, 23, 59, 59, 0, time.UTC)}, "8": {StandardSupportUntil: time.Date(2021, 12, 31, 23, 59, 59, 0, time.UTC)}, }[major(release)] + case constant.Alma: + eol, found = map[string]EOL{ + "8": {StandardSupportUntil: time.Date(2029, 12, 31, 23, 59, 59, 0, time.UTC)}, + }[major(release)] case constant.Rocky: eol, found = map[string]EOL{ "8": {StandardSupportUntil: time.Date(2029, 5, 31, 23, 59, 59, 0, time.UTC)}, diff --git a/config/os_test.go b/config/os_test.go index fd48ef84..9f00ee4e 100644 --- a/config/os_test.go +++ b/config/os_test.go @@ -111,6 +111,31 @@ func TestEOL_IsStandardSupportEnded(t *testing.T) { extEnded: false, found: false, }, + // Alma + { + name: "Alma Linux 8 supported", + fields: fields{family: Alma, release: "8"}, + now: time.Date(2021, 7, 2, 23, 59, 59, 0, time.UTC), + stdEnded: false, + extEnded: false, + found: true, + }, + { + name: "Alma Linux 8 EOL", + fields: fields{family: Alma, release: "8"}, + now: time.Date(2029, 2, 1, 0, 0, 0, 0, time.UTC), + stdEnded: false, + extEnded: false, + found: true, + }, + { + name: "Alma Linux 9 Not Found", + fields: fields{family: Alma, release: "9"}, + now: time.Date(2021, 7, 2, 23, 59, 59, 0, time.UTC), + stdEnded: false, + extEnded: false, + found: false, + }, // Rocky { name: "Rocky Linux 8 supported", diff --git a/constant/constant.go b/constant/constant.go index 6a27cffc..d1f667c5 100644 --- a/constant/constant.go +++ b/constant/constant.go @@ -17,6 +17,9 @@ const ( // CentOS is CentOS = "centos" + // Alma is + Alma = "alma" + // Rocky is Rocky = "rocky" diff --git a/gost/gost.go b/gost/gost.go index 88583359..cb420388 100644 --- a/gost/gost.go +++ b/gost/gost.go @@ -65,7 +65,7 @@ func NewClient(cnf config.GostConf, family string) (Client, error) { driver := DBDriver{DB: db, Cnf: &cnf} switch family { - case constant.RedHat, constant.CentOS, constant.Rocky: + case constant.RedHat, constant.CentOS, constant.Rocky, constant.Alma: return RedHat{Base{DBDriver: driver}}, nil case constant.Debian, constant.Raspbian: return Debian{Base{DBDriver: driver}}, nil diff --git a/models/cvecontents.go b/models/cvecontents.go index 33741bab..f69cb4d4 100644 --- a/models/cvecontents.go +++ b/models/cvecontents.go @@ -233,7 +233,7 @@ func NewCveContentType(name string) CveContentType { return Nvd case "jvn": return Jvn - case "redhat", "centos", "rocky": + case "redhat", "centos", "alma", "rocky": return RedHat case "oracle": return Oracle diff --git a/models/scanresults_test.go b/models/scanresults_test.go index c08a1923..2fa83b57 100644 --- a/models/scanresults_test.go +++ b/models/scanresults_test.go @@ -56,6 +56,11 @@ func TestIsDisplayUpdatableNum(t *testing.T) { family: constant.CentOS, expected: true, }, + { + mode: []byte{config.Fast}, + family: constant.Alma, + expected: true, + }, { mode: []byte{config.Fast}, family: constant.Rocky, diff --git a/oval/redhat.go b/oval/redhat.go index a5c137fc..06781df1 100644 --- a/oval/redhat.go +++ b/oval/redhat.go @@ -14,7 +14,7 @@ import ( ovalmodels "github.com/kotakanbe/goval-dictionary/models" ) -// RedHatBase is the base struct for RedHat, CentOS and Rocky +// RedHatBase is the base struct for RedHat, CentOS, Alma and Rocky type RedHatBase struct { Base } @@ -155,7 +155,7 @@ func (o RedHatBase) update(r *models.ScanResult, defPacks defPacks) (nCVEs int) func (o RedHatBase) convertToDistroAdvisory(def *ovalmodels.Definition) *models.DistroAdvisory { advisoryID := def.Title switch o.family { - case constant.RedHat, constant.CentOS, constant.Rocky, constant.Oracle: + case constant.RedHat, constant.CentOS, constant.Alma, constant.Rocky, constant.Oracle: if def.Title != "" { ss := strings.Fields(def.Title) advisoryID = strings.TrimSuffix(ss[0], ":") @@ -323,6 +323,24 @@ func NewAmazon(cnf config.VulnDictInterface) Amazon { } } +// Alma is the interface for RedhatBase OVAL +type Alma struct { + // Base + RedHatBase +} + +// NewAlma creates OVAL client for Alma Linux +func NewAlma(cnf config.VulnDictInterface) Alma { + return Alma{ + RedHatBase{ + Base{ + family: constant.Alma, + Cnf: cnf, + }, + }, + } +} + // Rocky is the interface for RedhatBase OVAL type Rocky struct { // Base diff --git a/oval/util.go b/oval/util.go index 76051ebb..c546105e 100644 --- a/oval/util.go +++ b/oval/util.go @@ -337,7 +337,7 @@ func isOvalDefAffected(def ovalmodels.Definition, req request, family string, ru if running.Release != "" { switch family { - case constant.RedHat, constant.CentOS, constant.Rocky, constant.Oracle: + case constant.RedHat, constant.CentOS, constant.Alma, constant.Rocky, constant.Oracle: // For kernel related packages, ignore OVAL information with different major versions if _, ok := kernelRelatedPackNames[ovalPack.Name]; ok { if util.Major(ovalPack.Version) != util.Major(running.Release) { @@ -377,7 +377,7 @@ func isOvalDefAffected(def ovalmodels.Definition, req request, family string, ru return true, false, ovalPack.Version, nil } - // But CentOS/Rocky can't judge whether fixed or unfixed. + // But CentOS/Alma/Rocky can't judge whether fixed or unfixed. // Because fixed state in RHEL OVAL is different. // So, it have to be judged version comparison. @@ -436,6 +436,7 @@ func lessThan(family, newVer string, packInOVAL ovalmodels.Package) (bool, error case constant.RedHat, constant.CentOS, + constant.Alma, constant.Rocky: vera := rpmver.NewVersion(rhelDownStreamOSVersionToRHEL(newVer)) verb := rpmver.NewVersion(rhelDownStreamOSVersionToRHEL(packInOVAL.Version)) @@ -446,7 +447,7 @@ func lessThan(family, newVer string, packInOVAL ovalmodels.Package) (bool, error } } -var rhelDownStreamOSVerPattern = regexp.MustCompile(`\.[es]l(\d+)(?:_\d+)?(?:\.(centos|rocky))?`) +var rhelDownStreamOSVerPattern = regexp.MustCompile(`\.[es]l(\d+)(?:_\d+)?(?:\.(centos|rocky|alma))?`) func rhelDownStreamOSVersionToRHEL(ver string) string { return rhelDownStreamOSVerPattern.ReplaceAllString(ver, ".el$1") @@ -463,6 +464,8 @@ func NewOVALClient(family string, cnf config.GovalDictConf) (Client, error) { return NewRedhat(&cnf), nil case constant.CentOS: return NewCentOS(&cnf), nil + case constant.Alma: + return NewAlma(&cnf), nil case constant.Rocky: return NewRocky(&cnf), nil case constant.Oracle: @@ -487,14 +490,14 @@ func NewOVALClient(family string, cnf config.GovalDictConf) (Client, error) { } // GetFamilyInOval returns the OS family name in OVAL -// For example, CentOS/Rocky uses Red Hat's OVAL, so return 'redhat' +// For example, CentOS/Alma/Rocky uses Red Hat's OVAL, so return 'redhat' func GetFamilyInOval(familyInScanResult string) (string, error) { switch familyInScanResult { case constant.Debian, constant.Raspbian: return constant.Debian, nil case constant.Ubuntu: return constant.Ubuntu, nil - case constant.RedHat, constant.CentOS, constant.Rocky: + case constant.RedHat, constant.CentOS, constant.Alma, constant.Rocky: return constant.RedHat, nil case constant.Oracle: return constant.Oracle, nil diff --git a/scanner/alma.go b/scanner/alma.go new file mode 100644 index 00000000..b6f877f5 --- /dev/null +++ b/scanner/alma.go @@ -0,0 +1,118 @@ +package scanner + +import ( + "github.com/future-architect/vuls/config" + "github.com/future-architect/vuls/logging" + "github.com/future-architect/vuls/models" +) + +// inherit OsTypeInterface +type alma struct { + redhatBase +} + +// NewAlma is constructor +func newAlma(c config.ServerInfo) *alma { + r := &alma{ + redhatBase{ + base: base{ + osPackages: osPackages{ + Packages: models.Packages{}, + VulnInfos: models.VulnInfos{}, + }, + }, + sudo: rootPrivAlma{}, + }, + } + r.log = logging.NewNormalLogger() + r.setServerInfo(c) + return r +} + +func (o *alma) checkScanMode() error { + return nil +} + +func (o *alma) checkDeps() error { + if o.getServerInfo().Mode.IsFast() { + return o.execCheckDeps(o.depsFast()) + } else if o.getServerInfo().Mode.IsFastRoot() { + return o.execCheckDeps(o.depsFastRoot()) + } else { + return o.execCheckDeps(o.depsDeep()) + } +} + +func (o *alma) depsFast() []string { + if o.getServerInfo().Mode.IsOffline() { + return []string{} + } + + // repoquery + // `rpm -qa` shows dnf-utils as yum-utils on RHEL8, CentOS8, Alma8, Rocky8 + return []string{"yum-utils"} +} + +func (o *alma) depsFastRoot() []string { + if o.getServerInfo().Mode.IsOffline() { + return []string{} + } + + // repoquery + // `rpm -qa` shows dnf-utils as yum-utils on RHEL8, CentOS8, Alma8, Rocky8 + return []string{"yum-utils"} +} + +func (o *alma) depsDeep() []string { + return o.depsFastRoot() +} + +func (o *alma) checkIfSudoNoPasswd() error { + if o.getServerInfo().Mode.IsFast() { + return o.execCheckIfSudoNoPasswd(o.sudoNoPasswdCmdsFast()) + } else if o.getServerInfo().Mode.IsFastRoot() { + return o.execCheckIfSudoNoPasswd(o.sudoNoPasswdCmdsFastRoot()) + } else { + return o.execCheckIfSudoNoPasswd(o.sudoNoPasswdCmdsDeep()) + } +} + +func (o *alma) sudoNoPasswdCmdsFast() []cmd { + return []cmd{} +} + +func (o *alma) sudoNoPasswdCmdsFastRoot() []cmd { + if !o.ServerInfo.IsContainer() { + return []cmd{ + {"repoquery -h", exitStatusZero}, + {"needs-restarting", exitStatusZero}, + {"which which", exitStatusZero}, + {"stat /proc/1/exe", exitStatusZero}, + {"ls -l /proc/1/exe", exitStatusZero}, + {"cat /proc/1/maps", exitStatusZero}, + {"lsof -i -P", exitStatusZero}, + } + } + return []cmd{ + {"repoquery -h", exitStatusZero}, + {"needs-restarting", exitStatusZero}, + } +} + +func (o *alma) sudoNoPasswdCmdsDeep() []cmd { + return o.sudoNoPasswdCmdsFastRoot() +} + +type rootPrivAlma struct{} + +func (o rootPrivAlma) repoquery() bool { + return false +} + +func (o rootPrivAlma) yumMakeCache() bool { + return false +} + +func (o rootPrivAlma) yumPS() bool { + return false +} diff --git a/scanner/centos.go b/scanner/centos.go index e5e8e8ca..a33a92ac 100644 --- a/scanner/centos.go +++ b/scanner/centos.go @@ -11,7 +11,7 @@ type centos struct { redhatBase } -// NewAmazon is constructor +// NewCentOS is constructor func newCentOS(c config.ServerInfo) *centos { r := ¢os{ redhatBase{ @@ -49,7 +49,7 @@ func (o *centos) depsFast() []string { } // repoquery - // `rpm -qa` shows dnf-utils as yum-utils on RHEL8, CentOS8, Rocky8 + // `rpm -qa` shows dnf-utils as yum-utils on RHEL8, CentOS8, Alma8, Rocky8 return []string{"yum-utils"} } @@ -59,7 +59,7 @@ func (o *centos) depsFastRoot() []string { } // repoquery - // `rpm -qa` shows dnf-utils as yum-utils on RHEL8, CentOS8, Rocky8 + // `rpm -qa` shows dnf-utils as yum-utils on RHEL8, CentOS8, Alma8, Rocky8 return []string{"yum-utils"} } diff --git a/scanner/oracle.go b/scanner/oracle.go index 60965a99..d9cb6f72 100644 --- a/scanner/oracle.go +++ b/scanner/oracle.go @@ -11,7 +11,7 @@ type oracle struct { redhatBase } -// NewAmazon is constructor +// NewOracle is constructor func newOracle(c config.ServerInfo) *oracle { r := &oracle{ redhatBase{ diff --git a/scanner/redhatbase.go b/scanner/redhatbase.go index 40cd12e4..d83bcd23 100644 --- a/scanner/redhatbase.go +++ b/scanner/redhatbase.go @@ -58,12 +58,37 @@ func detectRedhat(c config.ServerInfo) (bool, osTypeInterface) { cent := newCentOS(c) cent.setDistro(constant.CentOS, release) return true, cent + case "alma", "almalinux": + alma := newAlma(c) + alma.setDistro(constant.Alma, release) + return true, alma default: logging.Log.Warnf("Failed to parse CentOS: %s", r) } } } + if r := exec(c, "ls /etc/almalinux-release", noSudo); r.isSuccess() { + if r := exec(c, "cat /etc/almalinux-release", noSudo); r.isSuccess() { + re := regexp.MustCompile(`(.*) release (\d[\d\.]*)`) + result := re.FindStringSubmatch(strings.TrimSpace(r.Stdout)) + if len(result) != 3 { + logging.Log.Warnf("Failed to parse Alma version: %s", r) + return true, newAlma(c) + } + + release := result[2] + switch strings.ToLower(result[1]) { + case "alma", "almalinux": + alma := newAlma(c) + alma.setDistro(constant.Alma, release) + return true, alma + default: + logging.Log.Warnf("Failed to parse Alma: %s", r) + } + } + } + if r := exec(c, "ls /etc/rocky-release", noSudo); r.isSuccess() { if r := exec(c, "cat /etc/rocky-release", noSudo); r.isSuccess() { re := regexp.MustCompile(`(.*) release (\d[\d\.]*)`) @@ -104,6 +129,10 @@ func detectRedhat(c config.ServerInfo) (bool, osTypeInterface) { cent := newCentOS(c) cent.setDistro(constant.CentOS, release) return true, cent + case "alma", "almalinux": + alma := newAlma(c) + alma.setDistro(constant.Alma, release) + return true, alma case "rocky", "rocky linux": rocky := newRocky(c) rocky.setDistro(constant.Rocky, release) @@ -659,7 +688,7 @@ func (o *redhatBase) rpmQf() string { func (o *redhatBase) detectEnabledDnfModules() ([]string, error) { switch o.Distro.Family { - case constant.RedHat, constant.CentOS, constant.Rocky: + case constant.RedHat, constant.CentOS, constant.Alma, constant.Rocky: //TODO OracleLinux default: return nil, nil diff --git a/scanner/rhel.go b/scanner/rhel.go index 42348c25..42bfbda3 100644 --- a/scanner/rhel.go +++ b/scanner/rhel.go @@ -55,7 +55,7 @@ func (o *rhel) depsFastRoot() []string { } // repoquery - // `rpm -qa` shows dnf-utils as yum-utils on RHEL8, CentOS8, Rocky8 + // `rpm -qa` shows dnf-utils as yum-utils on RHEL8, CentOS8, Alma8, Rocky8 return []string{"yum-utils"} } diff --git a/scanner/rocky.go b/scanner/rocky.go index 5985e08e..0f9a8da4 100644 --- a/scanner/rocky.go +++ b/scanner/rocky.go @@ -49,7 +49,7 @@ func (o *rocky) depsFast() []string { } // repoquery - // `rpm -qa` shows dnf-utils as yum-utils on RHEL8, CentOS8, Rocky8 + // `rpm -qa` shows dnf-utils as yum-utils on RHEL8, CentOS8, Alma8, Rocky8 return []string{"yum-utils"} } @@ -59,7 +59,7 @@ func (o *rocky) depsFastRoot() []string { } // repoquery - // `rpm -qa` shows dnf-utils as yum-utils on RHEL8, CentOS8, Rocky8 + // `rpm -qa` shows dnf-utils as yum-utils on RHEL8, CentOS8, Alma8, Rocky8 return []string{"yum-utils"} } diff --git a/scanner/serverapi.go b/scanner/serverapi.go index d096c67e..abaf9a0e 100644 --- a/scanner/serverapi.go +++ b/scanner/serverapi.go @@ -217,6 +217,8 @@ func ParseInstalledPkgs(distro config.Distro, kernel models.Kernel, pkgList stri osType = &rhel{redhatBase: redhatBase{base: base}} case constant.CentOS: osType = ¢os{redhatBase: redhatBase{base: base}} + case constant.Alma: + osType = &alma{redhatBase: redhatBase{base: base}} case constant.Rocky: osType = &rocky{redhatBase: redhatBase{base: base}} case constant.Oracle: diff --git a/scanner/utils.go b/scanner/utils.go index 36be0caf..a37b6f51 100644 --- a/scanner/utils.go +++ b/scanner/utils.go @@ -26,7 +26,7 @@ func isRunningKernel(pack models.Package, family string, kernel models.Kernel) ( } return false, false - case constant.RedHat, constant.Oracle, constant.CentOS, constant.Rocky, constant.Amazon: + case constant.RedHat, constant.Oracle, constant.CentOS, constant.Alma, constant.Rocky, constant.Amazon: switch pack.Name { case "kernel", "kernel-devel", "kernel-core", "kernel-modules", "kernel-uek": ver := fmt.Sprintf("%s-%s.%s", pack.Version, pack.Release, pack.Arch)