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:
MaineK00n
2022-02-15 17:11:54 +09:00
committed by GitHub
parent 5164fb1423
commit 787604de6a
20 changed files with 471 additions and 204 deletions

View File

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