refactor(config): localize config used like a global variable (#1179)
* refactor(report): LocalFileWriter * refactor -format-json * refacotr: -format-one-email * refactor: -format-csv * refactor: -gzip * refactor: -format-full-text * refactor: -format-one-line-text * refactor: -format-list * refacotr: remove -to-* from config * refactor: IgnoreGitHubDismissed * refactor: GitHub * refactor: IgnoreUnsocred * refactor: diff * refacotr: lang * refacotr: cacheDBPath * refactor: Remove config references * refactor: ScanResults * refacotr: constant pkg * chore: comment * refactor: scanner * refactor: scanner * refactor: serverapi.go * refactor: serverapi * refactor: change pkg structure * refactor: serverapi.go * chore: remove emtpy file * fix(scan): remove -ssh-native-insecure option * fix(scan): remove the deprecated option `keypassword`
This commit is contained in:
		
							
								
								
									
										189
									
								
								scanner/alpine.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										189
									
								
								scanner/alpine.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,189 @@
 | 
			
		||||
package scanner
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bufio"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/constant"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	"github.com/future-architect/vuls/util"
 | 
			
		||||
	"golang.org/x/xerrors"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// inherit OsTypeInterface
 | 
			
		||||
type alpine struct {
 | 
			
		||||
	base
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewAlpine is constructor
 | 
			
		||||
func newAlpine(c config.ServerInfo) *alpine {
 | 
			
		||||
	d := &alpine{
 | 
			
		||||
		base: base{
 | 
			
		||||
			osPackages: osPackages{
 | 
			
		||||
				Packages:  models.Packages{},
 | 
			
		||||
				VulnInfos: models.VulnInfos{},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	d.log = util.NewCustomLogger(c)
 | 
			
		||||
	d.setServerInfo(c)
 | 
			
		||||
	return d
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Alpine
 | 
			
		||||
// https://github.com/mizzy/specinfra/blob/master/lib/specinfra/helper/detect_os/alpine.rb
 | 
			
		||||
func detectAlpine(c config.ServerInfo) (bool, osTypeInterface) {
 | 
			
		||||
	if r := exec(c, "ls /etc/alpine-release", noSudo); !r.isSuccess() {
 | 
			
		||||
		return false, nil
 | 
			
		||||
	}
 | 
			
		||||
	if r := exec(c, "cat /etc/alpine-release", noSudo); r.isSuccess() {
 | 
			
		||||
		os := newAlpine(c)
 | 
			
		||||
		os.setDistro(constant.Alpine, strings.TrimSpace(r.Stdout))
 | 
			
		||||
		return true, os
 | 
			
		||||
	}
 | 
			
		||||
	return false, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *alpine) checkScanMode() error {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *alpine) checkDeps() error {
 | 
			
		||||
	o.log.Infof("Dependencies... No need")
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *alpine) checkIfSudoNoPasswd() error {
 | 
			
		||||
	o.log.Infof("sudo ... No need")
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *alpine) apkUpdate() error {
 | 
			
		||||
	if o.getServerInfo().Mode.IsOffline() {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	r := o.exec("apk update", noSudo)
 | 
			
		||||
	if !r.isSuccess() {
 | 
			
		||||
		return xerrors.Errorf("Failed to SSH: %s", r)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *alpine) preCure() error {
 | 
			
		||||
	if err := o.detectIPAddr(); err != nil {
 | 
			
		||||
		o.log.Warnf("Failed to detect IP addresses: %s", err)
 | 
			
		||||
		o.warns = append(o.warns, err)
 | 
			
		||||
	}
 | 
			
		||||
	// Ignore this error as it just failed to detect the IP addresses
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *alpine) postScan() error {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *alpine) detectIPAddr() (err error) {
 | 
			
		||||
	o.ServerInfo.IPv4Addrs, o.ServerInfo.IPv6Addrs, err = o.ip()
 | 
			
		||||
	return err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *alpine) scanPackages() error {
 | 
			
		||||
	o.log.Infof("Scanning OS pkg in %s", o.getServerInfo().Mode)
 | 
			
		||||
	if err := o.apkUpdate(); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	// collect the running kernel information
 | 
			
		||||
	release, version, err := o.runningKernel()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		o.log.Errorf("Failed to scan the running kernel version: %s", err)
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	o.Kernel = models.Kernel{
 | 
			
		||||
		Release: release,
 | 
			
		||||
		Version: version,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	installed, err := o.scanInstalledPackages()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		o.log.Errorf("Failed to scan installed packages: %s", err)
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	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 *alpine) scanInstalledPackages() (models.Packages, error) {
 | 
			
		||||
	cmd := util.PrependProxyEnv("apk info -v")
 | 
			
		||||
	r := o.exec(cmd, noSudo)
 | 
			
		||||
	if !r.isSuccess() {
 | 
			
		||||
		return nil, xerrors.Errorf("Failed to SSH: %s", r)
 | 
			
		||||
	}
 | 
			
		||||
	return o.parseApkInfo(r.Stdout)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *alpine) parseInstalledPackages(stdout string) (models.Packages, models.SrcPackages, error) {
 | 
			
		||||
	installedPackages, err := o.parseApkInfo(stdout)
 | 
			
		||||
	return installedPackages, nil, err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *alpine) parseApkInfo(stdout string) (models.Packages, error) {
 | 
			
		||||
	packs := models.Packages{}
 | 
			
		||||
	scanner := bufio.NewScanner(strings.NewReader(stdout))
 | 
			
		||||
	for scanner.Scan() {
 | 
			
		||||
		line := scanner.Text()
 | 
			
		||||
		ss := strings.Split(line, "-")
 | 
			
		||||
		if len(ss) < 3 {
 | 
			
		||||
			if strings.Contains(ss[0], "WARNING") {
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
			return nil, xerrors.Errorf("Failed to parse apk info -v: %s", line)
 | 
			
		||||
		}
 | 
			
		||||
		name := strings.Join(ss[:len(ss)-2], "-")
 | 
			
		||||
		packs[name] = models.Package{
 | 
			
		||||
			Name:    name,
 | 
			
		||||
			Version: strings.Join(ss[len(ss)-2:], "-"),
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return packs, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *alpine) scanUpdatablePackages() (models.Packages, error) {
 | 
			
		||||
	cmd := util.PrependProxyEnv("apk version")
 | 
			
		||||
	r := o.exec(cmd, noSudo)
 | 
			
		||||
	if !r.isSuccess() {
 | 
			
		||||
		return nil, xerrors.Errorf("Failed to SSH: %s", r)
 | 
			
		||||
	}
 | 
			
		||||
	return o.parseApkVersion(r.Stdout)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *alpine) parseApkVersion(stdout string) (models.Packages, error) {
 | 
			
		||||
	packs := models.Packages{}
 | 
			
		||||
	scanner := bufio.NewScanner(strings.NewReader(stdout))
 | 
			
		||||
	for scanner.Scan() {
 | 
			
		||||
		line := scanner.Text()
 | 
			
		||||
		if !strings.Contains(line, "<") {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		ss := strings.Split(line, "<")
 | 
			
		||||
		namever := strings.TrimSpace(ss[0])
 | 
			
		||||
		tt := strings.Split(namever, "-")
 | 
			
		||||
		name := strings.Join(tt[:len(tt)-2], "-")
 | 
			
		||||
		packs[name] = models.Package{
 | 
			
		||||
			Name:       name,
 | 
			
		||||
			NewVersion: strings.TrimSpace(ss[1]),
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return packs, nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										75
									
								
								scanner/alpine_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										75
									
								
								scanner/alpine_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,75 @@
 | 
			
		||||
package scanner
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"reflect"
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestParseApkInfo(t *testing.T) {
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		in    string
 | 
			
		||||
		packs models.Packages
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			in: `musl-1.1.16-r14
 | 
			
		||||
busybox-1.26.2-r7
 | 
			
		||||
`,
 | 
			
		||||
			packs: models.Packages{
 | 
			
		||||
				"musl": {
 | 
			
		||||
					Name:    "musl",
 | 
			
		||||
					Version: "1.1.16-r14",
 | 
			
		||||
				},
 | 
			
		||||
				"busybox": {
 | 
			
		||||
					Name:    "busybox",
 | 
			
		||||
					Version: "1.26.2-r7",
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	d := newAlpine(config.ServerInfo{})
 | 
			
		||||
	for i, tt := range tests {
 | 
			
		||||
		pkgs, _ := d.parseApkInfo(tt.in)
 | 
			
		||||
		if !reflect.DeepEqual(tt.packs, pkgs) {
 | 
			
		||||
			t.Errorf("[%d] expected %v, actual %v", i, tt.packs, pkgs)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestParseApkVersion(t *testing.T) {
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		in    string
 | 
			
		||||
		packs models.Packages
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			in: `Installed:                                Available:
 | 
			
		||||
libcrypto1.0-1.0.1q-r0                  < 1.0.2m-r0
 | 
			
		||||
libssl1.0-1.0.1q-r0                     < 1.0.2m-r0
 | 
			
		||||
nrpe-2.14-r2                            < 2.15-r5
 | 
			
		||||
`,
 | 
			
		||||
			packs: models.Packages{
 | 
			
		||||
				"libcrypto1.0": {
 | 
			
		||||
					Name:       "libcrypto1.0",
 | 
			
		||||
					NewVersion: "1.0.2m-r0",
 | 
			
		||||
				},
 | 
			
		||||
				"libssl1.0": {
 | 
			
		||||
					Name:       "libssl1.0",
 | 
			
		||||
					NewVersion: "1.0.2m-r0",
 | 
			
		||||
				},
 | 
			
		||||
				"nrpe": {
 | 
			
		||||
					Name:       "nrpe",
 | 
			
		||||
					NewVersion: "2.15-r5",
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	d := newAlpine(config.ServerInfo{})
 | 
			
		||||
	for i, tt := range tests {
 | 
			
		||||
		pkgs, _ := d.parseApkVersion(tt.in)
 | 
			
		||||
		if !reflect.DeepEqual(tt.packs, pkgs) {
 | 
			
		||||
			t.Errorf("[%d] expected %v, actual %v", i, tt.packs, pkgs)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										107
									
								
								scanner/amazon.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										107
									
								
								scanner/amazon.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,107 @@
 | 
			
		||||
package scanner
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"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 amazon struct {
 | 
			
		||||
	redhatBase
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewAmazon is constructor
 | 
			
		||||
func newAmazon(c config.ServerInfo) *amazon {
 | 
			
		||||
	r := &amazon{
 | 
			
		||||
		redhatBase{
 | 
			
		||||
			base: base{
 | 
			
		||||
				osPackages: osPackages{
 | 
			
		||||
					Packages:  models.Packages{},
 | 
			
		||||
					VulnInfos: models.VulnInfos{},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			sudo: rootPrivAmazon{},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	r.log = util.NewCustomLogger(c)
 | 
			
		||||
	r.setServerInfo(c)
 | 
			
		||||
	return r
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *amazon) checkScanMode() error {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *amazon) checkDeps() error {
 | 
			
		||||
	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 *amazon) depsFast() []string {
 | 
			
		||||
	if o.getServerInfo().Mode.IsOffline() {
 | 
			
		||||
		return []string{}
 | 
			
		||||
	}
 | 
			
		||||
	// repoquery
 | 
			
		||||
	return []string{"yum-utils"}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *amazon) depsFastRoot() []string {
 | 
			
		||||
	return []string{
 | 
			
		||||
		"yum-utils",
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *amazon) depsDeep() []string {
 | 
			
		||||
	return o.depsFastRoot()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *amazon) 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 *amazon) sudoNoPasswdCmdsFast() []cmd {
 | 
			
		||||
	return []cmd{}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *amazon) sudoNoPasswdCmdsFastRoot() []cmd {
 | 
			
		||||
	return []cmd{
 | 
			
		||||
		{"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},
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *amazon) sudoNoPasswdCmdsDeep() []cmd {
 | 
			
		||||
	return o.sudoNoPasswdCmdsFastRoot()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type rootPrivAmazon struct{}
 | 
			
		||||
 | 
			
		||||
func (o rootPrivAmazon) repoquery() bool {
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o rootPrivAmazon) yumMakeCache() bool {
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o rootPrivAmazon) yumPS() bool {
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										1022
									
								
								scanner/base.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1022
									
								
								scanner/base.go
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										496
									
								
								scanner/base_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										496
									
								
								scanner/base_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,496 @@
 | 
			
		||||
package scanner
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"reflect"
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	_ "github.com/aquasecurity/fanal/analyzer/library/bundler"
 | 
			
		||||
	_ "github.com/aquasecurity/fanal/analyzer/library/cargo"
 | 
			
		||||
	_ "github.com/aquasecurity/fanal/analyzer/library/composer"
 | 
			
		||||
	_ "github.com/aquasecurity/fanal/analyzer/library/npm"
 | 
			
		||||
	_ "github.com/aquasecurity/fanal/analyzer/library/pipenv"
 | 
			
		||||
	_ "github.com/aquasecurity/fanal/analyzer/library/poetry"
 | 
			
		||||
	_ "github.com/aquasecurity/fanal/analyzer/library/yarn"
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestParseDockerPs(t *testing.T) {
 | 
			
		||||
	var test = struct {
 | 
			
		||||
		in       string
 | 
			
		||||
		expected []config.Container
 | 
			
		||||
	}{
 | 
			
		||||
		`c7ca0992415a romantic_goldberg ubuntu:14.04.5
 | 
			
		||||
f570ae647edc agitated_lovelace centos:latest`,
 | 
			
		||||
		[]config.Container{
 | 
			
		||||
			{
 | 
			
		||||
				ContainerID: "c7ca0992415a",
 | 
			
		||||
				Name:        "romantic_goldberg",
 | 
			
		||||
				Image:       "ubuntu:14.04.5",
 | 
			
		||||
			},
 | 
			
		||||
			{
 | 
			
		||||
				ContainerID: "f570ae647edc",
 | 
			
		||||
				Name:        "agitated_lovelace",
 | 
			
		||||
				Image:       "centos:latest",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	r := newRHEL(config.ServerInfo{})
 | 
			
		||||
	actual, err := r.parseDockerPs(test.in)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Errorf("Error occurred. in: %s, err: %s", test.in, err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	for i, e := range test.expected {
 | 
			
		||||
		if !reflect.DeepEqual(e, actual[i]) {
 | 
			
		||||
			t.Errorf("expected %v, actual %v", e, actual[i])
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestParseLxdPs(t *testing.T) {
 | 
			
		||||
	var test = struct {
 | 
			
		||||
		in       string
 | 
			
		||||
		expected []config.Container
 | 
			
		||||
	}{
 | 
			
		||||
		`+-------+
 | 
			
		||||
| NAME  |
 | 
			
		||||
+-------+
 | 
			
		||||
| test1 |
 | 
			
		||||
+-------+
 | 
			
		||||
| test2 |
 | 
			
		||||
+-------+`,
 | 
			
		||||
		[]config.Container{
 | 
			
		||||
			{
 | 
			
		||||
				ContainerID: "test1",
 | 
			
		||||
				Name:        "test1",
 | 
			
		||||
			},
 | 
			
		||||
			{
 | 
			
		||||
				ContainerID: "test2",
 | 
			
		||||
				Name:        "test2",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	r := newRHEL(config.ServerInfo{})
 | 
			
		||||
	actual, err := r.parseLxdPs(test.in)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Errorf("Error occurred. in: %s, err: %s", test.in, err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	for i, e := range test.expected {
 | 
			
		||||
		if !reflect.DeepEqual(e, actual[i]) {
 | 
			
		||||
			t.Errorf("expected %v, actual %v", e, actual[i])
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestParseIp(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
	var test = struct {
 | 
			
		||||
		in        string
 | 
			
		||||
		expected4 []string
 | 
			
		||||
		expected6 []string
 | 
			
		||||
	}{
 | 
			
		||||
		in: `1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN \    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
 | 
			
		||||
1: lo    inet 127.0.0.1/8 scope host lo
 | 
			
		||||
1: lo    inet6 ::1/128 scope host \       valid_lft forever preferred_lft forever
 | 
			
		||||
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP qlen 1000\    link/ether 52:54:00:2a:86:4c brd ff:ff:ff:ff:ff:ff
 | 
			
		||||
2: eth0    inet 10.0.2.15/24 brd 10.0.2.255 scope global eth0
 | 
			
		||||
2: eth0    inet6 fe80::5054:ff:fe2a:864c/64 scope link \       valid_lft forever preferred_lft forever
 | 
			
		||||
3: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP qlen 1000\    link/ether 08:00:27:36:76:60 brd ff:ff:ff:ff:ff:ff
 | 
			
		||||
3: eth1    inet 192.168.33.11/24 brd 192.168.33.255 scope global eth1
 | 
			
		||||
3: eth1    inet6 2001:db8::68/64 scope link \       valid_lft forever preferred_lft forever `,
 | 
			
		||||
		expected4: []string{"10.0.2.15", "192.168.33.11"},
 | 
			
		||||
		expected6: []string{"2001:db8::68"},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	r := newRHEL(config.ServerInfo{})
 | 
			
		||||
	actual4, actual6 := r.parseIP(test.in)
 | 
			
		||||
	if !reflect.DeepEqual(test.expected4, actual4) {
 | 
			
		||||
		t.Errorf("expected %v, actual %v", test.expected4, actual4)
 | 
			
		||||
	}
 | 
			
		||||
	if !reflect.DeepEqual(test.expected6, actual6) {
 | 
			
		||||
		t.Errorf("expected %v, actual %v", test.expected6, actual6)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestIsAwsInstanceID(t *testing.T) {
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		in       string
 | 
			
		||||
		expected bool
 | 
			
		||||
	}{
 | 
			
		||||
		{"i-1234567a", true},
 | 
			
		||||
		{"i-1234567890abcdef0", true},
 | 
			
		||||
		{"i-1234567890abcdef0000000", true},
 | 
			
		||||
		{"e-1234567890abcdef0", false},
 | 
			
		||||
		{"i-1234567890abcdef0 foo bar", false},
 | 
			
		||||
		{"no data", false},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	r := newAmazon(config.ServerInfo{})
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		actual := r.isAwsInstanceID(tt.in)
 | 
			
		||||
		if tt.expected != actual {
 | 
			
		||||
			t.Errorf("expected %t, actual %t, str: %s", tt.expected, actual, tt.in)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestParseSystemctlStatus(t *testing.T) {
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		in  string
 | 
			
		||||
		out string
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			in: `● NetworkManager.service - Network Manager
 | 
			
		||||
   Loaded: loaded (/usr/lib/systemd/system/NetworkManager.service; enabled; vendor preset: enabled)
 | 
			
		||||
   Active: active (running) since Wed 2018-01-10 17:15:39 JST; 2 months 10 days ago
 | 
			
		||||
     Docs: man:NetworkManager(8)
 | 
			
		||||
 Main PID: 437 (NetworkManager)
 | 
			
		||||
   Memory: 424.0K
 | 
			
		||||
   CGroup: /system.slice/NetworkManager.service
 | 
			
		||||
           ├─437 /usr/sbin/NetworkManager --no-daemon
 | 
			
		||||
           └─572 /sbin/dhclient -d -q -sf /usr/libexec/nm-dhcp-helper -pf /var/run/dhclient-ens160.pid -lf /var/lib/NetworkManager/dhclient-241ed966-e1c7-4d5c-a6a0-8a6dba457277-ens160.lease -cf /var/lib/NetworkManager/dhclient-ens160.conf ens160`,
 | 
			
		||||
			out: "NetworkManager.service",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			in:  `Failed to get unit for PID 700: PID 700 does not belong to any loaded unit.`,
 | 
			
		||||
			out: "",
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	r := newCentOS(config.ServerInfo{})
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		actual := r.parseSystemctlStatus(tt.in)
 | 
			
		||||
		if tt.out != actual {
 | 
			
		||||
			t.Errorf("expected %v, actual %v", tt.out, actual)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func Test_base_parseLsProcExe(t *testing.T) {
 | 
			
		||||
	type args struct {
 | 
			
		||||
		stdout string
 | 
			
		||||
	}
 | 
			
		||||
	tests := []struct {
 | 
			
		||||
		name    string
 | 
			
		||||
		args    args
 | 
			
		||||
		want    string
 | 
			
		||||
		wantErr bool
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			name: "systemd",
 | 
			
		||||
			args: args{
 | 
			
		||||
				stdout: "lrwxrwxrwx 1 root root 0 Jun 29 17:13 /proc/1/exe -> /lib/systemd/systemd",
 | 
			
		||||
			},
 | 
			
		||||
			want:    "/lib/systemd/systemd",
 | 
			
		||||
			wantErr: false,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		t.Run(tt.name, func(t *testing.T) {
 | 
			
		||||
			l := &base{}
 | 
			
		||||
			got, err := l.parseLsProcExe(tt.args.stdout)
 | 
			
		||||
			if (err != nil) != tt.wantErr {
 | 
			
		||||
				t.Errorf("base.parseLsProcExe() error = %v, wantErr %v", err, tt.wantErr)
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
			if got != tt.want {
 | 
			
		||||
				t.Errorf("base.parseLsProcExe() = %v, want %v", got, tt.want)
 | 
			
		||||
			}
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func Test_base_parseGrepProcMap(t *testing.T) {
 | 
			
		||||
	type args struct {
 | 
			
		||||
		stdout string
 | 
			
		||||
	}
 | 
			
		||||
	tests := []struct {
 | 
			
		||||
		name        string
 | 
			
		||||
		args        args
 | 
			
		||||
		wantSoPaths []string
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			name: "systemd",
 | 
			
		||||
			args: args{
 | 
			
		||||
				`/etc/selinux/targeted/contexts/files/file_contexts.bin
 | 
			
		||||
/etc/selinux/targeted/contexts/files/file_contexts.homedirs.bin
 | 
			
		||||
/usr/lib64/libdl-2.28.so
 | 
			
		||||
				/usr/lib64/libnss_files-2.17.so;601ccbf3`,
 | 
			
		||||
			},
 | 
			
		||||
			wantSoPaths: []string{
 | 
			
		||||
				"/etc/selinux/targeted/contexts/files/file_contexts.bin",
 | 
			
		||||
				"/etc/selinux/targeted/contexts/files/file_contexts.homedirs.bin",
 | 
			
		||||
				"/usr/lib64/libdl-2.28.so",
 | 
			
		||||
				`/usr/lib64/libnss_files-2.17.so`,
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		t.Run(tt.name, func(t *testing.T) {
 | 
			
		||||
			l := &base{}
 | 
			
		||||
			if gotSoPaths := l.parseGrepProcMap(tt.args.stdout); !reflect.DeepEqual(gotSoPaths, tt.wantSoPaths) {
 | 
			
		||||
				t.Errorf("base.parseGrepProcMap() = %v, want %v", gotSoPaths, tt.wantSoPaths)
 | 
			
		||||
			}
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func Test_base_parseLsOf(t *testing.T) {
 | 
			
		||||
	type args struct {
 | 
			
		||||
		stdout string
 | 
			
		||||
	}
 | 
			
		||||
	tests := []struct {
 | 
			
		||||
		name        string
 | 
			
		||||
		args        args
 | 
			
		||||
		wantPortPid map[string][]string
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			name: "lsof",
 | 
			
		||||
			args: args{
 | 
			
		||||
				stdout: `systemd-r   474 systemd-resolve   13u  IPv4  11904      0t0  TCP localhost:53 (LISTEN)
 | 
			
		||||
sshd        644            root    3u  IPv4  16714      0t0  TCP *:22 (LISTEN)
 | 
			
		||||
sshd        644            root    4u  IPv6  16716      0t0  TCP *:22 (LISTEN)
 | 
			
		||||
squid       959           proxy   11u  IPv6  16351      0t0  TCP *:3128 (LISTEN)
 | 
			
		||||
node       1498          ubuntu   21u  IPv6  20132      0t0  TCP *:35401 (LISTEN)
 | 
			
		||||
node       1498          ubuntu   22u  IPv6  20133      0t0  TCP *:44801 (LISTEN)
 | 
			
		||||
rpcbind   568    rpc    7u  IPv6  15149      0t0  UDP *:111
 | 
			
		||||
docker-pr  9135            root    4u  IPv6 297133      0t0  TCP *:6379 (LISTEN)`,
 | 
			
		||||
			},
 | 
			
		||||
			wantPortPid: map[string][]string{
 | 
			
		||||
				"localhost:53": {"474"},
 | 
			
		||||
				"*:22":         {"644"},
 | 
			
		||||
				"*:3128":       {"959"},
 | 
			
		||||
				"*:35401":      {"1498"},
 | 
			
		||||
				"*:44801":      {"1498"},
 | 
			
		||||
				"*:6379":       {"9135"},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "lsof-duplicate-port",
 | 
			
		||||
			args: args{
 | 
			
		||||
				stdout: `sshd      832   root    3u  IPv4  15731      0t0  TCP *:22 (LISTEN)
 | 
			
		||||
sshd      832   root    4u  IPv6  15740      0t0  TCP *:22 (LISTEN)
 | 
			
		||||
master   1099   root   13u  IPv4  16657      0t0  TCP 127.0.0.1:25 (LISTEN)
 | 
			
		||||
master   1099   root   14u  IPv6  16658      0t0  TCP [::1]:25 (LISTEN)
 | 
			
		||||
httpd   32250   root    4u  IPv6 334982      0t0  TCP *:80 (LISTEN)
 | 
			
		||||
httpd   32251 apache    4u  IPv6 334982      0t0  TCP *:80 (LISTEN)
 | 
			
		||||
httpd   32252 apache    4u  IPv6 334982      0t0  TCP *:80 (LISTEN)
 | 
			
		||||
httpd   32253 apache    4u  IPv6 334982      0t0  TCP *:80 (LISTEN)
 | 
			
		||||
httpd   32254 apache    4u  IPv6 334982      0t0  TCP *:80 (LISTEN)
 | 
			
		||||
httpd   32255 apache    4u  IPv6 334982      0t0  TCP *:80 (LISTEN)`,
 | 
			
		||||
			},
 | 
			
		||||
			wantPortPid: map[string][]string{
 | 
			
		||||
				"*:22":         {"832"},
 | 
			
		||||
				"127.0.0.1:25": {"1099"},
 | 
			
		||||
				"[::1]:25":     {"1099"},
 | 
			
		||||
				"*:80":         {"32250", "32251", "32252", "32253", "32254", "32255"},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		t.Run(tt.name, func(t *testing.T) {
 | 
			
		||||
			l := &base{}
 | 
			
		||||
			if gotPortPid := l.parseLsOf(tt.args.stdout); !reflect.DeepEqual(gotPortPid, tt.wantPortPid) {
 | 
			
		||||
				t.Errorf("base.parseLsOf() = %v, want %v", gotPortPid, tt.wantPortPid)
 | 
			
		||||
			}
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func Test_detectScanDest(t *testing.T) {
 | 
			
		||||
	tests := []struct {
 | 
			
		||||
		name   string
 | 
			
		||||
		args   base
 | 
			
		||||
		expect map[string][]string
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			name: "empty",
 | 
			
		||||
			args: base{osPackages: osPackages{
 | 
			
		||||
				Packages: models.Packages{"curl": models.Package{
 | 
			
		||||
					Name:       "curl",
 | 
			
		||||
					Version:    "7.64.0-4+deb10u1",
 | 
			
		||||
					NewVersion: "7.64.0-4+deb10u1",
 | 
			
		||||
				}},
 | 
			
		||||
			}},
 | 
			
		||||
			expect: map[string][]string{},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "single-addr",
 | 
			
		||||
			args: base{osPackages: osPackages{
 | 
			
		||||
				Packages: models.Packages{"libaudit1": models.Package{
 | 
			
		||||
					Name:       "libaudit1",
 | 
			
		||||
					Version:    "1:2.8.4-3",
 | 
			
		||||
					NewVersion: "1:2.8.4-3",
 | 
			
		||||
					AffectedProcs: []models.AffectedProcess{
 | 
			
		||||
						{PID: "21", Name: "sshd", ListenPortStats: []models.PortStat{{BindAddress: "127.0.0.1", Port: "22"}}}, {PID: "10876", Name: "sshd"}},
 | 
			
		||||
				},
 | 
			
		||||
				}},
 | 
			
		||||
			},
 | 
			
		||||
			expect: map[string][]string{"127.0.0.1": {"22"}},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "dup-addr-port",
 | 
			
		||||
			args: base{osPackages: osPackages{
 | 
			
		||||
				Packages: models.Packages{"libaudit1": models.Package{
 | 
			
		||||
					Name:       "libaudit1",
 | 
			
		||||
					Version:    "1:2.8.4-3",
 | 
			
		||||
					NewVersion: "1:2.8.4-3",
 | 
			
		||||
					AffectedProcs: []models.AffectedProcess{
 | 
			
		||||
						{PID: "21", Name: "sshd", ListenPortStats: []models.PortStat{{BindAddress: "127.0.0.1", Port: "22"}}}, {PID: "21", Name: "sshd", ListenPortStats: []models.PortStat{{BindAddress: "127.0.0.1", Port: "22"}}}},
 | 
			
		||||
				},
 | 
			
		||||
				}},
 | 
			
		||||
			},
 | 
			
		||||
			expect: map[string][]string{"127.0.0.1": {"22"}},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "multi-addr",
 | 
			
		||||
			args: base{osPackages: osPackages{
 | 
			
		||||
				Packages: models.Packages{"libaudit1": models.Package{
 | 
			
		||||
					Name:       "libaudit1",
 | 
			
		||||
					Version:    "1:2.8.4-3",
 | 
			
		||||
					NewVersion: "1:2.8.4-3",
 | 
			
		||||
					AffectedProcs: []models.AffectedProcess{
 | 
			
		||||
						{PID: "21", Name: "sshd", ListenPortStats: []models.PortStat{{BindAddress: "127.0.0.1", Port: "22"}}}, {PID: "21", Name: "sshd", ListenPortStats: []models.PortStat{{BindAddress: "192.168.1.1", Port: "22"}}}, {PID: "6261", Name: "nginx", ListenPortStats: []models.PortStat{{BindAddress: "127.0.0.1", Port: "80"}}}},
 | 
			
		||||
				},
 | 
			
		||||
				}},
 | 
			
		||||
			},
 | 
			
		||||
			expect: map[string][]string{"127.0.0.1": {"22", "80"}, "192.168.1.1": {"22"}},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "asterisk",
 | 
			
		||||
			args: base{
 | 
			
		||||
				osPackages: osPackages{
 | 
			
		||||
					Packages: models.Packages{"libaudit1": models.Package{
 | 
			
		||||
						Name:       "libaudit1",
 | 
			
		||||
						Version:    "1:2.8.4-3",
 | 
			
		||||
						NewVersion: "1:2.8.4-3",
 | 
			
		||||
						AffectedProcs: []models.AffectedProcess{
 | 
			
		||||
							{PID: "21", Name: "sshd", ListenPortStats: []models.PortStat{{BindAddress: "*", Port: "22"}}}},
 | 
			
		||||
					},
 | 
			
		||||
					}},
 | 
			
		||||
				ServerInfo: config.ServerInfo{
 | 
			
		||||
					IPv4Addrs: []string{"127.0.0.1", "192.168.1.1"},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			expect: map[string][]string{"127.0.0.1": {"22"}, "192.168.1.1": {"22"}},
 | 
			
		||||
		}}
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		t.Run(tt.name, func(t *testing.T) {
 | 
			
		||||
			if dest := tt.args.detectScanDest(); !reflect.DeepEqual(dest, tt.expect) {
 | 
			
		||||
				t.Errorf("base.detectScanDest() = %v, want %v", dest, tt.expect)
 | 
			
		||||
			}
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func Test_updatePortStatus(t *testing.T) {
 | 
			
		||||
	type args struct {
 | 
			
		||||
		l             base
 | 
			
		||||
		listenIPPorts []string
 | 
			
		||||
	}
 | 
			
		||||
	tests := []struct {
 | 
			
		||||
		name   string
 | 
			
		||||
		args   args
 | 
			
		||||
		expect models.Packages
 | 
			
		||||
	}{
 | 
			
		||||
		{name: "nil_affected_procs",
 | 
			
		||||
			args: args{
 | 
			
		||||
				l: base{osPackages: osPackages{
 | 
			
		||||
					Packages: models.Packages{"libc-bin": models.Package{Name: "libc-bin"}},
 | 
			
		||||
				}},
 | 
			
		||||
				listenIPPorts: []string{"127.0.0.1:22"}},
 | 
			
		||||
			expect: models.Packages{"libc-bin": models.Package{Name: "libc-bin"}}},
 | 
			
		||||
		{name: "nil_listen_ports",
 | 
			
		||||
			args: args{
 | 
			
		||||
				l: base{osPackages: osPackages{
 | 
			
		||||
					Packages: models.Packages{"bash": models.Package{Name: "bash", AffectedProcs: []models.AffectedProcess{{PID: "1", Name: "bash"}}}},
 | 
			
		||||
				}},
 | 
			
		||||
				listenIPPorts: []string{"127.0.0.1:22"}},
 | 
			
		||||
			expect: models.Packages{"bash": models.Package{Name: "bash", AffectedProcs: []models.AffectedProcess{{PID: "1", Name: "bash"}}}}},
 | 
			
		||||
		{name: "update_match_single_address",
 | 
			
		||||
			args: args{
 | 
			
		||||
				l: base{osPackages: osPackages{
 | 
			
		||||
					Packages: models.Packages{"libc6": models.Package{Name: "libc6", AffectedProcs: []models.AffectedProcess{{PID: "1", Name: "bash"}, {PID: "75", Name: "sshd", ListenPortStats: []models.PortStat{{BindAddress: "127.0.0.1", Port: "22"}}}}}},
 | 
			
		||||
				}},
 | 
			
		||||
				listenIPPorts: []string{"127.0.0.1:22"}},
 | 
			
		||||
			expect: models.Packages{"libc6": models.Package{Name: "libc6", AffectedProcs: []models.AffectedProcess{{PID: "1", Name: "bash"}, {PID: "75", Name: "sshd", ListenPortStats: []models.PortStat{{BindAddress: "127.0.0.1", Port: "22", PortReachableTo: []string{"127.0.0.1"}}}}}}}},
 | 
			
		||||
		{name: "update_match_multi_address",
 | 
			
		||||
			args: args{
 | 
			
		||||
				l: base{osPackages: osPackages{
 | 
			
		||||
					Packages: models.Packages{"libc6": models.Package{Name: "libc6", AffectedProcs: []models.AffectedProcess{{PID: "1", Name: "bash"}, {PID: "75", Name: "sshd", ListenPortStats: []models.PortStat{{BindAddress: "127.0.0.1", Port: "22"}, {BindAddress: "192.168.1.1", Port: "22"}}}}}},
 | 
			
		||||
				}},
 | 
			
		||||
				listenIPPorts: []string{"127.0.0.1:22", "192.168.1.1:22"}},
 | 
			
		||||
			expect: models.Packages{"libc6": models.Package{Name: "libc6", AffectedProcs: []models.AffectedProcess{{PID: "1", Name: "bash"}, {PID: "75", Name: "sshd", ListenPortStats: []models.PortStat{
 | 
			
		||||
				{BindAddress: "127.0.0.1", Port: "22", PortReachableTo: []string{"127.0.0.1"}},
 | 
			
		||||
				{BindAddress: "192.168.1.1", Port: "22", PortReachableTo: []string{"192.168.1.1"}},
 | 
			
		||||
			}}}}}},
 | 
			
		||||
		{name: "update_match_asterisk",
 | 
			
		||||
			args: args{
 | 
			
		||||
				l: base{osPackages: osPackages{
 | 
			
		||||
					Packages: models.Packages{"libc6": models.Package{Name: "libc6", AffectedProcs: []models.AffectedProcess{{PID: "1", Name: "bash"}, {PID: "75", Name: "sshd", ListenPortStats: []models.PortStat{{BindAddress: "*", Port: "22"}}}}}},
 | 
			
		||||
				}},
 | 
			
		||||
				listenIPPorts: []string{"127.0.0.1:22", "127.0.0.1:80", "192.168.1.1:22"}},
 | 
			
		||||
			expect: models.Packages{"libc6": models.Package{Name: "libc6", AffectedProcs: []models.AffectedProcess{{PID: "1", Name: "bash"}, {PID: "75", Name: "sshd", ListenPortStats: []models.PortStat{
 | 
			
		||||
				{BindAddress: "*", Port: "22", PortReachableTo: []string{"127.0.0.1", "192.168.1.1"}},
 | 
			
		||||
			}}}}}},
 | 
			
		||||
		{name: "update_multi_packages",
 | 
			
		||||
			args: args{
 | 
			
		||||
				l: base{osPackages: osPackages{
 | 
			
		||||
					Packages: models.Packages{
 | 
			
		||||
						"packa": models.Package{Name: "packa", AffectedProcs: []models.AffectedProcess{{PID: "75", Name: "sshd", ListenPortStats: []models.PortStat{{BindAddress: "127.0.0.1", Port: "80"}}}}},
 | 
			
		||||
						"packb": models.Package{Name: "packb", AffectedProcs: []models.AffectedProcess{{PID: "75", Name: "sshd", ListenPortStats: []models.PortStat{{BindAddress: "127.0.0.1", Port: "22"}}}}},
 | 
			
		||||
						"packc": models.Package{Name: "packc", AffectedProcs: []models.AffectedProcess{{PID: "75", Name: "sshd", ListenPortStats: []models.PortStat{{BindAddress: "127.0.0.1", Port: "22"}, {BindAddress: "192.168.1.1", Port: "22"}}}}},
 | 
			
		||||
						"packd": models.Package{Name: "packd", AffectedProcs: []models.AffectedProcess{{PID: "75", Name: "sshd", ListenPortStats: []models.PortStat{{BindAddress: "*", Port: "22"}}}}},
 | 
			
		||||
					},
 | 
			
		||||
				}},
 | 
			
		||||
				listenIPPorts: []string{"127.0.0.1:22", "192.168.1.1:22"}},
 | 
			
		||||
			expect: models.Packages{
 | 
			
		||||
				"packa": models.Package{Name: "packa", AffectedProcs: []models.AffectedProcess{{PID: "75", Name: "sshd", ListenPortStats: []models.PortStat{{BindAddress: "127.0.0.1", Port: "80", PortReachableTo: []string{}}}}}},
 | 
			
		||||
				"packb": models.Package{Name: "packb", AffectedProcs: []models.AffectedProcess{{PID: "75", Name: "sshd", ListenPortStats: []models.PortStat{{BindAddress: "127.0.0.1", Port: "22", PortReachableTo: []string{"127.0.0.1"}}}}}},
 | 
			
		||||
				"packc": models.Package{Name: "packc", AffectedProcs: []models.AffectedProcess{{PID: "75", Name: "sshd", ListenPortStats: []models.PortStat{{BindAddress: "127.0.0.1", Port: "22", PortReachableTo: []string{"127.0.0.1"}}, {BindAddress: "192.168.1.1", Port: "22", PortReachableTo: []string{"192.168.1.1"}}}}}},
 | 
			
		||||
				"packd": models.Package{Name: "packd", AffectedProcs: []models.AffectedProcess{{PID: "75", Name: "sshd", ListenPortStats: []models.PortStat{{BindAddress: "*", Port: "22", PortReachableTo: []string{"127.0.0.1", "192.168.1.1"}}}}}},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		t.Run(tt.name, func(t *testing.T) {
 | 
			
		||||
			tt.args.l.updatePortStatus(tt.args.listenIPPorts)
 | 
			
		||||
			if !reflect.DeepEqual(tt.args.l.osPackages.Packages, tt.expect) {
 | 
			
		||||
				t.Errorf("l.updatePortStatus() = %v, want %v", tt.args.l.osPackages.Packages, tt.expect)
 | 
			
		||||
			}
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func Test_matchListenPorts(t *testing.T) {
 | 
			
		||||
	type args struct {
 | 
			
		||||
		listenIPPorts    []string
 | 
			
		||||
		searchListenPort models.PortStat
 | 
			
		||||
	}
 | 
			
		||||
	tests := []struct {
 | 
			
		||||
		name   string
 | 
			
		||||
		args   args
 | 
			
		||||
		expect []string
 | 
			
		||||
	}{
 | 
			
		||||
		{name: "open_empty", args: args{listenIPPorts: []string{}, searchListenPort: models.PortStat{BindAddress: "127.0.0.1", Port: "22"}}, expect: []string{}},
 | 
			
		||||
		{name: "port_empty", args: args{listenIPPorts: []string{"127.0.0.1:22"}, searchListenPort: models.PortStat{}}, expect: []string{}},
 | 
			
		||||
		{name: "single_match", args: args{listenIPPorts: []string{"127.0.0.1:22"}, searchListenPort: models.PortStat{BindAddress: "127.0.0.1", Port: "22"}}, expect: []string{"127.0.0.1"}},
 | 
			
		||||
		{name: "no_match_address", args: args{listenIPPorts: []string{"127.0.0.1:22"}, searchListenPort: models.PortStat{BindAddress: "192.168.1.1", Port: "22"}}, expect: []string{}},
 | 
			
		||||
		{name: "no_match_port", args: args{listenIPPorts: []string{"127.0.0.1:22"}, searchListenPort: models.PortStat{BindAddress: "127.0.0.1", Port: "80"}}, expect: []string{}},
 | 
			
		||||
		{name: "asterisk_match", args: args{listenIPPorts: []string{"127.0.0.1:22", "127.0.0.1:80", "192.168.1.1:22"}, searchListenPort: models.PortStat{BindAddress: "*", Port: "22"}}, expect: []string{"127.0.0.1", "192.168.1.1"}},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	l := base{}
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		t.Run(tt.name, func(t *testing.T) {
 | 
			
		||||
			if match := l.findPortTestSuccessOn(tt.args.listenIPPorts, tt.args.searchListenPort); !reflect.DeepEqual(match, tt.expect) {
 | 
			
		||||
				t.Errorf("findPortTestSuccessOn() = %v, want %v", match, tt.expect)
 | 
			
		||||
			}
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										118
									
								
								scanner/centos.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										118
									
								
								scanner/centos.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,118 @@
 | 
			
		||||
package scanner
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	"github.com/future-architect/vuls/util"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// inherit OsTypeInterface
 | 
			
		||||
type centos struct {
 | 
			
		||||
	redhatBase
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewAmazon is constructor
 | 
			
		||||
func newCentOS(c config.ServerInfo) *centos {
 | 
			
		||||
	r := ¢os{
 | 
			
		||||
		redhatBase{
 | 
			
		||||
			base: base{
 | 
			
		||||
				osPackages: osPackages{
 | 
			
		||||
					Packages:  models.Packages{},
 | 
			
		||||
					VulnInfos: models.VulnInfos{},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			sudo: rootPrivCentos{},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	r.log = util.NewCustomLogger(c)
 | 
			
		||||
	r.setServerInfo(c)
 | 
			
		||||
	return r
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *centos) checkScanMode() error {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *centos) 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 *centos) depsFast() []string {
 | 
			
		||||
	if o.getServerInfo().Mode.IsOffline() {
 | 
			
		||||
		return []string{}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// repoquery
 | 
			
		||||
	// `rpm -qa` shows dnf-utils as yum-utils on RHEL8, CentOS8
 | 
			
		||||
	return []string{"yum-utils"}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *centos) depsFastRoot() []string {
 | 
			
		||||
	if o.getServerInfo().Mode.IsOffline() {
 | 
			
		||||
		return []string{}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// repoquery
 | 
			
		||||
	// `rpm -qa` shows dnf-utils as yum-utils on RHEL8, CentOS8
 | 
			
		||||
	return []string{"yum-utils"}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *centos) depsDeep() []string {
 | 
			
		||||
	return o.depsFastRoot()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *centos) 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 *centos) sudoNoPasswdCmdsFast() []cmd {
 | 
			
		||||
	return []cmd{}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *centos) 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 *centos) sudoNoPasswdCmdsDeep() []cmd {
 | 
			
		||||
	return o.sudoNoPasswdCmdsFastRoot()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type rootPrivCentos struct{}
 | 
			
		||||
 | 
			
		||||
func (o rootPrivCentos) repoquery() bool {
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o rootPrivCentos) yumMakeCache() bool {
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o rootPrivCentos) yumPS() bool {
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										1290
									
								
								scanner/debian.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1290
									
								
								scanner/debian.go
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										867
									
								
								scanner/debian_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										867
									
								
								scanner/debian_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,867 @@
 | 
			
		||||
package scanner
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"os"
 | 
			
		||||
	"reflect"
 | 
			
		||||
	"sort"
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/cache"
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/constant"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	"github.com/k0kubun/pp"
 | 
			
		||||
	"github.com/sirupsen/logrus"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestGetCveIDsFromChangelog(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		in        []string
 | 
			
		||||
		cveIDs    []DetectedCveID
 | 
			
		||||
		changelog models.Changelog
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			//0 verubuntu1
 | 
			
		||||
			[]string{
 | 
			
		||||
				"systemd",
 | 
			
		||||
				"228-4ubuntu1",
 | 
			
		||||
				`systemd (229-2) unstable; urgency=medium
 | 
			
		||||
systemd (229-1) unstable; urgency=medium
 | 
			
		||||
systemd (228-6) unstable; urgency=medium
 | 
			
		||||
CVE-2015-2325: heap buffer overflow in compile_branch(). (Closes: #781795)
 | 
			
		||||
CVE-2015-2326: heap buffer overflow in pcre_compile2(). (Closes: #783285)
 | 
			
		||||
CVE-2015-3210: heap buffer overflow in pcre_compile2() /
 | 
			
		||||
systemd (228-5) unstable; urgency=medium
 | 
			
		||||
systemd (228-4) unstable; urgency=medium
 | 
			
		||||
systemd (228-3) unstable; urgency=medium`,
 | 
			
		||||
			},
 | 
			
		||||
			[]DetectedCveID{
 | 
			
		||||
				{"CVE-2015-2325", models.ChangelogExactMatch},
 | 
			
		||||
				{"CVE-2015-2326", models.ChangelogExactMatch},
 | 
			
		||||
				{"CVE-2015-3210", models.ChangelogExactMatch},
 | 
			
		||||
			},
 | 
			
		||||
			models.Changelog{
 | 
			
		||||
				Contents: `systemd (229-2) unstable; urgency=medium
 | 
			
		||||
systemd (229-1) unstable; urgency=medium
 | 
			
		||||
systemd (228-6) unstable; urgency=medium
 | 
			
		||||
CVE-2015-2325: heap buffer overflow in compile_branch(). (Closes: #781795)
 | 
			
		||||
CVE-2015-2326: heap buffer overflow in pcre_compile2(). (Closes: #783285)
 | 
			
		||||
CVE-2015-3210: heap buffer overflow in pcre_compile2() /
 | 
			
		||||
systemd (228-5) unstable; urgency=medium`,
 | 
			
		||||
				Method: models.ChangelogExactMatchStr,
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			//1 ver
 | 
			
		||||
			[]string{
 | 
			
		||||
				"libpcre3",
 | 
			
		||||
				"2:8.35-7.1ubuntu1",
 | 
			
		||||
				`pcre3 (2:8.38-2) unstable; urgency=low
 | 
			
		||||
		 pcre3 (2:8.38-1) unstable; urgency=low
 | 
			
		||||
		 pcre3 (2:8.35-8) unstable; urgency=low
 | 
			
		||||
		 pcre3 (2:8.35-7.4) unstable; urgency=medium
 | 
			
		||||
		 pcre3 (2:8.35-7.3) unstable; urgency=medium
 | 
			
		||||
		 pcre3 (2:8.35-7.2) unstable; urgency=low
 | 
			
		||||
		 CVE-2015-2325: heap buffer overflow in compile_branch(). (Closes: #781795)
 | 
			
		||||
		 CVE-2015-2326: heap buffer overflow in pcre_compile2(). (Closes: #783285)
 | 
			
		||||
		 CVE-2015-3210: heap buffer overflow in pcre_compile2() /
 | 
			
		||||
		 pcre3 (2:8.35-7.1) unstable; urgency=medium
 | 
			
		||||
		 pcre3 (2:8.35-7) unstable; urgency=medium`,
 | 
			
		||||
			},
 | 
			
		||||
			[]DetectedCveID{
 | 
			
		||||
				{"CVE-2015-2325", models.ChangelogExactMatch},
 | 
			
		||||
				{"CVE-2015-2326", models.ChangelogExactMatch},
 | 
			
		||||
				{"CVE-2015-3210", models.ChangelogExactMatch},
 | 
			
		||||
			},
 | 
			
		||||
			models.Changelog{
 | 
			
		||||
				Contents: `pcre3 (2:8.38-2) unstable; urgency=low
 | 
			
		||||
		 pcre3 (2:8.38-1) unstable; urgency=low
 | 
			
		||||
		 pcre3 (2:8.35-8) unstable; urgency=low
 | 
			
		||||
		 pcre3 (2:8.35-7.4) unstable; urgency=medium
 | 
			
		||||
		 pcre3 (2:8.35-7.3) unstable; urgency=medium
 | 
			
		||||
		 pcre3 (2:8.35-7.2) unstable; urgency=low
 | 
			
		||||
		 CVE-2015-2325: heap buffer overflow in compile_branch(). (Closes: #781795)
 | 
			
		||||
		 CVE-2015-2326: heap buffer overflow in pcre_compile2(). (Closes: #783285)
 | 
			
		||||
		 CVE-2015-3210: heap buffer overflow in pcre_compile2() /`,
 | 
			
		||||
				Method: models.ChangelogExactMatchStr,
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			//2 ver-ubuntu3
 | 
			
		||||
			[]string{
 | 
			
		||||
				"sysvinit",
 | 
			
		||||
				"2.88dsf-59.2ubuntu3",
 | 
			
		||||
				`sysvinit (2.88dsf-59.3ubuntu1) xenial; urgency=low
 | 
			
		||||
		 sysvinit (2.88dsf-59.3) unstable; urgency=medium
 | 
			
		||||
		 CVE-2015-2325: heap buffer overflow in compile_branch(). (Closes: #781795)
 | 
			
		||||
		 CVE-2015-2326: heap buffer overflow in pcre_compile2(). (Closes: #783285)
 | 
			
		||||
		 CVE-2015-3210: heap buffer overflow in pcre_compile2() /
 | 
			
		||||
		 sysvinit (2.88dsf-59.2ubuntu3) xenial; urgency=medium
 | 
			
		||||
		 sysvinit (2.88dsf-59.2ubuntu2) wily; urgency=medium
 | 
			
		||||
		 sysvinit (2.88dsf-59.2ubuntu1) wily; urgency=medium
 | 
			
		||||
		 CVE-2015-2321: heap buffer overflow in pcre_compile2(). (Closes: #783285)
 | 
			
		||||
		 sysvinit (2.88dsf-59.2) unstable; urgency=medium
 | 
			
		||||
		 sysvinit (2.88dsf-59.1ubuntu3) wily; urgency=medium
 | 
			
		||||
		 CVE-2015-2322: heap buffer overflow in pcre_compile2(). (Closes: #783285)
 | 
			
		||||
		 sysvinit (2.88dsf-59.1ubuntu2) wily; urgency=medium
 | 
			
		||||
		 sysvinit (2.88dsf-59.1ubuntu1) wily; urgency=medium
 | 
			
		||||
		 sysvinit (2.88dsf-59.1) unstable; urgency=medium
 | 
			
		||||
		 CVE-2015-2326: heap buffer overflow in pcre_compile2(). (Closes: #783285)
 | 
			
		||||
		 sysvinit (2.88dsf-59) unstable; urgency=medium
 | 
			
		||||
		 sysvinit (2.88dsf-58) unstable; urgency=low
 | 
			
		||||
		 sysvinit (2.88dsf-57) unstable; urgency=low`,
 | 
			
		||||
			},
 | 
			
		||||
			[]DetectedCveID{
 | 
			
		||||
				{"CVE-2015-2325", models.ChangelogExactMatch},
 | 
			
		||||
				{"CVE-2015-2326", models.ChangelogExactMatch},
 | 
			
		||||
				{"CVE-2015-3210", models.ChangelogExactMatch},
 | 
			
		||||
			},
 | 
			
		||||
			models.Changelog{
 | 
			
		||||
				Contents: `sysvinit (2.88dsf-59.3ubuntu1) xenial; urgency=low
 | 
			
		||||
		 sysvinit (2.88dsf-59.3) unstable; urgency=medium
 | 
			
		||||
		 CVE-2015-2325: heap buffer overflow in compile_branch(). (Closes: #781795)
 | 
			
		||||
		 CVE-2015-2326: heap buffer overflow in pcre_compile2(). (Closes: #783285)
 | 
			
		||||
		 CVE-2015-3210: heap buffer overflow in pcre_compile2() /`,
 | 
			
		||||
				Method: models.ChangelogExactMatchStr,
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			//3  1:ver-ubuntu3
 | 
			
		||||
			[]string{
 | 
			
		||||
				"bsdutils",
 | 
			
		||||
				"1:2.27.1-1ubuntu3",
 | 
			
		||||
				`util-linux (2.27.1-3ubuntu1) xenial; urgency=medium
 | 
			
		||||
		 util-linux (2.27.1-3) unstable; urgency=medium
 | 
			
		||||
		 CVE-2015-2325: heap buffer overflow in compile_branch(). (Closes: #781795)
 | 
			
		||||
		 CVE-2015-2326: heap buffer overflow in pcre_compile2(). (Closes: #783285)
 | 
			
		||||
		 CVE-2015-3210: CVE-2016-1000000heap buffer overflow in pcre_compile2() /
 | 
			
		||||
		 util-linux (2.27.1-2) unstable; urgency=medium
 | 
			
		||||
		 util-linux (2.27.1-1ubuntu4) xenial; urgency=medium
 | 
			
		||||
		 util-linux (2.27.1-1ubuntu3) xenial; urgency=medium
 | 
			
		||||
		 util-linux (2.27.1-1ubuntu2) xenial; urgency=medium
 | 
			
		||||
		 util-linux (2.27.1-1ubuntu1) xenial; urgency=medium
 | 
			
		||||
		 util-linux (2.27.1-1) unstable; urgency=medium
 | 
			
		||||
		 util-linux (2.27-3ubuntu1) xenial; urgency=medium`,
 | 
			
		||||
			},
 | 
			
		||||
			[]DetectedCveID{
 | 
			
		||||
				// {"CVE-2015-2325", models.ChangelogLenientMatch},
 | 
			
		||||
				// {"CVE-2015-2326", models.ChangelogLenientMatch},
 | 
			
		||||
				// {"CVE-2015-3210", models.ChangelogLenientMatch},
 | 
			
		||||
				// {"CVE-2016-1000000", models.ChangelogLenientMatch},
 | 
			
		||||
			},
 | 
			
		||||
			models.Changelog{
 | 
			
		||||
				// Contents: `util-linux (2.27.1-3ubuntu1) xenial; urgency=medium
 | 
			
		||||
				// util-linux (2.27.1-3) unstable; urgency=medium
 | 
			
		||||
				// CVE-2015-2325: heap buffer overflow in compile_branch(). (Closes: #781795)
 | 
			
		||||
				// CVE-2015-2326: heap buffer overflow in pcre_compile2(). (Closes: #783285)
 | 
			
		||||
				// CVE-2015-3210: CVE-2016-1000000heap buffer overflow in pcre_compile2() /
 | 
			
		||||
				// util-linux (2.27.1-2) unstable; urgency=medium
 | 
			
		||||
				// util-linux (2.27.1-1ubuntu4) xenial; urgency=medium
 | 
			
		||||
				// util-linux (2.27.1-1ubuntu3) xenial; urgency=medium`,
 | 
			
		||||
				Method: models.ChangelogExactMatchStr,
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			//4 1:ver-ubuntu3
 | 
			
		||||
			[]string{
 | 
			
		||||
				"bsdutils",
 | 
			
		||||
				"1:2.27-3ubuntu3",
 | 
			
		||||
				`util-linux (2.27.1-3ubuntu1) xenial; urgency=medium
 | 
			
		||||
		 util-linux (2.27.1-3) unstable; urgency=medium
 | 
			
		||||
		 CVE-2015-2325: heap buffer overflow in compile_branch(). (Closes: #781795)
 | 
			
		||||
		 CVE-2015-2326: heap buffer overflow in pcre_compile2(). (Closes: #783285)
 | 
			
		||||
		 CVE-2015-3210: CVE-2016-1000000heap buffer overflow in pcre_compile2() /
 | 
			
		||||
		 util-linux (2.27.1-2) unstable; urgency=medium
 | 
			
		||||
		 util-linux (2.27.1-1ubuntu4) xenial; urgency=medium
 | 
			
		||||
		 util-linux (2.27.1-1ubuntu3) xenial; urgency=medium
 | 
			
		||||
		 util-linux (2.27.1-1ubuntu2) xenial; urgency=medium
 | 
			
		||||
		 util-linux (2.27.1-1ubuntu1) xenial; urgency=medium
 | 
			
		||||
		 util-linux (2.27.1-1) unstable; urgency=medium
 | 
			
		||||
		 util-linux (2.27-3) xenial; urgency=medium`,
 | 
			
		||||
			},
 | 
			
		||||
			[]DetectedCveID{
 | 
			
		||||
				// {"CVE-2015-2325", models.ChangelogLenientMatch},
 | 
			
		||||
				// {"CVE-2015-2326", models.ChangelogLenientMatch},
 | 
			
		||||
				// {"CVE-2015-3210", models.ChangelogLenientMatch},
 | 
			
		||||
				// {"CVE-2016-1000000", models.ChangelogLenientMatch},
 | 
			
		||||
			},
 | 
			
		||||
			models.Changelog{
 | 
			
		||||
				// Contents: `util-linux (2.27.1-3ubuntu1) xenial; urgency=medium
 | 
			
		||||
				// util-linux (2.27.1-3) unstable; urgency=medium
 | 
			
		||||
				// CVE-2015-2325: heap buffer overflow in compile_branch(). (Closes: #781795)
 | 
			
		||||
				// CVE-2015-2326: heap buffer overflow in pcre_compile2(). (Closes: #783285)
 | 
			
		||||
				// CVE-2015-3210: CVE-2016-1000000heap buffer overflow in pcre_compile2() /
 | 
			
		||||
				// util-linux (2.27.1-2) unstable; urgency=medium
 | 
			
		||||
				// util-linux (2.27.1-1ubuntu4) xenial; urgency=medium
 | 
			
		||||
				// util-linux (2.27.1-1ubuntu3) xenial; urgency=medium
 | 
			
		||||
				// util-linux (2.27.1-1ubuntu2) xenial; urgency=medium
 | 
			
		||||
				// util-linux (2.27.1-1ubuntu1) xenial; urgency=medium
 | 
			
		||||
				// util-linux (2.27.1-1) unstable; urgency=medium`,
 | 
			
		||||
				Method: models.ChangelogExactMatchStr,
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			//5 https://github.com/future-architect/vuls/pull/350
 | 
			
		||||
			[]string{
 | 
			
		||||
				"tar",
 | 
			
		||||
				"1.27.1-2+b1",
 | 
			
		||||
				`tar (1.27.1-2+deb8u1) jessie-security; urgency=high
 | 
			
		||||
		   * CVE-2016-6321: Bypassing the extract path name.
 | 
			
		||||
		 tar (1.27.1-2) unstable; urgency=low`,
 | 
			
		||||
			},
 | 
			
		||||
			[]DetectedCveID{
 | 
			
		||||
				{"CVE-2016-6321", models.ChangelogExactMatch},
 | 
			
		||||
			},
 | 
			
		||||
			models.Changelog{
 | 
			
		||||
				Contents: `tar (1.27.1-2+deb8u1) jessie-security; urgency=high
 | 
			
		||||
		   * CVE-2016-6321: Bypassing the extract path name.`,
 | 
			
		||||
				Method: models.ChangelogExactMatchStr,
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	d := newDebian(config.ServerInfo{})
 | 
			
		||||
	d.Distro.Family = "ubuntu"
 | 
			
		||||
	for i, tt := range tests {
 | 
			
		||||
		aCveIDs, aPack := d.getCveIDsFromChangelog(tt.in[2], tt.in[0], tt.in[1])
 | 
			
		||||
		if len(aCveIDs) != len(tt.cveIDs) {
 | 
			
		||||
			t.Errorf("[%d] Len of return array aren't same. expected %#v, actual %#v", i, tt.cveIDs, aCveIDs)
 | 
			
		||||
			t.Errorf(pp.Sprintf("%s", tt.in))
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		for j := range tt.cveIDs {
 | 
			
		||||
			if !reflect.DeepEqual(tt.cveIDs[j], aCveIDs[j]) {
 | 
			
		||||
				t.Errorf("[%d] expected %v, actual %v", i, tt.cveIDs[j], aCveIDs[j])
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if aPack.Changelog.Contents != tt.changelog.Contents {
 | 
			
		||||
			t.Error(pp.Sprintf("[%d] expected: %s, actual: %s", i, tt.changelog.Contents, aPack.Changelog.Contents))
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if aPack.Changelog.Method != tt.changelog.Method {
 | 
			
		||||
			t.Error(pp.Sprintf("[%d] expected: %s, actual: %s", i, tt.changelog.Method, aPack.Changelog.Method))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestGetUpdatablePackNames(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		in       string
 | 
			
		||||
		expected []string
 | 
			
		||||
	}{
 | 
			
		||||
		{ // Ubuntu 12.04
 | 
			
		||||
			`Reading package lists... Done
 | 
			
		||||
Building dependency tree
 | 
			
		||||
Reading state information... Done
 | 
			
		||||
The following packages will be upgraded:
 | 
			
		||||
  apt ca-certificates cpio dpkg e2fslibs e2fsprogs gnupg gpgv libc-bin libc6 libcomerr2 libpcre3
 | 
			
		||||
  libpng12-0 libss2 libssl1.0.0 libudev0 multiarch-support openssl tzdata udev upstart
 | 
			
		||||
21 upgraded, 0 newly installed, 0 to remove and 0 not upgraded.`,
 | 
			
		||||
			[]string{
 | 
			
		||||
				"apt",
 | 
			
		||||
				"ca-certificates",
 | 
			
		||||
				"cpio",
 | 
			
		||||
				"dpkg",
 | 
			
		||||
				"e2fslibs",
 | 
			
		||||
				"e2fsprogs",
 | 
			
		||||
				"gnupg",
 | 
			
		||||
				"gpgv",
 | 
			
		||||
				"libc-bin",
 | 
			
		||||
				"libc6",
 | 
			
		||||
				"libcomerr2",
 | 
			
		||||
				"libpcre3",
 | 
			
		||||
				"libpng12-0",
 | 
			
		||||
				"libss2",
 | 
			
		||||
				"libssl1.0.0",
 | 
			
		||||
				"libudev0",
 | 
			
		||||
				"multiarch-support",
 | 
			
		||||
				"openssl",
 | 
			
		||||
				"tzdata",
 | 
			
		||||
				"udev",
 | 
			
		||||
				"upstart",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{ // Ubuntu 14.04
 | 
			
		||||
			`Reading package lists... Done
 | 
			
		||||
Building dependency tree
 | 
			
		||||
Reading state information... Done
 | 
			
		||||
Calculating upgrade... Done
 | 
			
		||||
The following packages will be upgraded:
 | 
			
		||||
  apt apt-utils base-files bsdutils coreutils cpio dh-python dpkg e2fslibs
 | 
			
		||||
  e2fsprogs gcc-4.8-base gcc-4.9-base gnupg gpgv ifupdown initscripts iproute2
 | 
			
		||||
  isc-dhcp-client isc-dhcp-common libapt-inst1.5 libapt-pkg4.12 libblkid1
 | 
			
		||||
  libc-bin libc6 libcgmanager0 libcomerr2 libdrm2 libexpat1 libffi6 libgcc1
 | 
			
		||||
  libgcrypt11 libgnutls-openssl27 libgnutls26 libmount1 libpcre3 libpng12-0
 | 
			
		||||
  libpython3.4-minimal libpython3.4-stdlib libsqlite3-0 libss2 libssl1.0.0
 | 
			
		||||
  libstdc++6 libtasn1-6 libudev1 libuuid1 login mount multiarch-support
 | 
			
		||||
  ntpdate passwd python3.4 python3.4-minimal rsyslog sudo sysv-rc
 | 
			
		||||
  sysvinit-utils tzdata udev util-linux
 | 
			
		||||
59 upgraded, 0 newly installed, 0 to remove and 0 not upgraded.
 | 
			
		||||
`,
 | 
			
		||||
			[]string{
 | 
			
		||||
				"apt",
 | 
			
		||||
				"apt-utils",
 | 
			
		||||
				"base-files",
 | 
			
		||||
				"bsdutils",
 | 
			
		||||
				"coreutils",
 | 
			
		||||
				"cpio",
 | 
			
		||||
				"dh-python",
 | 
			
		||||
				"dpkg",
 | 
			
		||||
				"e2fslibs",
 | 
			
		||||
				"e2fsprogs",
 | 
			
		||||
				"gcc-4.8-base",
 | 
			
		||||
				"gcc-4.9-base",
 | 
			
		||||
				"gnupg",
 | 
			
		||||
				"gpgv",
 | 
			
		||||
				"ifupdown",
 | 
			
		||||
				"initscripts",
 | 
			
		||||
				"iproute2",
 | 
			
		||||
				"isc-dhcp-client",
 | 
			
		||||
				"isc-dhcp-common",
 | 
			
		||||
				"libapt-inst1.5",
 | 
			
		||||
				"libapt-pkg4.12",
 | 
			
		||||
				"libblkid1",
 | 
			
		||||
				"libc-bin",
 | 
			
		||||
				"libc6",
 | 
			
		||||
				"libcgmanager0",
 | 
			
		||||
				"libcomerr2",
 | 
			
		||||
				"libdrm2",
 | 
			
		||||
				"libexpat1",
 | 
			
		||||
				"libffi6",
 | 
			
		||||
				"libgcc1",
 | 
			
		||||
				"libgcrypt11",
 | 
			
		||||
				"libgnutls-openssl27",
 | 
			
		||||
				"libgnutls26",
 | 
			
		||||
				"libmount1",
 | 
			
		||||
				"libpcre3",
 | 
			
		||||
				"libpng12-0",
 | 
			
		||||
				"libpython3.4-minimal",
 | 
			
		||||
				"libpython3.4-stdlib",
 | 
			
		||||
				"libsqlite3-0",
 | 
			
		||||
				"libss2",
 | 
			
		||||
				"libssl1.0.0",
 | 
			
		||||
				"libstdc++6",
 | 
			
		||||
				"libtasn1-6",
 | 
			
		||||
				"libudev1",
 | 
			
		||||
				"libuuid1",
 | 
			
		||||
				"login",
 | 
			
		||||
				"mount",
 | 
			
		||||
				"multiarch-support",
 | 
			
		||||
				"ntpdate",
 | 
			
		||||
				"passwd",
 | 
			
		||||
				"python3.4",
 | 
			
		||||
				"python3.4-minimal",
 | 
			
		||||
				"rsyslog",
 | 
			
		||||
				"sudo",
 | 
			
		||||
				"sysv-rc",
 | 
			
		||||
				"sysvinit-utils",
 | 
			
		||||
				"tzdata",
 | 
			
		||||
				"udev",
 | 
			
		||||
				"util-linux",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			//Ubuntu12.04
 | 
			
		||||
			`Reading package lists... Done
 | 
			
		||||
Building dependency tree
 | 
			
		||||
Reading state information... Done
 | 
			
		||||
0 upgraded, 0 newly installed, 0 to remove and 0 not upgraded.`,
 | 
			
		||||
			[]string{},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			//Ubuntu14.04
 | 
			
		||||
			`Reading package lists... Done
 | 
			
		||||
Building dependency tree
 | 
			
		||||
Reading state information... Done
 | 
			
		||||
Calculating upgrade... Done
 | 
			
		||||
0 upgraded, 0 newly installed, 0 to remove and 0 not upgraded.`,
 | 
			
		||||
			[]string{},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	d := newDebian(config.ServerInfo{})
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		actual, err := d.parseAptGetUpgrade(tt.in)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			t.Errorf("Returning error is unexpected")
 | 
			
		||||
		}
 | 
			
		||||
		if len(tt.expected) != len(actual) {
 | 
			
		||||
			t.Errorf("Result length is not as same as expected. expected: %d, actual: %d", len(tt.expected), len(actual))
 | 
			
		||||
			_, _ = pp.Println(tt.expected)
 | 
			
		||||
			_, _ = pp.Println(actual)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		for i := range tt.expected {
 | 
			
		||||
			if tt.expected[i] != actual[i] {
 | 
			
		||||
				t.Errorf("[%d] expected %s, actual %s", i, tt.expected[i], actual[i])
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestGetChangelogCache(t *testing.T) {
 | 
			
		||||
	const servername = "server1"
 | 
			
		||||
	pack := models.Package{
 | 
			
		||||
		Name:       "apt",
 | 
			
		||||
		Version:    "1.0.0",
 | 
			
		||||
		NewVersion: "1.0.1",
 | 
			
		||||
	}
 | 
			
		||||
	var meta = cache.Meta{
 | 
			
		||||
		Name: servername,
 | 
			
		||||
		Distro: config.Distro{
 | 
			
		||||
			Family:  "ubuntu",
 | 
			
		||||
			Release: "16.04",
 | 
			
		||||
		},
 | 
			
		||||
		Packs: models.Packages{
 | 
			
		||||
			"apt": pack,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	const path = "/tmp/vuls-test-cache-11111111.db"
 | 
			
		||||
	log := logrus.NewEntry(&logrus.Logger{})
 | 
			
		||||
	if err := cache.SetupBolt(path, log); err != nil {
 | 
			
		||||
		t.Errorf("Failed to setup bolt: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
	defer os.Remove(path)
 | 
			
		||||
 | 
			
		||||
	if err := cache.DB.EnsureBuckets(meta); err != nil {
 | 
			
		||||
		t.Errorf("Failed to ensure buckets: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	d := newDebian(config.ServerInfo{})
 | 
			
		||||
	actual := d.getChangelogCache(&meta, pack)
 | 
			
		||||
	if actual != "" {
 | 
			
		||||
		t.Errorf("Failed to get empty string from cache:")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	clog := "changelog-text"
 | 
			
		||||
	if err := cache.DB.PutChangelog(servername, "apt", clog); err != nil {
 | 
			
		||||
		t.Errorf("Failed to put changelog: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	actual = d.getChangelogCache(&meta, pack)
 | 
			
		||||
	if actual != clog {
 | 
			
		||||
		t.Errorf("Failed to get changelog from cache: %s", actual)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// increment a version of the pack
 | 
			
		||||
	pack.NewVersion = "1.0.2"
 | 
			
		||||
	actual = d.getChangelogCache(&meta, pack)
 | 
			
		||||
	if actual != "" {
 | 
			
		||||
		t.Errorf("The changelog is not invalidated: %s", actual)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// change a name of the pack
 | 
			
		||||
	pack.Name = "bash"
 | 
			
		||||
	actual = d.getChangelogCache(&meta, pack)
 | 
			
		||||
	if actual != "" {
 | 
			
		||||
		t.Errorf("The changelog is not invalidated: %s", actual)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestSplitAptCachePolicy(t *testing.T) {
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		stdout   string
 | 
			
		||||
		expected map[string]string
 | 
			
		||||
	}{
 | 
			
		||||
		// This function parse apt-cache policy by using Regexp multi-line mode.
 | 
			
		||||
		// So, test data includes "\r\n"
 | 
			
		||||
		{
 | 
			
		||||
			"apt:\r\n  Installed: 1.2.6\r\n  Candidate: 1.2.12~ubuntu16.04.1\r\n  Version table:\r\n     1.2.12~ubuntu16.04.1 500\r\n        500 http://archive.ubuntu.com/ubuntu xenial-updates/main amd64 Packages\r\n     1.2.10ubuntu1 500\r\n        500 http://archive.ubuntu.com/ubuntu xenial/main amd64 Packages\r\n *** 1.2.6 100\r\n        100 /var/lib/dpkg/status\r\napt-utils:\r\n  Installed: 1.2.6\r\n  Candidate: 1.2.12~ubuntu16.04.1\r\n  Version table:\r\n     1.2.12~ubuntu16.04.1 500\r\n        500 http://archive.ubuntu.com/ubuntu xenial-updates/main amd64 Packages\r\n     1.2.10ubuntu1 500\r\n        500 http://archive.ubuntu.com/ubuntu xenial/main amd64 Packages\r\n *** 1.2.6 100\r\n        100 /var/lib/dpkg/status\r\nbase-files:\r\n  Installed: 9.4ubuntu3\r\n  Candidate: 9.4ubuntu4.2\r\n  Version table:\r\n     9.4ubuntu4.2 500\r\n        500 http://archive.ubuntu.com/ubuntu xenial-updates/main amd64 Packages\r\n     9.4ubuntu4 500\r\n        500 http://archive.ubuntu.com/ubuntu xenial/main amd64 Packages\r\n *** 9.4ubuntu3 100\r\n        100 /var/lib/dpkg/status\r\n",
 | 
			
		||||
 | 
			
		||||
			map[string]string{
 | 
			
		||||
				"apt": "apt:\r\n  Installed: 1.2.6\r\n  Candidate: 1.2.12~ubuntu16.04.1\r\n  Version table:\r\n     1.2.12~ubuntu16.04.1 500\r\n        500 http://archive.ubuntu.com/ubuntu xenial-updates/main amd64 Packages\r\n     1.2.10ubuntu1 500\r\n        500 http://archive.ubuntu.com/ubuntu xenial/main amd64 Packages\r\n *** 1.2.6 100\r\n        100 /var/lib/dpkg/status\r\n",
 | 
			
		||||
 | 
			
		||||
				"apt-utils": "apt-utils:\r\n  Installed: 1.2.6\r\n  Candidate: 1.2.12~ubuntu16.04.1\r\n  Version table:\r\n     1.2.12~ubuntu16.04.1 500\r\n        500 http://archive.ubuntu.com/ubuntu xenial-updates/main amd64 Packages\r\n     1.2.10ubuntu1 500\r\n        500 http://archive.ubuntu.com/ubuntu xenial/main amd64 Packages\r\n *** 1.2.6 100\r\n        100 /var/lib/dpkg/status\r\n",
 | 
			
		||||
 | 
			
		||||
				"base-files": "base-files:\r\n  Installed: 9.4ubuntu3\r\n  Candidate: 9.4ubuntu4.2\r\n  Version table:\r\n     9.4ubuntu4.2 500\r\n        500 http://archive.ubuntu.com/ubuntu xenial-updates/main amd64 Packages\r\n     9.4ubuntu4 500\r\n        500 http://archive.ubuntu.com/ubuntu xenial/main amd64 Packages\r\n *** 9.4ubuntu3 100\r\n        100 /var/lib/dpkg/status\r\n",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	d := newDebian(config.ServerInfo{})
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		actual := d.splitAptCachePolicy(tt.stdout)
 | 
			
		||||
		if !reflect.DeepEqual(tt.expected, actual) {
 | 
			
		||||
			e := pp.Sprintf("%v", tt.expected)
 | 
			
		||||
			a := pp.Sprintf("%v", actual)
 | 
			
		||||
			t.Errorf("expected %s, actual %s", e, a)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestParseAptCachePolicy(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		stdout   string
 | 
			
		||||
		name     string
 | 
			
		||||
		expected packCandidateVer
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			// Ubuntu 16.04
 | 
			
		||||
			`openssl:
 | 
			
		||||
  Installed: 1.0.2f-2ubuntu1
 | 
			
		||||
  Candidate: 1.0.2g-1ubuntu2
 | 
			
		||||
  Version table:
 | 
			
		||||
     1.0.2g-1ubuntu2 500
 | 
			
		||||
        500 http://archive.ubuntu.com/ubuntu xenial/main amd64 Packages
 | 
			
		||||
 *** 1.0.2f-2ubuntu1 100
 | 
			
		||||
        100 /var/lib/dpkg/status`,
 | 
			
		||||
			"openssl",
 | 
			
		||||
			packCandidateVer{
 | 
			
		||||
				Name:      "openssl",
 | 
			
		||||
				Installed: "1.0.2f-2ubuntu1",
 | 
			
		||||
				Candidate: "1.0.2g-1ubuntu2",
 | 
			
		||||
				Repo:      "xenial/main",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			// Ubuntu 14.04
 | 
			
		||||
			`openssl:
 | 
			
		||||
  Installed: 1.0.1f-1ubuntu2.16
 | 
			
		||||
  Candidate: 1.0.1f-1ubuntu2.17
 | 
			
		||||
  Version table:
 | 
			
		||||
     1.0.1f-1ubuntu2.17 0
 | 
			
		||||
        500 http://archive.ubuntu.com/ubuntu/ trusty-updates/main amd64 Packages
 | 
			
		||||
        500 http://archive.ubuntu.com/ubuntu/ trusty-security/main amd64 Packages
 | 
			
		||||
 *** 1.0.1f-1ubuntu2.16 0
 | 
			
		||||
        100 /var/lib/dpkg/status
 | 
			
		||||
     1.0.1f-1ubuntu2 0
 | 
			
		||||
        500 http://archive.ubuntu.com/ubuntu/ trusty/main amd64 Packages`,
 | 
			
		||||
			"openssl",
 | 
			
		||||
			packCandidateVer{
 | 
			
		||||
				Name:      "openssl",
 | 
			
		||||
				Installed: "1.0.1f-1ubuntu2.16",
 | 
			
		||||
				Candidate: "1.0.1f-1ubuntu2.17",
 | 
			
		||||
				Repo:      "trusty-updates/main",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			// Ubuntu 12.04
 | 
			
		||||
			`openssl:
 | 
			
		||||
  Installed: 1.0.1-4ubuntu5.33
 | 
			
		||||
  Candidate: 1.0.1-4ubuntu5.34
 | 
			
		||||
  Version table:
 | 
			
		||||
     1.0.1-4ubuntu5.34 0
 | 
			
		||||
        500 http://archive.ubuntu.com/ubuntu/ precise-updates/main amd64 Packages
 | 
			
		||||
        500 http://archive.ubuntu.com/ubuntu/ precise-security/main amd64 Packages
 | 
			
		||||
 *** 1.0.1-4ubuntu5.33 0
 | 
			
		||||
        100 /var/lib/dpkg/status
 | 
			
		||||
     1.0.1-4ubuntu3 0
 | 
			
		||||
        500 http://archive.ubuntu.com/ubuntu/ precise/main amd64 Packages`,
 | 
			
		||||
			"openssl",
 | 
			
		||||
			packCandidateVer{
 | 
			
		||||
				Name:      "openssl",
 | 
			
		||||
				Installed: "1.0.1-4ubuntu5.33",
 | 
			
		||||
				Candidate: "1.0.1-4ubuntu5.34",
 | 
			
		||||
				Repo:      "precise-updates/main",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	d := newDebian(config.ServerInfo{})
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		actual, err := d.parseAptCachePolicy(tt.stdout, tt.name)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			t.Errorf("Error has occurred: %s, actual: %#v", err, actual)
 | 
			
		||||
		}
 | 
			
		||||
		if !reflect.DeepEqual(tt.expected, actual) {
 | 
			
		||||
			e := pp.Sprintf("%v", tt.expected)
 | 
			
		||||
			a := pp.Sprintf("%v", actual)
 | 
			
		||||
			t.Errorf("expected %s, actual %s", e, a)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestParseCheckRestart(t *testing.T) {
 | 
			
		||||
	r := newDebian(config.ServerInfo{})
 | 
			
		||||
	r.Distro = config.Distro{Family: "debian"}
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		in              string
 | 
			
		||||
		out             models.Packages
 | 
			
		||||
		unknownServices []string
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			in: `Found 27 processes using old versions of upgraded files
 | 
			
		||||
(19 distinct programs)
 | 
			
		||||
(15 distinct packages)
 | 
			
		||||
 | 
			
		||||
Of these, 14 seem to contain systemd service definitions or init scripts which can be used to restart them.
 | 
			
		||||
The following packages seem to have definitions that could be used
 | 
			
		||||
to restart their services:
 | 
			
		||||
varnish:
 | 
			
		||||
	3490	/usr/sbin/varnishd
 | 
			
		||||
	3704	/usr/sbin/varnishd
 | 
			
		||||
memcached:
 | 
			
		||||
	3636	/usr/bin/memcached
 | 
			
		||||
openssh-server:
 | 
			
		||||
	1252	/usr/sbin/sshd
 | 
			
		||||
	1184	/usr/sbin/sshd
 | 
			
		||||
accountsservice:
 | 
			
		||||
	462     /usr/lib/accountsservice/accounts-daemon
 | 
			
		||||
 | 
			
		||||
These are the systemd services:
 | 
			
		||||
systemctl restart accounts-daemon.service
 | 
			
		||||
 | 
			
		||||
These are the initd scripts:
 | 
			
		||||
service varnish restart
 | 
			
		||||
service memcached restart
 | 
			
		||||
service ssh restart
 | 
			
		||||
 | 
			
		||||
These processes (1) do not seem to have an associated init script to restart them:
 | 
			
		||||
util-linux:
 | 
			
		||||
	3650	/sbin/agetty
 | 
			
		||||
	3648	/sbin/agetty`,
 | 
			
		||||
			out: models.NewPackages(
 | 
			
		||||
				models.Package{
 | 
			
		||||
					Name: "varnish",
 | 
			
		||||
					NeedRestartProcs: []models.NeedRestartProcess{
 | 
			
		||||
						{
 | 
			
		||||
							PID:         "3490",
 | 
			
		||||
							Path:        "/usr/sbin/varnishd",
 | 
			
		||||
							ServiceName: "varnish",
 | 
			
		||||
							HasInit:     true,
 | 
			
		||||
						},
 | 
			
		||||
						{
 | 
			
		||||
							PID:         "3704",
 | 
			
		||||
							Path:        "/usr/sbin/varnishd",
 | 
			
		||||
							ServiceName: "varnish",
 | 
			
		||||
							HasInit:     true,
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
				models.Package{
 | 
			
		||||
					Name: "memcached",
 | 
			
		||||
					NeedRestartProcs: []models.NeedRestartProcess{
 | 
			
		||||
						{
 | 
			
		||||
							PID:         "3636",
 | 
			
		||||
							Path:        "/usr/bin/memcached",
 | 
			
		||||
							ServiceName: "memcached",
 | 
			
		||||
							HasInit:     true,
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
				models.Package{
 | 
			
		||||
					Name: "openssh-server",
 | 
			
		||||
					NeedRestartProcs: []models.NeedRestartProcess{
 | 
			
		||||
						{
 | 
			
		||||
							PID:         "1252",
 | 
			
		||||
							Path:        "/usr/sbin/sshd",
 | 
			
		||||
							ServiceName: "",
 | 
			
		||||
							HasInit:     true,
 | 
			
		||||
						},
 | 
			
		||||
						{
 | 
			
		||||
							PID:         "1184",
 | 
			
		||||
							Path:        "/usr/sbin/sshd",
 | 
			
		||||
							ServiceName: "",
 | 
			
		||||
							HasInit:     true,
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
				models.Package{
 | 
			
		||||
					Name: "accountsservice",
 | 
			
		||||
					NeedRestartProcs: []models.NeedRestartProcess{
 | 
			
		||||
						{
 | 
			
		||||
							PID:         "462",
 | 
			
		||||
							Path:        "/usr/lib/accountsservice/accounts-daemon",
 | 
			
		||||
							ServiceName: "",
 | 
			
		||||
							HasInit:     true,
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
				models.Package{
 | 
			
		||||
					Name: "util-linux",
 | 
			
		||||
					NeedRestartProcs: []models.NeedRestartProcess{
 | 
			
		||||
						{
 | 
			
		||||
							PID:     "3650",
 | 
			
		||||
							Path:    "/sbin/agetty",
 | 
			
		||||
							HasInit: false,
 | 
			
		||||
						},
 | 
			
		||||
						{
 | 
			
		||||
							PID:     "3648",
 | 
			
		||||
							Path:    "/sbin/agetty",
 | 
			
		||||
							HasInit: false,
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			),
 | 
			
		||||
			unknownServices: []string{"ssh"},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			in:              `Found 0 processes using old versions of upgraded files`,
 | 
			
		||||
			out:             models.Packages{},
 | 
			
		||||
			unknownServices: []string{},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		packages, services := r.parseCheckRestart(tt.in)
 | 
			
		||||
		for name, ePack := range tt.out {
 | 
			
		||||
			if !reflect.DeepEqual(ePack, packages[name]) {
 | 
			
		||||
				e := pp.Sprintf("%v", ePack)
 | 
			
		||||
				a := pp.Sprintf("%v", packages[name])
 | 
			
		||||
				t.Errorf("expected %s, actual %s", e, a)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		if !reflect.DeepEqual(tt.unknownServices, services) {
 | 
			
		||||
			t.Errorf("expected %s, actual %s", tt.unknownServices, services)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func Test_debian_parseGetPkgName(t *testing.T) {
 | 
			
		||||
	type args struct {
 | 
			
		||||
		stdout string
 | 
			
		||||
	}
 | 
			
		||||
	tests := []struct {
 | 
			
		||||
		name         string
 | 
			
		||||
		args         args
 | 
			
		||||
		wantPkgNames []string
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			name: "success",
 | 
			
		||||
			args: args{
 | 
			
		||||
				stdout: `udev: /lib/systemd/systemd-udevd
 | 
			
		||||
dpkg-query: no path found matching pattern /lib/modules/3.16.0-6-amd64/modules.alias.bin
 | 
			
		||||
udev: /lib/systemd/systemd-udevd
 | 
			
		||||
dpkg-query: no path found matching pattern /lib/udev/hwdb.bin
 | 
			
		||||
libuuid1:amd64: /lib/x86_64-linux-gnu/libuuid.so.1.3.0`,
 | 
			
		||||
			},
 | 
			
		||||
			wantPkgNames: []string{
 | 
			
		||||
				"libuuid1",
 | 
			
		||||
				"udev",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		t.Run(tt.name, func(t *testing.T) {
 | 
			
		||||
			o := &debian{}
 | 
			
		||||
			gotPkgNames := o.parseGetPkgName(tt.args.stdout)
 | 
			
		||||
			sort.Strings(gotPkgNames)
 | 
			
		||||
			if !reflect.DeepEqual(gotPkgNames, tt.wantPkgNames) {
 | 
			
		||||
				t.Errorf("debian.parseGetPkgName() = %v, want %v", gotPkgNames, tt.wantPkgNames)
 | 
			
		||||
			}
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestParseChangelog(t *testing.T) {
 | 
			
		||||
	type args struct {
 | 
			
		||||
		changelog string
 | 
			
		||||
		name      string
 | 
			
		||||
		ver       string
 | 
			
		||||
	}
 | 
			
		||||
	type expect struct {
 | 
			
		||||
		cveIDs []DetectedCveID
 | 
			
		||||
		pack   models.Package
 | 
			
		||||
	}
 | 
			
		||||
	tests := []struct {
 | 
			
		||||
		packName string
 | 
			
		||||
		args     args
 | 
			
		||||
		expect   expect
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			packName: "vlc",
 | 
			
		||||
			args: args{
 | 
			
		||||
				changelog: `vlc (3.0.11-0+deb10u1+rpt2) buster; urgency=medium
 | 
			
		||||
 | 
			
		||||
  * Add MMAL patch 19
 | 
			
		||||
 | 
			
		||||
 -- Serge Schneider <serge@raspberrypi.com>  Wed, 29 Jul 2020 14:28:28 +0100
 | 
			
		||||
 | 
			
		||||
vlc (3.0.11-0+deb10u1+rpt1) buster; urgency=high
 | 
			
		||||
 | 
			
		||||
  * Add MMAL patch 18
 | 
			
		||||
  * Add libxrandr-dev dependency
 | 
			
		||||
  * Add libdrm-dev dependency
 | 
			
		||||
  * Disable vdpau, libva, aom
 | 
			
		||||
  * Enable dav1d
 | 
			
		||||
 | 
			
		||||
 -- Serge Schneider <serge@raspberrypi.com>  Wed, 17 Jun 2020 10:30:58 +0100
 | 
			
		||||
 | 
			
		||||
vlc (3.0.11-0+deb10u1) buster-security; urgency=high
 | 
			
		||||
 | 
			
		||||
  * New upstream release
 | 
			
		||||
    - Fix heap-based buffer overflow in hxxx_nall (CVE-2020-13428)
 | 
			
		||||
 | 
			
		||||
 -- Sebastian Ramacher <sramacher@debian.org>  Mon, 15 Jun 2020 23:08:37 +0200
 | 
			
		||||
 | 
			
		||||
vlc (3.0.10-0+deb10u1) buster-security; urgency=medium`,
 | 
			
		||||
				name: "vlc",
 | 
			
		||||
				ver:  "3.0.10-0+deb10u1+rpt2",
 | 
			
		||||
			},
 | 
			
		||||
			expect: expect{
 | 
			
		||||
				cveIDs: []DetectedCveID{{"CVE-2020-13428", models.ChangelogExactMatch}},
 | 
			
		||||
				pack: models.Package{Changelog: &models.Changelog{
 | 
			
		||||
					Contents: `vlc (3.0.11-0+deb10u1+rpt2) buster; urgency=medium
 | 
			
		||||
 | 
			
		||||
  * Add MMAL patch 19
 | 
			
		||||
 | 
			
		||||
 -- Serge Schneider <serge@raspberrypi.com>  Wed, 29 Jul 2020 14:28:28 +0100
 | 
			
		||||
 | 
			
		||||
vlc (3.0.11-0+deb10u1+rpt1) buster; urgency=high
 | 
			
		||||
 | 
			
		||||
  * Add MMAL patch 18
 | 
			
		||||
  * Add libxrandr-dev dependency
 | 
			
		||||
  * Add libdrm-dev dependency
 | 
			
		||||
  * Disable vdpau, libva, aom
 | 
			
		||||
  * Enable dav1d
 | 
			
		||||
 | 
			
		||||
 -- Serge Schneider <serge@raspberrypi.com>  Wed, 17 Jun 2020 10:30:58 +0100
 | 
			
		||||
 | 
			
		||||
vlc (3.0.11-0+deb10u1) buster-security; urgency=high
 | 
			
		||||
 | 
			
		||||
  * New upstream release
 | 
			
		||||
    - Fix heap-based buffer overflow in hxxx_nall (CVE-2020-13428)
 | 
			
		||||
 | 
			
		||||
 -- Sebastian Ramacher <sramacher@debian.org>  Mon, 15 Jun 2020 23:08:37 +0200
 | 
			
		||||
`,
 | 
			
		||||
					Method: models.ChangelogExactMatchStr,
 | 
			
		||||
				}},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			packName: "realvnc-vnc-server",
 | 
			
		||||
			args: args{
 | 
			
		||||
				changelog: `realvnc-vnc (6.7.2.42622) stable; urgency=low
 | 
			
		||||
 | 
			
		||||
  * Debian package for VNC Server
 | 
			
		||||
 | 
			
		||||
 -- RealVNC <noreply@realvnc.com>  Wed, 13 May 2020 19:51:40 +0100
 | 
			
		||||
 | 
			
		||||
`,
 | 
			
		||||
				name: "realvnc-vnc-server",
 | 
			
		||||
				ver:  "6.7.1.42348",
 | 
			
		||||
			},
 | 
			
		||||
			expect: expect{
 | 
			
		||||
				cveIDs: []DetectedCveID{},
 | 
			
		||||
				pack: models.Package{Changelog: &models.Changelog{
 | 
			
		||||
					Contents: `realvnc-vnc (6.7.2.42622) stable; urgency=low
 | 
			
		||||
 | 
			
		||||
  * Debian package for VNC Server
 | 
			
		||||
 | 
			
		||||
 -- RealVNC <noreply@realvnc.com>  Wed, 13 May 2020 19:51:40 +0100
 | 
			
		||||
`,
 | 
			
		||||
					Method: models.ChangelogLenientMatchStr,
 | 
			
		||||
				}},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	o := newDebian(config.ServerInfo{})
 | 
			
		||||
	o.Distro = config.Distro{Family: constant.Raspbian}
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		t.Run(tt.packName, func(t *testing.T) {
 | 
			
		||||
			cveIDs, pack, _ := o.parseChangelog(tt.args.changelog, tt.args.name, tt.args.ver, models.ChangelogExactMatch)
 | 
			
		||||
			if !reflect.DeepEqual(cveIDs, tt.expect.cveIDs) {
 | 
			
		||||
				t.Errorf("[%s]->cveIDs: expected: %s, actual: %s", tt.packName, tt.expect.cveIDs, cveIDs)
 | 
			
		||||
			}
 | 
			
		||||
			if !reflect.DeepEqual(pack.Changelog.Contents, tt.expect.pack.Changelog.Contents) {
 | 
			
		||||
				t.Errorf("[%s]->changelog.Contents: expected: %s, actual: %s", tt.packName, tt.expect.pack.Changelog.Contents, pack.Changelog.Contents)
 | 
			
		||||
			}
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										315
									
								
								scanner/executil.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										315
									
								
								scanner/executil.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,315 @@
 | 
			
		||||
package scanner
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	ex "os/exec"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"syscall"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"golang.org/x/xerrors"
 | 
			
		||||
 | 
			
		||||
	conf "github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/util"
 | 
			
		||||
	homedir "github.com/mitchellh/go-homedir"
 | 
			
		||||
	"github.com/sirupsen/logrus"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type execResult struct {
 | 
			
		||||
	Servername string
 | 
			
		||||
	Container  conf.Container
 | 
			
		||||
	Host       string
 | 
			
		||||
	Port       string
 | 
			
		||||
	Cmd        string
 | 
			
		||||
	Stdout     string
 | 
			
		||||
	Stderr     string
 | 
			
		||||
	ExitStatus int
 | 
			
		||||
	Error      error
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s execResult) String() string {
 | 
			
		||||
	sname := ""
 | 
			
		||||
	if s.Container.ContainerID == "" {
 | 
			
		||||
		sname = s.Servername
 | 
			
		||||
	} else {
 | 
			
		||||
		sname = s.Container.Name + "@" + s.Servername
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return fmt.Sprintf(
 | 
			
		||||
		"execResult: servername: %s\n  cmd: %s\n  exitstatus: %d\n  stdout: %s\n  stderr: %s\n  err: %s",
 | 
			
		||||
		sname, s.Cmd, s.ExitStatus, s.Stdout, s.Stderr, s.Error)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s execResult) isSuccess(expectedStatusCodes ...int) bool {
 | 
			
		||||
	if len(expectedStatusCodes) == 0 {
 | 
			
		||||
		return s.ExitStatus == 0
 | 
			
		||||
	}
 | 
			
		||||
	for _, code := range expectedStatusCodes {
 | 
			
		||||
		if code == s.ExitStatus {
 | 
			
		||||
			return true
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if s.Error != nil {
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// sudo is Const value for sudo mode
 | 
			
		||||
const sudo = true
 | 
			
		||||
 | 
			
		||||
// noSudo is Const value for normal user mode
 | 
			
		||||
const noSudo = false
 | 
			
		||||
 | 
			
		||||
//  Issue commands to the target servers in parallel via SSH or local execution.  If execution fails, the server will be excluded from the target server list(servers) and added to the error server list(errServers).
 | 
			
		||||
func parallelExec(fn func(osTypeInterface) error, timeoutSec ...int) {
 | 
			
		||||
	resChan := make(chan osTypeInterface, len(servers))
 | 
			
		||||
	defer close(resChan)
 | 
			
		||||
 | 
			
		||||
	for _, s := range servers {
 | 
			
		||||
		go func(s osTypeInterface) {
 | 
			
		||||
			defer func() {
 | 
			
		||||
				if p := recover(); p != nil {
 | 
			
		||||
					util.Log.Debugf("Panic: %s on %s",
 | 
			
		||||
						p, s.getServerInfo().GetServerName())
 | 
			
		||||
				}
 | 
			
		||||
			}()
 | 
			
		||||
			if err := fn(s); err != nil {
 | 
			
		||||
				s.setErrs([]error{err})
 | 
			
		||||
				resChan <- s
 | 
			
		||||
			} else {
 | 
			
		||||
				resChan <- s
 | 
			
		||||
			}
 | 
			
		||||
		}(s)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var timeout int
 | 
			
		||||
	if len(timeoutSec) == 0 {
 | 
			
		||||
		timeout = 10 * 60
 | 
			
		||||
	} else {
 | 
			
		||||
		timeout = timeoutSec[0]
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var successes []osTypeInterface
 | 
			
		||||
	isTimedout := false
 | 
			
		||||
	for i := 0; i < len(servers); i++ {
 | 
			
		||||
		select {
 | 
			
		||||
		case s := <-resChan:
 | 
			
		||||
			if len(s.getErrs()) == 0 {
 | 
			
		||||
				successes = append(successes, s)
 | 
			
		||||
			} else {
 | 
			
		||||
				util.Log.Errorf("Error on %s, err: %+v",
 | 
			
		||||
					s.getServerInfo().GetServerName(), s.getErrs())
 | 
			
		||||
				errServers = append(errServers, s)
 | 
			
		||||
			}
 | 
			
		||||
		case <-time.After(time.Duration(timeout) * time.Second):
 | 
			
		||||
			isTimedout = true
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if isTimedout {
 | 
			
		||||
		// set timed out error and append to errServers
 | 
			
		||||
		for _, s := range servers {
 | 
			
		||||
			name := s.getServerInfo().GetServerName()
 | 
			
		||||
			found := false
 | 
			
		||||
			for _, ss := range successes {
 | 
			
		||||
				if name == ss.getServerInfo().GetServerName() {
 | 
			
		||||
					found = true
 | 
			
		||||
					break
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			if !found {
 | 
			
		||||
				err := xerrors.Errorf("Timed out: %s",
 | 
			
		||||
					s.getServerInfo().GetServerName())
 | 
			
		||||
				util.Log.Errorf("%+v", err)
 | 
			
		||||
				s.setErrs([]error{err})
 | 
			
		||||
				errServers = append(errServers, s)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	servers = successes
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func exec(c conf.ServerInfo, cmd string, sudo bool, log ...*logrus.Entry) (result execResult) {
 | 
			
		||||
	logger := getSSHLogger(log...)
 | 
			
		||||
	logger.Debugf("Executing... %s", strings.Replace(cmd, "\n", "", -1))
 | 
			
		||||
 | 
			
		||||
	if isLocalExec(c.Port, c.Host) {
 | 
			
		||||
		result = localExec(c, cmd, sudo)
 | 
			
		||||
	} else {
 | 
			
		||||
		result = sshExecExternal(c, cmd, sudo)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	logger.Debug(result)
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func isLocalExec(port, host string) bool {
 | 
			
		||||
	return port == "local" && (host == "127.0.0.1" || host == "localhost")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func localExec(c conf.ServerInfo, cmdstr string, sudo bool) (result execResult) {
 | 
			
		||||
	cmdstr = decorateCmd(c, cmdstr, sudo)
 | 
			
		||||
	var cmd *ex.Cmd
 | 
			
		||||
	switch c.Distro.Family {
 | 
			
		||||
	// case conf.FreeBSD, conf.Alpine, conf.Debian:
 | 
			
		||||
	// cmd = ex.Command("/bin/sh", "-c", cmdstr)
 | 
			
		||||
	default:
 | 
			
		||||
		cmd = ex.Command("/bin/sh", "-c", cmdstr)
 | 
			
		||||
	}
 | 
			
		||||
	var stdoutBuf, stderrBuf bytes.Buffer
 | 
			
		||||
	cmd.Stdout = &stdoutBuf
 | 
			
		||||
	cmd.Stderr = &stderrBuf
 | 
			
		||||
 | 
			
		||||
	if err := cmd.Run(); err != nil {
 | 
			
		||||
		result.Error = err
 | 
			
		||||
		if exitError, ok := err.(*ex.ExitError); ok {
 | 
			
		||||
			waitStatus := exitError.Sys().(syscall.WaitStatus)
 | 
			
		||||
			result.ExitStatus = waitStatus.ExitStatus()
 | 
			
		||||
		} else {
 | 
			
		||||
			result.ExitStatus = 999
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		result.ExitStatus = 0
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	result.Stdout = stdoutBuf.String()
 | 
			
		||||
	result.Stderr = stderrBuf.String()
 | 
			
		||||
	result.Cmd = strings.Replace(cmdstr, "\n", "", -1)
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func sshExecExternal(c conf.ServerInfo, cmd string, sudo bool) (result execResult) {
 | 
			
		||||
	sshBinaryPath, err := ex.LookPath("ssh")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return execResult{Error: err}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	defaultSSHArgs := []string{"-tt"}
 | 
			
		||||
 | 
			
		||||
	if 0 < len(c.SSHConfigPath) {
 | 
			
		||||
		defaultSSHArgs = append(defaultSSHArgs, "-F", c.SSHConfigPath)
 | 
			
		||||
	} else {
 | 
			
		||||
		home, err := homedir.Dir()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			msg := fmt.Sprintf("Failed to get HOME directory: %s", err)
 | 
			
		||||
			result.Stderr = msg
 | 
			
		||||
			result.ExitStatus = 997
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		controlPath := filepath.Join(home, ".vuls", `controlmaster-%r-`+c.ServerName+`.%p`)
 | 
			
		||||
 | 
			
		||||
		defaultSSHArgs = append(defaultSSHArgs,
 | 
			
		||||
			"-o", "StrictHostKeyChecking=yes",
 | 
			
		||||
			"-o", "LogLevel=quiet",
 | 
			
		||||
			"-o", "ConnectionAttempts=3",
 | 
			
		||||
			"-o", "ConnectTimeout=10",
 | 
			
		||||
			"-o", "ControlMaster=auto",
 | 
			
		||||
			"-o", fmt.Sprintf("ControlPath=%s", controlPath),
 | 
			
		||||
			"-o", "Controlpersist=10m",
 | 
			
		||||
		)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if conf.Conf.Vvv {
 | 
			
		||||
		defaultSSHArgs = append(defaultSSHArgs, "-vvv")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(c.JumpServer) != 0 {
 | 
			
		||||
		defaultSSHArgs = append(defaultSSHArgs, "-J", strings.Join(c.JumpServer, ","))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	args := append(defaultSSHArgs, fmt.Sprintf("%s@%s", c.User, c.Host))
 | 
			
		||||
	args = append(args, "-p", c.Port)
 | 
			
		||||
	if 0 < len(c.KeyPath) {
 | 
			
		||||
		args = append(args, "-i", c.KeyPath)
 | 
			
		||||
		args = append(args, "-o", "PasswordAuthentication=no")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	cmd = decorateCmd(c, cmd, sudo)
 | 
			
		||||
	cmd = fmt.Sprintf("stty cols 1000; %s", cmd)
 | 
			
		||||
 | 
			
		||||
	args = append(args, cmd)
 | 
			
		||||
	execCmd := ex.Command(sshBinaryPath, args...)
 | 
			
		||||
 | 
			
		||||
	var stdoutBuf, stderrBuf bytes.Buffer
 | 
			
		||||
	execCmd.Stdout = &stdoutBuf
 | 
			
		||||
	execCmd.Stderr = &stderrBuf
 | 
			
		||||
	if err := execCmd.Run(); err != nil {
 | 
			
		||||
		if e, ok := err.(*ex.ExitError); ok {
 | 
			
		||||
			if s, ok := e.Sys().(syscall.WaitStatus); ok {
 | 
			
		||||
				result.ExitStatus = s.ExitStatus()
 | 
			
		||||
			} else {
 | 
			
		||||
				result.ExitStatus = 998
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			result.ExitStatus = 999
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		result.ExitStatus = 0
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	result.Stdout = stdoutBuf.String()
 | 
			
		||||
	result.Stderr = stderrBuf.String()
 | 
			
		||||
	result.Servername = c.ServerName
 | 
			
		||||
	result.Container = c.Container
 | 
			
		||||
	result.Host = c.Host
 | 
			
		||||
	result.Port = c.Port
 | 
			
		||||
	result.Cmd = fmt.Sprintf("%s %s", sshBinaryPath, strings.Join(args, " "))
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func getSSHLogger(log ...*logrus.Entry) *logrus.Entry {
 | 
			
		||||
	if len(log) == 0 {
 | 
			
		||||
		return util.Log
 | 
			
		||||
	}
 | 
			
		||||
	return log[0]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func dockerShell(family string) string {
 | 
			
		||||
	switch family {
 | 
			
		||||
	// case conf.Alpine, conf.Debian:
 | 
			
		||||
	// return "/bin/sh"
 | 
			
		||||
	default:
 | 
			
		||||
		// return "/bin/bash"
 | 
			
		||||
		return "/bin/sh"
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func decorateCmd(c conf.ServerInfo, cmd string, sudo bool) string {
 | 
			
		||||
	if sudo && c.User != "root" && !c.IsContainer() {
 | 
			
		||||
		cmd = fmt.Sprintf("sudo -S %s", cmd)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// If you are using pipe and you want to detect preprocessing errors, remove comment out
 | 
			
		||||
	//  switch c.Distro.Family {
 | 
			
		||||
	//  case "FreeBSD", "ubuntu", "debian", "raspbian":
 | 
			
		||||
	//  default:
 | 
			
		||||
	//      // set pipefail option. Bash only
 | 
			
		||||
	//      // http://unix.stackexchange.com/questions/14270/get-exit-status-of-process-thats-piped-to-another
 | 
			
		||||
	//      cmd = fmt.Sprintf("set -o pipefail; %s", cmd)
 | 
			
		||||
	//  }
 | 
			
		||||
 | 
			
		||||
	if c.IsContainer() {
 | 
			
		||||
		switch c.ContainerType {
 | 
			
		||||
		case "", "docker":
 | 
			
		||||
			cmd = fmt.Sprintf(`docker exec --user 0 %s %s -c '%s'`,
 | 
			
		||||
				c.Container.ContainerID, dockerShell(c.Distro.Family), cmd)
 | 
			
		||||
		case "lxd":
 | 
			
		||||
			// If the user belong to the "lxd" group, root privilege is not required.
 | 
			
		||||
			cmd = fmt.Sprintf(`lxc exec %s -- %s -c '%s'`,
 | 
			
		||||
				c.Container.Name, dockerShell(c.Distro.Family), cmd)
 | 
			
		||||
		case "lxc":
 | 
			
		||||
			cmd = fmt.Sprintf(`lxc-attach -n %s 2>/dev/null -- %s -c '%s'`,
 | 
			
		||||
				c.Container.Name, dockerShell(c.Distro.Family), cmd)
 | 
			
		||||
			// LXC required root privilege
 | 
			
		||||
			if c.User != "root" {
 | 
			
		||||
				cmd = fmt.Sprintf("sudo -S %s", cmd)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	//  cmd = fmt.Sprintf("set -x; %s", cmd)
 | 
			
		||||
	return cmd
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										227
									
								
								scanner/executil_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										227
									
								
								scanner/executil_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,227 @@
 | 
			
		||||
package scanner
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestDecorateCmd(t *testing.T) {
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		conf     config.ServerInfo
 | 
			
		||||
		cmd      string
 | 
			
		||||
		sudo     bool
 | 
			
		||||
		expected string
 | 
			
		||||
	}{
 | 
			
		||||
		// root sudo false
 | 
			
		||||
		{
 | 
			
		||||
			conf:     config.ServerInfo{User: "root"},
 | 
			
		||||
			cmd:      "ls",
 | 
			
		||||
			sudo:     false,
 | 
			
		||||
			expected: "ls",
 | 
			
		||||
		},
 | 
			
		||||
		// root sudo true
 | 
			
		||||
		{
 | 
			
		||||
			conf:     config.ServerInfo{User: "root"},
 | 
			
		||||
			cmd:      "ls",
 | 
			
		||||
			sudo:     false,
 | 
			
		||||
			expected: "ls",
 | 
			
		||||
		},
 | 
			
		||||
		// non-root sudo false
 | 
			
		||||
		{
 | 
			
		||||
			conf:     config.ServerInfo{User: "non-root"},
 | 
			
		||||
			cmd:      "ls",
 | 
			
		||||
			sudo:     false,
 | 
			
		||||
			expected: "ls",
 | 
			
		||||
		},
 | 
			
		||||
		// non-root sudo true
 | 
			
		||||
		{
 | 
			
		||||
			conf:     config.ServerInfo{User: "non-root"},
 | 
			
		||||
			cmd:      "ls",
 | 
			
		||||
			sudo:     true,
 | 
			
		||||
			expected: "sudo -S ls",
 | 
			
		||||
		},
 | 
			
		||||
		// non-root sudo true
 | 
			
		||||
		{
 | 
			
		||||
			conf:     config.ServerInfo{User: "non-root"},
 | 
			
		||||
			cmd:      "ls | grep hoge",
 | 
			
		||||
			sudo:     true,
 | 
			
		||||
			expected: "sudo -S ls | grep hoge",
 | 
			
		||||
		},
 | 
			
		||||
		// -------------docker-------------
 | 
			
		||||
		// root sudo false docker
 | 
			
		||||
		{
 | 
			
		||||
			conf: config.ServerInfo{
 | 
			
		||||
				User:          "root",
 | 
			
		||||
				Container:     config.Container{ContainerID: "abc", Name: "def"},
 | 
			
		||||
				ContainerType: "docker",
 | 
			
		||||
			},
 | 
			
		||||
			cmd:      "ls",
 | 
			
		||||
			sudo:     false,
 | 
			
		||||
			expected: `docker exec --user 0 abc /bin/sh -c 'ls'`,
 | 
			
		||||
		},
 | 
			
		||||
		// root sudo true docker
 | 
			
		||||
		{
 | 
			
		||||
			conf: config.ServerInfo{
 | 
			
		||||
				User:          "root",
 | 
			
		||||
				Container:     config.Container{ContainerID: "abc", Name: "def"},
 | 
			
		||||
				ContainerType: "docker",
 | 
			
		||||
			},
 | 
			
		||||
			cmd:      "ls",
 | 
			
		||||
			sudo:     true,
 | 
			
		||||
			expected: `docker exec --user 0 abc /bin/sh -c 'ls'`,
 | 
			
		||||
		},
 | 
			
		||||
		// non-root sudo false, docker
 | 
			
		||||
		{
 | 
			
		||||
			conf: config.ServerInfo{
 | 
			
		||||
				User:          "non-root",
 | 
			
		||||
				Container:     config.Container{ContainerID: "abc", Name: "def"},
 | 
			
		||||
				ContainerType: "docker",
 | 
			
		||||
			},
 | 
			
		||||
			cmd:      "ls",
 | 
			
		||||
			sudo:     false,
 | 
			
		||||
			expected: `docker exec --user 0 abc /bin/sh -c 'ls'`,
 | 
			
		||||
		},
 | 
			
		||||
		// non-root sudo true, docker
 | 
			
		||||
		{
 | 
			
		||||
			conf: config.ServerInfo{
 | 
			
		||||
				User:          "non-root",
 | 
			
		||||
				Container:     config.Container{ContainerID: "abc", Name: "def"},
 | 
			
		||||
				ContainerType: "docker",
 | 
			
		||||
			},
 | 
			
		||||
			cmd:      "ls",
 | 
			
		||||
			sudo:     true,
 | 
			
		||||
			expected: `docker exec --user 0 abc /bin/sh -c 'ls'`,
 | 
			
		||||
		},
 | 
			
		||||
		// non-root sudo true, docker
 | 
			
		||||
		{
 | 
			
		||||
			conf: config.ServerInfo{
 | 
			
		||||
				User:          "non-root",
 | 
			
		||||
				Container:     config.Container{ContainerID: "abc", Name: "def"},
 | 
			
		||||
				ContainerType: "docker",
 | 
			
		||||
			},
 | 
			
		||||
			cmd:      "ls | grep hoge",
 | 
			
		||||
			sudo:     true,
 | 
			
		||||
			expected: `docker exec --user 0 abc /bin/sh -c 'ls | grep hoge'`,
 | 
			
		||||
		},
 | 
			
		||||
		// -------------lxd-------------
 | 
			
		||||
		// root sudo false lxd
 | 
			
		||||
		{
 | 
			
		||||
			conf: config.ServerInfo{
 | 
			
		||||
				User:          "root",
 | 
			
		||||
				Container:     config.Container{ContainerID: "abc", Name: "def"},
 | 
			
		||||
				ContainerType: "lxd",
 | 
			
		||||
			},
 | 
			
		||||
			cmd:      "ls",
 | 
			
		||||
			sudo:     false,
 | 
			
		||||
			expected: `lxc exec def -- /bin/sh -c 'ls'`,
 | 
			
		||||
		},
 | 
			
		||||
		// root sudo true lxd
 | 
			
		||||
		{
 | 
			
		||||
			conf: config.ServerInfo{
 | 
			
		||||
				User:          "root",
 | 
			
		||||
				Container:     config.Container{ContainerID: "abc", Name: "def"},
 | 
			
		||||
				ContainerType: "lxd",
 | 
			
		||||
			},
 | 
			
		||||
			cmd:      "ls",
 | 
			
		||||
			sudo:     true,
 | 
			
		||||
			expected: `lxc exec def -- /bin/sh -c 'ls'`,
 | 
			
		||||
		},
 | 
			
		||||
		// non-root sudo false, lxd
 | 
			
		||||
		{
 | 
			
		||||
			conf: config.ServerInfo{
 | 
			
		||||
				User:          "non-root",
 | 
			
		||||
				Container:     config.Container{ContainerID: "abc", Name: "def"},
 | 
			
		||||
				ContainerType: "lxd",
 | 
			
		||||
			},
 | 
			
		||||
			cmd:      "ls",
 | 
			
		||||
			sudo:     false,
 | 
			
		||||
			expected: `lxc exec def -- /bin/sh -c 'ls'`,
 | 
			
		||||
		},
 | 
			
		||||
		// non-root sudo true, lxd
 | 
			
		||||
		{
 | 
			
		||||
			conf: config.ServerInfo{
 | 
			
		||||
				User:          "non-root",
 | 
			
		||||
				Container:     config.Container{ContainerID: "abc", Name: "def"},
 | 
			
		||||
				ContainerType: "lxd",
 | 
			
		||||
			},
 | 
			
		||||
			cmd:      "ls",
 | 
			
		||||
			sudo:     true,
 | 
			
		||||
			expected: `lxc exec def -- /bin/sh -c 'ls'`,
 | 
			
		||||
		},
 | 
			
		||||
		// non-root sudo true lxd
 | 
			
		||||
		{
 | 
			
		||||
			conf: config.ServerInfo{
 | 
			
		||||
				User:          "non-root",
 | 
			
		||||
				Container:     config.Container{ContainerID: "abc", Name: "def"},
 | 
			
		||||
				ContainerType: "lxd",
 | 
			
		||||
			},
 | 
			
		||||
			cmd:      "ls | grep hoge",
 | 
			
		||||
			sudo:     true,
 | 
			
		||||
			expected: `lxc exec def -- /bin/sh -c 'ls | grep hoge'`,
 | 
			
		||||
		},
 | 
			
		||||
		// -------------lxc-------------
 | 
			
		||||
		// root sudo false lxc
 | 
			
		||||
		{
 | 
			
		||||
			conf: config.ServerInfo{
 | 
			
		||||
				User:          "root",
 | 
			
		||||
				Container:     config.Container{ContainerID: "abc", Name: "def"},
 | 
			
		||||
				ContainerType: "lxc",
 | 
			
		||||
			},
 | 
			
		||||
			cmd:      "ls",
 | 
			
		||||
			sudo:     false,
 | 
			
		||||
			expected: `lxc-attach -n def 2>/dev/null -- /bin/sh -c 'ls'`,
 | 
			
		||||
		},
 | 
			
		||||
		// root sudo true lxc
 | 
			
		||||
		{
 | 
			
		||||
			conf: config.ServerInfo{
 | 
			
		||||
				User:          "root",
 | 
			
		||||
				Container:     config.Container{ContainerID: "abc", Name: "def"},
 | 
			
		||||
				ContainerType: "lxc",
 | 
			
		||||
			},
 | 
			
		||||
			cmd:      "ls",
 | 
			
		||||
			sudo:     true,
 | 
			
		||||
			expected: `lxc-attach -n def 2>/dev/null -- /bin/sh -c 'ls'`,
 | 
			
		||||
		},
 | 
			
		||||
		// non-root sudo false, lxc
 | 
			
		||||
		{
 | 
			
		||||
			conf: config.ServerInfo{
 | 
			
		||||
				User:          "non-root",
 | 
			
		||||
				Container:     config.Container{ContainerID: "abc", Name: "def"},
 | 
			
		||||
				ContainerType: "lxc",
 | 
			
		||||
			},
 | 
			
		||||
			cmd:      "ls",
 | 
			
		||||
			sudo:     false,
 | 
			
		||||
			expected: `sudo -S lxc-attach -n def 2>/dev/null -- /bin/sh -c 'ls'`,
 | 
			
		||||
		},
 | 
			
		||||
		// non-root sudo true, lxc
 | 
			
		||||
		{
 | 
			
		||||
			conf: config.ServerInfo{
 | 
			
		||||
				User:          "non-root",
 | 
			
		||||
				Container:     config.Container{ContainerID: "abc", Name: "def"},
 | 
			
		||||
				ContainerType: "lxc",
 | 
			
		||||
			},
 | 
			
		||||
			cmd:      "ls",
 | 
			
		||||
			sudo:     true,
 | 
			
		||||
			expected: `sudo -S lxc-attach -n def 2>/dev/null -- /bin/sh -c 'ls'`,
 | 
			
		||||
		},
 | 
			
		||||
		// non-root sudo true lxc
 | 
			
		||||
		{
 | 
			
		||||
			conf: config.ServerInfo{
 | 
			
		||||
				User:          "non-root",
 | 
			
		||||
				Container:     config.Container{ContainerID: "abc", Name: "def"},
 | 
			
		||||
				ContainerType: "lxc",
 | 
			
		||||
			},
 | 
			
		||||
			cmd:      "ls | grep hoge",
 | 
			
		||||
			sudo:     true,
 | 
			
		||||
			expected: `sudo -S lxc-attach -n def 2>/dev/null -- /bin/sh -c 'ls | grep hoge'`,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		actual := decorateCmd(tt.conf, tt.cmd, tt.sudo)
 | 
			
		||||
		if actual != tt.expected {
 | 
			
		||||
			t.Errorf("expected: %s, actual: %s", tt.expected, actual)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										366
									
								
								scanner/freebsd.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										366
									
								
								scanner/freebsd.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,366 @@
 | 
			
		||||
package scanner
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"net"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/constant"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	"github.com/future-architect/vuls/util"
 | 
			
		||||
	"golang.org/x/xerrors"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// inherit OsTypeInterface
 | 
			
		||||
type bsd struct {
 | 
			
		||||
	base
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewBSD constructor
 | 
			
		||||
func newBsd(c config.ServerInfo) *bsd {
 | 
			
		||||
	d := &bsd{
 | 
			
		||||
		base: base{
 | 
			
		||||
			osPackages: osPackages{
 | 
			
		||||
				Packages:  models.Packages{},
 | 
			
		||||
				VulnInfos: models.VulnInfos{},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	d.log = util.NewCustomLogger(c)
 | 
			
		||||
	d.setServerInfo(c)
 | 
			
		||||
	return d
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//https://github.com/mizzy/specinfra/blob/master/lib/specinfra/helper/detect_os/freebsd.rb
 | 
			
		||||
func detectFreebsd(c config.ServerInfo) (bool, osTypeInterface) {
 | 
			
		||||
	// Prevent from adding `set -o pipefail` option
 | 
			
		||||
	c.Distro = config.Distro{Family: constant.FreeBSD}
 | 
			
		||||
 | 
			
		||||
	if r := exec(c, "uname", noSudo); r.isSuccess() {
 | 
			
		||||
		if strings.Contains(strings.ToLower(r.Stdout), constant.FreeBSD) == true {
 | 
			
		||||
			if b := exec(c, "freebsd-version", noSudo); b.isSuccess() {
 | 
			
		||||
				bsd := newBsd(c)
 | 
			
		||||
				rel := strings.TrimSpace(b.Stdout)
 | 
			
		||||
				bsd.setDistro(constant.FreeBSD, rel)
 | 
			
		||||
				return true, bsd
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	util.Log.Debugf("Not FreeBSD. servernam: %s", c.ServerName)
 | 
			
		||||
	return false, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *bsd) checkScanMode() error {
 | 
			
		||||
	if o.getServerInfo().Mode.IsOffline() {
 | 
			
		||||
		return xerrors.New("Remove offline scan mode, FreeBSD needs internet connection")
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *bsd) checkIfSudoNoPasswd() error {
 | 
			
		||||
	// FreeBSD doesn't need root privilege
 | 
			
		||||
	o.log.Infof("sudo ... No need")
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *bsd) checkDeps() error {
 | 
			
		||||
	o.log.Infof("Dependencies... No need")
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *bsd) preCure() error {
 | 
			
		||||
	if err := o.detectIPAddr(); err != nil {
 | 
			
		||||
		o.log.Warnf("Failed to detect IP addresses: %s", err)
 | 
			
		||||
		o.warns = append(o.warns, err)
 | 
			
		||||
	}
 | 
			
		||||
	// Ignore this error as it just failed to detect the IP addresses
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *bsd) postScan() error {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *bsd) detectIPAddr() (err error) {
 | 
			
		||||
	r := o.exec("/sbin/ifconfig", noSudo)
 | 
			
		||||
	if !r.isSuccess() {
 | 
			
		||||
		return xerrors.Errorf("Failed to detect IP address: %v", r)
 | 
			
		||||
	}
 | 
			
		||||
	o.ServerInfo.IPv4Addrs, o.ServerInfo.IPv6Addrs = o.parseIfconfig(r.Stdout)
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (l *base) parseIfconfig(stdout string) (ipv4Addrs []string, ipv6Addrs []string) {
 | 
			
		||||
	lines := strings.Split(stdout, "\n")
 | 
			
		||||
	for _, line := range lines {
 | 
			
		||||
		line = strings.TrimSpace(line)
 | 
			
		||||
		fields := strings.Fields(line)
 | 
			
		||||
		if len(fields) < 4 || !strings.HasPrefix(fields[0], "inet") {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		ip := net.ParseIP(fields[1])
 | 
			
		||||
		if ip == nil {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		if !ip.IsGlobalUnicast() {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		if ipv4 := ip.To4(); ipv4 != nil {
 | 
			
		||||
			ipv4Addrs = append(ipv4Addrs, ipv4.String())
 | 
			
		||||
		} else {
 | 
			
		||||
			ipv6Addrs = append(ipv6Addrs, ip.String())
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *bsd) scanPackages() error {
 | 
			
		||||
	o.log.Infof("Scanning OS pkg in %s", o.getServerInfo().Mode)
 | 
			
		||||
	// collect the running kernel information
 | 
			
		||||
	release, version, err := o.runningKernel()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		o.log.Errorf("Failed to scan the running kernel version: %s", err)
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	o.Kernel = models.Kernel{
 | 
			
		||||
		Release: release,
 | 
			
		||||
		Version: version,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	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
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	packs, err := o.scanInstalledPackages()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		o.log.Errorf("Failed to scan installed packages: %s", err)
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	o.Packages = packs
 | 
			
		||||
 | 
			
		||||
	unsecures, err := o.scanUnsecurePackages()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		o.log.Errorf("Failed to scan vulnerable packages: %s", err)
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	o.VulnInfos = unsecures
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *bsd) parseInstalledPackages(string) (models.Packages, models.SrcPackages, error) {
 | 
			
		||||
	return nil, nil, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *bsd) rebootRequired() (bool, error) {
 | 
			
		||||
	r := o.exec("freebsd-version -k", noSudo)
 | 
			
		||||
	if !r.isSuccess() {
 | 
			
		||||
		return false, xerrors.Errorf("Failed to SSH: %s", r)
 | 
			
		||||
	}
 | 
			
		||||
	return o.Kernel.Release != strings.TrimSpace(r.Stdout), nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *bsd) scanInstalledPackages() (models.Packages, error) {
 | 
			
		||||
	// https://github.com/future-architect/vuls/issues/1042
 | 
			
		||||
	cmd := util.PrependProxyEnv("pkg info")
 | 
			
		||||
	r := o.exec(cmd, noSudo)
 | 
			
		||||
	if !r.isSuccess() {
 | 
			
		||||
		return nil, xerrors.Errorf("Failed to SSH: %s", r)
 | 
			
		||||
	}
 | 
			
		||||
	pkgs := o.parsePkgInfo(r.Stdout)
 | 
			
		||||
 | 
			
		||||
	cmd = util.PrependProxyEnv("pkg version -v")
 | 
			
		||||
	r = o.exec(cmd, noSudo)
 | 
			
		||||
	if !r.isSuccess() {
 | 
			
		||||
		return nil, xerrors.Errorf("Failed to SSH: %s", r)
 | 
			
		||||
	}
 | 
			
		||||
	// `pkg-audit` has a new version, overwrite it.
 | 
			
		||||
	for name, p := range o.parsePkgVersion(r.Stdout) {
 | 
			
		||||
		pkgs[name] = p
 | 
			
		||||
	}
 | 
			
		||||
	return pkgs, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *bsd) scanUnsecurePackages() (models.VulnInfos, error) {
 | 
			
		||||
	const vulndbPath = "/tmp/vuln.db"
 | 
			
		||||
	cmd := "rm -f " + vulndbPath
 | 
			
		||||
	r := o.exec(cmd, noSudo)
 | 
			
		||||
	if !r.isSuccess(0) {
 | 
			
		||||
		return nil, xerrors.Errorf("Failed to SSH: %s", r)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	cmd = util.PrependProxyEnv("pkg audit -F -r -f " + vulndbPath)
 | 
			
		||||
	r = o.exec(cmd, noSudo)
 | 
			
		||||
	if !r.isSuccess(0, 1) {
 | 
			
		||||
		return nil, xerrors.Errorf("Failed to SSH: %s", r)
 | 
			
		||||
	}
 | 
			
		||||
	if r.ExitStatus == 0 {
 | 
			
		||||
		// no vulnerabilities
 | 
			
		||||
		return nil, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	packAdtRslt := []pkgAuditResult{}
 | 
			
		||||
	blocks := o.splitIntoBlocks(r.Stdout)
 | 
			
		||||
	for _, b := range blocks {
 | 
			
		||||
		name, cveIDs, vulnID := o.parseBlock(b)
 | 
			
		||||
		if len(cveIDs) == 0 {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		pack, found := o.Packages[name]
 | 
			
		||||
		if !found {
 | 
			
		||||
			return nil, xerrors.Errorf("Vulnerable package: %s is not found", name)
 | 
			
		||||
		}
 | 
			
		||||
		packAdtRslt = append(packAdtRslt, pkgAuditResult{
 | 
			
		||||
			pack: pack,
 | 
			
		||||
			vulnIDCveIDs: vulnIDCveIDs{
 | 
			
		||||
				vulnID: vulnID,
 | 
			
		||||
				cveIDs: cveIDs,
 | 
			
		||||
			},
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// { CVE ID: []pkgAuditResult }
 | 
			
		||||
	cveIDAdtMap := make(map[string][]pkgAuditResult)
 | 
			
		||||
	for _, p := range packAdtRslt {
 | 
			
		||||
		for _, cid := range p.vulnIDCveIDs.cveIDs {
 | 
			
		||||
			cveIDAdtMap[cid] = append(cveIDAdtMap[cid], p)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	vinfos := models.VulnInfos{}
 | 
			
		||||
	for cveID := range cveIDAdtMap {
 | 
			
		||||
		packs := models.Packages{}
 | 
			
		||||
		for _, r := range cveIDAdtMap[cveID] {
 | 
			
		||||
			packs[r.pack.Name] = r.pack
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		disAdvs := []models.DistroAdvisory{}
 | 
			
		||||
		for _, r := range cveIDAdtMap[cveID] {
 | 
			
		||||
			disAdvs = append(disAdvs, models.DistroAdvisory{
 | 
			
		||||
				AdvisoryID: r.vulnIDCveIDs.vulnID,
 | 
			
		||||
			})
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		affected := models.PackageFixStatuses{}
 | 
			
		||||
		for name := range packs {
 | 
			
		||||
			affected = append(affected, models.PackageFixStatus{
 | 
			
		||||
				Name: name,
 | 
			
		||||
			})
 | 
			
		||||
		}
 | 
			
		||||
		vinfos[cveID] = models.VulnInfo{
 | 
			
		||||
			CveID:            cveID,
 | 
			
		||||
			AffectedPackages: affected,
 | 
			
		||||
			DistroAdvisories: disAdvs,
 | 
			
		||||
			Confidences:      models.Confidences{models.PkgAuditMatch},
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return vinfos, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *bsd) parsePkgInfo(stdout string) models.Packages {
 | 
			
		||||
	packs := models.Packages{}
 | 
			
		||||
	lines := strings.Split(stdout, "\n")
 | 
			
		||||
	for _, l := range lines {
 | 
			
		||||
		fields := strings.Fields(l)
 | 
			
		||||
		if len(fields) < 2 {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		packVer := fields[0]
 | 
			
		||||
		splitted := strings.Split(packVer, "-")
 | 
			
		||||
		ver := splitted[len(splitted)-1]
 | 
			
		||||
		name := strings.Join(splitted[:len(splitted)-1], "-")
 | 
			
		||||
		packs[name] = models.Package{
 | 
			
		||||
			Name:    name,
 | 
			
		||||
			Version: ver,
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return packs
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *bsd) parsePkgVersion(stdout string) models.Packages {
 | 
			
		||||
	packs := models.Packages{}
 | 
			
		||||
	lines := strings.Split(stdout, "\n")
 | 
			
		||||
	for _, l := range lines {
 | 
			
		||||
		fields := strings.Fields(l)
 | 
			
		||||
		if len(fields) < 2 {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		packVer := fields[0]
 | 
			
		||||
		splitted := strings.Split(packVer, "-")
 | 
			
		||||
		ver := splitted[len(splitted)-1]
 | 
			
		||||
		name := strings.Join(splitted[:len(splitted)-1], "-")
 | 
			
		||||
 | 
			
		||||
		switch fields[1] {
 | 
			
		||||
		case "?", "=":
 | 
			
		||||
			packs[name] = models.Package{
 | 
			
		||||
				Name:    name,
 | 
			
		||||
				Version: ver,
 | 
			
		||||
			}
 | 
			
		||||
		case "<":
 | 
			
		||||
			candidate := strings.TrimSuffix(fields[6], ")")
 | 
			
		||||
			packs[name] = models.Package{
 | 
			
		||||
				Name:       name,
 | 
			
		||||
				Version:    ver,
 | 
			
		||||
				NewVersion: candidate,
 | 
			
		||||
			}
 | 
			
		||||
		case ">":
 | 
			
		||||
			o.log.Warnf("The installed version of the %s is newer than the current version. *This situation can arise with an out of date index file, or when testing new ports.*", name)
 | 
			
		||||
			packs[name] = models.Package{
 | 
			
		||||
				Name:    name,
 | 
			
		||||
				Version: ver,
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return packs
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type vulnIDCveIDs struct {
 | 
			
		||||
	vulnID string
 | 
			
		||||
	cveIDs []string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type pkgAuditResult struct {
 | 
			
		||||
	pack         models.Package
 | 
			
		||||
	vulnIDCveIDs vulnIDCveIDs
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *bsd) splitIntoBlocks(stdout string) (blocks []string) {
 | 
			
		||||
	lines := strings.Split(stdout, "\n")
 | 
			
		||||
	block := []string{}
 | 
			
		||||
	for _, l := range lines {
 | 
			
		||||
		if len(strings.TrimSpace(l)) == 0 {
 | 
			
		||||
			if 0 < len(block) {
 | 
			
		||||
				blocks = append(blocks, strings.Join(block, "\n"))
 | 
			
		||||
				block = []string{}
 | 
			
		||||
			}
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		block = append(block, strings.TrimSpace(l))
 | 
			
		||||
	}
 | 
			
		||||
	if 0 < len(block) {
 | 
			
		||||
		blocks = append(blocks, strings.Join(block, "\n"))
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *bsd) parseBlock(block string) (packName string, cveIDs []string, vulnID string) {
 | 
			
		||||
	lines := strings.Split(block, "\n")
 | 
			
		||||
	for _, l := range lines {
 | 
			
		||||
		if strings.HasSuffix(l, " is vulnerable:") {
 | 
			
		||||
			packVer := strings.Fields(l)[0]
 | 
			
		||||
			splitted := strings.Split(packVer, "-")
 | 
			
		||||
			packName = strings.Join(splitted[:len(splitted)-1], "-")
 | 
			
		||||
		} else if strings.HasPrefix(l, "CVE:") {
 | 
			
		||||
			cveIDs = append(cveIDs, strings.Fields(l)[1])
 | 
			
		||||
		} else if strings.HasPrefix(l, "WWW:") {
 | 
			
		||||
			splitted := strings.Split(l, "/")
 | 
			
		||||
			vulnID = strings.TrimSuffix(splitted[len(splitted)-1], ".html")
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										246
									
								
								scanner/freebsd_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										246
									
								
								scanner/freebsd_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,246 @@
 | 
			
		||||
package scanner
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"reflect"
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	"github.com/k0kubun/pp"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestParseIfconfig(t *testing.T) {
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		in        string
 | 
			
		||||
		expected4 []string
 | 
			
		||||
		expected6 []string
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			in: `em0: flags=8843<UP,BROADCAST,RUNNING,SIMPLEX,MULTICAST> metric 0 mtu 1500
 | 
			
		||||
			options=9b<RXCSUM,TXCSUM,VLAN_MTU,VLAN_HWTAGGING,VLAN_HWCSUM>
 | 
			
		||||
			ether 08:00:27:81:82:fa
 | 
			
		||||
			hwaddr 08:00:27:81:82:fa
 | 
			
		||||
			inet 10.0.2.15 netmask 0xffffff00 broadcast 10.0.2.255
 | 
			
		||||
			inet6 2001:db8::68 netmask 0xffffff00 broadcast 10.0.2.255
 | 
			
		||||
			nd6 options=29<PERFORMNUD,IFDISABLED,AUTO_LINKLOCAL>
 | 
			
		||||
			media: Ethernet autoselect (1000baseT <full-duplex>)
 | 
			
		||||
			status: active
 | 
			
		||||
	lo0: flags=8049<UP,LOOPBACK,RUNNING,MULTICAST> metric 0 mtu 16384
 | 
			
		||||
			options=600003<RXCSUM,TXCSUM,RXCSUM_IPV6,TXCSUM_IPV6>
 | 
			
		||||
			inet6 ::1 prefixlen 128
 | 
			
		||||
			inet6 fe80::1%lo0 prefixlen 64 scopeid 0x2
 | 
			
		||||
			inet 127.0.0.1 netmask 0xff000000
 | 
			
		||||
			nd6 options=21<PERFORMNUD,AUTO_LINKLOCAL>`,
 | 
			
		||||
			expected4: []string{"10.0.2.15"},
 | 
			
		||||
			expected6: []string{"2001:db8::68"},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	d := newBsd(config.ServerInfo{})
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		actual4, actual6 := d.parseIfconfig(tt.in)
 | 
			
		||||
		if !reflect.DeepEqual(tt.expected4, actual4) {
 | 
			
		||||
			t.Errorf("expected %s, actual %s", tt.expected4, actual4)
 | 
			
		||||
		}
 | 
			
		||||
		if !reflect.DeepEqual(tt.expected6, actual6) {
 | 
			
		||||
			t.Errorf("expected %s, actual %s", tt.expected6, actual6)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestParsePkgVersion(t *testing.T) {
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		in       string
 | 
			
		||||
		expected models.Packages
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			`Updating FreeBSD repository catalogue...
 | 
			
		||||
FreeBSD repository is up-to-date.
 | 
			
		||||
All repositories are up-to-date.
 | 
			
		||||
bash-4.2.45                        <   needs updating (remote has 4.3.42_1)
 | 
			
		||||
gettext-0.18.3.1                   <   needs updating (remote has 0.19.7)
 | 
			
		||||
tcl84-8.4.20_2,1                   =   up-to-date with remote
 | 
			
		||||
ntp-4.2.8p8_1                      >   succeeds port (port has 4.2.8p6)
 | 
			
		||||
teTeX-base-3.0_25                  ?   orphaned: print/teTeX-base`,
 | 
			
		||||
 | 
			
		||||
			models.Packages{
 | 
			
		||||
				"bash": {
 | 
			
		||||
					Name:       "bash",
 | 
			
		||||
					Version:    "4.2.45",
 | 
			
		||||
					NewVersion: "4.3.42_1",
 | 
			
		||||
				},
 | 
			
		||||
				"gettext": {
 | 
			
		||||
					Name:       "gettext",
 | 
			
		||||
					Version:    "0.18.3.1",
 | 
			
		||||
					NewVersion: "0.19.7",
 | 
			
		||||
				},
 | 
			
		||||
				"tcl84": {
 | 
			
		||||
					Name:    "tcl84",
 | 
			
		||||
					Version: "8.4.20_2,1",
 | 
			
		||||
				},
 | 
			
		||||
				"teTeX-base": {
 | 
			
		||||
					Name:    "teTeX-base",
 | 
			
		||||
					Version: "3.0_25",
 | 
			
		||||
				},
 | 
			
		||||
				"ntp": {
 | 
			
		||||
					Name:    "ntp",
 | 
			
		||||
					Version: "4.2.8p8_1",
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	d := newBsd(config.ServerInfo{})
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		actual := d.parsePkgVersion(tt.in)
 | 
			
		||||
		if !reflect.DeepEqual(tt.expected, actual) {
 | 
			
		||||
			e := pp.Sprintf("%v", tt.expected)
 | 
			
		||||
			a := pp.Sprintf("%v", actual)
 | 
			
		||||
			t.Errorf("expected %s, actual %s", e, a)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestSplitIntoBlocks(t *testing.T) {
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		in       string
 | 
			
		||||
		expected []string
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			`
 | 
			
		||||
block1
 | 
			
		||||
 | 
			
		||||
block2
 | 
			
		||||
block2
 | 
			
		||||
block2
 | 
			
		||||
 | 
			
		||||
block3
 | 
			
		||||
block3`,
 | 
			
		||||
			[]string{
 | 
			
		||||
				`block1`,
 | 
			
		||||
				"block2\nblock2\nblock2",
 | 
			
		||||
				"block3\nblock3",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	d := newBsd(config.ServerInfo{})
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		actual := d.splitIntoBlocks(tt.in)
 | 
			
		||||
		if !reflect.DeepEqual(tt.expected, actual) {
 | 
			
		||||
			e := pp.Sprintf("%v", tt.expected)
 | 
			
		||||
			a := pp.Sprintf("%v", actual)
 | 
			
		||||
			t.Errorf("expected %s, actual %s", e, a)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestParseBlock(t *testing.T) {
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		in     string
 | 
			
		||||
		name   string
 | 
			
		||||
		cveIDs []string
 | 
			
		||||
		vulnID string
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
 | 
			
		||||
			in: `vulnxml file up-to-date
 | 
			
		||||
bind96-9.6.3.2.ESV.R10_2 is vulnerable:
 | 
			
		||||
bind -- denial of service vulnerability
 | 
			
		||||
CVE: CVE-2014-0591
 | 
			
		||||
WWW: https://vuxml.FreeBSD.org/freebsd/cb252f01-7c43-11e3-b0a6-005056a37f68.html`,
 | 
			
		||||
			name:   "bind96",
 | 
			
		||||
			cveIDs: []string{"CVE-2014-0591"},
 | 
			
		||||
			vulnID: "cb252f01-7c43-11e3-b0a6-005056a37f68",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			in: `bind96-9.6.3.2.ESV.R10_2 is vulnerable:
 | 
			
		||||
bind -- denial of service vulnerability
 | 
			
		||||
CVE: CVE-2014-8680
 | 
			
		||||
CVE: CVE-2014-8500
 | 
			
		||||
WWW: https://vuxml.FreeBSD.org/freebsd/ab3e98d9-8175-11e4-907d-d050992ecde8.html`,
 | 
			
		||||
			name:   "bind96",
 | 
			
		||||
			cveIDs: []string{"CVE-2014-8680", "CVE-2014-8500"},
 | 
			
		||||
			vulnID: "ab3e98d9-8175-11e4-907d-d050992ecde8",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			in: `hoge-hoge-9.6.3.2.ESV.R10_2 is vulnerable:
 | 
			
		||||
bind -- denial of service vulnerability
 | 
			
		||||
CVE: CVE-2014-8680
 | 
			
		||||
CVE: CVE-2014-8500
 | 
			
		||||
WWW: https://vuxml.FreeBSD.org/freebsd/ab3e98d9-8175-11e4-907d-d050992ecde8.html`,
 | 
			
		||||
			name:   "hoge-hoge",
 | 
			
		||||
			cveIDs: []string{"CVE-2014-8680", "CVE-2014-8500"},
 | 
			
		||||
			vulnID: "ab3e98d9-8175-11e4-907d-d050992ecde8",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			in:     `1 problem(s) in the installed packages found.`,
 | 
			
		||||
			cveIDs: []string{},
 | 
			
		||||
			vulnID: "",
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	d := newBsd(config.ServerInfo{})
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		aName, aCveIDs, aVulnID := d.parseBlock(tt.in)
 | 
			
		||||
		if tt.name != aName {
 | 
			
		||||
			t.Errorf("expected vulnID: %s, actual %s", tt.vulnID, aVulnID)
 | 
			
		||||
		}
 | 
			
		||||
		for i := range tt.cveIDs {
 | 
			
		||||
			if tt.cveIDs[i] != aCveIDs[i] {
 | 
			
		||||
				t.Errorf("expected cveID: %s, actual %s", tt.cveIDs[i], aCveIDs[i])
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		if tt.vulnID != aVulnID {
 | 
			
		||||
			t.Errorf("expected vulnID: %s, actual %s", tt.vulnID, aVulnID)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestParsePkgInfo(t *testing.T) {
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		in       string
 | 
			
		||||
		expected models.Packages
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			`bash-4.2.45                        Universal Command Line Interface for Amazon Web Services
 | 
			
		||||
gettext-0.18.3.1                   Startup scripts for FreeBSD/EC2 environment
 | 
			
		||||
tcl84-8.4.20_2,1                   Update the system using freebsd-update when it first boots
 | 
			
		||||
ntp-4.2.8p8_1                      GNU gettext runtime libraries and programs
 | 
			
		||||
teTeX-base-3.0_25                  Foreign Function Interface`,
 | 
			
		||||
			models.Packages{
 | 
			
		||||
				"bash": {
 | 
			
		||||
					Name:    "bash",
 | 
			
		||||
					Version: "4.2.45",
 | 
			
		||||
				},
 | 
			
		||||
				"gettext": {
 | 
			
		||||
					Name:    "gettext",
 | 
			
		||||
					Version: "0.18.3.1",
 | 
			
		||||
				},
 | 
			
		||||
				"tcl84": {
 | 
			
		||||
					Name:    "tcl84",
 | 
			
		||||
					Version: "8.4.20_2,1",
 | 
			
		||||
				},
 | 
			
		||||
				"teTeX-base": {
 | 
			
		||||
					Name:    "teTeX-base",
 | 
			
		||||
					Version: "3.0_25",
 | 
			
		||||
				},
 | 
			
		||||
				"ntp": {
 | 
			
		||||
					Name:    "ntp",
 | 
			
		||||
					Version: "4.2.8p8_1",
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	d := newBsd(config.ServerInfo{})
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		actual := d.parsePkgInfo(tt.in)
 | 
			
		||||
		if !reflect.DeepEqual(tt.expected, actual) {
 | 
			
		||||
			e := pp.Sprintf("%v", tt.expected)
 | 
			
		||||
			a := pp.Sprintf("%v", actual)
 | 
			
		||||
			t.Errorf("expected %s, actual %s", e, a)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										26
									
								
								scanner/library.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								scanner/library.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,26 @@
 | 
			
		||||
package scanner
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"github.com/aquasecurity/fanal/types"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
 | 
			
		||||
	trivyTypes "github.com/aquasecurity/trivy/pkg/types"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func convertLibWithScanner(apps []types.Application) ([]models.LibraryScanner, error) {
 | 
			
		||||
	scanners := []models.LibraryScanner{}
 | 
			
		||||
	for _, app := range apps {
 | 
			
		||||
		libs := []trivyTypes.Library{}
 | 
			
		||||
		for _, lib := range app.Libraries {
 | 
			
		||||
			libs = append(libs, trivyTypes.Library{
 | 
			
		||||
				Name:    lib.Library.Name,
 | 
			
		||||
				Version: lib.Library.Version,
 | 
			
		||||
			})
 | 
			
		||||
		}
 | 
			
		||||
		scanners = append(scanners, models.LibraryScanner{
 | 
			
		||||
			Path: app.FilePath,
 | 
			
		||||
			Libs: libs,
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
	return scanners, nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										109
									
								
								scanner/oracle.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										109
									
								
								scanner/oracle.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,109 @@
 | 
			
		||||
package scanner
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	"github.com/future-architect/vuls/util"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// inherit OsTypeInterface
 | 
			
		||||
type oracle struct {
 | 
			
		||||
	redhatBase
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewAmazon is constructor
 | 
			
		||||
func newOracle(c config.ServerInfo) *oracle {
 | 
			
		||||
	r := &oracle{
 | 
			
		||||
		redhatBase{
 | 
			
		||||
			base: base{
 | 
			
		||||
				osPackages: osPackages{
 | 
			
		||||
					Packages:  models.Packages{},
 | 
			
		||||
					VulnInfos: models.VulnInfos{},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			sudo: rootPrivOracle{},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	r.log = util.NewCustomLogger(c)
 | 
			
		||||
	r.setServerInfo(c)
 | 
			
		||||
	return r
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *oracle) checkScanMode() error {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *oracle) 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 *oracle) depsFast() []string {
 | 
			
		||||
	if o.getServerInfo().Mode.IsOffline() {
 | 
			
		||||
		return []string{}
 | 
			
		||||
	}
 | 
			
		||||
	// repoquery
 | 
			
		||||
	return []string{"yum-utils"}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *oracle) depsFastRoot() []string {
 | 
			
		||||
	return []string{"yum-utils"}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *oracle) depsDeep() []string {
 | 
			
		||||
	return o.depsFastRoot()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *oracle) 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 *oracle) sudoNoPasswdCmdsFast() []cmd {
 | 
			
		||||
	return []cmd{}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *oracle) 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},
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return []cmd{
 | 
			
		||||
		{"repoquery -h", exitStatusZero},
 | 
			
		||||
		{"needs-restarting", exitStatusZero},
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *oracle) sudoNoPasswdCmdsDeep() []cmd {
 | 
			
		||||
	return o.sudoNoPasswdCmdsFastRoot()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type rootPrivOracle struct{}
 | 
			
		||||
 | 
			
		||||
func (o rootPrivOracle) repoquery() bool {
 | 
			
		||||
	return true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o rootPrivOracle) yumMakeCache() bool {
 | 
			
		||||
	return true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o rootPrivOracle) yumPS() bool {
 | 
			
		||||
	return true
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										69
									
								
								scanner/pseudo.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										69
									
								
								scanner/pseudo.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,69 @@
 | 
			
		||||
package scanner
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/constant"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	"github.com/future-architect/vuls/util"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// inherit OsTypeInterface
 | 
			
		||||
type pseudo struct {
 | 
			
		||||
	base
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func detectPseudo(c config.ServerInfo) (itsMe bool, pseudo osTypeInterface, err error) {
 | 
			
		||||
	if c.Type == constant.ServerTypePseudo {
 | 
			
		||||
		p := newPseudo(c)
 | 
			
		||||
		p.setDistro(constant.ServerTypePseudo, "")
 | 
			
		||||
		return true, p, nil
 | 
			
		||||
	}
 | 
			
		||||
	return false, nil, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func newPseudo(c config.ServerInfo) *pseudo {
 | 
			
		||||
	d := &pseudo{
 | 
			
		||||
		base: base{
 | 
			
		||||
			osPackages: osPackages{
 | 
			
		||||
				Packages:  models.Packages{},
 | 
			
		||||
				VulnInfos: models.VulnInfos{},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	d.log = util.NewCustomLogger(c)
 | 
			
		||||
	d.setServerInfo(c)
 | 
			
		||||
	return d
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *pseudo) checkScanMode() error {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *pseudo) checkIfSudoNoPasswd() error {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *pseudo) checkDeps() error {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *pseudo) preCure() error {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *pseudo) postScan() error {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *pseudo) scanPackages() error {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *pseudo) parseInstalledPackages(string) (models.Packages, models.SrcPackages, error) {
 | 
			
		||||
	return nil, nil, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *pseudo) detectPlatform() {
 | 
			
		||||
	o.setPlatform(models.Platform{Name: "other"})
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										664
									
								
								scanner/redhatbase.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										664
									
								
								scanner/redhatbase.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,664 @@
 | 
			
		||||
package scanner
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bufio"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"regexp"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/constant"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	"github.com/future-architect/vuls/util"
 | 
			
		||||
	"golang.org/x/xerrors"
 | 
			
		||||
 | 
			
		||||
	ver "github.com/knqyf263/go-rpm-version"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// https://github.com/serverspec/specinfra/blob/master/lib/specinfra/helper/detect_os/redhat.rb
 | 
			
		||||
func detectRedhat(c config.ServerInfo) (bool, osTypeInterface) {
 | 
			
		||||
	if r := exec(c, "ls /etc/fedora-release", noSudo); r.isSuccess() {
 | 
			
		||||
		util.Log.Warnf("Fedora not tested yet: %s", r)
 | 
			
		||||
		return true, &unknown{}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if r := exec(c, "ls /etc/oracle-release", noSudo); r.isSuccess() {
 | 
			
		||||
		// Need to discover Oracle Linux first, because it provides an
 | 
			
		||||
		// /etc/redhat-release that matches the upstream distribution
 | 
			
		||||
		if r := exec(c, "cat /etc/oracle-release", noSudo); r.isSuccess() {
 | 
			
		||||
			re := regexp.MustCompile(`(.*) release (\d[\d\.]*)`)
 | 
			
		||||
			result := re.FindStringSubmatch(strings.TrimSpace(r.Stdout))
 | 
			
		||||
			if len(result) != 3 {
 | 
			
		||||
				util.Log.Warnf("Failed to parse Oracle Linux version: %s", r)
 | 
			
		||||
				return true, newOracle(c)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			ora := newOracle(c)
 | 
			
		||||
			release := result[2]
 | 
			
		||||
			ora.setDistro(constant.Oracle, release)
 | 
			
		||||
			return true, ora
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// https://bugzilla.redhat.com/show_bug.cgi?id=1332025
 | 
			
		||||
	// CentOS cloud image
 | 
			
		||||
	if r := exec(c, "ls /etc/centos-release", noSudo); r.isSuccess() {
 | 
			
		||||
		if r := exec(c, "cat /etc/centos-release", noSudo); r.isSuccess() {
 | 
			
		||||
			re := regexp.MustCompile(`(.*) release (\d[\d\.]*)`)
 | 
			
		||||
			result := re.FindStringSubmatch(strings.TrimSpace(r.Stdout))
 | 
			
		||||
			if len(result) != 3 {
 | 
			
		||||
				util.Log.Warnf("Failed to parse CentOS version: %s", r)
 | 
			
		||||
				return true, newCentOS(c)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			release := result[2]
 | 
			
		||||
			switch strings.ToLower(result[1]) {
 | 
			
		||||
			case "centos", "centos linux", "centos stream":
 | 
			
		||||
				cent := newCentOS(c)
 | 
			
		||||
				cent.setDistro(constant.CentOS, release)
 | 
			
		||||
				return true, cent
 | 
			
		||||
			default:
 | 
			
		||||
				util.Log.Warnf("Failed to parse CentOS: %s", r)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if r := exec(c, "ls /etc/redhat-release", noSudo); r.isSuccess() {
 | 
			
		||||
		// https://www.rackaid.com/blog/how-to-determine-centos-or-red-hat-version/
 | 
			
		||||
		// e.g.
 | 
			
		||||
		// $ cat /etc/redhat-release
 | 
			
		||||
		// CentOS release 6.5 (Final)
 | 
			
		||||
		if r := exec(c, "cat /etc/redhat-release", noSudo); r.isSuccess() {
 | 
			
		||||
			re := regexp.MustCompile(`(.*) release (\d[\d\.]*)`)
 | 
			
		||||
			result := re.FindStringSubmatch(strings.TrimSpace(r.Stdout))
 | 
			
		||||
			if len(result) != 3 {
 | 
			
		||||
				util.Log.Warnf("Failed to parse RedHat/CentOS version: %s", r)
 | 
			
		||||
				return true, newCentOS(c)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			release := result[2]
 | 
			
		||||
			switch strings.ToLower(result[1]) {
 | 
			
		||||
			case "centos", "centos linux":
 | 
			
		||||
				cent := newCentOS(c)
 | 
			
		||||
				cent.setDistro(constant.CentOS, release)
 | 
			
		||||
				return true, cent
 | 
			
		||||
			default:
 | 
			
		||||
				// RHEL
 | 
			
		||||
				rhel := newRHEL(c)
 | 
			
		||||
				rhel.setDistro(constant.RedHat, release)
 | 
			
		||||
				return true, rhel
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if r := exec(c, "ls /etc/system-release", noSudo); r.isSuccess() {
 | 
			
		||||
		family := constant.Amazon
 | 
			
		||||
		release := "unknown"
 | 
			
		||||
		if r := exec(c, "cat /etc/system-release", noSudo); r.isSuccess() {
 | 
			
		||||
			if strings.HasPrefix(r.Stdout, "Amazon Linux release 2") {
 | 
			
		||||
				fields := strings.Fields(r.Stdout)
 | 
			
		||||
				release = fmt.Sprintf("%s %s", fields[3], fields[4])
 | 
			
		||||
			} else if strings.HasPrefix(r.Stdout, "Amazon Linux 2") {
 | 
			
		||||
				fields := strings.Fields(r.Stdout)
 | 
			
		||||
				release = strings.Join(fields[2:], " ")
 | 
			
		||||
			} else {
 | 
			
		||||
				fields := strings.Fields(r.Stdout)
 | 
			
		||||
				if len(fields) == 5 {
 | 
			
		||||
					release = fields[4]
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		amazon := newAmazon(c)
 | 
			
		||||
		amazon.setDistro(family, release)
 | 
			
		||||
		return true, amazon
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	util.Log.Debugf("Not RedHat like Linux. servername: %s", c.ServerName)
 | 
			
		||||
	return false, &unknown{}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// inherit OsTypeInterface
 | 
			
		||||
type redhatBase struct {
 | 
			
		||||
	base
 | 
			
		||||
	sudo rootPriv
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type rootPriv interface {
 | 
			
		||||
	repoquery() bool
 | 
			
		||||
	yumMakeCache() bool
 | 
			
		||||
	yumPS() bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type cmd struct {
 | 
			
		||||
	cmd                 string
 | 
			
		||||
	expectedStatusCodes []int
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var exitStatusZero = []int{0}
 | 
			
		||||
 | 
			
		||||
func (o *redhatBase) execCheckIfSudoNoPasswd(cmds []cmd) error {
 | 
			
		||||
	for _, c := range cmds {
 | 
			
		||||
		cmd := util.PrependProxyEnv(c.cmd)
 | 
			
		||||
		o.log.Infof("Checking... sudo %s", cmd)
 | 
			
		||||
		r := o.exec(util.PrependProxyEnv(cmd), sudo)
 | 
			
		||||
		if !r.isSuccess(c.expectedStatusCodes...) {
 | 
			
		||||
			o.log.Errorf("Check sudo or proxy settings: %s", r)
 | 
			
		||||
			return xerrors.Errorf("Failed to sudo: %s", r)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	o.log.Infof("Sudo... Pass")
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *redhatBase) execCheckDeps(packNames []string) error {
 | 
			
		||||
	for _, name := range packNames {
 | 
			
		||||
		cmd := "rpm -q " + name
 | 
			
		||||
		if r := o.exec(cmd, noSudo); !r.isSuccess() {
 | 
			
		||||
			msg := fmt.Sprintf("%s is not installed", name)
 | 
			
		||||
			o.log.Errorf(msg)
 | 
			
		||||
			return xerrors.New(msg)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	o.log.Infof("Dependencies ... Pass")
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *redhatBase) preCure() error {
 | 
			
		||||
	if err := o.detectIPAddr(); err != nil {
 | 
			
		||||
		o.log.Warnf("Failed to detect IP addresses: %s", err)
 | 
			
		||||
		o.warns = append(o.warns, err)
 | 
			
		||||
	}
 | 
			
		||||
	// Ignore this error as it just failed to detect the IP addresses
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *redhatBase) postScan() error {
 | 
			
		||||
	if o.isExecYumPS() {
 | 
			
		||||
		if err := o.pkgPs(o.getOwnerPkgs); err != nil {
 | 
			
		||||
			err = xerrors.Errorf("Failed to execute yum-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 *redhatBase) detectIPAddr() (err error) {
 | 
			
		||||
	o.ServerInfo.IPv4Addrs, o.ServerInfo.IPv6Addrs, err = o.ip()
 | 
			
		||||
	return err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *redhatBase) scanPackages() (err error) {
 | 
			
		||||
	o.log.Infof("Scanning OS pkg in %s", o.getServerInfo().Mode)
 | 
			
		||||
	o.Packages, err = o.scanInstalledPackages()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return xerrors.Errorf("Failed to scan installed packages: %w", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if o.EnabledDnfModules, err = o.detectEnabledDnfModules(); err != nil {
 | 
			
		||||
		return xerrors.Errorf("Failed to detect installed dnf modules: %w", 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() {
 | 
			
		||||
		return nil
 | 
			
		||||
	} else if o.Distro.Family == constant.RedHat {
 | 
			
		||||
		if o.getServerInfo().Mode.IsFast() {
 | 
			
		||||
			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 {
 | 
			
		||||
		o.Packages.MergeNewVersion(updatable)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *redhatBase) rebootRequired() (bool, error) {
 | 
			
		||||
	r := o.exec("rpm -q --last kernel", noSudo)
 | 
			
		||||
	scanner := bufio.NewScanner(strings.NewReader(r.Stdout))
 | 
			
		||||
	if !r.isSuccess(0, 1) {
 | 
			
		||||
		return false, xerrors.Errorf("Failed to detect the last installed kernel : %v", r)
 | 
			
		||||
	}
 | 
			
		||||
	if !r.isSuccess() || !scanner.Scan() {
 | 
			
		||||
		return false, nil
 | 
			
		||||
	}
 | 
			
		||||
	lastInstalledKernelVer := strings.Fields(scanner.Text())[0]
 | 
			
		||||
	running := fmt.Sprintf("kernel-%s", o.Kernel.Release)
 | 
			
		||||
	return running != lastInstalledKernelVer, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *redhatBase) scanInstalledPackages() (models.Packages, error) {
 | 
			
		||||
	release, version, err := o.runningKernel()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	o.Kernel = models.Kernel{
 | 
			
		||||
		Release: release,
 | 
			
		||||
		Version: version,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	r := o.exec(o.rpmQa(), noSudo)
 | 
			
		||||
	if !r.isSuccess() {
 | 
			
		||||
		return nil, xerrors.Errorf("Scan packages failed: %s", r)
 | 
			
		||||
	}
 | 
			
		||||
	installed, _, err := o.parseInstalledPackages(r.Stdout)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	return installed, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *redhatBase) parseInstalledPackages(stdout string) (models.Packages, models.SrcPackages, error) {
 | 
			
		||||
	installed := models.Packages{}
 | 
			
		||||
	latestKernelRelease := ver.NewVersion("")
 | 
			
		||||
 | 
			
		||||
	// openssl 0 1.0.1e	30.el6.11 x86_64
 | 
			
		||||
	lines := strings.Split(stdout, "\n")
 | 
			
		||||
	for _, line := range lines {
 | 
			
		||||
		if trimmed := strings.TrimSpace(line); trimmed == "" {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		pack, err := o.parseInstalledPackagesLine(line)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, nil, err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// `Kernel` and `kernel-devel` package may be installed multiple versions.
 | 
			
		||||
		// From the viewpoint of vulnerability detection,
 | 
			
		||||
		// pay attention only to the running kernel
 | 
			
		||||
		isKernel, running := isRunningKernel(*pack, o.Distro.Family, o.Kernel)
 | 
			
		||||
		if isKernel {
 | 
			
		||||
			if o.Kernel.Release == "" {
 | 
			
		||||
				// When the running kernel release is unknown,
 | 
			
		||||
				// use the latest release among the installed release
 | 
			
		||||
				kernelRelease := ver.NewVersion(fmt.Sprintf("%s-%s", pack.Version, pack.Release))
 | 
			
		||||
				if kernelRelease.LessThan(latestKernelRelease) {
 | 
			
		||||
					continue
 | 
			
		||||
				}
 | 
			
		||||
				latestKernelRelease = kernelRelease
 | 
			
		||||
			} else if !running {
 | 
			
		||||
				o.log.Debugf("Not a running kernel. pack: %#v, kernel: %#v", pack, o.Kernel)
 | 
			
		||||
				continue
 | 
			
		||||
			} else {
 | 
			
		||||
				o.log.Debugf("Found a running kernel. pack: %#v, kernel: %#v", pack, o.Kernel)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		installed[pack.Name] = *pack
 | 
			
		||||
	}
 | 
			
		||||
	return installed, nil, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *redhatBase) parseInstalledPackagesLine(line string) (*models.Package, error) {
 | 
			
		||||
	fields := strings.Fields(line)
 | 
			
		||||
	if len(fields) != 5 {
 | 
			
		||||
		return nil,
 | 
			
		||||
			xerrors.Errorf("Failed to parse package line: %s", line)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ver := ""
 | 
			
		||||
	epoch := fields[1]
 | 
			
		||||
	if epoch == "0" || epoch == "(none)" {
 | 
			
		||||
		ver = fields[2]
 | 
			
		||||
	} else {
 | 
			
		||||
		ver = fmt.Sprintf("%s:%s", epoch, fields[2])
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return &models.Package{
 | 
			
		||||
		Name:    fields[0],
 | 
			
		||||
		Version: ver,
 | 
			
		||||
		Release: fields[3],
 | 
			
		||||
		Arch:    fields[4],
 | 
			
		||||
	}, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *redhatBase) parseRpmQfLine(line string) (pkg *models.Package, ignored bool, err error) {
 | 
			
		||||
	for _, suffix := range []string{
 | 
			
		||||
		"Permission denied",
 | 
			
		||||
		"is not owned by any package",
 | 
			
		||||
		"No such file or directory",
 | 
			
		||||
	} {
 | 
			
		||||
		if strings.HasSuffix(line, suffix) {
 | 
			
		||||
			return nil, true, nil
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	pkg, err = o.parseInstalledPackagesLine(line)
 | 
			
		||||
	return pkg, false, err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *redhatBase) yumMakeCache() error {
 | 
			
		||||
	cmd := `yum makecache --assumeyes`
 | 
			
		||||
	r := o.exec(util.PrependProxyEnv(cmd), o.sudo.yumMakeCache())
 | 
			
		||||
	if !r.isSuccess(0, 1) {
 | 
			
		||||
		return xerrors.Errorf("Failed to SSH: %s", r)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *redhatBase) scanUpdatablePackages() (models.Packages, error) {
 | 
			
		||||
	if err := o.yumMakeCache(); err != nil {
 | 
			
		||||
		return nil, xerrors.Errorf("Failed to `yum makecache`: %w", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	isDnf := o.exec(util.PrependProxyEnv(`repoquery --version | grep dnf`), o.sudo.repoquery()).isSuccess()
 | 
			
		||||
	cmd := `repoquery --all --pkgnarrow=updates --qf='%{NAME} %{EPOCH} %{VERSION} %{RELEASE} %{REPO}'`
 | 
			
		||||
	if isDnf {
 | 
			
		||||
		cmd = `repoquery --upgrades --qf='%{NAME} %{EPOCH} %{VERSION} %{RELEASE} %{REPONAME}' -q`
 | 
			
		||||
	}
 | 
			
		||||
	for _, repo := range o.getServerInfo().Enablerepo {
 | 
			
		||||
		cmd += " --enablerepo=" + repo
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	r := o.exec(util.PrependProxyEnv(cmd), o.sudo.repoquery())
 | 
			
		||||
	if !r.isSuccess() {
 | 
			
		||||
		return nil, xerrors.Errorf("Failed to SSH: %s", r)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Collect Updatable packages, installed, candidate version and repository.
 | 
			
		||||
	return o.parseUpdatablePacksLines(r.Stdout)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// parseUpdatablePacksLines parse the stdout of repoquery to get package name, candidate version
 | 
			
		||||
func (o *redhatBase) parseUpdatablePacksLines(stdout string) (models.Packages, error) {
 | 
			
		||||
	updatable := models.Packages{}
 | 
			
		||||
	lines := strings.Split(stdout, "\n")
 | 
			
		||||
	for _, line := range lines {
 | 
			
		||||
		if len(strings.TrimSpace(line)) == 0 {
 | 
			
		||||
			continue
 | 
			
		||||
		} else if strings.HasPrefix(line, "Loading") {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		pack, err := o.parseUpdatablePacksLine(line)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return updatable, err
 | 
			
		||||
		}
 | 
			
		||||
		updatable[pack.Name] = pack
 | 
			
		||||
	}
 | 
			
		||||
	return updatable, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *redhatBase) parseUpdatablePacksLine(line string) (models.Package, error) {
 | 
			
		||||
	fields := strings.Fields(line)
 | 
			
		||||
	if len(fields) < 5 {
 | 
			
		||||
		return models.Package{}, xerrors.Errorf("Unknown format: %s, fields: %s", line, fields)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ver := ""
 | 
			
		||||
	epoch := fields[1]
 | 
			
		||||
	if epoch == "0" {
 | 
			
		||||
		ver = fields[2]
 | 
			
		||||
	} else {
 | 
			
		||||
		ver = fmt.Sprintf("%s:%s", epoch, fields[2])
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	repos := strings.Join(fields[4:], " ")
 | 
			
		||||
 | 
			
		||||
	p := models.Package{
 | 
			
		||||
		Name:       fields[0],
 | 
			
		||||
		NewVersion: ver,
 | 
			
		||||
		NewRelease: fields[3],
 | 
			
		||||
		Repository: repos,
 | 
			
		||||
	}
 | 
			
		||||
	return p, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *redhatBase) isExecYumPS() bool {
 | 
			
		||||
	switch o.Distro.Family {
 | 
			
		||||
	case constant.Oracle,
 | 
			
		||||
		constant.OpenSUSE,
 | 
			
		||||
		constant.OpenSUSELeap,
 | 
			
		||||
		constant.SUSEEnterpriseServer,
 | 
			
		||||
		constant.SUSEEnterpriseDesktop,
 | 
			
		||||
		constant.SUSEOpenstackCloud:
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
	return !o.getServerInfo().Mode.IsFast()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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
 | 
			
		||||
		return false
 | 
			
		||||
	case constant.RedHat, constant.CentOS, constant.Oracle:
 | 
			
		||||
		majorVersion, err := o.Distro.MajorVersion()
 | 
			
		||||
		if err != nil || majorVersion < 6 {
 | 
			
		||||
			o.log.Errorf("Not implemented yet: %s, err: %s", o.Distro, err)
 | 
			
		||||
			return false
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if o.getServerInfo().Mode.IsOffline() {
 | 
			
		||||
			return false
 | 
			
		||||
		} else if o.getServerInfo().Mode.IsFastRoot() ||
 | 
			
		||||
			o.getServerInfo().Mode.IsDeep() {
 | 
			
		||||
			return true
 | 
			
		||||
		}
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if o.getServerInfo().Mode.IsFast() {
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
	return true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *redhatBase) needsRestarting() error {
 | 
			
		||||
	initName, err := o.detectInitSystem()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		o.log.Warn(err)
 | 
			
		||||
		// continue scanning
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	cmd := "LANGUAGE=en_US.UTF-8 needs-restarting"
 | 
			
		||||
	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 *redhatBase) parseNeedsRestarting(stdout string) (procs []models.NeedRestartProcess) {
 | 
			
		||||
	scanner := bufio.NewScanner(strings.NewReader(stdout))
 | 
			
		||||
	for scanner.Scan() {
 | 
			
		||||
		line := scanner.Text()
 | 
			
		||||
		line = strings.Replace(line, "\x00", " ", -1) // for CentOS6.9
 | 
			
		||||
		ss := strings.Split(line, " : ")
 | 
			
		||||
		if len(ss) < 2 {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		// https://unix.stackexchange.com/a/419375
 | 
			
		||||
		if ss[0] == "1" {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		path := ss[1]
 | 
			
		||||
		if !strings.HasPrefix(path, "/") {
 | 
			
		||||
			path = strings.Fields(path)[0]
 | 
			
		||||
			// [ec2-user@ip-172-31-11-139 ~]$ sudo needs-restarting
 | 
			
		||||
			// 2024 : auditd
 | 
			
		||||
			// [ec2-user@ip-172-31-11-139 ~]$ type -p auditd
 | 
			
		||||
			// /sbin/auditd
 | 
			
		||||
			cmd := fmt.Sprintf("LANGUAGE=en_US.UTF-8 which %s", path)
 | 
			
		||||
			r := o.exec(cmd, sudo)
 | 
			
		||||
			if !r.isSuccess() {
 | 
			
		||||
				o.log.Debugf("Failed to exec which %s: %s", path, r)
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
			path = strings.TrimSpace(r.Stdout)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		procs = append(procs, models.NeedRestartProcess{
 | 
			
		||||
			PID:     ss[0],
 | 
			
		||||
			Path:    path,
 | 
			
		||||
			HasInit: true,
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//TODO refactor
 | 
			
		||||
// procPathToFQPN returns Fully-Qualified-Package-Name from the command
 | 
			
		||||
func (o *redhatBase) procPathToFQPN(execCommand string) (string, error) {
 | 
			
		||||
	execCommand = strings.Replace(execCommand, "\x00", " ", -1) // for CentOS6.9
 | 
			
		||||
	path := strings.Fields(execCommand)[0]
 | 
			
		||||
	cmd := `LANGUAGE=en_US.UTF-8 rpm -qf --queryformat "%{NAME}-%{EPOCH}:%{VERSION}-%{RELEASE}\n" ` + path
 | 
			
		||||
	r := o.exec(cmd, noSudo)
 | 
			
		||||
	if !r.isSuccess() {
 | 
			
		||||
		return "", xerrors.Errorf("Failed to SSH: %s", r)
 | 
			
		||||
	}
 | 
			
		||||
	fqpn := strings.TrimSpace(r.Stdout)
 | 
			
		||||
	return strings.Replace(fqpn, "-(none):", "-", -1), nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *redhatBase) getOwnerPkgs(paths []string) (names []string, _ error) {
 | 
			
		||||
	cmd := o.rpmQf() + strings.Join(paths, " ")
 | 
			
		||||
	r := o.exec(util.PrependProxyEnv(cmd), noSudo)
 | 
			
		||||
	// rpm exit code means `the number` of errors.
 | 
			
		||||
	// https://listman.redhat.com/archives/rpm-list/2005-July/msg00071.html
 | 
			
		||||
	// If we treat non-zero exit codes of `rpm` as errors,
 | 
			
		||||
	// we will be missing a partial package list we can get.
 | 
			
		||||
 | 
			
		||||
	scanner := bufio.NewScanner(strings.NewReader(r.Stdout))
 | 
			
		||||
	for scanner.Scan() {
 | 
			
		||||
		line := scanner.Text()
 | 
			
		||||
		pack, ignored, err := o.parseRpmQfLine(line)
 | 
			
		||||
		if ignored {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			o.log.Debugf("Failed to parse rpm -qf line: %s, err: %+v", line, err)
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		if _, ok := o.Packages[pack.Name]; !ok {
 | 
			
		||||
			o.log.Debugf("Failed to rpm -qf. pkg: %+v not found, line: %s", pack, line)
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		names = append(names, pack.Name)
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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:
 | 
			
		||||
		if v, _ := o.Distro.MajorVersion(); v < 12 {
 | 
			
		||||
			return old
 | 
			
		||||
		}
 | 
			
		||||
		return new
 | 
			
		||||
	default:
 | 
			
		||||
		if v, _ := o.Distro.MajorVersion(); v < 6 {
 | 
			
		||||
			return old
 | 
			
		||||
		}
 | 
			
		||||
		return new
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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:
 | 
			
		||||
		if v, _ := o.Distro.MajorVersion(); v < 12 {
 | 
			
		||||
			return old
 | 
			
		||||
		}
 | 
			
		||||
		return new
 | 
			
		||||
	default:
 | 
			
		||||
		if v, _ := o.Distro.MajorVersion(); v < 6 {
 | 
			
		||||
			return old
 | 
			
		||||
		}
 | 
			
		||||
		return new
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *redhatBase) detectEnabledDnfModules() ([]string, error) {
 | 
			
		||||
	switch o.Distro.Family {
 | 
			
		||||
	case constant.RedHat, constant.CentOS:
 | 
			
		||||
		//TODO OracleLinux
 | 
			
		||||
	default:
 | 
			
		||||
		return nil, nil
 | 
			
		||||
	}
 | 
			
		||||
	if v, _ := o.Distro.MajorVersion(); v < 8 {
 | 
			
		||||
		return nil, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	cmd := `dnf --nogpgcheck --cacheonly --color=never --quiet module list --enabled`
 | 
			
		||||
	r := o.exec(util.PrependProxyEnv(cmd), noSudo)
 | 
			
		||||
	if !r.isSuccess() {
 | 
			
		||||
		if strings.Contains(r.Stdout, "Cache-only enabled but no cache") {
 | 
			
		||||
			return nil, xerrors.Errorf("sudo yum check-update to make local cache before scanning: %s", r)
 | 
			
		||||
		}
 | 
			
		||||
		return nil, xerrors.Errorf("Failed to dnf module list: %s", r)
 | 
			
		||||
	}
 | 
			
		||||
	return o.parseDnfModuleList(r.Stdout)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *redhatBase) parseDnfModuleList(stdout string) (labels []string, err error) {
 | 
			
		||||
	scanner := bufio.NewScanner(strings.NewReader(stdout))
 | 
			
		||||
	for scanner.Scan() {
 | 
			
		||||
		line := scanner.Text()
 | 
			
		||||
		if strings.HasPrefix(line, "Hint:") || !strings.Contains(line, "[i]") {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		ss := strings.Fields(line)
 | 
			
		||||
		if len(ss) < 2 {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		labels = append(labels, fmt.Sprintf("%s:%s", ss[0], ss[1]))
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										519
									
								
								scanner/redhatbase_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										519
									
								
								scanner/redhatbase_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,519 @@
 | 
			
		||||
package scanner
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"reflect"
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/constant"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	"github.com/k0kubun/pp"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
//  func unixtimeNoerr(s string) time.Time {
 | 
			
		||||
//      t, _ := unixtime(s)
 | 
			
		||||
//      return t
 | 
			
		||||
//  }
 | 
			
		||||
 | 
			
		||||
func TestParseInstalledPackagesLinesRedhat(t *testing.T) {
 | 
			
		||||
	r := newRHEL(config.ServerInfo{})
 | 
			
		||||
	r.Distro = config.Distro{Family: constant.RedHat}
 | 
			
		||||
 | 
			
		||||
	var packagetests = []struct {
 | 
			
		||||
		in       string
 | 
			
		||||
		kernel   models.Kernel
 | 
			
		||||
		packages models.Packages
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			in: `openssl	0	1.0.1e	30.el6.11 x86_64
 | 
			
		||||
Percona-Server-shared-56	1	5.6.19	rel67.0.el6 x84_64
 | 
			
		||||
kernel 0 2.6.32 696.20.1.el6 x86_64
 | 
			
		||||
kernel 0 2.6.32 696.20.3.el6 x86_64
 | 
			
		||||
kernel 0 2.6.32 695.20.3.el6 x86_64`,
 | 
			
		||||
			kernel: models.Kernel{},
 | 
			
		||||
			packages: models.Packages{
 | 
			
		||||
				"openssl": models.Package{
 | 
			
		||||
					Name:    "openssl",
 | 
			
		||||
					Version: "1.0.1e",
 | 
			
		||||
					Release: "30.el6.11",
 | 
			
		||||
				},
 | 
			
		||||
				"Percona-Server-shared-56": models.Package{
 | 
			
		||||
					Name:    "Percona-Server-shared-56",
 | 
			
		||||
					Version: "1:5.6.19",
 | 
			
		||||
					Release: "rel67.0.el6",
 | 
			
		||||
				},
 | 
			
		||||
				"kernel": models.Package{
 | 
			
		||||
					Name:    "kernel",
 | 
			
		||||
					Version: "2.6.32",
 | 
			
		||||
					Release: "696.20.3.el6",
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			in: `openssl	0	1.0.1e	30.el6.11 x86_64
 | 
			
		||||
Percona-Server-shared-56	1	5.6.19	rel67.0.el6 x84_64
 | 
			
		||||
kernel 0 2.6.32 696.20.1.el6 x86_64
 | 
			
		||||
kernel 0 2.6.32 696.20.3.el6 x86_64
 | 
			
		||||
kernel 0 2.6.32 695.20.3.el6 x86_64
 | 
			
		||||
kernel-devel 0 2.6.32 696.20.1.el6 x86_64
 | 
			
		||||
kernel-devel 0 2.6.32 696.20.3.el6 x86_64
 | 
			
		||||
kernel-devel 0 2.6.32 695.20.3.el6 x86_64`,
 | 
			
		||||
			kernel: models.Kernel{Release: "2.6.32-696.20.3.el6.x86_64"},
 | 
			
		||||
			packages: models.Packages{
 | 
			
		||||
				"openssl": models.Package{
 | 
			
		||||
					Name:    "openssl",
 | 
			
		||||
					Version: "1.0.1e",
 | 
			
		||||
					Release: "30.el6.11",
 | 
			
		||||
				},
 | 
			
		||||
				"Percona-Server-shared-56": models.Package{
 | 
			
		||||
					Name:    "Percona-Server-shared-56",
 | 
			
		||||
					Version: "1:5.6.19",
 | 
			
		||||
					Release: "rel67.0.el6",
 | 
			
		||||
				},
 | 
			
		||||
				"kernel": models.Package{
 | 
			
		||||
					Name:    "kernel",
 | 
			
		||||
					Version: "2.6.32",
 | 
			
		||||
					Release: "696.20.3.el6",
 | 
			
		||||
				},
 | 
			
		||||
				"kernel-devel": models.Package{
 | 
			
		||||
					Name:    "kernel-devel",
 | 
			
		||||
					Version: "2.6.32",
 | 
			
		||||
					Release: "696.20.3.el6",
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			in: `openssl	0	1.0.1e	30.el6.11 x86_64
 | 
			
		||||
Percona-Server-shared-56	1	5.6.19	rel67.0.el6 x84_64
 | 
			
		||||
kernel 0 2.6.32 696.20.1.el6 x86_64
 | 
			
		||||
kernel 0 2.6.32 696.20.3.el6 x86_64
 | 
			
		||||
kernel 0 2.6.32 695.20.3.el6 x86_64
 | 
			
		||||
kernel-devel 0 2.6.32 696.20.1.el6 x86_64
 | 
			
		||||
kernel-devel 0 2.6.32 696.20.3.el6 x86_64
 | 
			
		||||
kernel-devel 0 2.6.32 695.20.3.el6 x86_64`,
 | 
			
		||||
			kernel: models.Kernel{Release: "2.6.32-695.20.3.el6.x86_64"},
 | 
			
		||||
			packages: models.Packages{
 | 
			
		||||
				"openssl": models.Package{
 | 
			
		||||
					Name:    "openssl",
 | 
			
		||||
					Version: "1.0.1e",
 | 
			
		||||
					Release: "30.el6.11",
 | 
			
		||||
				},
 | 
			
		||||
				"Percona-Server-shared-56": models.Package{
 | 
			
		||||
					Name:    "Percona-Server-shared-56",
 | 
			
		||||
					Version: "1:5.6.19",
 | 
			
		||||
					Release: "rel67.0.el6",
 | 
			
		||||
				},
 | 
			
		||||
				"kernel": models.Package{
 | 
			
		||||
					Name:    "kernel",
 | 
			
		||||
					Version: "2.6.32",
 | 
			
		||||
					Release: "695.20.3.el6",
 | 
			
		||||
				},
 | 
			
		||||
				"kernel-devel": models.Package{
 | 
			
		||||
					Name:    "kernel-devel",
 | 
			
		||||
					Version: "2.6.32",
 | 
			
		||||
					Release: "695.20.3.el6",
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, tt := range packagetests {
 | 
			
		||||
		r.Kernel = tt.kernel
 | 
			
		||||
		packages, _, err := r.parseInstalledPackages(tt.in)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			t.Errorf("Unexpected error: %s", err)
 | 
			
		||||
		}
 | 
			
		||||
		for name, expectedPack := range tt.packages {
 | 
			
		||||
			pack := packages[name]
 | 
			
		||||
			if pack.Name != expectedPack.Name {
 | 
			
		||||
				t.Errorf("name: expected %s, actual %s", expectedPack.Name, pack.Name)
 | 
			
		||||
			}
 | 
			
		||||
			if pack.Version != expectedPack.Version {
 | 
			
		||||
				t.Errorf("version: expected %s, actual %s", expectedPack.Version, pack.Version)
 | 
			
		||||
			}
 | 
			
		||||
			if pack.Release != expectedPack.Release {
 | 
			
		||||
				t.Errorf("release: expected %s, actual %s", expectedPack.Release, pack.Release)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
func TestParseInstalledPackagesLine(t *testing.T) {
 | 
			
		||||
	r := newRHEL(config.ServerInfo{})
 | 
			
		||||
 | 
			
		||||
	var packagetests = []struct {
 | 
			
		||||
		in   string
 | 
			
		||||
		pack models.Package
 | 
			
		||||
		err  bool
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			"openssl	0	1.0.1e	30.el6.11 x86_64",
 | 
			
		||||
			models.Package{
 | 
			
		||||
				Name:    "openssl",
 | 
			
		||||
				Version: "1.0.1e",
 | 
			
		||||
				Release: "30.el6.11",
 | 
			
		||||
			},
 | 
			
		||||
			false,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			"Percona-Server-shared-56	1	5.6.19	rel67.0.el6 x84_64",
 | 
			
		||||
			models.Package{
 | 
			
		||||
				Name:    "Percona-Server-shared-56",
 | 
			
		||||
				Version: "1:5.6.19",
 | 
			
		||||
				Release: "rel67.0.el6",
 | 
			
		||||
			},
 | 
			
		||||
			false,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for i, tt := range packagetests {
 | 
			
		||||
		p, err := r.parseInstalledPackagesLine(tt.in)
 | 
			
		||||
		if err == nil && tt.err {
 | 
			
		||||
			t.Errorf("Expected err not occurred: %d", i)
 | 
			
		||||
		}
 | 
			
		||||
		if err != nil && !tt.err {
 | 
			
		||||
			t.Errorf("UnExpected err not occurred: %d", i)
 | 
			
		||||
		}
 | 
			
		||||
		if p.Name != tt.pack.Name {
 | 
			
		||||
			t.Errorf("name: expected %s, actual %s", tt.pack.Name, p.Name)
 | 
			
		||||
		}
 | 
			
		||||
		if p.Version != tt.pack.Version {
 | 
			
		||||
			t.Errorf("version: expected %s, actual %s", tt.pack.Version, p.Version)
 | 
			
		||||
		}
 | 
			
		||||
		if p.Release != tt.pack.Release {
 | 
			
		||||
			t.Errorf("release: expected %s, actual %s", tt.pack.Release, p.Release)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestParseYumCheckUpdateLine(t *testing.T) {
 | 
			
		||||
	r := newCentOS(config.ServerInfo{})
 | 
			
		||||
	r.Distro = config.Distro{Family: "centos"}
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		in  string
 | 
			
		||||
		out models.Package
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			"zlib 0 1.2.7 17.el7 rhui-REGION-rhel-server-releases",
 | 
			
		||||
			models.Package{
 | 
			
		||||
				Name:       "zlib",
 | 
			
		||||
				NewVersion: "1.2.7",
 | 
			
		||||
				NewRelease: "17.el7",
 | 
			
		||||
				Repository: "rhui-REGION-rhel-server-releases",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			"shadow-utils 2 4.1.5.1 24.el7 rhui-REGION-rhel-server-releases",
 | 
			
		||||
			models.Package{
 | 
			
		||||
				Name:       "shadow-utils",
 | 
			
		||||
				NewVersion: "2:4.1.5.1",
 | 
			
		||||
				NewRelease: "24.el7",
 | 
			
		||||
				Repository: "rhui-REGION-rhel-server-releases",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		aPack, err := r.parseUpdatablePacksLine(tt.in)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			t.Errorf("Error has occurred, err: %s\ntt.in: %v", err, tt.in)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		if !reflect.DeepEqual(tt.out, aPack) {
 | 
			
		||||
			e := pp.Sprintf("%v", tt.out)
 | 
			
		||||
			a := pp.Sprintf("%v", aPack)
 | 
			
		||||
			t.Errorf("expected %s, actual %s", e, a)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestParseYumCheckUpdateLines(t *testing.T) {
 | 
			
		||||
	r := newCentOS(config.ServerInfo{})
 | 
			
		||||
	r.Distro = config.Distro{Family: "centos"}
 | 
			
		||||
	stdout := `audit-libs 0 2.3.7 5.el6 base
 | 
			
		||||
bash 0 4.1.2 33.el6_7.1 updates
 | 
			
		||||
python-libs 0 2.6.6 64.el6 rhui-REGION-rhel-server-releases
 | 
			
		||||
python-ordereddict 0 1.1 3.el6ev installed
 | 
			
		||||
bind-utils 30 9.3.6 25.P1.el5_11.8 updates
 | 
			
		||||
pytalloc 0 2.0.7 2.el6 @CentOS 6.5/6.5`
 | 
			
		||||
 | 
			
		||||
	r.Packages = models.NewPackages(
 | 
			
		||||
		models.Package{Name: "audit-libs"},
 | 
			
		||||
		models.Package{Name: "bash"},
 | 
			
		||||
		models.Package{Name: "python-libs"},
 | 
			
		||||
		models.Package{Name: "python-ordereddict"},
 | 
			
		||||
		models.Package{Name: "bind-utils"},
 | 
			
		||||
		models.Package{Name: "pytalloc"},
 | 
			
		||||
	)
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		in  string
 | 
			
		||||
		out models.Packages
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			stdout,
 | 
			
		||||
			models.NewPackages(
 | 
			
		||||
				models.Package{
 | 
			
		||||
					Name:       "audit-libs",
 | 
			
		||||
					NewVersion: "2.3.7",
 | 
			
		||||
					NewRelease: "5.el6",
 | 
			
		||||
					Repository: "base",
 | 
			
		||||
				},
 | 
			
		||||
				models.Package{
 | 
			
		||||
					Name:       "bash",
 | 
			
		||||
					NewVersion: "4.1.2",
 | 
			
		||||
					NewRelease: "33.el6_7.1",
 | 
			
		||||
					Repository: "updates",
 | 
			
		||||
				},
 | 
			
		||||
				models.Package{
 | 
			
		||||
					Name:       "python-libs",
 | 
			
		||||
					NewVersion: "2.6.6",
 | 
			
		||||
					NewRelease: "64.el6",
 | 
			
		||||
					Repository: "rhui-REGION-rhel-server-releases",
 | 
			
		||||
				},
 | 
			
		||||
				models.Package{
 | 
			
		||||
					Name:       "python-ordereddict",
 | 
			
		||||
					NewVersion: "1.1",
 | 
			
		||||
					NewRelease: "3.el6ev",
 | 
			
		||||
					Repository: "installed",
 | 
			
		||||
				},
 | 
			
		||||
				models.Package{
 | 
			
		||||
					Name:       "bind-utils",
 | 
			
		||||
					NewVersion: "30:9.3.6",
 | 
			
		||||
					NewRelease: "25.P1.el5_11.8",
 | 
			
		||||
					Repository: "updates",
 | 
			
		||||
				},
 | 
			
		||||
				models.Package{
 | 
			
		||||
					Name:       "pytalloc",
 | 
			
		||||
					NewVersion: "2.0.7",
 | 
			
		||||
					NewRelease: "2.el6",
 | 
			
		||||
					Repository: "@CentOS 6.5/6.5",
 | 
			
		||||
				},
 | 
			
		||||
			),
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		packages, err := r.parseUpdatablePacksLines(tt.in)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			t.Errorf("Error has occurred, err: %s\ntt.in: %v", err, tt.in)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		for name, ePack := range tt.out {
 | 
			
		||||
			if !reflect.DeepEqual(ePack, packages[name]) {
 | 
			
		||||
				e := pp.Sprintf("%v", ePack)
 | 
			
		||||
				a := pp.Sprintf("%v", packages[name])
 | 
			
		||||
				t.Errorf("expected %s, actual %s", e, a)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestParseYumCheckUpdateLinesAmazon(t *testing.T) {
 | 
			
		||||
	r := newAmazon(config.ServerInfo{})
 | 
			
		||||
	r.Distro = config.Distro{Family: "amazon"}
 | 
			
		||||
	stdout := `bind-libs 32 9.8.2 0.37.rc1.45.amzn1 amzn-main
 | 
			
		||||
java-1.7.0-openjdk  0 1.7.0.95 2.6.4.0.65.amzn1 amzn-main
 | 
			
		||||
if-not-architecture 0 100 200 amzn-main`
 | 
			
		||||
	r.Packages = models.NewPackages(
 | 
			
		||||
		models.Package{Name: "bind-libs"},
 | 
			
		||||
		models.Package{Name: "java-1.7.0-openjdk"},
 | 
			
		||||
		models.Package{Name: "if-not-architecture"},
 | 
			
		||||
	)
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		in  string
 | 
			
		||||
		out models.Packages
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			stdout,
 | 
			
		||||
			models.NewPackages(
 | 
			
		||||
				models.Package{
 | 
			
		||||
					Name:       "bind-libs",
 | 
			
		||||
					NewVersion: "32:9.8.2",
 | 
			
		||||
					NewRelease: "0.37.rc1.45.amzn1",
 | 
			
		||||
					Repository: "amzn-main",
 | 
			
		||||
				},
 | 
			
		||||
				models.Package{
 | 
			
		||||
					Name:       "java-1.7.0-openjdk",
 | 
			
		||||
					NewVersion: "1.7.0.95",
 | 
			
		||||
					NewRelease: "2.6.4.0.65.amzn1",
 | 
			
		||||
					Repository: "amzn-main",
 | 
			
		||||
				},
 | 
			
		||||
				models.Package{
 | 
			
		||||
					Name:       "if-not-architecture",
 | 
			
		||||
					NewVersion: "100",
 | 
			
		||||
					NewRelease: "200",
 | 
			
		||||
					Repository: "amzn-main",
 | 
			
		||||
				},
 | 
			
		||||
			),
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		packages, err := r.parseUpdatablePacksLines(tt.in)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			t.Errorf("Error has occurred, err: %s\ntt.in: %v", err, tt.in)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		for name, ePack := range tt.out {
 | 
			
		||||
			if !reflect.DeepEqual(ePack, packages[name]) {
 | 
			
		||||
				e := pp.Sprintf("%v", ePack)
 | 
			
		||||
				a := pp.Sprintf("%v", packages[name])
 | 
			
		||||
				t.Errorf("[%s] expected %s, actual %s", name, e, a)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestParseNeedsRestarting(t *testing.T) {
 | 
			
		||||
	r := newRHEL(config.ServerInfo{})
 | 
			
		||||
	r.Distro = config.Distro{Family: "centos"}
 | 
			
		||||
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		in  string
 | 
			
		||||
		out []models.NeedRestartProcess
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			`1 : /usr/lib/systemd/systemd --switched-root --system --deserialize 21kk
 | 
			
		||||
437 : /usr/sbin/NetworkManager --no-daemon`,
 | 
			
		||||
			[]models.NeedRestartProcess{
 | 
			
		||||
				{
 | 
			
		||||
					PID:     "437",
 | 
			
		||||
					Path:    "/usr/sbin/NetworkManager --no-daemon",
 | 
			
		||||
					HasInit: true,
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		procs := r.parseNeedsRestarting(tt.in)
 | 
			
		||||
		if !reflect.DeepEqual(tt.out, procs) {
 | 
			
		||||
			t.Errorf("expected %#v, actual %#v", tt.out, procs)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func Test_redhatBase_parseDnfModuleList(t *testing.T) {
 | 
			
		||||
	type args struct {
 | 
			
		||||
		stdout string
 | 
			
		||||
	}
 | 
			
		||||
	tests := []struct {
 | 
			
		||||
		name       string
 | 
			
		||||
		args       args
 | 
			
		||||
		wantLabels []string
 | 
			
		||||
		wantErr    bool
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			name: "Success",
 | 
			
		||||
			args: args{
 | 
			
		||||
				stdout: `Red Hat Enterprise Linux 8 for x86_64 - AppStream from RHUI (RPMs)
 | 
			
		||||
Name                                     Stream                                         Profiles                                          Summary
 | 
			
		||||
virt                 rhel [d][e] common [d]                               Virtualization module
 | 
			
		||||
nginx                                    1.14 [d][e]                                    common [d] [i]                                    nginx webserver
 | 
			
		||||
 | 
			
		||||
Hint: [d]efault, [e]nabled, [x]disabled, [i]nstalled`,
 | 
			
		||||
			},
 | 
			
		||||
			wantLabels: []string{
 | 
			
		||||
				"nginx:1.14",
 | 
			
		||||
			},
 | 
			
		||||
			wantErr: false,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		t.Run(tt.name, func(t *testing.T) {
 | 
			
		||||
			o := &redhatBase{}
 | 
			
		||||
			gotLabels, err := o.parseDnfModuleList(tt.args.stdout)
 | 
			
		||||
			if (err != nil) != tt.wantErr {
 | 
			
		||||
				t.Errorf("redhatBase.parseDnfModuleList() error = %v, wantErr %v", err, tt.wantErr)
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
			if !reflect.DeepEqual(gotLabels, tt.wantLabels) {
 | 
			
		||||
				t.Errorf("redhatBase.parseDnfModuleList() = %v, want %v", gotLabels, tt.wantLabels)
 | 
			
		||||
			}
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func Test_redhatBase_parseRpmQfLine(t *testing.T) {
 | 
			
		||||
	type fields struct {
 | 
			
		||||
		base base
 | 
			
		||||
		sudo rootPriv
 | 
			
		||||
	}
 | 
			
		||||
	type args struct {
 | 
			
		||||
		line string
 | 
			
		||||
	}
 | 
			
		||||
	tests := []struct {
 | 
			
		||||
		name        string
 | 
			
		||||
		fields      fields
 | 
			
		||||
		args        args
 | 
			
		||||
		wantPkg     *models.Package
 | 
			
		||||
		wantIgnored bool
 | 
			
		||||
		wantErr     bool
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			name:        "permission denied will be ignored",
 | 
			
		||||
			fields:      fields{base: base{}},
 | 
			
		||||
			args:        args{line: "/tmp/hogehoge Permission denied"},
 | 
			
		||||
			wantPkg:     nil,
 | 
			
		||||
			wantIgnored: true,
 | 
			
		||||
			wantErr:     false,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name:        "is not owned by any package",
 | 
			
		||||
			fields:      fields{base: base{}},
 | 
			
		||||
			args:        args{line: "/tmp/hogehoge is not owned by any package"},
 | 
			
		||||
			wantPkg:     nil,
 | 
			
		||||
			wantIgnored: true,
 | 
			
		||||
			wantErr:     false,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name:        "No such file or directory will be ignored",
 | 
			
		||||
			fields:      fields{base: base{}},
 | 
			
		||||
			args:        args{line: "/tmp/hogehoge No such file or directory"},
 | 
			
		||||
			wantPkg:     nil,
 | 
			
		||||
			wantIgnored: true,
 | 
			
		||||
			wantErr:     false,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name:   "valid line",
 | 
			
		||||
			fields: fields{base: base{}},
 | 
			
		||||
			args: args{line: "Percona-Server-shared-56	1	5.6.19	rel67.0.el6 x86_64"},
 | 
			
		||||
			wantPkg: &models.Package{
 | 
			
		||||
				Name:    "Percona-Server-shared-56",
 | 
			
		||||
				Version: "1:5.6.19",
 | 
			
		||||
				Release: "rel67.0.el6",
 | 
			
		||||
				Arch:    "x86_64",
 | 
			
		||||
			},
 | 
			
		||||
			wantIgnored: false,
 | 
			
		||||
			wantErr:     false,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name:        "err",
 | 
			
		||||
			fields:      fields{base: base{}},
 | 
			
		||||
			args:        args{line: "/tmp/hogehoge something unknown format"},
 | 
			
		||||
			wantPkg:     nil,
 | 
			
		||||
			wantIgnored: false,
 | 
			
		||||
			wantErr:     true,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		t.Run(tt.name, func(t *testing.T) {
 | 
			
		||||
			o := &redhatBase{
 | 
			
		||||
				base: tt.fields.base,
 | 
			
		||||
				sudo: tt.fields.sudo,
 | 
			
		||||
			}
 | 
			
		||||
			gotPkg, gotIgnored, err := o.parseRpmQfLine(tt.args.line)
 | 
			
		||||
			if (err != nil) != tt.wantErr {
 | 
			
		||||
				t.Errorf("redhatBase.parseRpmQfLine() error = %v, wantErr %v", err, tt.wantErr)
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
			if !reflect.DeepEqual(gotPkg, tt.wantPkg) {
 | 
			
		||||
				t.Errorf("redhatBase.parseRpmQfLine() gotPkg = %v, want %v", gotPkg, tt.wantPkg)
 | 
			
		||||
			}
 | 
			
		||||
			if gotIgnored != tt.wantIgnored {
 | 
			
		||||
				t.Errorf("redhatBase.parseRpmQfLine() gotIgnored = %v, want %v", gotIgnored, tt.wantIgnored)
 | 
			
		||||
			}
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										114
									
								
								scanner/rhel.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										114
									
								
								scanner/rhel.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,114 @@
 | 
			
		||||
package scanner
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"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 rhel struct {
 | 
			
		||||
	redhatBase
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewRHEL is constructor
 | 
			
		||||
func newRHEL(c config.ServerInfo) *rhel {
 | 
			
		||||
	r := &rhel{
 | 
			
		||||
		redhatBase{
 | 
			
		||||
			base: base{
 | 
			
		||||
				osPackages: osPackages{
 | 
			
		||||
					Packages:  models.Packages{},
 | 
			
		||||
					VulnInfos: models.VulnInfos{},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			sudo: rootPrivRHEL{},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	r.log = util.NewCustomLogger(c)
 | 
			
		||||
	r.setServerInfo(c)
 | 
			
		||||
	return r
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *rhel) checkScanMode() error {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *rhel) checkDeps() error {
 | 
			
		||||
	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 *rhel) depsFast() []string {
 | 
			
		||||
	return []string{}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *rhel) depsFastRoot() []string {
 | 
			
		||||
	if o.getServerInfo().Mode.IsOffline() {
 | 
			
		||||
		return []string{}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// repoquery
 | 
			
		||||
	// `rpm -qa` shows dnf-utils as yum-utils on RHEL8, CentOS8
 | 
			
		||||
	return []string{"yum-utils"}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *rhel) depsDeep() []string {
 | 
			
		||||
	return o.depsFastRoot()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *rhel) 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 *rhel) sudoNoPasswdCmdsFast() []cmd {
 | 
			
		||||
	return []cmd{}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *rhel) 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 *rhel) sudoNoPasswdCmdsDeep() []cmd {
 | 
			
		||||
	return o.sudoNoPasswdCmdsFastRoot()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type rootPrivRHEL struct{}
 | 
			
		||||
 | 
			
		||||
func (o rootPrivRHEL) repoquery() bool {
 | 
			
		||||
	return true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o rootPrivRHEL) yumMakeCache() bool {
 | 
			
		||||
	return true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o rootPrivRHEL) yumPS() bool {
 | 
			
		||||
	return true
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										678
									
								
								scanner/serverapi.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										678
									
								
								scanner/serverapi.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,678 @@
 | 
			
		||||
package scanner
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"os"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/cache"
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/constant"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	"github.com/future-architect/vuls/util"
 | 
			
		||||
	"golang.org/x/xerrors"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	scannedViaRemote = "remote"
 | 
			
		||||
	scannedViaLocal  = "local"
 | 
			
		||||
	scannedViaPseudo = "pseudo"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
	errOSFamilyHeader      = xerrors.New("X-Vuls-OS-Family header is required")
 | 
			
		||||
	errOSReleaseHeader     = xerrors.New("X-Vuls-OS-Release header is required")
 | 
			
		||||
	errKernelVersionHeader = xerrors.New("X-Vuls-Kernel-Version header is required")
 | 
			
		||||
	errServerNameHeader    = xerrors.New("X-Vuls-Server-Name header is required")
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var servers, errServers []osTypeInterface
 | 
			
		||||
 | 
			
		||||
// Base Interface
 | 
			
		||||
type osTypeInterface interface {
 | 
			
		||||
	setServerInfo(config.ServerInfo)
 | 
			
		||||
	getServerInfo() config.ServerInfo
 | 
			
		||||
	setDistro(string, string)
 | 
			
		||||
	getDistro() config.Distro
 | 
			
		||||
	detectPlatform()
 | 
			
		||||
	detectIPS()
 | 
			
		||||
	getPlatform() models.Platform
 | 
			
		||||
 | 
			
		||||
	checkScanMode() error
 | 
			
		||||
	checkDeps() error
 | 
			
		||||
	checkIfSudoNoPasswd() error
 | 
			
		||||
 | 
			
		||||
	preCure() error
 | 
			
		||||
	postScan() error
 | 
			
		||||
	scanWordPress() error
 | 
			
		||||
	scanLibraries() error
 | 
			
		||||
	scanPorts() error
 | 
			
		||||
	scanPackages() error
 | 
			
		||||
	convertToModel() models.ScanResult
 | 
			
		||||
 | 
			
		||||
	parseInstalledPackages(string) (models.Packages, models.SrcPackages, error)
 | 
			
		||||
 | 
			
		||||
	runningContainers() ([]config.Container, error)
 | 
			
		||||
	exitedContainers() ([]config.Container, error)
 | 
			
		||||
	allContainers() ([]config.Container, error)
 | 
			
		||||
 | 
			
		||||
	getErrs() []error
 | 
			
		||||
	setErrs([]error)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Scanner has functions for scan
 | 
			
		||||
type Scanner struct {
 | 
			
		||||
	TimeoutSec     int
 | 
			
		||||
	ScanTimeoutSec int
 | 
			
		||||
	CacheDBPath    string
 | 
			
		||||
 | 
			
		||||
	Targets map[string]config.ServerInfo
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Scan execute scan
 | 
			
		||||
func (s Scanner) Scan() error {
 | 
			
		||||
	util.Log.Info("Detecting Server/Container OS... ")
 | 
			
		||||
	if err := s.initServers(); err != nil {
 | 
			
		||||
		return xerrors.Errorf("Failed to init servers. err: %w", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	util.Log.Info("Checking Scan Modes... ")
 | 
			
		||||
	if err := s.checkScanModes(); err != nil {
 | 
			
		||||
		return xerrors.Errorf("Fix config.toml. err: %w", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	util.Log.Info("Detecting Platforms... ")
 | 
			
		||||
	s.detectPlatform()
 | 
			
		||||
 | 
			
		||||
	util.Log.Info("Detecting IPS identifiers... ")
 | 
			
		||||
	s.detectIPS()
 | 
			
		||||
 | 
			
		||||
	if err := s.execScan(); err != nil {
 | 
			
		||||
		return xerrors.Errorf("Failed to scan. err: %w", err)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Configtest checks if the server is scannable.
 | 
			
		||||
func (s Scanner) Configtest() error {
 | 
			
		||||
	util.Log.Info("Detecting Server/Container OS... ")
 | 
			
		||||
	if err := s.initServers(); err != nil {
 | 
			
		||||
		return xerrors.Errorf("Failed to init servers. err: %w", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	util.Log.Info("Checking Scan Modes...")
 | 
			
		||||
	if err := s.checkScanModes(); err != nil {
 | 
			
		||||
		return xerrors.Errorf("Fix config.toml. err: %w", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	util.Log.Info("Checking dependencies...")
 | 
			
		||||
	s.checkDependencies()
 | 
			
		||||
 | 
			
		||||
	util.Log.Info("Checking sudo settings...")
 | 
			
		||||
	s.checkIfSudoNoPasswd()
 | 
			
		||||
 | 
			
		||||
	util.Log.Info("It can be scanned with fast scan mode even if warn or err messages are displayed due to lack of dependent packages or sudo settings in fast-root or deep scan mode")
 | 
			
		||||
 | 
			
		||||
	if len(servers) == 0 {
 | 
			
		||||
		return xerrors.Errorf("No scannable servers")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	util.Log.Info("Scannable servers are below...")
 | 
			
		||||
	for _, s := range servers {
 | 
			
		||||
		if s.getServerInfo().IsContainer() {
 | 
			
		||||
			fmt.Printf("%s@%s ",
 | 
			
		||||
				s.getServerInfo().Container.Name,
 | 
			
		||||
				s.getServerInfo().ServerName,
 | 
			
		||||
			)
 | 
			
		||||
		} else {
 | 
			
		||||
			fmt.Printf("%s ", s.getServerInfo().ServerName)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	fmt.Printf("\n")
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ViaHTTP scans servers by HTTP header and body
 | 
			
		||||
func ViaHTTP(header http.Header, body string, toLocalFile bool) (models.ScanResult, error) {
 | 
			
		||||
	family := header.Get("X-Vuls-OS-Family")
 | 
			
		||||
	if family == "" {
 | 
			
		||||
		return models.ScanResult{}, errOSFamilyHeader
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	release := header.Get("X-Vuls-OS-Release")
 | 
			
		||||
	if release == "" {
 | 
			
		||||
		return models.ScanResult{}, errOSReleaseHeader
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	kernelRelease := header.Get("X-Vuls-Kernel-Release")
 | 
			
		||||
	if kernelRelease == "" {
 | 
			
		||||
		util.Log.Warn("If X-Vuls-Kernel-Release is not specified, there is a possibility of false detection")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	kernelVersion := header.Get("X-Vuls-Kernel-Version")
 | 
			
		||||
	if family == constant.Debian && kernelVersion == "" {
 | 
			
		||||
		return models.ScanResult{}, errKernelVersionHeader
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	serverName := header.Get("X-Vuls-Server-Name")
 | 
			
		||||
	if toLocalFile && serverName == "" {
 | 
			
		||||
		return models.ScanResult{}, errServerNameHeader
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	distro := config.Distro{
 | 
			
		||||
		Family:  family,
 | 
			
		||||
		Release: release,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	kernel := models.Kernel{
 | 
			
		||||
		Release: kernelRelease,
 | 
			
		||||
		Version: kernelVersion,
 | 
			
		||||
	}
 | 
			
		||||
	base := base{
 | 
			
		||||
		Distro: distro,
 | 
			
		||||
		osPackages: osPackages{
 | 
			
		||||
			Kernel: kernel,
 | 
			
		||||
		},
 | 
			
		||||
		log: util.Log,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var osType osTypeInterface
 | 
			
		||||
	switch family {
 | 
			
		||||
	case constant.Debian, constant.Ubuntu:
 | 
			
		||||
		osType = &debian{base: base}
 | 
			
		||||
	case constant.RedHat:
 | 
			
		||||
		osType = &rhel{
 | 
			
		||||
			redhatBase: redhatBase{base: base},
 | 
			
		||||
		}
 | 
			
		||||
	case constant.CentOS:
 | 
			
		||||
		osType = ¢os{
 | 
			
		||||
			redhatBase: redhatBase{base: base},
 | 
			
		||||
		}
 | 
			
		||||
	case constant.Oracle:
 | 
			
		||||
		osType = &oracle{
 | 
			
		||||
			redhatBase: redhatBase{base: base},
 | 
			
		||||
		}
 | 
			
		||||
	case constant.Amazon:
 | 
			
		||||
		osType = &amazon{
 | 
			
		||||
			redhatBase: redhatBase{base: base},
 | 
			
		||||
		}
 | 
			
		||||
	default:
 | 
			
		||||
		return models.ScanResult{}, xerrors.Errorf("Server mode for %s is not implemented yet", family)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	installedPackages, srcPackages, err := osType.parseInstalledPackages(body)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return models.ScanResult{}, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	result := models.ScanResult{
 | 
			
		||||
		ServerName: serverName,
 | 
			
		||||
		Family:     family,
 | 
			
		||||
		Release:    release,
 | 
			
		||||
		RunningKernel: models.Kernel{
 | 
			
		||||
			Release: kernelRelease,
 | 
			
		||||
			Version: kernelVersion,
 | 
			
		||||
		},
 | 
			
		||||
		Packages:    installedPackages,
 | 
			
		||||
		SrcPackages: srcPackages,
 | 
			
		||||
		ScannedCves: models.VulnInfos{},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return result, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// initServers detect the kind of OS distribution of target servers
 | 
			
		||||
func (s Scanner) initServers() error {
 | 
			
		||||
	hosts, errHosts := s.detectServerOSes()
 | 
			
		||||
	if len(hosts) == 0 {
 | 
			
		||||
		return xerrors.New("No scannable host OS")
 | 
			
		||||
	}
 | 
			
		||||
	containers, errContainers := s.detectContainerOSes(hosts)
 | 
			
		||||
 | 
			
		||||
	// set to pkg global variable
 | 
			
		||||
	for _, host := range hosts {
 | 
			
		||||
		if !host.getServerInfo().ContainersOnly {
 | 
			
		||||
			servers = append(servers, host)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	servers = append(servers, containers...)
 | 
			
		||||
	errServers = append(errHosts, errContainers...)
 | 
			
		||||
 | 
			
		||||
	if len(servers) == 0 {
 | 
			
		||||
		return xerrors.New("No scannable servers")
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s Scanner) detectServerOSes() (servers, errServers []osTypeInterface) {
 | 
			
		||||
	util.Log.Info("Detecting OS of servers... ")
 | 
			
		||||
	osTypeChan := make(chan osTypeInterface, len(s.Targets))
 | 
			
		||||
	defer close(osTypeChan)
 | 
			
		||||
	for _, target := range s.Targets {
 | 
			
		||||
		go func(srv config.ServerInfo) {
 | 
			
		||||
			defer func() {
 | 
			
		||||
				if p := recover(); p != nil {
 | 
			
		||||
					util.Log.Debugf("Panic: %s on %s", p, srv.ServerName)
 | 
			
		||||
				}
 | 
			
		||||
			}()
 | 
			
		||||
			osTypeChan <- s.detectOS(srv)
 | 
			
		||||
		}(target)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	timeout := time.After(time.Duration(s.TimeoutSec) * time.Second)
 | 
			
		||||
	for i := 0; i < len(s.Targets); i++ {
 | 
			
		||||
		select {
 | 
			
		||||
		case res := <-osTypeChan:
 | 
			
		||||
			if 0 < len(res.getErrs()) {
 | 
			
		||||
				errServers = append(errServers, res)
 | 
			
		||||
				util.Log.Errorf("(%d/%d) Failed: %s, err: %+v",
 | 
			
		||||
					i+1, len(s.Targets), res.getServerInfo().ServerName, res.getErrs())
 | 
			
		||||
			} else {
 | 
			
		||||
				servers = append(servers, res)
 | 
			
		||||
				util.Log.Infof("(%d/%d) Detected: %s: %s",
 | 
			
		||||
					i+1, len(s.Targets), res.getServerInfo().ServerName, res.getDistro())
 | 
			
		||||
			}
 | 
			
		||||
		case <-timeout:
 | 
			
		||||
			msg := "Timed out while detecting servers"
 | 
			
		||||
			util.Log.Error(msg)
 | 
			
		||||
			for servername, sInfo := range s.Targets {
 | 
			
		||||
				found := false
 | 
			
		||||
				for _, o := range append(servers, errServers...) {
 | 
			
		||||
					if servername == o.getServerInfo().ServerName {
 | 
			
		||||
						found = true
 | 
			
		||||
						break
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
				if !found {
 | 
			
		||||
					u := &unknown{}
 | 
			
		||||
					u.setServerInfo(sInfo)
 | 
			
		||||
					u.setErrs([]error{xerrors.New("Timed out")})
 | 
			
		||||
					errServers = append(errServers, u)
 | 
			
		||||
					util.Log.Errorf("(%d/%d) Timed out: %s", i+1, len(s.Targets), servername)
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s Scanner) detectContainerOSes(hosts []osTypeInterface) (actives, inactives []osTypeInterface) {
 | 
			
		||||
	util.Log.Info("Detecting OS of containers... ")
 | 
			
		||||
	osTypesChan := make(chan []osTypeInterface, len(hosts))
 | 
			
		||||
	defer close(osTypesChan)
 | 
			
		||||
	for _, host := range hosts {
 | 
			
		||||
		go func(h osTypeInterface) {
 | 
			
		||||
			defer func() {
 | 
			
		||||
				if p := recover(); p != nil {
 | 
			
		||||
					util.Log.Debugf("Panic: %s on %s",
 | 
			
		||||
						p, h.getServerInfo().GetServerName())
 | 
			
		||||
				}
 | 
			
		||||
			}()
 | 
			
		||||
			osTypesChan <- s.detectContainerOSesOnServer(h)
 | 
			
		||||
		}(host)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	timeout := time.After(time.Duration(s.TimeoutSec) * time.Second)
 | 
			
		||||
	for i := 0; i < len(hosts); i++ {
 | 
			
		||||
		select {
 | 
			
		||||
		case res := <-osTypesChan:
 | 
			
		||||
			for _, osi := range res {
 | 
			
		||||
				sinfo := osi.getServerInfo()
 | 
			
		||||
				if 0 < len(osi.getErrs()) {
 | 
			
		||||
					inactives = append(inactives, osi)
 | 
			
		||||
					util.Log.Errorf("Failed: %s err: %+v", sinfo.ServerName, osi.getErrs())
 | 
			
		||||
					continue
 | 
			
		||||
				}
 | 
			
		||||
				actives = append(actives, osi)
 | 
			
		||||
				util.Log.Infof("Detected: %s@%s: %s",
 | 
			
		||||
					sinfo.Container.Name, sinfo.ServerName, osi.getDistro())
 | 
			
		||||
			}
 | 
			
		||||
		case <-timeout:
 | 
			
		||||
			util.Log.Error("Some containers timed out")
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s Scanner) detectContainerOSesOnServer(containerHost osTypeInterface) (oses []osTypeInterface) {
 | 
			
		||||
	containerHostInfo := containerHost.getServerInfo()
 | 
			
		||||
	if len(containerHostInfo.ContainersIncluded) == 0 {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	running, err := containerHost.runningContainers()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		containerHost.setErrs([]error{xerrors.Errorf(
 | 
			
		||||
			"Failed to get running containers on %s. err: %w",
 | 
			
		||||
			containerHost.getServerInfo().ServerName, err)})
 | 
			
		||||
		return append(oses, containerHost)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if containerHostInfo.ContainersIncluded[0] == "${running}" {
 | 
			
		||||
		for _, containerInfo := range running {
 | 
			
		||||
			found := false
 | 
			
		||||
			for _, ex := range containerHost.getServerInfo().ContainersExcluded {
 | 
			
		||||
				if containerInfo.Name == ex || containerInfo.ContainerID == ex {
 | 
			
		||||
					found = true
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			if found {
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			copied := containerHostInfo
 | 
			
		||||
			copied.SetContainer(config.Container{
 | 
			
		||||
				ContainerID: containerInfo.ContainerID,
 | 
			
		||||
				Name:        containerInfo.Name,
 | 
			
		||||
				Image:       containerInfo.Image,
 | 
			
		||||
			})
 | 
			
		||||
			os := s.detectOS(copied)
 | 
			
		||||
			oses = append(oses, os)
 | 
			
		||||
		}
 | 
			
		||||
		return oses
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	exitedContainers, err := containerHost.exitedContainers()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		containerHost.setErrs([]error{xerrors.Errorf(
 | 
			
		||||
			"Failed to get exited containers on %s. err: %w",
 | 
			
		||||
			containerHost.getServerInfo().ServerName, err)})
 | 
			
		||||
		return append(oses, containerHost)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var exited, unknown []string
 | 
			
		||||
	for _, container := range containerHostInfo.ContainersIncluded {
 | 
			
		||||
		found := false
 | 
			
		||||
		for _, c := range running {
 | 
			
		||||
			if c.ContainerID == container || c.Name == container {
 | 
			
		||||
				copied := containerHostInfo
 | 
			
		||||
				copied.SetContainer(c)
 | 
			
		||||
				os := s.detectOS(copied)
 | 
			
		||||
				oses = append(oses, os)
 | 
			
		||||
				found = true
 | 
			
		||||
				break
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if !found {
 | 
			
		||||
			foundInExitedContainers := false
 | 
			
		||||
			for _, c := range exitedContainers {
 | 
			
		||||
				if c.ContainerID == container || c.Name == container {
 | 
			
		||||
					exited = append(exited, container)
 | 
			
		||||
					foundInExitedContainers = true
 | 
			
		||||
					break
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			if !foundInExitedContainers {
 | 
			
		||||
				unknown = append(unknown, container)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if 0 < len(exited) || 0 < len(unknown) {
 | 
			
		||||
		containerHost.setErrs([]error{xerrors.Errorf(
 | 
			
		||||
			"Some containers on %s are exited or unknown. exited: %s, unknown: %s",
 | 
			
		||||
			containerHost.getServerInfo().ServerName, exited, unknown)})
 | 
			
		||||
		return append(oses, containerHost)
 | 
			
		||||
	}
 | 
			
		||||
	return oses
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s Scanner) detectOS(c config.ServerInfo) (osType osTypeInterface) {
 | 
			
		||||
	var itsMe bool
 | 
			
		||||
	var fatalErr error
 | 
			
		||||
 | 
			
		||||
	if itsMe, osType, _ = detectPseudo(c); itsMe {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	itsMe, osType, fatalErr = s.detectDebianWithRetry(c)
 | 
			
		||||
	if fatalErr != nil {
 | 
			
		||||
		osType.setErrs([]error{
 | 
			
		||||
			xerrors.Errorf("Failed to detect OS: %w", fatalErr)})
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if itsMe {
 | 
			
		||||
		util.Log.Debugf("Debian like Linux. Host: %s:%s", c.Host, c.Port)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if itsMe, osType = detectRedhat(c); itsMe {
 | 
			
		||||
		util.Log.Debugf("Redhat like Linux. Host: %s:%s", c.Host, c.Port)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if itsMe, osType = detectSUSE(c); itsMe {
 | 
			
		||||
		util.Log.Debugf("SUSE Linux. Host: %s:%s", c.Host, c.Port)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if itsMe, osType = detectFreebsd(c); itsMe {
 | 
			
		||||
		util.Log.Debugf("FreeBSD. Host: %s:%s", c.Host, c.Port)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if itsMe, osType = detectAlpine(c); itsMe {
 | 
			
		||||
		util.Log.Debugf("Alpine. Host: %s:%s", c.Host, c.Port)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	//TODO darwin https://github.com/mizzy/specinfra/blob/master/lib/specinfra/helper/detect_os/darwin.rb
 | 
			
		||||
	osType.setErrs([]error{xerrors.New("Unknown OS Type")})
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Retry as it may stall on the first SSH connection
 | 
			
		||||
// https://github.com/future-architect/vuls/pull/753
 | 
			
		||||
func (s Scanner) detectDebianWithRetry(c config.ServerInfo) (itsMe bool, deb osTypeInterface, err error) {
 | 
			
		||||
	type Response struct {
 | 
			
		||||
		itsMe bool
 | 
			
		||||
		deb   osTypeInterface
 | 
			
		||||
		err   error
 | 
			
		||||
	}
 | 
			
		||||
	resChan := make(chan Response, 1)
 | 
			
		||||
	go func(c config.ServerInfo) {
 | 
			
		||||
		itsMe, osType, fatalErr := detectDebian(c)
 | 
			
		||||
		resChan <- Response{itsMe, osType, fatalErr}
 | 
			
		||||
	}(c)
 | 
			
		||||
 | 
			
		||||
	timeout := time.After(time.Duration(3) * time.Second)
 | 
			
		||||
	select {
 | 
			
		||||
	case res := <-resChan:
 | 
			
		||||
		return res.itsMe, res.deb, res.err
 | 
			
		||||
	case <-timeout:
 | 
			
		||||
		time.Sleep(100 * time.Millisecond)
 | 
			
		||||
		return detectDebian(c)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// checkScanModes checks scan mode
 | 
			
		||||
func (s Scanner) checkScanModes() error {
 | 
			
		||||
	for _, s := range servers {
 | 
			
		||||
		if err := s.checkScanMode(); err != nil {
 | 
			
		||||
			return xerrors.Errorf("servers.%s.scanMode err: %w",
 | 
			
		||||
				s.getServerInfo().GetServerName(), err)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// checkDependencies checks dependencies are installed on target servers.
 | 
			
		||||
func (s Scanner) checkDependencies() {
 | 
			
		||||
	parallelExec(func(o osTypeInterface) error {
 | 
			
		||||
		return o.checkDeps()
 | 
			
		||||
	}, s.TimeoutSec)
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// checkIfSudoNoPasswd checks whether vuls can sudo with nopassword via SSH
 | 
			
		||||
func (s Scanner) checkIfSudoNoPasswd() {
 | 
			
		||||
	parallelExec(func(o osTypeInterface) error {
 | 
			
		||||
		return o.checkIfSudoNoPasswd()
 | 
			
		||||
	}, s.TimeoutSec)
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// detectPlatform detects the platform of each servers.
 | 
			
		||||
func (s Scanner) detectPlatform() {
 | 
			
		||||
	parallelExec(func(o osTypeInterface) error {
 | 
			
		||||
		o.detectPlatform()
 | 
			
		||||
		// Logging only if platform can not be specified
 | 
			
		||||
		return nil
 | 
			
		||||
	}, s.TimeoutSec)
 | 
			
		||||
 | 
			
		||||
	for i, s := range servers {
 | 
			
		||||
		if s.getServerInfo().IsContainer() {
 | 
			
		||||
			util.Log.Infof("(%d/%d) %s on %s is running on %s",
 | 
			
		||||
				i+1, len(servers),
 | 
			
		||||
				s.getServerInfo().Container.Name,
 | 
			
		||||
				s.getServerInfo().ServerName,
 | 
			
		||||
				s.getPlatform().Name,
 | 
			
		||||
			)
 | 
			
		||||
 | 
			
		||||
		} else {
 | 
			
		||||
			util.Log.Infof("(%d/%d) %s is running on %s",
 | 
			
		||||
				i+1, len(servers),
 | 
			
		||||
				s.getServerInfo().ServerName,
 | 
			
		||||
				s.getPlatform().Name,
 | 
			
		||||
			)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// detectIPS detects the IPS of each servers.
 | 
			
		||||
func (s Scanner) detectIPS() {
 | 
			
		||||
	parallelExec(func(o osTypeInterface) error {
 | 
			
		||||
		o.detectIPS()
 | 
			
		||||
		// Logging only if IPS can not be specified
 | 
			
		||||
		return nil
 | 
			
		||||
	}, s.TimeoutSec)
 | 
			
		||||
 | 
			
		||||
	for i, s := range servers {
 | 
			
		||||
		if !s.getServerInfo().IsContainer() {
 | 
			
		||||
			util.Log.Infof("(%d/%d) %s has %d IPS integration",
 | 
			
		||||
				i+1, len(servers),
 | 
			
		||||
				s.getServerInfo().ServerName,
 | 
			
		||||
				len(s.getServerInfo().IPSIdentifiers),
 | 
			
		||||
			)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// execScan scan
 | 
			
		||||
func (s Scanner) execScan() error {
 | 
			
		||||
	if len(servers) == 0 {
 | 
			
		||||
		return xerrors.New("No server defined. Check the configuration")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := s.setupChangelogCache(); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	defer func() {
 | 
			
		||||
		if cache.DB != nil {
 | 
			
		||||
			cache.DB.Close()
 | 
			
		||||
		}
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	scannedAt := time.Now()
 | 
			
		||||
	dir, err := EnsureResultDir(scannedAt)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	results, err := s.getScanResults(scannedAt)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for i, r := range results {
 | 
			
		||||
		if server, ok := s.Targets[r.ServerName]; ok {
 | 
			
		||||
			results[i] = r.ClearFields(server.IgnoredJSONKeys)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return writeScanResults(dir, results)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s Scanner) setupChangelogCache() error {
 | 
			
		||||
	needToSetupCache := false
 | 
			
		||||
	for _, s := range servers {
 | 
			
		||||
		switch s.getDistro().Family {
 | 
			
		||||
		case constant.Raspbian:
 | 
			
		||||
			needToSetupCache = true
 | 
			
		||||
			break
 | 
			
		||||
		case constant.Ubuntu, constant.Debian:
 | 
			
		||||
			//TODO changelog cache for RedHat, Oracle, Amazon, CentOS is not implemented yet.
 | 
			
		||||
			if s.getServerInfo().Mode.IsDeep() {
 | 
			
		||||
				needToSetupCache = true
 | 
			
		||||
			}
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if needToSetupCache {
 | 
			
		||||
		if err := cache.SetupBolt(s.CacheDBPath, util.Log); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// getScanResults returns ScanResults
 | 
			
		||||
func (s Scanner) getScanResults(scannedAt time.Time) (results models.ScanResults, err error) {
 | 
			
		||||
	parallelExec(func(o osTypeInterface) (err error) {
 | 
			
		||||
		if o.getServerInfo().Module.IsScanOSPkg() {
 | 
			
		||||
			if err = o.preCure(); err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
			if err = o.scanPackages(); err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
			if err = o.postScan(); err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		if o.getServerInfo().Module.IsScanPort() {
 | 
			
		||||
			if err = o.scanPorts(); err != nil {
 | 
			
		||||
				return xerrors.Errorf("Failed to scan Ports: %w", err)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		if o.getServerInfo().Module.IsScanWordPress() {
 | 
			
		||||
			if err = o.scanWordPress(); err != nil {
 | 
			
		||||
				return xerrors.Errorf("Failed to scan WordPress: %w", err)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		if o.getServerInfo().Module.IsScanLockFile() {
 | 
			
		||||
			if err = o.scanLibraries(); err != nil {
 | 
			
		||||
				return xerrors.Errorf("Failed to scan Library: %w", err)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		return nil
 | 
			
		||||
	}, s.ScanTimeoutSec)
 | 
			
		||||
 | 
			
		||||
	hostname, _ := os.Hostname()
 | 
			
		||||
	ipv4s, ipv6s, err := util.IP()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		util.Log.Errorf("Failed to fetch scannedIPs. err: %+v", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, s := range append(servers, errServers...) {
 | 
			
		||||
		r := s.convertToModel()
 | 
			
		||||
		r.CheckEOL()
 | 
			
		||||
		r.ScannedAt = scannedAt
 | 
			
		||||
		r.ScannedVersion = config.Version
 | 
			
		||||
		r.ScannedRevision = config.Revision
 | 
			
		||||
		r.ScannedBy = hostname
 | 
			
		||||
		r.ScannedIPv4Addrs = ipv4s
 | 
			
		||||
		r.ScannedIPv6Addrs = ipv6s
 | 
			
		||||
		r.Config.Scan = config.Conf
 | 
			
		||||
		results = append(results, r)
 | 
			
		||||
 | 
			
		||||
		if 0 < len(r.Warnings) {
 | 
			
		||||
			util.Log.Warnf("Some warnings occurred during scanning on %s. Please fix the warnings to get a useful information. Execute configtest subcommand before scanning to know the cause of the warnings. warnings: %v",
 | 
			
		||||
				r.ServerName, r.Warnings)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return results, nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										139
									
								
								scanner/serverapi_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										139
									
								
								scanner/serverapi_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,139 @@
 | 
			
		||||
package scanner
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/constant"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestViaHTTP(t *testing.T) {
 | 
			
		||||
	r := newRHEL(config.ServerInfo{})
 | 
			
		||||
	r.Distro = config.Distro{Family: constant.RedHat}
 | 
			
		||||
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		header         map[string]string
 | 
			
		||||
		body           string
 | 
			
		||||
		packages       models.Packages
 | 
			
		||||
		expectedResult models.ScanResult
 | 
			
		||||
		wantErr        error
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			header: map[string]string{
 | 
			
		||||
				"X-Vuls-OS-Release":     "6.9",
 | 
			
		||||
				"X-Vuls-Kernel-Release": "2.6.32-695.20.3.el6.x86_64",
 | 
			
		||||
			},
 | 
			
		||||
			wantErr: errOSFamilyHeader,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			header: map[string]string{
 | 
			
		||||
				"X-Vuls-OS-Family":      "redhat",
 | 
			
		||||
				"X-Vuls-Kernel-Release": "2.6.32-695.20.3.el6.x86_64",
 | 
			
		||||
			},
 | 
			
		||||
			wantErr: errOSReleaseHeader,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			header: map[string]string{
 | 
			
		||||
				"X-Vuls-OS-Family":      "debian",
 | 
			
		||||
				"X-Vuls-OS-Release":     "8",
 | 
			
		||||
				"X-Vuls-Kernel-Release": "2.6.32-695.20.3.el6.x86_64",
 | 
			
		||||
			},
 | 
			
		||||
			wantErr: errKernelVersionHeader,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			header: map[string]string{
 | 
			
		||||
				"X-Vuls-OS-Family":      "centos",
 | 
			
		||||
				"X-Vuls-OS-Release":     "6.9",
 | 
			
		||||
				"X-Vuls-Kernel-Release": "2.6.32-695.20.3.el6.x86_64",
 | 
			
		||||
			},
 | 
			
		||||
			body: `openssl	0	1.0.1e	30.el6.11 x86_64
 | 
			
		||||
			Percona-Server-shared-56	1	5.6.19	rel67.0.el6 x84_64
 | 
			
		||||
			kernel 0 2.6.32 696.20.1.el6 x86_64
 | 
			
		||||
			kernel 0 2.6.32 696.20.3.el6 x86_64
 | 
			
		||||
			kernel 0 2.6.32 695.20.3.el6 x86_64`,
 | 
			
		||||
			expectedResult: models.ScanResult{
 | 
			
		||||
				Family:  "centos",
 | 
			
		||||
				Release: "6.9",
 | 
			
		||||
				RunningKernel: models.Kernel{
 | 
			
		||||
					Release: "2.6.32-695.20.3.el6.x86_64",
 | 
			
		||||
				},
 | 
			
		||||
				Packages: models.Packages{
 | 
			
		||||
					"openssl": models.Package{
 | 
			
		||||
						Name:    "openssl",
 | 
			
		||||
						Version: "1.0.1e",
 | 
			
		||||
						Release: "30.el6.11",
 | 
			
		||||
					},
 | 
			
		||||
					"Percona-Server-shared-56": models.Package{
 | 
			
		||||
						Name:    "Percona-Server-shared-56",
 | 
			
		||||
						Version: "1:5.6.19",
 | 
			
		||||
						Release: "rel67.0.el6",
 | 
			
		||||
					},
 | 
			
		||||
					"kernel": models.Package{
 | 
			
		||||
						Name:    "kernel",
 | 
			
		||||
						Version: "2.6.32",
 | 
			
		||||
						Release: "695.20.3.el6",
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			header: map[string]string{
 | 
			
		||||
				"X-Vuls-OS-Family":      "debian",
 | 
			
		||||
				"X-Vuls-OS-Release":     "8.10",
 | 
			
		||||
				"X-Vuls-Kernel-Release": "3.16.0-4-amd64",
 | 
			
		||||
				"X-Vuls-Kernel-Version": "3.16.51-2",
 | 
			
		||||
			},
 | 
			
		||||
			body: "",
 | 
			
		||||
			expectedResult: models.ScanResult{
 | 
			
		||||
				Family:  "debian",
 | 
			
		||||
				Release: "8.10",
 | 
			
		||||
				RunningKernel: models.Kernel{
 | 
			
		||||
					Release: "3.16.0-4-amd64",
 | 
			
		||||
					Version: "3.16.51-2",
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		header := http.Header{}
 | 
			
		||||
		for k, v := range tt.header {
 | 
			
		||||
			header.Set(k, v)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		result, err := ViaHTTP(header, tt.body, false)
 | 
			
		||||
		if err != tt.wantErr {
 | 
			
		||||
			t.Errorf("error: expected %s, actual: %s", tt.wantErr, err)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if result.Family != tt.expectedResult.Family {
 | 
			
		||||
			t.Errorf("os family: expected %s, actual %s", tt.expectedResult.Family, result.Family)
 | 
			
		||||
		}
 | 
			
		||||
		if result.Release != tt.expectedResult.Release {
 | 
			
		||||
			t.Errorf("os release: expected %s, actual %s", tt.expectedResult.Release, result.Release)
 | 
			
		||||
		}
 | 
			
		||||
		if result.RunningKernel.Release != tt.expectedResult.RunningKernel.Release {
 | 
			
		||||
			t.Errorf("kernel release: expected %s, actual %s",
 | 
			
		||||
				tt.expectedResult.RunningKernel.Release, result.RunningKernel.Release)
 | 
			
		||||
		}
 | 
			
		||||
		if result.RunningKernel.Version != tt.expectedResult.RunningKernel.Version {
 | 
			
		||||
			t.Errorf("kernel version: expected %s, actual %s",
 | 
			
		||||
				tt.expectedResult.RunningKernel.Version, result.RunningKernel.Version)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		for name, expectedPack := range tt.expectedResult.Packages {
 | 
			
		||||
			pack := result.Packages[name]
 | 
			
		||||
			if pack.Name != expectedPack.Name {
 | 
			
		||||
				t.Errorf("name: expected %s, actual %s", expectedPack.Name, pack.Name)
 | 
			
		||||
			}
 | 
			
		||||
			if pack.Version != expectedPack.Version {
 | 
			
		||||
				t.Errorf("version: expected %s, actual %s", expectedPack.Version, pack.Version)
 | 
			
		||||
			}
 | 
			
		||||
			if pack.Release != expectedPack.Release {
 | 
			
		||||
				t.Errorf("release: expected %s, actual %s", expectedPack.Release, pack.Release)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										214
									
								
								scanner/suse.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										214
									
								
								scanner/suse.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,214 @@
 | 
			
		||||
package scanner
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bufio"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"regexp"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/constant"
 | 
			
		||||
	"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) {
 | 
			
		||||
	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() {
 | 
			
		||||
				s := newSUSE(c)
 | 
			
		||||
				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 := newSUSE(c)
 | 
			
		||||
					s.setDistro(constant.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 := newSUSE(c)
 | 
			
		||||
						s.setDistro(constant.SUSEEnterpriseServer,
 | 
			
		||||
							fmt.Sprintf("%s.%s", version, result[1]))
 | 
			
		||||
						return true, s
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
				util.Log.Warnf("Failed to parse SUSE Linux version: %s", r)
 | 
			
		||||
				return true, newSUSE(c)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	util.Log.Debugf("Not SUSE Linux. servername: %s", c.ServerName)
 | 
			
		||||
	return false, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *suse) parseOSRelease(content string) (name string, ver string) {
 | 
			
		||||
	if strings.Contains(content, "ID=opensuse") {
 | 
			
		||||
		//TODO check opensuse or opensuse.leap
 | 
			
		||||
		name = constant.OpenSUSE
 | 
			
		||||
	} else if strings.Contains(content, `NAME="SLES"`) {
 | 
			
		||||
		name = constant.SUSEEnterpriseServer
 | 
			
		||||
	} else if strings.Contains(content, `NAME="SLES_SAP"`) {
 | 
			
		||||
		name = constant.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 {
 | 
			
		||||
	o.log.Infof("Scanning OS pkg in %s", o.getServerInfo().Mode)
 | 
			
		||||
	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
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										142
									
								
								scanner/suse_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										142
									
								
								scanner/suse_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,142 @@
 | 
			
		||||
package scanner
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"reflect"
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/constant"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	"github.com/k0kubun/pp"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestScanUpdatablePackages(t *testing.T) {
 | 
			
		||||
	r := newSUSE(config.ServerInfo{})
 | 
			
		||||
	r.Distro = config.Distro{Family: "sles"}
 | 
			
		||||
	stdout := `S | Repository                                  | Name                          | Current Version             | Available Version           | Arch
 | 
			
		||||
--+---------------------------------------------+-------------------------------+-----------------------------+-----------------------------+-------
 | 
			
		||||
v | SLES12-SP2-Updates                          | SUSEConnect                   | 0.3.0-19.8.1                | 0.3.1-19.11.2               | x86_64
 | 
			
		||||
v | SLES12-SP2-Updates                          | SuSEfirewall2                 | 3.6.312-2.3.1               | 3.6.312-2.10.1              | noarch
 | 
			
		||||
v | Clone of SLES11-SP3-Updates for x86_64 | ConsoleKit | 0.2.10-64.65.1 | 0.2.10-64.69.1 | x86_64`
 | 
			
		||||
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		in  string
 | 
			
		||||
		out models.Packages
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			stdout,
 | 
			
		||||
			models.NewPackages(
 | 
			
		||||
				models.Package{
 | 
			
		||||
					Name:       "SUSEConnect",
 | 
			
		||||
					NewVersion: "0.3.1",
 | 
			
		||||
					NewRelease: "19.11.2",
 | 
			
		||||
					Arch:       "x86_64",
 | 
			
		||||
				},
 | 
			
		||||
				models.Package{
 | 
			
		||||
					Name:       "SuSEfirewall2",
 | 
			
		||||
					NewVersion: "3.6.312",
 | 
			
		||||
					NewRelease: "2.10.1",
 | 
			
		||||
					Arch:       "noarch",
 | 
			
		||||
				},
 | 
			
		||||
				models.Package{
 | 
			
		||||
					Name:       "ConsoleKit",
 | 
			
		||||
					NewVersion: "0.2.10",
 | 
			
		||||
					NewRelease: "64.69.1",
 | 
			
		||||
					Arch:       "x86_64",
 | 
			
		||||
				},
 | 
			
		||||
			),
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		packages, err := r.parseZypperLULines(tt.in)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			t.Errorf("Error has occurred, err: %s\ntt.in: %v", err, tt.in)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		for name, ePack := range tt.out {
 | 
			
		||||
			if !reflect.DeepEqual(ePack, packages[name]) {
 | 
			
		||||
				e := pp.Sprintf("%v", ePack)
 | 
			
		||||
				a := pp.Sprintf("%v", packages[name])
 | 
			
		||||
				t.Errorf("expected %s, actual %s", e, a)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestScanUpdatablePackage(t *testing.T) {
 | 
			
		||||
	r := newSUSE(config.ServerInfo{})
 | 
			
		||||
	r.Distro = config.Distro{Family: "sles"}
 | 
			
		||||
	stdout := `v | SLES12-SP2-Updates                          | SUSEConnect                   | 0.3.0-19.8.1                | 0.3.1-19.11.2               | x86_64`
 | 
			
		||||
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		in  string
 | 
			
		||||
		out models.Package
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			stdout,
 | 
			
		||||
			models.Package{
 | 
			
		||||
				Name:       "SUSEConnect",
 | 
			
		||||
				NewVersion: "0.3.1",
 | 
			
		||||
				NewRelease: "19.11.2",
 | 
			
		||||
				Arch:       "x86_64",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		pack, err := r.parseZypperLUOneLine(tt.in)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			t.Errorf("Error has occurred, err: %s\ntt.in: %v", err, tt.in)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		if !reflect.DeepEqual(*pack, tt.out) {
 | 
			
		||||
			e := pp.Sprintf("%v", tt.out)
 | 
			
		||||
			a := pp.Sprintf("%v", pack)
 | 
			
		||||
			t.Errorf("expected %s, actual %s", e, a)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestParseOSRelease(t *testing.T) {
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		in   string
 | 
			
		||||
		name string
 | 
			
		||||
		ver  string
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			in: `NAME="openSUSE Leap"
 | 
			
		||||
ID=opensuse
 | 
			
		||||
VERSION_ID="42.3.4"`,
 | 
			
		||||
			name: constant.OpenSUSE,
 | 
			
		||||
			ver:  "42.3.4",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			in: `NAME="SLES"
 | 
			
		||||
VERSION="12-SP1"
 | 
			
		||||
VERSION_ID="12.1"
 | 
			
		||||
ID="sles"`,
 | 
			
		||||
			name: constant.SUSEEnterpriseServer,
 | 
			
		||||
			ver:  "12.1",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			in: `NAME="SLES_SAP"
 | 
			
		||||
VERSION="12-SP1"
 | 
			
		||||
VERSION_ID="12.1.0.1"
 | 
			
		||||
ID="sles"`,
 | 
			
		||||
			name: constant.SUSEEnterpriseServer,
 | 
			
		||||
			ver:  "12.1.0.1",
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	r := newSUSE(config.ServerInfo{})
 | 
			
		||||
	for i, tt := range tests {
 | 
			
		||||
		name, ver := r.parseOSRelease(tt.in)
 | 
			
		||||
		if tt.name != name {
 | 
			
		||||
			t.Errorf("[%d] expected %s, actual %s", i, tt.name, name)
 | 
			
		||||
		}
 | 
			
		||||
		if tt.ver != ver {
 | 
			
		||||
			t.Errorf("[%d] expected %s, actual %s", i, tt.ver, ver)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										36
									
								
								scanner/unknownDistro.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								scanner/unknownDistro.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,36 @@
 | 
			
		||||
package scanner
 | 
			
		||||
 | 
			
		||||
import "github.com/future-architect/vuls/models"
 | 
			
		||||
 | 
			
		||||
// inherit OsTypeInterface
 | 
			
		||||
type unknown struct {
 | 
			
		||||
	base
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *unknown) checkScanMode() error {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *unknown) checkIfSudoNoPasswd() error {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *unknown) checkDeps() error {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *unknown) preCure() error {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *unknown) postScan() error {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *unknown) scanPackages() error {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *unknown) parseInstalledPackages(string) (models.Packages, models.SrcPackages, error) {
 | 
			
		||||
	return nil, nil, nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										96
									
								
								scanner/utils.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										96
									
								
								scanner/utils.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,96 @@
 | 
			
		||||
package scanner
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/constant"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	"github.com/future-architect/vuls/reporter"
 | 
			
		||||
	"github.com/future-architect/vuls/util"
 | 
			
		||||
	"golang.org/x/xerrors"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func isRunningKernel(pack models.Package, family string, kernel models.Kernel) (isKernel, running bool) {
 | 
			
		||||
	switch family {
 | 
			
		||||
	case constant.SUSEEnterpriseServer:
 | 
			
		||||
		if pack.Name == "kernel-default" {
 | 
			
		||||
			// Remove the last period and later because uname don't show that.
 | 
			
		||||
			ss := strings.Split(pack.Release, ".")
 | 
			
		||||
			rel := strings.Join(ss[0:len(ss)-1], ".")
 | 
			
		||||
			ver := fmt.Sprintf("%s-%s-default", pack.Version, rel)
 | 
			
		||||
			return true, kernel.Release == ver
 | 
			
		||||
		}
 | 
			
		||||
		return false, false
 | 
			
		||||
 | 
			
		||||
	case constant.RedHat, constant.Oracle, constant.CentOS, constant.Amazon:
 | 
			
		||||
		switch pack.Name {
 | 
			
		||||
		case "kernel", "kernel-devel":
 | 
			
		||||
			ver := fmt.Sprintf("%s-%s.%s", pack.Version, pack.Release, pack.Arch)
 | 
			
		||||
			return true, kernel.Release == ver
 | 
			
		||||
		}
 | 
			
		||||
		return false, false
 | 
			
		||||
 | 
			
		||||
	default:
 | 
			
		||||
		util.Log.Warnf("Reboot required is not implemented yet: %s, %v", family, kernel)
 | 
			
		||||
	}
 | 
			
		||||
	return false, false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// EnsureResultDir ensures the directory for scan results
 | 
			
		||||
func EnsureResultDir(scannedAt time.Time) (currentDir string, err error) {
 | 
			
		||||
	jsonDirName := scannedAt.Format(time.RFC3339)
 | 
			
		||||
 | 
			
		||||
	resultsDir := config.Conf.ResultsDir
 | 
			
		||||
	if len(resultsDir) == 0 {
 | 
			
		||||
		wd, _ := os.Getwd()
 | 
			
		||||
		resultsDir = filepath.Join(wd, "results")
 | 
			
		||||
	}
 | 
			
		||||
	jsonDir := filepath.Join(resultsDir, jsonDirName)
 | 
			
		||||
	if err := os.MkdirAll(jsonDir, 0700); err != nil {
 | 
			
		||||
		return "", xerrors.Errorf("Failed to create dir: %w", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	symlinkPath := filepath.Join(resultsDir, "current")
 | 
			
		||||
	if _, err := os.Lstat(symlinkPath); err == nil {
 | 
			
		||||
		if err := os.Remove(symlinkPath); err != nil {
 | 
			
		||||
			return "", xerrors.Errorf(
 | 
			
		||||
				"Failed to remove symlink. path: %s, err: %w", symlinkPath, err)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := os.Symlink(jsonDir, symlinkPath); err != nil {
 | 
			
		||||
		return "", xerrors.Errorf(
 | 
			
		||||
			"Failed to create symlink: path: %s, err: %w", symlinkPath, err)
 | 
			
		||||
	}
 | 
			
		||||
	return jsonDir, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func writeScanResults(jsonDir string, results models.ScanResults) error {
 | 
			
		||||
	ws := []reporter.ResultWriter{reporter.LocalFileWriter{
 | 
			
		||||
		CurrentDir: jsonDir,
 | 
			
		||||
		FormatJSON: true,
 | 
			
		||||
	}}
 | 
			
		||||
	for _, w := range ws {
 | 
			
		||||
		if err := w.Write(results...); err != nil {
 | 
			
		||||
			return xerrors.Errorf("Failed to write summary: %s", err)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	reporter.StdoutWriter{}.WriteScanSummary(results...)
 | 
			
		||||
 | 
			
		||||
	errServerNames := []string{}
 | 
			
		||||
	for _, r := range results {
 | 
			
		||||
		if 0 < len(r.Errors) {
 | 
			
		||||
			errServerNames = append(errServerNames, r.ServerName)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if 0 < len(errServerNames) {
 | 
			
		||||
		return fmt.Errorf("An error occurred on %s", errServerNames)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										103
									
								
								scanner/utils_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										103
									
								
								scanner/utils_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,103 @@
 | 
			
		||||
package scanner
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/constant"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestIsRunningKernelSUSE(t *testing.T) {
 | 
			
		||||
	r := newSUSE(config.ServerInfo{})
 | 
			
		||||
	r.Distro = config.Distro{Family: constant.SUSEEnterpriseServer}
 | 
			
		||||
 | 
			
		||||
	kernel := models.Kernel{
 | 
			
		||||
		Release: "4.4.74-92.35-default",
 | 
			
		||||
		Version: "",
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		pack     models.Package
 | 
			
		||||
		family   string
 | 
			
		||||
		kernel   models.Kernel
 | 
			
		||||
		expected bool
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			pack: models.Package{
 | 
			
		||||
				Name:    "kernel-default",
 | 
			
		||||
				Version: "4.4.74",
 | 
			
		||||
				Release: "92.35.1",
 | 
			
		||||
				Arch:    "x86_64",
 | 
			
		||||
			},
 | 
			
		||||
			family:   constant.SUSEEnterpriseServer,
 | 
			
		||||
			kernel:   kernel,
 | 
			
		||||
			expected: true,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			pack: models.Package{
 | 
			
		||||
				Name:    "kernel-default",
 | 
			
		||||
				Version: "4.4.59",
 | 
			
		||||
				Release: "92.20.2",
 | 
			
		||||
				Arch:    "x86_64",
 | 
			
		||||
			},
 | 
			
		||||
			family:   constant.SUSEEnterpriseServer,
 | 
			
		||||
			kernel:   kernel,
 | 
			
		||||
			expected: false,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for i, tt := range tests {
 | 
			
		||||
		_, actual := isRunningKernel(tt.pack, tt.family, tt.kernel)
 | 
			
		||||
		if tt.expected != actual {
 | 
			
		||||
			t.Errorf("[%d] expected %t, actual %t", i, tt.expected, actual)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestIsRunningKernelRedHatLikeLinux(t *testing.T) {
 | 
			
		||||
	r := newAmazon(config.ServerInfo{})
 | 
			
		||||
	r.Distro = config.Distro{Family: constant.Amazon}
 | 
			
		||||
 | 
			
		||||
	kernel := models.Kernel{
 | 
			
		||||
		Release: "4.9.43-17.38.amzn1.x86_64",
 | 
			
		||||
		Version: "",
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		pack     models.Package
 | 
			
		||||
		family   string
 | 
			
		||||
		kernel   models.Kernel
 | 
			
		||||
		expected bool
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			pack: models.Package{
 | 
			
		||||
				Name:    "kernel",
 | 
			
		||||
				Version: "4.9.43",
 | 
			
		||||
				Release: "17.38.amzn1",
 | 
			
		||||
				Arch:    "x86_64",
 | 
			
		||||
			},
 | 
			
		||||
			family:   constant.Amazon,
 | 
			
		||||
			kernel:   kernel,
 | 
			
		||||
			expected: true,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			pack: models.Package{
 | 
			
		||||
				Name:    "kernel",
 | 
			
		||||
				Version: "4.9.38",
 | 
			
		||||
				Release: "16.35.amzn1",
 | 
			
		||||
				Arch:    "x86_64",
 | 
			
		||||
			},
 | 
			
		||||
			family:   constant.Amazon,
 | 
			
		||||
			kernel:   kernel,
 | 
			
		||||
			expected: false,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for i, tt := range tests {
 | 
			
		||||
		_, actual := isRunningKernel(tt.pack, tt.family, tt.kernel)
 | 
			
		||||
		if tt.expected != actual {
 | 
			
		||||
			t.Errorf("[%d] expected %t, actual %t", i, tt.expected, actual)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user