fix(suse): fix openSUSE, openSUSE Leap, SLES, SLED scan (#1384)
* fix(suse): fix openSUSE, openSUSE Leap scan * docs: update README * fix: unknown CveContent.Type * fix: tui reporting * fix: listening port was duplicated in format-full-text * fix .gitignore * fix: add EOL data for SLES12.5 Co-authored-by: Kota Kanbe <kotakanbe@gmail.com>
This commit is contained in:
@@ -614,12 +614,7 @@ func (o *redhatBase) parseUpdatablePacksLine(line string) (models.Package, error
|
||||
|
||||
func (o *redhatBase) isExecYumPS() bool {
|
||||
switch o.Distro.Family {
|
||||
case constant.Oracle,
|
||||
constant.OpenSUSE,
|
||||
constant.OpenSUSELeap,
|
||||
constant.SUSEEnterpriseServer,
|
||||
constant.SUSEEnterpriseDesktop,
|
||||
constant.SUSEOpenstackCloud:
|
||||
case constant.Oracle:
|
||||
return false
|
||||
}
|
||||
return !o.getServerInfo().Mode.IsFast()
|
||||
@@ -627,13 +622,12 @@ func (o *redhatBase) isExecYumPS() bool {
|
||||
|
||||
func (o *redhatBase) isExecNeedsRestarting() bool {
|
||||
switch o.Distro.Family {
|
||||
case constant.OpenSUSE,
|
||||
constant.OpenSUSELeap,
|
||||
constant.SUSEEnterpriseServer,
|
||||
constant.SUSEEnterpriseDesktop,
|
||||
constant.SUSEOpenstackCloud:
|
||||
// TODO zypper ps
|
||||
// https://github.com/future-architect/vuls/issues/696
|
||||
case constant.OpenSUSE, constant.OpenSUSELeap, constant.SUSEEnterpriseServer, constant.SUSEEnterpriseDesktop:
|
||||
if o.getServerInfo().Mode.IsOffline() {
|
||||
return false
|
||||
} else if o.getServerInfo().Mode.IsFastRoot() || o.getServerInfo().Mode.IsDeep() {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
case constant.RedHat, constant.CentOS, constant.Alma, constant.Rocky, constant.Oracle:
|
||||
majorVersion, err := o.Distro.MajorVersion()
|
||||
@@ -644,8 +638,7 @@ func (o *redhatBase) isExecNeedsRestarting() bool {
|
||||
|
||||
if o.getServerInfo().Mode.IsOffline() {
|
||||
return false
|
||||
} else if o.getServerInfo().Mode.IsFastRoot() ||
|
||||
o.getServerInfo().Mode.IsDeep() {
|
||||
} else if o.getServerInfo().Mode.IsFastRoot() || o.getServerInfo().Mode.IsDeep() {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
@@ -793,7 +786,14 @@ func (o *redhatBase) rpmQa() string {
|
||||
const old = `rpm -qa --queryformat "%{NAME} %{EPOCH} %{VERSION} %{RELEASE} %{ARCH}\n"`
|
||||
const new = `rpm -qa --queryformat "%{NAME} %{EPOCHNUM} %{VERSION} %{RELEASE} %{ARCH}\n"`
|
||||
switch o.Distro.Family {
|
||||
case constant.SUSEEnterpriseServer:
|
||||
case constant.OpenSUSE:
|
||||
if o.Distro.Release == "tumbleweed" {
|
||||
return new
|
||||
}
|
||||
return old
|
||||
case constant.OpenSUSELeap:
|
||||
return new
|
||||
case constant.SUSEEnterpriseServer, constant.SUSEEnterpriseDesktop:
|
||||
if v, _ := o.Distro.MajorVersion(); v < 12 {
|
||||
return old
|
||||
}
|
||||
@@ -810,7 +810,14 @@ func (o *redhatBase) rpmQf() string {
|
||||
const old = `rpm -qf --queryformat "%{NAME} %{EPOCH} %{VERSION} %{RELEASE} %{ARCH}\n" `
|
||||
const new = `rpm -qf --queryformat "%{NAME} %{EPOCHNUM} %{VERSION} %{RELEASE} %{ARCH}\n" `
|
||||
switch o.Distro.Family {
|
||||
case constant.SUSEEnterpriseServer:
|
||||
case constant.OpenSUSE:
|
||||
if o.Distro.Release == "tumbleweed" {
|
||||
return new
|
||||
}
|
||||
return old
|
||||
case constant.OpenSUSELeap:
|
||||
return new
|
||||
case constant.SUSEEnterpriseServer, constant.SUSEEnterpriseDesktop:
|
||||
if v, _ := o.Distro.MajorVersion(); v < 12 {
|
||||
return old
|
||||
}
|
||||
|
||||
201
scanner/suse.go
201
scanner/suse.go
@@ -19,7 +19,7 @@ type suse struct {
|
||||
redhatBase
|
||||
}
|
||||
|
||||
// NewRedhat is constructor
|
||||
// newSUSE is constructor
|
||||
func newSUSE(c config.ServerInfo) *suse {
|
||||
r := &suse{
|
||||
redhatBase: redhatBase{
|
||||
@@ -43,6 +43,10 @@ func detectSUSE(c config.ServerInfo) (bool, osTypeInterface) {
|
||||
if r := exec(c, "cat /etc/os-release", noSudo); r.isSuccess() {
|
||||
s := newSUSE(c)
|
||||
name, ver := s.parseOSRelease(r.Stdout)
|
||||
if name == "" || ver == "" {
|
||||
s.setErrs([]error{xerrors.Errorf("Failed to parse /etc/os-release: %s", r.Stdout)})
|
||||
return true, s
|
||||
}
|
||||
s.setDistro(name, ver)
|
||||
return true, s
|
||||
}
|
||||
@@ -50,11 +54,10 @@ func detectSUSE(c config.ServerInfo) (bool, osTypeInterface) {
|
||||
} else if r := exec(c, "ls /etc/SuSE-release", noSudo); r.isSuccess() {
|
||||
if r := exec(c, "zypper -V", noSudo); r.isSuccess() {
|
||||
if r := exec(c, "cat /etc/SuSE-release", noSudo); r.isSuccess() {
|
||||
s := newSUSE(c)
|
||||
re := regexp.MustCompile(`openSUSE (\d+\.\d+|\d+)`)
|
||||
result := re.FindStringSubmatch(strings.TrimSpace(r.Stdout))
|
||||
if len(result) == 2 {
|
||||
//TODO check opensuse or opensuse.leap
|
||||
s := newSUSE(c)
|
||||
s.setDistro(constant.OpenSUSE, result[1])
|
||||
return true, s
|
||||
}
|
||||
@@ -66,14 +69,12 @@ func detectSUSE(c config.ServerInfo) (bool, osTypeInterface) {
|
||||
re = regexp.MustCompile(`PATCHLEVEL = (\d+)`)
|
||||
result = re.FindStringSubmatch(strings.TrimSpace(r.Stdout))
|
||||
if len(result) == 2 {
|
||||
s := newSUSE(c)
|
||||
s.setDistro(constant.SUSEEnterpriseServer,
|
||||
fmt.Sprintf("%s.%s", version, result[1]))
|
||||
s.setDistro(constant.SUSEEnterpriseServer, fmt.Sprintf("%s.%s", version, result[1]))
|
||||
return true, s
|
||||
}
|
||||
}
|
||||
logging.Log.Warnf("Failed to parse SUSE Linux version: %s", r)
|
||||
return true, newSUSE(c)
|
||||
s.setErrs([]error{xerrors.Errorf("Failed to parse /etc/SuSE-release: %s", r.Stdout)})
|
||||
return true, s
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -82,23 +83,24 @@ func detectSUSE(c config.ServerInfo) (bool, osTypeInterface) {
|
||||
}
|
||||
|
||||
func (o *suse) parseOSRelease(content string) (name string, ver string) {
|
||||
if strings.Contains(content, "ID=opensuse") {
|
||||
//TODO check opensuse or opensuse.leap
|
||||
if strings.Contains(content, `CPE_NAME="cpe:/o:opensuse:opensuse`) {
|
||||
name = constant.OpenSUSE
|
||||
} else if strings.Contains(content, `NAME="SLES"`) {
|
||||
name = constant.SUSEEnterpriseServer
|
||||
} else if strings.Contains(content, `NAME="SLES_SAP"`) {
|
||||
} else if strings.Contains(content, `CPE_NAME="cpe:/o:opensuse:tumbleweed`) {
|
||||
return constant.OpenSUSE, "tumbleweed"
|
||||
} else if strings.Contains(content, `CPE_NAME="cpe:/o:opensuse:leap`) {
|
||||
name = constant.OpenSUSELeap
|
||||
} else if strings.Contains(content, `CPE_NAME="cpe:/o:suse:sles`) {
|
||||
name = constant.SUSEEnterpriseServer
|
||||
} else if strings.Contains(content, `CPE_NAME="cpe:/o:suse:sled`) {
|
||||
name = constant.SUSEEnterpriseDesktop
|
||||
} else {
|
||||
logging.Log.Warnf("Failed to parse SUSE edition: %s", content)
|
||||
return "unknown", "unknown"
|
||||
return "", ""
|
||||
}
|
||||
|
||||
re := regexp.MustCompile(`VERSION_ID=\"(.+)\"`)
|
||||
result := re.FindStringSubmatch(strings.TrimSpace(content))
|
||||
if len(result) != 2 {
|
||||
logging.Log.Warnf("Failed to parse SUSE Linux version: %s", content)
|
||||
return "unknown", "unknown"
|
||||
return "", ""
|
||||
}
|
||||
return name, result[1]
|
||||
}
|
||||
@@ -108,14 +110,60 @@ func (o *suse) checkScanMode() error {
|
||||
}
|
||||
|
||||
func (o *suse) checkDeps() error {
|
||||
o.log.Infof("Dependencies... No need")
|
||||
return nil
|
||||
if o.getServerInfo().Mode.IsFast() {
|
||||
return o.execCheckDeps(o.depsFast())
|
||||
} else if o.getServerInfo().Mode.IsFastRoot() {
|
||||
return o.execCheckDeps(o.depsFastRoot())
|
||||
} else if o.getServerInfo().Mode.IsDeep() {
|
||||
return o.execCheckDeps(o.depsDeep())
|
||||
}
|
||||
return xerrors.New("Unknown scan mode")
|
||||
}
|
||||
|
||||
func (o *suse) depsFast() []string {
|
||||
return []string{}
|
||||
}
|
||||
|
||||
func (o *suse) depsFastRoot() []string {
|
||||
return []string{}
|
||||
}
|
||||
|
||||
func (o *suse) depsDeep() []string {
|
||||
return o.depsFastRoot()
|
||||
}
|
||||
|
||||
func (o *suse) checkIfSudoNoPasswd() error {
|
||||
// SUSE doesn't need root privilege
|
||||
o.log.Infof("sudo ... No need")
|
||||
return nil
|
||||
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 *suse) sudoNoPasswdCmdsFast() []cmd {
|
||||
return []cmd{}
|
||||
}
|
||||
|
||||
func (o *suse) sudoNoPasswdCmdsDeep() []cmd {
|
||||
return o.sudoNoPasswdCmdsFastRoot()
|
||||
}
|
||||
|
||||
func (o *suse) sudoNoPasswdCmdsFastRoot() []cmd {
|
||||
if !o.ServerInfo.IsContainer() {
|
||||
return []cmd{
|
||||
{"zypper ps -s", exitStatusZero},
|
||||
{"which which", exitStatusZero},
|
||||
{"stat /proc/1/exe", exitStatusZero},
|
||||
{"ls -l /proc/1/exe", exitStatusZero},
|
||||
{"cat /proc/1/maps", exitStatusZero},
|
||||
{"lsof -i -P -n", exitStatusZero},
|
||||
}
|
||||
}
|
||||
return []cmd{
|
||||
{"zypper ps -s", exitStatusZero},
|
||||
}
|
||||
}
|
||||
|
||||
func (o *suse) scanPackages() error {
|
||||
@@ -176,13 +224,14 @@ func (o *suse) scanUpdatablePackages() (models.Packages, error) {
|
||||
return o.parseZypperLULines(r.Stdout)
|
||||
}
|
||||
|
||||
var warnRepoPattern = regexp.MustCompile(`Warning: Repository '.+' appears to be outdated\. Consider using a different mirror or server\.`)
|
||||
|
||||
func (o *suse) parseZypperLULines(stdout string) (models.Packages, error) {
|
||||
updatables := models.Packages{}
|
||||
scanner := bufio.NewScanner(strings.NewReader(stdout))
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
if strings.Index(line, "S | Repository") != -1 ||
|
||||
strings.Index(line, "--+----------------") != -1 {
|
||||
if strings.Contains(line, "S | Repository") || strings.Contains(line, "--+----------------") || warnRepoPattern.MatchString(line) {
|
||||
continue
|
||||
}
|
||||
pack, err := o.parseZypperLUOneLine(line)
|
||||
@@ -213,3 +262,107 @@ func (o *suse) hasZypperColorOption() bool {
|
||||
r := o.exec(util.PrependProxyEnv(cmd), noSudo)
|
||||
return len(r.Stdout) > 0
|
||||
}
|
||||
|
||||
func (o *suse) postScan() error {
|
||||
if o.isExecYumPS() {
|
||||
if err := o.pkgPs(o.getOwnerPkgs); err != nil {
|
||||
err = xerrors.Errorf("Failed to execute zypper-ps: %w", err)
|
||||
o.log.Warnf("err: %+v", err)
|
||||
o.warns = append(o.warns, err)
|
||||
// Only warning this error
|
||||
}
|
||||
}
|
||||
|
||||
if o.isExecNeedsRestarting() {
|
||||
if err := o.needsRestarting(); err != nil {
|
||||
err = xerrors.Errorf("Failed to execute need-restarting: %w", err)
|
||||
o.log.Warnf("err: %+v", err)
|
||||
o.warns = append(o.warns, err)
|
||||
// Only warning this error
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *suse) needsRestarting() error {
|
||||
initName, err := o.detectInitSystem()
|
||||
if err != nil {
|
||||
o.log.Warn(err)
|
||||
// continue scanning
|
||||
}
|
||||
|
||||
cmd := "LANGUAGE=en_US.UTF-8 zypper ps -s"
|
||||
r := o.exec(cmd, sudo)
|
||||
if !r.isSuccess() {
|
||||
return xerrors.Errorf("Failed to SSH: %w", r)
|
||||
}
|
||||
procs := o.parseNeedsRestarting(r.Stdout)
|
||||
for _, proc := range procs {
|
||||
//TODO refactor
|
||||
fqpn, err := o.procPathToFQPN(proc.Path)
|
||||
if err != nil {
|
||||
o.log.Warnf("Failed to detect a package name of need restarting process from the command path: %s, %s",
|
||||
proc.Path, err)
|
||||
continue
|
||||
}
|
||||
pack, err := o.Packages.FindByFQPN(fqpn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if initName == systemd {
|
||||
name, err := o.detectServiceName(proc.PID)
|
||||
if err != nil {
|
||||
o.log.Warn(err)
|
||||
// continue scanning
|
||||
}
|
||||
proc.ServiceName = name
|
||||
proc.InitSystem = systemd
|
||||
}
|
||||
pack.NeedRestartProcs = append(pack.NeedRestartProcs, proc)
|
||||
o.Packages[pack.Name] = *pack
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *suse) parseNeedsRestarting(stdout string) []models.NeedRestartProcess {
|
||||
procs := []models.NeedRestartProcess{}
|
||||
|
||||
// PID | PPID | UID | User | Command | Service
|
||||
// ----+------+-----+------+---------+-----------
|
||||
// 9 | 7 | 0 | root | bash | containerd
|
||||
// 53 | 9 | 0 | root | zypper | containerd
|
||||
// 55 | 53 | 0 | root | lsof |
|
||||
|
||||
scanner := bufio.NewScanner(strings.NewReader(stdout))
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
ss := strings.Split(line, " | ")
|
||||
if len(ss) < 6 {
|
||||
continue
|
||||
}
|
||||
pid := strings.TrimSpace(ss[0])
|
||||
if strings.HasPrefix(pid, "PID") {
|
||||
continue
|
||||
}
|
||||
// https://unix.stackexchange.com/a/419375
|
||||
if pid == "1" {
|
||||
continue
|
||||
}
|
||||
|
||||
cmd := strings.TrimSpace(ss[4])
|
||||
whichCmd := fmt.Sprintf("LANGUAGE=en_US.UTF-8 which %s", cmd)
|
||||
r := o.exec(whichCmd, sudo)
|
||||
if !r.isSuccess() {
|
||||
o.log.Debugf("Failed to exec which %s: %s", cmd, r)
|
||||
continue
|
||||
}
|
||||
path := strings.TrimSpace(r.Stdout)
|
||||
|
||||
procs = append(procs, models.NeedRestartProcess{
|
||||
PID: pid,
|
||||
Path: path,
|
||||
HasInit: true,
|
||||
})
|
||||
}
|
||||
return procs
|
||||
}
|
||||
|
||||
@@ -105,28 +105,41 @@ func TestParseOSRelease(t *testing.T) {
|
||||
ver string
|
||||
}{
|
||||
{
|
||||
in: `NAME="openSUSE Leap"
|
||||
ID=opensuse
|
||||
VERSION_ID="42.3.4"`,
|
||||
in: `CPE_NAME="cpe:/o:opensuse:opensuse:13.2"
|
||||
VERSION_ID="13.2"`,
|
||||
name: constant.OpenSUSE,
|
||||
ver: "13.2",
|
||||
},
|
||||
{
|
||||
in: `CPE_NAME="cpe:/o:opensuse:tumbleweed:20220124"
|
||||
VERSION_ID="20220124"`,
|
||||
name: constant.OpenSUSE,
|
||||
ver: "tumbleweed",
|
||||
},
|
||||
{
|
||||
in: `CPE_NAME="cpe:/o:opensuse:leap:42.3"
|
||||
VERSION_ID="42.3.4"`,
|
||||
name: constant.OpenSUSELeap,
|
||||
ver: "42.3.4",
|
||||
},
|
||||
{
|
||||
in: `NAME="SLES"
|
||||
VERSION="12-SP1"
|
||||
VERSION_ID="12.1"
|
||||
ID="sles"`,
|
||||
in: `CPE_NAME="cpe:/o:suse:sles:12:sp1"
|
||||
VERSION_ID="12.1"`,
|
||||
name: constant.SUSEEnterpriseServer,
|
||||
ver: "12.1",
|
||||
},
|
||||
{
|
||||
in: `NAME="SLES_SAP"
|
||||
VERSION="12-SP1"
|
||||
VERSION_ID="12.1.0.1"
|
||||
ID="sles"`,
|
||||
in: `CPE_NAME="cpe:/o:suse:sles_sap:12:sp1"
|
||||
VERSION_ID="12.1.0.1"`,
|
||||
name: constant.SUSEEnterpriseServer,
|
||||
ver: "12.1.0.1",
|
||||
},
|
||||
{
|
||||
in: `CPE_NAME="cpe:/o:suse:sled:15"
|
||||
VERSION_ID="15"`,
|
||||
name: constant.SUSEEnterpriseDesktop,
|
||||
ver: "15",
|
||||
},
|
||||
}
|
||||
|
||||
r := newSUSE(config.ServerInfo{})
|
||||
|
||||
@@ -16,7 +16,7 @@ import (
|
||||
|
||||
func isRunningKernel(pack models.Package, family string, kernel models.Kernel) (isKernel, running bool) {
|
||||
switch family {
|
||||
case constant.SUSEEnterpriseServer:
|
||||
case constant.OpenSUSE, constant.OpenSUSELeap, constant.SUSEEnterpriseServer, constant.SUSEEnterpriseDesktop:
|
||||
if pack.Name == "kernel-default" {
|
||||
// Remove the last period and later because uname don't show that.
|
||||
ss := strings.Split(pack.Release, ".")
|
||||
|
||||
Reference in New Issue
Block a user