feat(os) : support Alma Linux (#1261)

* support Alma Linux

* fix miss

* feat(os) : support Rocky linux  (#1260)

* support rocky linux scan

* fix miss

* lint

* fix : like #1266 and error Failed to parse CentOS

* pass make test

* fix miss

* fix pointed out with comment

* fix golangci-lint error
This commit is contained in:
kazuminn
2021-08-02 04:36:43 +09:00
committed by GitHub
parent e8c09282d9
commit ff83cadd6e
18 changed files with 230 additions and 23 deletions

View File

@@ -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)

View File

@@ -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"`

View File

@@ -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)},

View File

@@ -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",

View File

@@ -17,6 +17,9 @@ const (
// CentOS is
CentOS = "centos"
// Alma is
Alma = "alma"
// Rocky is
Rocky = "rocky"

View File

@@ -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

View File

@@ -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

View File

@@ -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,

View File

@@ -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

View File

@@ -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

118
scanner/alma.go Normal file
View File

@@ -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
}

View File

@@ -11,7 +11,7 @@ type centos struct {
redhatBase
}
// NewAmazon is constructor
// NewCentOS is constructor
func newCentOS(c config.ServerInfo) *centos {
r := &centos{
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"}
}

View File

@@ -11,7 +11,7 @@ type oracle struct {
redhatBase
}
// NewAmazon is constructor
// NewOracle is constructor
func newOracle(c config.ServerInfo) *oracle {
r := &oracle{
redhatBase{

View File

@@ -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

View File

@@ -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"}
}

View File

@@ -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"}
}

View File

@@ -217,6 +217,8 @@ func ParseInstalledPkgs(distro config.Distro, kernel models.Kernel, pkgList stri
osType = &rhel{redhatBase: redhatBase{base: base}}
case constant.CentOS:
osType = &centos{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:

View File

@@ -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)