* fix(scan): enable to report if some warnings occured on scanning * alpine, debian, freebsd, suse * -format-full-text, -format-list, -format-one-line-text * implement slack.go * implement tui.go * go fmt
		
			
				
	
	
		
			211 lines
		
	
	
		
			5.6 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			211 lines
		
	
	
		
			5.6 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
package scan
 | 
						|
 | 
						|
import (
 | 
						|
	"bufio"
 | 
						|
	"fmt"
 | 
						|
	"regexp"
 | 
						|
	"strings"
 | 
						|
 | 
						|
	"github.com/future-architect/vuls/config"
 | 
						|
	"github.com/future-architect/vuls/models"
 | 
						|
	"github.com/future-architect/vuls/util"
 | 
						|
	"golang.org/x/xerrors"
 | 
						|
)
 | 
						|
 | 
						|
// inherit OsTypeInterface
 | 
						|
type suse struct {
 | 
						|
	redhatBase
 | 
						|
}
 | 
						|
 | 
						|
// NewRedhat is constructor
 | 
						|
func newSUSE(c config.ServerInfo) *suse {
 | 
						|
	r := &suse{
 | 
						|
		redhatBase: redhatBase{
 | 
						|
			base: base{
 | 
						|
				osPackages: osPackages{
 | 
						|
					Packages:  models.Packages{},
 | 
						|
					VulnInfos: models.VulnInfos{},
 | 
						|
				},
 | 
						|
			},
 | 
						|
		},
 | 
						|
	}
 | 
						|
	r.log = util.NewCustomLogger(c)
 | 
						|
	r.setServerInfo(c)
 | 
						|
	return r
 | 
						|
}
 | 
						|
 | 
						|
// https://github.com/mizzy/specinfra/blob/master/lib/specinfra/helper/detect_os/suse.rb
 | 
						|
func detectSUSE(c config.ServerInfo) (bool, osTypeInterface) {
 | 
						|
	s := newSUSE(c)
 | 
						|
	if r := exec(c, "ls /etc/os-release", noSudo); r.isSuccess() {
 | 
						|
		if r := exec(c, "zypper -V", noSudo); r.isSuccess() {
 | 
						|
			if r := exec(c, "cat /etc/os-release", noSudo); r.isSuccess() {
 | 
						|
				name, ver := s.parseOSRelease(r.Stdout)
 | 
						|
				s.setDistro(name, ver)
 | 
						|
				return true, s
 | 
						|
			}
 | 
						|
		}
 | 
						|
	} 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() {
 | 
						|
				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.setDistro(config.OpenSUSE, result[1])
 | 
						|
					return true, s
 | 
						|
				}
 | 
						|
 | 
						|
				re = regexp.MustCompile(`VERSION = (\d+)`)
 | 
						|
				result = re.FindStringSubmatch(strings.TrimSpace(r.Stdout))
 | 
						|
				if len(result) == 2 {
 | 
						|
					version := result[1]
 | 
						|
					re = regexp.MustCompile(`PATCHLEVEL = (\d+)`)
 | 
						|
					result = re.FindStringSubmatch(strings.TrimSpace(r.Stdout))
 | 
						|
					if len(result) == 2 {
 | 
						|
						s.setDistro(config.SUSEEnterpriseServer,
 | 
						|
							fmt.Sprintf("%s.%s", version, result[1]))
 | 
						|
						return true, s
 | 
						|
					}
 | 
						|
				}
 | 
						|
				util.Log.Warnf("Failed to parse SUSE Linux version: %s", r)
 | 
						|
				return true, s
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
	util.Log.Debugf("Not SUSE Linux. servername: %s", c.ServerName)
 | 
						|
	return false, s
 | 
						|
}
 | 
						|
 | 
						|
func (o *suse) parseOSRelease(content string) (name string, ver string) {
 | 
						|
	if strings.Contains(content, "ID=opensuse") {
 | 
						|
		//TODO check opensuse or opensuse.leap
 | 
						|
		name = config.OpenSUSE
 | 
						|
	} else if strings.Contains(content, `NAME="SLES"`) {
 | 
						|
		name = config.SUSEEnterpriseServer
 | 
						|
	} else if strings.Contains(content, `NAME="SLES_SAP"`) {
 | 
						|
		name = config.SUSEEnterpriseServer
 | 
						|
	} else {
 | 
						|
		util.Log.Warnf("Failed to parse SUSE edition: %s", content)
 | 
						|
		return "unknown", "unknown"
 | 
						|
	}
 | 
						|
 | 
						|
	re := regexp.MustCompile(`VERSION_ID=\"(.+)\"`)
 | 
						|
	result := re.FindStringSubmatch(strings.TrimSpace(content))
 | 
						|
	if len(result) != 2 {
 | 
						|
		util.Log.Warnf("Failed to parse SUSE Linux version: %s", content)
 | 
						|
		return "unknown", "unknown"
 | 
						|
	}
 | 
						|
	return name, result[1]
 | 
						|
}
 | 
						|
 | 
						|
func (o *suse) checkScanMode() error {
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
func (o *suse) checkDeps() error {
 | 
						|
	o.log.Infof("Dependencies... No need")
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
func (o *suse) checkIfSudoNoPasswd() error {
 | 
						|
	// SUSE doesn't need root privilege
 | 
						|
	o.log.Infof("sudo ... No need")
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
func (o *suse) scanPackages() error {
 | 
						|
	installed, err := o.scanInstalledPackages()
 | 
						|
	if err != nil {
 | 
						|
		o.log.Errorf("Failed to scan installed packages: %s", err)
 | 
						|
		return err
 | 
						|
	}
 | 
						|
 | 
						|
	o.Kernel.RebootRequired, err = o.rebootRequired()
 | 
						|
	if err != nil {
 | 
						|
		err = xerrors.Errorf("Failed to detect the kernel reboot required: %w", err)
 | 
						|
		o.log.Warnf("err: %+v", err)
 | 
						|
		o.warns = append(o.warns, err)
 | 
						|
		// Only warning this error
 | 
						|
	}
 | 
						|
 | 
						|
	if o.getServerInfo().Mode.IsOffline() {
 | 
						|
		o.Packages = installed
 | 
						|
		return nil
 | 
						|
	}
 | 
						|
 | 
						|
	updatable, err := o.scanUpdatablePackages()
 | 
						|
	if err != nil {
 | 
						|
		err = xerrors.Errorf("Failed to scan updatable packages: %w", err)
 | 
						|
		o.log.Warnf("err: %+v", err)
 | 
						|
		o.warns = append(o.warns, err)
 | 
						|
		// Only warning this error
 | 
						|
	} else {
 | 
						|
		installed.MergeNewVersion(updatable)
 | 
						|
	}
 | 
						|
 | 
						|
	o.Packages = installed
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
func (o *suse) rebootRequired() (bool, error) {
 | 
						|
	r := o.exec("rpm -q --last kernel-default", noSudo)
 | 
						|
	if !r.isSuccess() {
 | 
						|
		o.log.Warnf("Failed to detect the last installed kernel : %v", r)
 | 
						|
		// continue scanning
 | 
						|
		return false, nil
 | 
						|
	}
 | 
						|
	stdout := strings.Fields(r.Stdout)[0]
 | 
						|
	return !strings.Contains(stdout, strings.TrimSuffix(o.Kernel.Release, "-default")), nil
 | 
						|
}
 | 
						|
 | 
						|
func (o *suse) scanUpdatablePackages() (models.Packages, error) {
 | 
						|
	cmd := "zypper -q lu"
 | 
						|
	if o.hasZypperColorOption() {
 | 
						|
		cmd = "zypper -q --no-color lu"
 | 
						|
	}
 | 
						|
	r := o.exec(cmd, noSudo)
 | 
						|
	if !r.isSuccess() {
 | 
						|
		return nil, xerrors.Errorf("Failed to scan updatable packages: %v", r)
 | 
						|
	}
 | 
						|
	return o.parseZypperLULines(r.Stdout)
 | 
						|
}
 | 
						|
 | 
						|
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 {
 | 
						|
			continue
 | 
						|
		}
 | 
						|
		pack, err := o.parseZypperLUOneLine(line)
 | 
						|
		if err != nil {
 | 
						|
			return nil, err
 | 
						|
		}
 | 
						|
		updatables[pack.Name] = *pack
 | 
						|
	}
 | 
						|
	return updatables, nil
 | 
						|
}
 | 
						|
 | 
						|
func (o *suse) parseZypperLUOneLine(line string) (*models.Package, error) {
 | 
						|
	ss := strings.Split(line, "|")
 | 
						|
	if len(ss) != 6 {
 | 
						|
		return nil, xerrors.Errorf("zypper -q lu Unknown format: %s", line)
 | 
						|
	}
 | 
						|
	available := strings.Split(strings.TrimSpace(ss[4]), "-")
 | 
						|
	return &models.Package{
 | 
						|
		Name:       strings.TrimSpace(ss[2]),
 | 
						|
		NewVersion: available[0],
 | 
						|
		NewRelease: available[1],
 | 
						|
		Arch:       strings.TrimSpace(ss[5]),
 | 
						|
	}, nil
 | 
						|
}
 | 
						|
 | 
						|
func (o *suse) hasZypperColorOption() bool {
 | 
						|
	cmd := "zypper --help | grep color"
 | 
						|
	r := o.exec(util.PrependProxyEnv(cmd), noSudo)
 | 
						|
	return len(r.Stdout) > 0
 | 
						|
}
 |