v0.5.0 (no backwards compatibility) (#478)
* Change config.toml, Auto-generate UUIDs, change structure of optional field * Detect processes affected by update using yum-ps (#482) Detect processes affected by update using yum-ps * Detect processes needs restart using checkrestart on Debian and Ubuntu. * pass cpename by args when calling FillCveInfo (#513) * fix new db (#502) * Include Version,Revision in JSON * Include hostname in JSON * Update goval-dictionary's commit hash in Gopkg.lock * Remove README.ja.md * update packages (#596) * fix: change ControlPath to .vuls of SSH option (#618) * feat: checkrestart for Ubuntu and Debian (#622) * feat: checkrestart for Ubuntu and Debian * fix: dependencies check logic of configtest * feat: need-restarting on RedHat * refactor: Process.ProcName to Process.Name * feat: detect a systemd service name of need-restarting-process * feat: detect a systemd service name of need-restarting-process on Ubuntu * feat: fill a service name of need-restarting-process, init-system * Support NVD JSON and CVSS3 of JVN (#605) * fix: compile errors * fix: Show CVSS3 on TUI * fix: test cases * fix: Avoid null in JSON * Fix maxCvssScore (#621) * Fix maxCvssScore * Update vulninfos.go * fix(init): remove unnecessary log initialization * refactor(nvd): use only json feed if exists json data. if not, use xml feed * fix(scan): make Confidence slice * feat(CWE): Display CWE name to TUI * feat(cwe): import CWE defs in Japanese * feat(cwe): add OWASP Top 10 ranking to CWE if applicable * feat(scan): add -fast-root mode, implement scan/amazon.go * refactor(const): change const name JVN to Jvn * feat(scan): add -fast-root mode, implement scan/centos.go * refactor(dep): update deps * fix(amazon): deps check * feat(scan): add -fast-root mode, implement scan/rhel.go * feat(scan): add -fast-root mode, implement scan/oracle.go * fix complile err * feat(scan): add -fast-root mode, implement scan/debian.go * fix testcase * fix(amazon): scan using yum * fix(configtest): change error message, status when no scannnable servers * Fix(scan): detect init process logic * fix(tui): display cvss as table format * fix(scan): parse a output of reboot-notifier on CentOS6.9 * fix(tui): don't display score, vector when score is zero * fix(scan): add -offline mode to suse scanner * fix(scan): fix help message * feat(scan): enable to define scan mode for each servers in config.toml #510 * refactor(config): chagne cpeNames to cpeURIs * refactor(config): change dependencyCheckXMLPath to owaspDCXMLPath * fix(config): containers -> containersIncluded, Excluded, containerType * feature(report): enable to define cpeURIs for each contaner * feature(report): enable to specify owasp dc xml path for each container * fix(discover): fix a template displayed at the end of discover * feature(report): add ignorePkgsRegexp #665 * feature(report): enable to define ignoreCves for each container #666 * fix(report): Displayed nothing in TUI detail area when CweID is nil * Gopkg.toml diet * feat(server): support server mode (#678) * feat(server): support server mode * Lock go version * Use the latest kernel release among the installed release when the running kernel release is unknown * Add TestViaHTTP * Set logger to go-cve-dictionary client * Add -to-localfile * Add -to-http option to report * Load -to-http conf from config.toml * Support gost (#676) * feat(gost): Support RedHat API * feat(gost): Support Debian Security Tracker * feat(db): display error msg when SQLite3 is locked at the beginning of reporting. * feat(gost): TUI * Only use RedHat information of installed packages * feat(tui): show mitigation on TUI * feat(gost): support redis backend * fix test case * fix nil pointer when db is nil * fix(gost): detect vulns of src packages for Debian * feat(gost): implement redis backend for gost redhat api * feat(report): display fixState of unfixed pkgs * fix(report): display distincted cweIDs * feat(slack): display gost info * feat(slack): display mitigation * feat(report): display available patch state as fixed/total * fix(tui): display - if source of reference is empty * update deps * fix(report): key in ScanResult JSON be lowerCamelcase. * some keys to lower camel * fix(configtest): dep check logic of yum-plugin-ps * fix(tui): format * feat(report): add -format-list option * fix(report): -format-full-text * fix(report): report -format-full-text * fix(report): display v3 score detected by gost * fix(scan): scan in fast mode if not defined in config.toml * fix(gost): fetch RedHat data for fixed CVEs * feat(report): show number of cves detected in each database * fix(report): show new version as `Unknown` in offline and fast scan mode * fix(report): fix num of upadtable and fixed * fix(report): set `Not fixed yet` if packageStatus is empty * refact(gost): make convertToModel public * fix(test): fix test case * update deps * fix(report): include gost score in MaxCvssScore * [WIP] feat(config): enable to set options in config.toml instead of cmd opt (#690) * feat(config): enable to set options in config.toml instead of cmd opt * fix(config): change Conf.Report.Slack to Conf.Slack * fix(discover): change tempalte * fix(report): fix config.toml auto-generate with -uuid * Add endpoint for health check and change endpoint * refact(cmd): refactor flag set * fix(report): enable to specify opts with cmd arg and env value * fix(scan): enable to parse the release version of amazon linux 2 * add(report) add -to-saas option (#695) * add(report) add -to-saas option * ignore other writer if -to-saas * fix(saas) fix bug * fix(scan): need-restarting needs internet connection * fix(scan,configtest): check scan mode * refactor(scan): change func name * fix(suse): support offline mode, bug fix on AWS, zypper --no-color * fix(tui): fix nil pointer when no vulns in tui * feat(report): enable to define CPE FS format in config.toml * fix(vet): fix warnings of go vet * fix(travis): go version to 1.11 * update deps
This commit is contained in:
@@ -64,7 +64,14 @@ func detectAlpine(c config.ServerInfo) (itsMe bool, os osTypeInterface) {
|
||||
return false, os
|
||||
}
|
||||
|
||||
func (o *alpine) checkDependencies() error {
|
||||
func (o *alpine) checkScanMode() error {
|
||||
if o.getServerInfo().Mode.IsOffline() {
|
||||
return fmt.Errorf("Remove offline scan mode, Alpine needs internet connection")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *alpine) checkDeps() error {
|
||||
o.log.Infof("Dependencies... No need")
|
||||
return nil
|
||||
}
|
||||
@@ -83,6 +90,7 @@ func (o *alpine) apkUpdate() error {
|
||||
}
|
||||
|
||||
func (o *alpine) preCure() error {
|
||||
o.log.Infof("Scanning in %s", o.getServerInfo().Mode)
|
||||
if err := o.detectIPAddr(); err != nil {
|
||||
o.log.Debugf("Failed to detect IP addresses: %s", err)
|
||||
}
|
||||
@@ -140,6 +148,11 @@ func (o *alpine) scanInstalledPackages() (models.Packages, error) {
|
||||
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))
|
||||
|
||||
114
scan/amazon.go
Normal file
114
scan/amazon.go
Normal file
@@ -0,0 +1,114 @@
|
||||
package scan
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/future-architect/vuls/config"
|
||||
"github.com/future-architect/vuls/models"
|
||||
"github.com/future-architect/vuls/util"
|
||||
)
|
||||
|
||||
// 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 {
|
||||
if o.getServerInfo().Mode.IsOffline() {
|
||||
return fmt.Errorf("Remove offline scan mode, Amazon needs internet connection")
|
||||
}
|
||||
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 fmt.Errorf("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",
|
||||
"yum-plugin-ps",
|
||||
}
|
||||
}
|
||||
|
||||
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{
|
||||
{"yum -q ps all --color=never", exitStatusZero},
|
||||
{"stat /proc/1/exe", exitStatusZero},
|
||||
{"needs-restarting", exitStatusZero},
|
||||
{"which which", exitStatusZero},
|
||||
}
|
||||
}
|
||||
|
||||
func (o *amazon) sudoNoPasswdCmdsDeep() []cmd {
|
||||
return o.sudoNoPasswdCmdsFastRoot()
|
||||
}
|
||||
|
||||
type rootPrivAmazon struct{}
|
||||
|
||||
func (o rootPrivAmazon) repoquery() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (o rootPrivAmazon) yumRepolist() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (o rootPrivAmazon) yumUpdateInfo() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (o rootPrivAmazon) yumChangelog() bool {
|
||||
return false
|
||||
}
|
||||
81
scan/base.go
81
scan/base.go
@@ -18,6 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
package scan
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"net"
|
||||
"regexp"
|
||||
@@ -33,7 +34,6 @@ type base struct {
|
||||
ServerInfo config.ServerInfo
|
||||
Distro config.Distro
|
||||
Platform models.Platform
|
||||
|
||||
osPackages
|
||||
|
||||
log *logrus.Entry
|
||||
@@ -98,7 +98,7 @@ func (l *base) runningKernel() (release, version string, err error) {
|
||||
}
|
||||
|
||||
func (l *base) allContainers() (containers []config.Container, err error) {
|
||||
switch l.ServerInfo.Containers.Type {
|
||||
switch l.ServerInfo.ContainerType {
|
||||
case "", "docker":
|
||||
stdout, err := l.dockerPs("-a --format '{{.ID}} {{.Names}} {{.Image}}'")
|
||||
if err != nil {
|
||||
@@ -119,12 +119,12 @@ func (l *base) allContainers() (containers []config.Container, err error) {
|
||||
return l.parseLxcPs(stdout)
|
||||
default:
|
||||
return containers, fmt.Errorf(
|
||||
"Not supported yet: %s", l.ServerInfo.Containers.Type)
|
||||
"Not supported yet: %s", l.ServerInfo.ContainerType)
|
||||
}
|
||||
}
|
||||
|
||||
func (l *base) runningContainers() (containers []config.Container, err error) {
|
||||
switch l.ServerInfo.Containers.Type {
|
||||
switch l.ServerInfo.ContainerType {
|
||||
case "", "docker":
|
||||
stdout, err := l.dockerPs("--format '{{.ID}} {{.Names}} {{.Image}}'")
|
||||
if err != nil {
|
||||
@@ -145,12 +145,12 @@ func (l *base) runningContainers() (containers []config.Container, err error) {
|
||||
return l.parseLxcPs(stdout)
|
||||
default:
|
||||
return containers, fmt.Errorf(
|
||||
"Not supported yet: %s", l.ServerInfo.Containers.Type)
|
||||
"Not supported yet: %s", l.ServerInfo.ContainerType)
|
||||
}
|
||||
}
|
||||
|
||||
func (l *base) exitedContainers() (containers []config.Container, err error) {
|
||||
switch l.ServerInfo.Containers.Type {
|
||||
switch l.ServerInfo.ContainerType {
|
||||
case "", "docker":
|
||||
stdout, err := l.dockerPs("--filter 'status=exited' --format '{{.ID}} {{.Names}} {{.Image}}'")
|
||||
if err != nil {
|
||||
@@ -171,7 +171,7 @@ func (l *base) exitedContainers() (containers []config.Container, err error) {
|
||||
return l.parseLxcPs(stdout)
|
||||
default:
|
||||
return containers, fmt.Errorf(
|
||||
"Not supported yet: %s", l.ServerInfo.Containers.Type)
|
||||
"Not supported yet: %s", l.ServerInfo.ContainerType)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -296,6 +296,10 @@ func (l *base) parseIP(stdout string) (ipv4Addrs []string, ipv6Addrs []string) {
|
||||
}
|
||||
|
||||
func (l *base) detectPlatform() {
|
||||
if l.getServerInfo().Mode.IsOffline() {
|
||||
l.setPlatform(models.Platform{Name: "unknown"})
|
||||
return
|
||||
}
|
||||
ok, instanceID, err := l.detectRunningOnAws()
|
||||
if err != nil {
|
||||
l.setPlatform(models.Platform{Name: "other"})
|
||||
@@ -316,7 +320,7 @@ func (l *base) detectPlatform() {
|
||||
|
||||
func (l *base) detectRunningOnAws() (ok bool, instanceID string, err error) {
|
||||
if r := l.exec("type curl", noSudo); r.isSuccess() {
|
||||
cmd := "curl --max-time 1 --retry 3 --noproxy 169.254.169.254 http://169.254.169.254/latest/meta-data/instance-id"
|
||||
cmd := "curl --max-time 1 --noproxy 169.254.169.254 http://169.254.169.254/latest/meta-data/instance-id"
|
||||
r := l.exec(cmd, noSudo)
|
||||
if r.isSuccess() {
|
||||
id := strings.TrimSpace(r.Stdout)
|
||||
@@ -367,7 +371,7 @@ func (l *base) isAwsInstanceID(str string) bool {
|
||||
}
|
||||
|
||||
func (l *base) convertToModel() models.ScanResult {
|
||||
ctype := l.ServerInfo.Containers.Type
|
||||
ctype := l.ServerInfo.ContainerType
|
||||
if l.ServerInfo.Container.ContainerID != "" && ctype == "" {
|
||||
ctype = "docker"
|
||||
}
|
||||
@@ -409,3 +413,62 @@ func (l *base) setErrs(errs []error) {
|
||||
func (l *base) getErrs() []error {
|
||||
return l.errs
|
||||
}
|
||||
|
||||
const (
|
||||
systemd = "systemd"
|
||||
upstart = "upstart"
|
||||
sysVinit = "init"
|
||||
)
|
||||
|
||||
// https://unix.stackexchange.com/questions/196166/how-to-find-out-if-a-system-uses-sysv-upstart-or-systemd-initsystem
|
||||
func (l *base) detectInitSystem() (string, error) {
|
||||
var f func(string) (string, error)
|
||||
f = func(cmd string) (string, error) {
|
||||
r := l.exec(cmd, sudo)
|
||||
if !r.isSuccess() {
|
||||
return "", fmt.Errorf("Failed to stat %s: %s", cmd, r)
|
||||
}
|
||||
scanner := bufio.NewScanner(strings.NewReader(r.Stdout))
|
||||
scanner.Scan()
|
||||
line := strings.TrimSpace(scanner.Text())
|
||||
if strings.Contains(line, "systemd") {
|
||||
return systemd, nil
|
||||
} else if strings.Contains(line, "upstart") {
|
||||
return upstart, nil
|
||||
} else if strings.Contains(line, "File: ‘/proc/1/exe’ -> ‘/sbin/init’") ||
|
||||
strings.Contains(line, "File: `/proc/1/exe' -> `/sbin/init'") {
|
||||
return f("stat /sbin/init")
|
||||
} else if line == "File: ‘/sbin/init’" ||
|
||||
line == "File: `/sbin/init'" {
|
||||
r := l.exec("/sbin/init --version", noSudo)
|
||||
if r.isSuccess() {
|
||||
if strings.Contains(r.Stdout, "upstart") {
|
||||
return upstart, nil
|
||||
}
|
||||
}
|
||||
return sysVinit, nil
|
||||
}
|
||||
return "", fmt.Errorf("Failed to detect a init system: %s", line)
|
||||
}
|
||||
return f("stat /proc/1/exe")
|
||||
}
|
||||
|
||||
func (l *base) detectServiceName(pid string) (string, error) {
|
||||
cmd := fmt.Sprintf("systemctl status --quiet --no-pager %s", pid)
|
||||
r := l.exec(cmd, noSudo)
|
||||
if !r.isSuccess() {
|
||||
return "", fmt.Errorf("Failed to stat %s: %s", cmd, r)
|
||||
}
|
||||
return l.parseSystemctlStatus(r.Stdout), nil
|
||||
}
|
||||
|
||||
func (l *base) parseSystemctlStatus(stdout string) string {
|
||||
scanner := bufio.NewScanner(strings.NewReader(stdout))
|
||||
scanner.Scan()
|
||||
line := scanner.Text()
|
||||
ss := strings.Fields(line)
|
||||
if len(ss) < 2 || strings.HasPrefix(line, "Failed to get unit for PID") {
|
||||
return ""
|
||||
}
|
||||
return ss[1]
|
||||
}
|
||||
|
||||
@@ -25,7 +25,6 @@ import (
|
||||
)
|
||||
|
||||
func TestParseDockerPs(t *testing.T) {
|
||||
|
||||
var test = struct {
|
||||
in string
|
||||
expected []config.Container
|
||||
@@ -46,7 +45,7 @@ f570ae647edc agitated_lovelace centos:latest`,
|
||||
},
|
||||
}
|
||||
|
||||
r := newRedhat(config.ServerInfo{})
|
||||
r := newRHEL(config.ServerInfo{})
|
||||
actual, err := r.parseDockerPs(test.in)
|
||||
if err != nil {
|
||||
t.Errorf("Error occurred. in: %s, err: %s", test.in, err)
|
||||
@@ -60,7 +59,6 @@ f570ae647edc agitated_lovelace centos:latest`,
|
||||
}
|
||||
|
||||
func TestParseLxdPs(t *testing.T) {
|
||||
|
||||
var test = struct {
|
||||
in string
|
||||
expected []config.Container
|
||||
@@ -84,7 +82,7 @@ func TestParseLxdPs(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
r := newRedhat(config.ServerInfo{})
|
||||
r := newRHEL(config.ServerInfo{})
|
||||
actual, err := r.parseLxdPs(test.in)
|
||||
if err != nil {
|
||||
t.Errorf("Error occurred. in: %s, err: %s", test.in, err)
|
||||
@@ -117,7 +115,7 @@ func TestParseIp(t *testing.T) {
|
||||
expected6: []string{"2001:db8::68"},
|
||||
}
|
||||
|
||||
r := newRedhat(config.ServerInfo{})
|
||||
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)
|
||||
@@ -140,7 +138,7 @@ func TestIsAwsInstanceID(t *testing.T) {
|
||||
{"no data", false},
|
||||
}
|
||||
|
||||
r := newRedhat(config.ServerInfo{})
|
||||
r := newAmazon(config.ServerInfo{})
|
||||
for _, tt := range tests {
|
||||
actual := r.isAwsInstanceID(tt.in)
|
||||
if tt.expected != actual {
|
||||
@@ -148,3 +146,35 @@ func TestIsAwsInstanceID(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
120
scan/centos.go
Normal file
120
scan/centos.go
Normal file
@@ -0,0 +1,120 @@
|
||||
package scan
|
||||
|
||||
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
|
||||
return []string{"yum-utils"}
|
||||
}
|
||||
|
||||
func (o *centos) depsFastRoot() []string {
|
||||
return []string{
|
||||
"yum-utils",
|
||||
"yum-plugin-ps",
|
||||
}
|
||||
}
|
||||
|
||||
func (o *centos) depsDeep() []string {
|
||||
return []string{
|
||||
"yum-utils",
|
||||
"yum-plugin-ps",
|
||||
"yum-plugin-changelog",
|
||||
}
|
||||
}
|
||||
|
||||
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.getServerInfo().Mode.IsOffline() {
|
||||
// yum ps needs internet connection
|
||||
return []cmd{
|
||||
{"stat /proc/1/exe", exitStatusZero},
|
||||
{"needs-restarting", exitStatusZero},
|
||||
{"which which", exitStatusZero},
|
||||
}
|
||||
}
|
||||
return []cmd{
|
||||
{"yum -q ps all --color=never", exitStatusZero},
|
||||
{"stat /proc/1/exe", exitStatusZero},
|
||||
{"needs-restarting", exitStatusZero},
|
||||
{"which which", exitStatusZero},
|
||||
}
|
||||
}
|
||||
|
||||
func (o *centos) sudoNoPasswdCmdsDeep() []cmd {
|
||||
return o.sudoNoPasswdCmdsFastRoot()
|
||||
}
|
||||
|
||||
type rootPrivCentos struct{}
|
||||
|
||||
func (o rootPrivCentos) repoquery() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (o rootPrivCentos) yumRepolist() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (o rootPrivCentos) yumUpdateInfo() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (o rootPrivCentos) yumChangelog() bool {
|
||||
return false
|
||||
}
|
||||
328
scan/debian.go
328
scan/debian.go
@@ -135,20 +135,47 @@ func trim(str string) string {
|
||||
return strings.TrimSpace(str)
|
||||
}
|
||||
|
||||
func (o *debian) checkScanMode() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *debian) checkIfSudoNoPasswd() error {
|
||||
if config.Conf.Deep || o.Distro.Family == config.Raspbian {
|
||||
cmd := util.PrependProxyEnv("apt-get update")
|
||||
if o.getServerInfo().Mode.IsFast() {
|
||||
o.log.Infof("sudo ... No need")
|
||||
return nil
|
||||
}
|
||||
|
||||
cmds := []string{
|
||||
"checkrestart",
|
||||
"stat /proc/1/exe",
|
||||
}
|
||||
|
||||
if !o.getServerInfo().Mode.IsOffline() {
|
||||
cmds = append(cmds, "apt-get update")
|
||||
}
|
||||
|
||||
for _, cmd := range cmds {
|
||||
cmd = util.PrependProxyEnv(cmd)
|
||||
o.log.Infof("Checking... sudo %s", cmd)
|
||||
r := o.exec(cmd, sudo)
|
||||
if !r.isSuccess() {
|
||||
o.log.Errorf("sudo error on %s", r)
|
||||
return fmt.Errorf("Failed to sudo: %s", r)
|
||||
}
|
||||
o.log.Infof("Sudo... Pass")
|
||||
return nil
|
||||
}
|
||||
|
||||
o.log.Infof("sudo ... No need")
|
||||
initName, err := o.detectInitSystem()
|
||||
if initName == upstart && err == nil {
|
||||
cmd := util.PrependProxyEnv("initctl status --help")
|
||||
o.log.Infof("Checking... sudo %s", cmd)
|
||||
r := o.exec(cmd, sudo)
|
||||
if !r.isSuccess() {
|
||||
o.log.Errorf("sudo error on %s", r)
|
||||
return fmt.Errorf("Failed to sudo: %s", r)
|
||||
}
|
||||
}
|
||||
|
||||
o.log.Infof("Sudo... Pass")
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -159,43 +186,57 @@ type dep struct {
|
||||
additionalMsg string
|
||||
}
|
||||
|
||||
func (o *debian) checkDependencies() error {
|
||||
func (o *debian) checkDeps() error {
|
||||
deps := []dep{}
|
||||
if o.getServerInfo().Mode.IsDeep() || o.getServerInfo().Mode.IsFastRoot() {
|
||||
// checkrestart
|
||||
deps = append(deps, dep{
|
||||
packName: "debian-goodies",
|
||||
required: true,
|
||||
logFunc: o.log.Errorf,
|
||||
})
|
||||
}
|
||||
|
||||
switch o.Distro.Family {
|
||||
case config.Ubuntu, config.Raspbian:
|
||||
o.log.Infof("Dependencies... No need")
|
||||
return nil
|
||||
|
||||
case config.Debian:
|
||||
if o.Distro.Family == config.Debian {
|
||||
// https://askubuntu.com/a/742844
|
||||
if !o.ServerInfo.IsContainer() {
|
||||
deps = append(deps, dep{
|
||||
packName: "reboot-notifier",
|
||||
required: false,
|
||||
logFunc: o.log.Warnf,
|
||||
additionalMsg: ". If you want to detect whether not rebooted after kernel update.",
|
||||
additionalMsg: ". Install it if you want to detect whether not rebooted after kernel update. To install `reboot-notifier` on Debian, see https://feeding.cloud.geek.nz/posts/introducing-reboot-notifier/",
|
||||
})
|
||||
}
|
||||
|
||||
if config.Conf.Deep {
|
||||
// Changelogs will be fetched only in deep scan mode
|
||||
if o.getServerInfo().Mode.IsDeep() {
|
||||
// Debian needs aptitude to get changelogs.
|
||||
// Because unable to get changelogs via apt-get changelog on Debian.
|
||||
// Because unable to get changelogs via `apt-get changelog` on Debian.
|
||||
deps = append(deps, dep{
|
||||
packName: "aptitude",
|
||||
required: true,
|
||||
logFunc: o.log.Errorf,
|
||||
})
|
||||
}
|
||||
|
||||
default:
|
||||
return fmt.Errorf("Not implemented yet: %s", o.Distro)
|
||||
}
|
||||
|
||||
for _, dep := range deps {
|
||||
cmd := "dpkg-query -W " + dep.packName
|
||||
if r := o.exec(cmd, noSudo); !r.isSuccess() {
|
||||
msg := fmt.Sprintf("%s is not installed", dep.packName)
|
||||
cmd := fmt.Sprintf("%s %s", dpkgQuery, dep.packName)
|
||||
msg := fmt.Sprintf("%s is not installed", dep.packName)
|
||||
r := o.exec(cmd, noSudo)
|
||||
if !r.isSuccess() {
|
||||
if dep.additionalMsg != "" {
|
||||
msg += dep.additionalMsg
|
||||
}
|
||||
dep.logFunc(msg)
|
||||
if dep.required {
|
||||
return fmt.Errorf(msg)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
_, status, _, _, _, _ := o.parseScannedPackagesLine(r.Stdout)
|
||||
if status != "ii" {
|
||||
if dep.additionalMsg != "" {
|
||||
msg += dep.additionalMsg
|
||||
}
|
||||
@@ -204,12 +245,14 @@ func (o *debian) checkDependencies() error {
|
||||
return fmt.Errorf(msg)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
o.log.Infof("Dependencies... Pass")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *debian) preCure() error {
|
||||
o.log.Infof("Scanning in %s", o.getServerInfo().Mode)
|
||||
if err := o.detectIPAddr(); err != nil {
|
||||
o.log.Debugf("Failed to detect IP addresses: %s", err)
|
||||
}
|
||||
@@ -218,6 +261,9 @@ func (o *debian) preCure() error {
|
||||
}
|
||||
|
||||
func (o *debian) postScan() error {
|
||||
if o.getServerInfo().Mode.IsDeep() || o.getServerInfo().Mode.IsFastRoot() {
|
||||
return o.checkrestart()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -252,11 +298,11 @@ func (o *debian) scanPackages() error {
|
||||
o.Packages = installed
|
||||
o.SrcPackages = srcPacks
|
||||
|
||||
if config.Conf.Offline {
|
||||
if o.getServerInfo().Mode.IsOffline() {
|
||||
return nil
|
||||
}
|
||||
|
||||
if config.Conf.Deep || o.Distro.Family == config.Raspbian {
|
||||
if o.getServerInfo().Mode.IsDeep() || o.Distro.Family == config.Raspbian {
|
||||
unsecures, err := o.scanUnsecurePackages(updatable)
|
||||
if err != nil {
|
||||
o.log.Errorf("Failed to scan vulnerable packages: %s", err)
|
||||
@@ -281,23 +327,63 @@ func (o *debian) rebootRequired() (bool, error) {
|
||||
}
|
||||
}
|
||||
|
||||
const dpkgQuery = `dpkg-query -W -f="\${binary:Package},\${db:Status-Abbrev},\${Version},\${Source},\${source:Version}\n"`
|
||||
|
||||
func (o *debian) scanInstalledPackages() (models.Packages, models.Packages, models.SrcPackages, error) {
|
||||
installed, updatable, srcPacks := models.Packages{}, models.Packages{}, models.SrcPackages{}
|
||||
r := o.exec(`dpkg-query -W -f="\${binary:Package},\${db:Status-Abbrev},\${Version},\${Source},\${source:Version}\n"`, noSudo)
|
||||
updatable := models.Packages{}
|
||||
r := o.exec(dpkgQuery, noSudo)
|
||||
if !r.isSuccess() {
|
||||
return nil, nil, nil, fmt.Errorf("Failed to SSH: %s", r)
|
||||
}
|
||||
|
||||
installed, srcPacks, err := o.parseInstalledPackages(r.Stdout)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
if o.getServerInfo().Mode.IsOffline() || o.getServerInfo().Mode.IsFast() {
|
||||
return installed, updatable, srcPacks, nil
|
||||
}
|
||||
|
||||
if err := o.aptGetUpdate(); err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
updatableNames, err := o.getUpdatablePackNames()
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
for _, name := range updatableNames {
|
||||
for _, pack := range installed {
|
||||
if pack.Name == name {
|
||||
updatable[name] = pack
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fill the candidate versions of upgradable packages
|
||||
err = o.fillCandidateVersion(updatable)
|
||||
if err != nil {
|
||||
return nil, nil, nil, fmt.Errorf("Failed to fill candidate versions. err: %s", err)
|
||||
}
|
||||
installed.MergeNewVersion(updatable)
|
||||
|
||||
return installed, updatable, srcPacks, nil
|
||||
}
|
||||
|
||||
func (o *debian) parseInstalledPackages(stdout string) (models.Packages, models.SrcPackages, error) {
|
||||
installed, srcPacks := models.Packages{}, models.SrcPackages{}
|
||||
|
||||
// e.g.
|
||||
// curl,ii ,7.38.0-4+deb8u2,,7.38.0-4+deb8u2
|
||||
// openssh-server,ii ,1:6.7p1-5+deb8u3,openssh,1:6.7p1-5+deb8u3
|
||||
// tar,ii ,1.27.1-2+b1,tar (1.27.1-2),1.27.1-2
|
||||
lines := strings.Split(r.Stdout, "\n")
|
||||
lines := strings.Split(stdout, "\n")
|
||||
for _, line := range lines {
|
||||
if trimmed := strings.TrimSpace(line); len(trimmed) != 0 {
|
||||
name, status, version, srcName, srcVersion, err := o.parseScannedPackagesLine(trimmed)
|
||||
if err != nil {
|
||||
return nil, nil, nil, fmt.Errorf(
|
||||
if err != nil || len(status) < 2 {
|
||||
return nil, nil, fmt.Errorf(
|
||||
"Debian: Failed to parse package line: %s", line)
|
||||
}
|
||||
|
||||
@@ -343,35 +429,7 @@ func (o *debian) scanInstalledPackages() (models.Packages, models.Packages, mode
|
||||
for name := range installed {
|
||||
delete(srcPacks, name)
|
||||
}
|
||||
|
||||
if config.Conf.Offline {
|
||||
return installed, updatable, srcPacks, nil
|
||||
}
|
||||
|
||||
if err := o.aptGetUpdate(); err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
updatableNames, err := o.getUpdatablePackNames()
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
for _, name := range updatableNames {
|
||||
for _, pack := range installed {
|
||||
if pack.Name == name {
|
||||
updatable[name] = pack
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fill the candidate versions of upgradable packages
|
||||
err = o.fillCandidateVersion(updatable)
|
||||
if err != nil {
|
||||
return nil, nil, nil, fmt.Errorf("Failed to fill candidate versions. err: %s", err)
|
||||
}
|
||||
installed.MergeNewVersion(updatable)
|
||||
|
||||
return installed, updatable, srcPacks, nil
|
||||
return installed, srcPacks, nil
|
||||
}
|
||||
|
||||
func (o *debian) parseScannedPackagesLine(line string) (name, status, version, srcName, srcVersion string, err error) {
|
||||
@@ -382,7 +440,7 @@ func (o *debian) parseScannedPackagesLine(line string) (name, status, version, s
|
||||
if i := strings.IndexRune(name, ':'); i >= 0 {
|
||||
name = name[:i]
|
||||
}
|
||||
status = ss[1]
|
||||
status = strings.TrimSpace(ss[1])
|
||||
version = ss[2]
|
||||
// remove version. ex: tar (1.27.1-2)
|
||||
srcName = strings.Split(ss[3], " ")[0]
|
||||
@@ -417,7 +475,7 @@ func (o *debian) scanUnsecurePackages(updatable models.Packages) (models.VulnInf
|
||||
}
|
||||
|
||||
// Collect CVE information of upgradable packages
|
||||
vulnInfos, err := o.scanVulnInfos(updatable, meta)
|
||||
vulnInfos, err := o.scanChangelogs(updatable, meta)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed to scan unsecure packages. err: %s", err)
|
||||
}
|
||||
@@ -547,7 +605,7 @@ type DetectedCveID struct {
|
||||
Confidence models.Confidence
|
||||
}
|
||||
|
||||
func (o *debian) scanVulnInfos(updatablePacks models.Packages, meta *cache.Meta) (models.VulnInfos, error) {
|
||||
func (o *debian) scanChangelogs(updatablePacks models.Packages, meta *cache.Meta) (models.VulnInfos, error) {
|
||||
type response struct {
|
||||
pack *models.Package
|
||||
DetectedCveIDs []DetectedCveID
|
||||
@@ -583,7 +641,7 @@ func (o *debian) scanVulnInfos(updatablePacks models.Packages, meta *cache.Meta)
|
||||
// if the changelog is not in cache or failed to get from local cache,
|
||||
// get the changelog of the package via internet.
|
||||
// After that, store it in the cache.
|
||||
if cveIDs, pack, err := o.scanPackageCveIDs(p); err != nil {
|
||||
if cveIDs, pack, err := o.fetchParseChangelog(p); err != nil {
|
||||
errChan <- err
|
||||
} else {
|
||||
resChan <- response{pack, cveIDs}
|
||||
@@ -639,7 +697,7 @@ func (o *debian) scanVulnInfos(updatablePacks models.Packages, meta *cache.Meta)
|
||||
|
||||
vinfos[cveID.CveID] = models.VulnInfo{
|
||||
CveID: cveID.CveID,
|
||||
Confidence: cveID.Confidence,
|
||||
Confidences: models.Confidences{cveID.Confidence},
|
||||
AffectedPackages: affected,
|
||||
}
|
||||
}
|
||||
@@ -681,7 +739,7 @@ func (o *debian) getChangelogCache(meta *cache.Meta, pack models.Package) string
|
||||
return changelog
|
||||
}
|
||||
|
||||
func (o *debian) scanPackageCveIDs(pack models.Package) ([]DetectedCveID, *models.Package, error) {
|
||||
func (o *debian) fetchParseChangelog(pack models.Package) ([]DetectedCveID, *models.Package, error) {
|
||||
cmd := ""
|
||||
switch o.Distro.Family {
|
||||
case config.Ubuntu, config.Raspbian:
|
||||
@@ -760,7 +818,7 @@ func (o *debian) getCveIDsFromChangelog(
|
||||
|
||||
// Only logging the error.
|
||||
o.log.Warnf("Failed to find the version in changelog: %s-%s", name, ver)
|
||||
o.log.Debugf("Changelog of : %s-%s-%s", name, ver, changelog)
|
||||
o.log.Debugf("Changelog of %s-%s: %s", name, ver, changelog)
|
||||
|
||||
// If the version is not in changelog, return entire changelog to put into cache
|
||||
pack := o.Packages[name]
|
||||
@@ -886,3 +944,145 @@ func (o *debian) parseAptCachePolicy(stdout, name string) (packCandidateVer, err
|
||||
}
|
||||
return ver, fmt.Errorf("Unknown Format: %s", stdout)
|
||||
}
|
||||
|
||||
func (o *debian) checkrestart() error {
|
||||
initName, err := o.detectInitSystem()
|
||||
if err != nil {
|
||||
o.log.Warn(err)
|
||||
// continue scanning
|
||||
}
|
||||
|
||||
cmd := "LANGUAGE=en_US.UTF-8 checkrestart"
|
||||
r := o.exec(cmd, sudo)
|
||||
if !r.isSuccess() {
|
||||
return fmt.Errorf(
|
||||
"Failed to %s. status: %d, stdout: %s, stderr: %s",
|
||||
cmd, r.ExitStatus, r.Stdout, r.Stderr)
|
||||
}
|
||||
packs, unknownServices := o.parseCheckRestart(r.Stdout)
|
||||
pidService := map[string]string{}
|
||||
if initName == upstart {
|
||||
for _, s := range unknownServices {
|
||||
cmd := "LANGUAGE=en_US.UTF-8 initctl status " + s
|
||||
r := o.exec(cmd, sudo)
|
||||
if !r.isSuccess() {
|
||||
continue
|
||||
}
|
||||
if ss := strings.Fields(r.Stdout); len(ss) == 4 && ss[2] == "process" {
|
||||
pidService[ss[3]] = s
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for i, p := range packs {
|
||||
pack := o.Packages[p.Name]
|
||||
pack.NeedRestartProcs = p.NeedRestartProcs
|
||||
o.Packages[p.Name] = pack
|
||||
|
||||
for j, proc := range p.NeedRestartProcs {
|
||||
if proc.HasInit == false {
|
||||
continue
|
||||
}
|
||||
packs[i].NeedRestartProcs[j].InitSystem = initName
|
||||
if initName == systemd {
|
||||
name, err := o.detectServiceName(proc.PID)
|
||||
if err != nil {
|
||||
o.log.Warn(err)
|
||||
// continue scanning
|
||||
}
|
||||
packs[i].NeedRestartProcs[j].ServiceName = name
|
||||
} else {
|
||||
if proc.ServiceName == "" {
|
||||
if ss := strings.Fields(r.Stdout); len(ss) == 4 && ss[2] == "process" {
|
||||
if name, ok := pidService[ss[3]]; ok {
|
||||
packs[i].NeedRestartProcs[j].ServiceName = name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
o.Packages[p.Name] = p
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *debian) parseCheckRestart(stdout string) (models.Packages, []string) {
|
||||
services := []string{}
|
||||
scanner := bufio.NewScanner(strings.NewReader(stdout))
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
if strings.HasPrefix(line, "service") && strings.HasSuffix(line, "restart") {
|
||||
ss := strings.Fields(line)
|
||||
if len(ss) != 3 {
|
||||
continue
|
||||
}
|
||||
services = append(services, ss[1])
|
||||
}
|
||||
}
|
||||
|
||||
packs := models.Packages{}
|
||||
packName := ""
|
||||
hasInit := true
|
||||
scanner = bufio.NewScanner(strings.NewReader(stdout))
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
if strings.HasSuffix(line, "do not seem to have an associated init script to restart them:") {
|
||||
hasInit = false
|
||||
continue
|
||||
}
|
||||
if strings.HasSuffix(line, ":") && len(strings.Fields(line)) == 1 {
|
||||
packName = strings.TrimSuffix(line, ":")
|
||||
continue
|
||||
}
|
||||
if strings.HasPrefix(line, "\t") {
|
||||
ss := strings.Fields(line)
|
||||
if len(ss) != 2 {
|
||||
continue
|
||||
}
|
||||
|
||||
serviceName := ""
|
||||
for _, s := range services {
|
||||
if packName == s {
|
||||
serviceName = s
|
||||
}
|
||||
}
|
||||
if p, ok := packs[packName]; ok {
|
||||
p.NeedRestartProcs = append(p.NeedRestartProcs, models.NeedRestartProcess{
|
||||
PID: ss[0],
|
||||
Path: ss[1],
|
||||
ServiceName: serviceName,
|
||||
HasInit: hasInit,
|
||||
})
|
||||
packs[packName] = p
|
||||
} else {
|
||||
packs[packName] = models.Package{
|
||||
Name: packName,
|
||||
NeedRestartProcs: []models.NeedRestartProcess{
|
||||
{
|
||||
PID: ss[0],
|
||||
Path: ss[1],
|
||||
ServiceName: serviceName,
|
||||
HasInit: hasInit,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unknownServices := []string{}
|
||||
for _, s := range services {
|
||||
found := false
|
||||
for _, p := range packs {
|
||||
for _, proc := range p.NeedRestartProcs {
|
||||
if proc.ServiceName == s {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
unknownServices = append(unknownServices, s)
|
||||
}
|
||||
}
|
||||
return packs, unknownServices
|
||||
}
|
||||
|
||||
@@ -587,3 +587,139 @@ func TestParseAptCachePolicy(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -370,7 +370,7 @@ func decorateCmd(c conf.ServerInfo, cmd string, sudo bool) string {
|
||||
// }
|
||||
|
||||
if c.IsContainer() {
|
||||
switch c.Containers.Type {
|
||||
switch c.ContainerType {
|
||||
case "", "docker":
|
||||
cmd = fmt.Sprintf(`docker exec --user 0 %s %s -c '%s'`,
|
||||
c.Container.ContainerID, dockerShell(c.Distro.Family), cmd)
|
||||
|
||||
@@ -69,9 +69,9 @@ func TestDecorateCmd(t *testing.T) {
|
||||
// root sudo false docker
|
||||
{
|
||||
conf: config.ServerInfo{
|
||||
User: "root",
|
||||
Container: config.Container{ContainerID: "abc", Name: "def"},
|
||||
Containers: config.Containers{Type: "docker"},
|
||||
User: "root",
|
||||
Container: config.Container{ContainerID: "abc", Name: "def"},
|
||||
ContainerType: "docker",
|
||||
},
|
||||
cmd: "ls",
|
||||
sudo: false,
|
||||
@@ -80,9 +80,9 @@ func TestDecorateCmd(t *testing.T) {
|
||||
// root sudo true docker
|
||||
{
|
||||
conf: config.ServerInfo{
|
||||
User: "root",
|
||||
Container: config.Container{ContainerID: "abc", Name: "def"},
|
||||
Containers: config.Containers{Type: "docker"},
|
||||
User: "root",
|
||||
Container: config.Container{ContainerID: "abc", Name: "def"},
|
||||
ContainerType: "docker",
|
||||
},
|
||||
cmd: "ls",
|
||||
sudo: true,
|
||||
@@ -91,9 +91,9 @@ func TestDecorateCmd(t *testing.T) {
|
||||
// non-root sudo false, docker
|
||||
{
|
||||
conf: config.ServerInfo{
|
||||
User: "non-root",
|
||||
Container: config.Container{ContainerID: "abc", Name: "def"},
|
||||
Containers: config.Containers{Type: "docker"},
|
||||
User: "non-root",
|
||||
Container: config.Container{ContainerID: "abc", Name: "def"},
|
||||
ContainerType: "docker",
|
||||
},
|
||||
cmd: "ls",
|
||||
sudo: false,
|
||||
@@ -102,9 +102,9 @@ func TestDecorateCmd(t *testing.T) {
|
||||
// non-root sudo true, docker
|
||||
{
|
||||
conf: config.ServerInfo{
|
||||
User: "non-root",
|
||||
Container: config.Container{ContainerID: "abc", Name: "def"},
|
||||
Containers: config.Containers{Type: "docker"},
|
||||
User: "non-root",
|
||||
Container: config.Container{ContainerID: "abc", Name: "def"},
|
||||
ContainerType: "docker",
|
||||
},
|
||||
cmd: "ls",
|
||||
sudo: true,
|
||||
@@ -113,9 +113,9 @@ func TestDecorateCmd(t *testing.T) {
|
||||
// non-root sudo true, docker
|
||||
{
|
||||
conf: config.ServerInfo{
|
||||
User: "non-root",
|
||||
Container: config.Container{ContainerID: "abc", Name: "def"},
|
||||
Containers: config.Containers{Type: "docker"},
|
||||
User: "non-root",
|
||||
Container: config.Container{ContainerID: "abc", Name: "def"},
|
||||
ContainerType: "docker",
|
||||
},
|
||||
cmd: "ls | grep hoge",
|
||||
sudo: true,
|
||||
@@ -125,9 +125,9 @@ func TestDecorateCmd(t *testing.T) {
|
||||
// root sudo false lxd
|
||||
{
|
||||
conf: config.ServerInfo{
|
||||
User: "root",
|
||||
Container: config.Container{ContainerID: "abc", Name: "def"},
|
||||
Containers: config.Containers{Type: "lxd"},
|
||||
User: "root",
|
||||
Container: config.Container{ContainerID: "abc", Name: "def"},
|
||||
ContainerType: "lxd",
|
||||
},
|
||||
cmd: "ls",
|
||||
sudo: false,
|
||||
@@ -136,9 +136,9 @@ func TestDecorateCmd(t *testing.T) {
|
||||
// root sudo true lxd
|
||||
{
|
||||
conf: config.ServerInfo{
|
||||
User: "root",
|
||||
Container: config.Container{ContainerID: "abc", Name: "def"},
|
||||
Containers: config.Containers{Type: "lxd"},
|
||||
User: "root",
|
||||
Container: config.Container{ContainerID: "abc", Name: "def"},
|
||||
ContainerType: "lxd",
|
||||
},
|
||||
cmd: "ls",
|
||||
sudo: true,
|
||||
@@ -147,9 +147,9 @@ func TestDecorateCmd(t *testing.T) {
|
||||
// non-root sudo false, lxd
|
||||
{
|
||||
conf: config.ServerInfo{
|
||||
User: "non-root",
|
||||
Container: config.Container{ContainerID: "abc", Name: "def"},
|
||||
Containers: config.Containers{Type: "lxd"},
|
||||
User: "non-root",
|
||||
Container: config.Container{ContainerID: "abc", Name: "def"},
|
||||
ContainerType: "lxd",
|
||||
},
|
||||
cmd: "ls",
|
||||
sudo: false,
|
||||
@@ -158,9 +158,9 @@ func TestDecorateCmd(t *testing.T) {
|
||||
// non-root sudo true, lxd
|
||||
{
|
||||
conf: config.ServerInfo{
|
||||
User: "non-root",
|
||||
Container: config.Container{ContainerID: "abc", Name: "def"},
|
||||
Containers: config.Containers{Type: "lxd"},
|
||||
User: "non-root",
|
||||
Container: config.Container{ContainerID: "abc", Name: "def"},
|
||||
ContainerType: "lxd",
|
||||
},
|
||||
cmd: "ls",
|
||||
sudo: true,
|
||||
@@ -169,9 +169,9 @@ func TestDecorateCmd(t *testing.T) {
|
||||
// non-root sudo true lxd
|
||||
{
|
||||
conf: config.ServerInfo{
|
||||
User: "non-root",
|
||||
Container: config.Container{ContainerID: "abc", Name: "def"},
|
||||
Containers: config.Containers{Type: "lxd"},
|
||||
User: "non-root",
|
||||
Container: config.Container{ContainerID: "abc", Name: "def"},
|
||||
ContainerType: "lxd",
|
||||
},
|
||||
cmd: "ls | grep hoge",
|
||||
sudo: true,
|
||||
@@ -181,9 +181,9 @@ func TestDecorateCmd(t *testing.T) {
|
||||
// root sudo false lxc
|
||||
{
|
||||
conf: config.ServerInfo{
|
||||
User: "root",
|
||||
Container: config.Container{ContainerID: "abc", Name: "def"},
|
||||
Containers: config.Containers{Type: "lxc"},
|
||||
User: "root",
|
||||
Container: config.Container{ContainerID: "abc", Name: "def"},
|
||||
ContainerType: "lxc",
|
||||
},
|
||||
cmd: "ls",
|
||||
sudo: false,
|
||||
@@ -192,9 +192,9 @@ func TestDecorateCmd(t *testing.T) {
|
||||
// root sudo true lxc
|
||||
{
|
||||
conf: config.ServerInfo{
|
||||
User: "root",
|
||||
Container: config.Container{ContainerID: "abc", Name: "def"},
|
||||
Containers: config.Containers{Type: "lxc"},
|
||||
User: "root",
|
||||
Container: config.Container{ContainerID: "abc", Name: "def"},
|
||||
ContainerType: "lxc",
|
||||
},
|
||||
cmd: "ls",
|
||||
sudo: true,
|
||||
@@ -203,9 +203,9 @@ func TestDecorateCmd(t *testing.T) {
|
||||
// non-root sudo false, lxc
|
||||
{
|
||||
conf: config.ServerInfo{
|
||||
User: "non-root",
|
||||
Container: config.Container{ContainerID: "abc", Name: "def"},
|
||||
Containers: config.Containers{Type: "lxc"},
|
||||
User: "non-root",
|
||||
Container: config.Container{ContainerID: "abc", Name: "def"},
|
||||
ContainerType: "lxc",
|
||||
},
|
||||
cmd: "ls",
|
||||
sudo: false,
|
||||
@@ -214,9 +214,9 @@ func TestDecorateCmd(t *testing.T) {
|
||||
// non-root sudo true, lxc
|
||||
{
|
||||
conf: config.ServerInfo{
|
||||
User: "non-root",
|
||||
Container: config.Container{ContainerID: "abc", Name: "def"},
|
||||
Containers: config.Containers{Type: "lxc"},
|
||||
User: "non-root",
|
||||
Container: config.Container{ContainerID: "abc", Name: "def"},
|
||||
ContainerType: "lxc",
|
||||
},
|
||||
cmd: "ls",
|
||||
sudo: true,
|
||||
@@ -225,9 +225,9 @@ func TestDecorateCmd(t *testing.T) {
|
||||
// non-root sudo true lxc
|
||||
{
|
||||
conf: config.ServerInfo{
|
||||
User: "non-root",
|
||||
Container: config.Container{ContainerID: "abc", Name: "def"},
|
||||
Containers: config.Containers{Type: "lxc"},
|
||||
User: "non-root",
|
||||
Container: config.Container{ContainerID: "abc", Name: "def"},
|
||||
ContainerType: "lxc",
|
||||
},
|
||||
cmd: "ls | grep hoge",
|
||||
sudo: true,
|
||||
|
||||
@@ -67,18 +67,26 @@ func detectFreebsd(c config.ServerInfo) (itsMe bool, bsd osTypeInterface) {
|
||||
return false, bsd
|
||||
}
|
||||
|
||||
func (o *bsd) checkScanMode() error {
|
||||
if o.getServerInfo().Mode.IsOffline() {
|
||||
return fmt.Errorf("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) checkDependencies() error {
|
||||
func (o *bsd) checkDeps() error {
|
||||
o.log.Infof("Dependencies... No need")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *bsd) preCure() error {
|
||||
o.log.Infof("Scanning in %s", o.getServerInfo().Mode)
|
||||
if err := o.detectIPAddr(); err != nil {
|
||||
o.log.Debugf("Failed to detect IP addresses: %s", err)
|
||||
}
|
||||
@@ -158,6 +166,10 @@ func (o *bsd) scanPackages() error {
|
||||
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() {
|
||||
@@ -245,7 +257,7 @@ func (o *bsd) scanUnsecurePackages() (models.VulnInfos, error) {
|
||||
CveID: cveID,
|
||||
AffectedPackages: affected,
|
||||
DistroAdvisories: disAdvs,
|
||||
Confidence: models.PkgAuditMatch,
|
||||
Confidences: models.Confidences{models.PkgAuditMatch},
|
||||
}
|
||||
}
|
||||
return vinfos, nil
|
||||
|
||||
167
scan/oracle.go
Normal file
167
scan/oracle.go
Normal file
@@ -0,0 +1,167 @@
|
||||
package scan
|
||||
|
||||
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 {
|
||||
if o.getServerInfo().Mode.IsOffline() {
|
||||
//TODO
|
||||
// return []string{"yum-plugin-ps"}
|
||||
}
|
||||
|
||||
majorVersion, _ := o.Distro.MajorVersion()
|
||||
switch majorVersion {
|
||||
case 5:
|
||||
return []string{
|
||||
"yum-utils",
|
||||
"yum-security",
|
||||
}
|
||||
case 6:
|
||||
return []string{
|
||||
"yum-utils",
|
||||
"yum-plugin-security",
|
||||
//TODO
|
||||
// return []string{"yum-plugin-ps"}
|
||||
}
|
||||
default:
|
||||
return []string{
|
||||
"yum-utils",
|
||||
//TODO
|
||||
// return []string{"yum-plugin-ps"}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (o *oracle) depsDeep() []string {
|
||||
majorVersion, _ := o.Distro.MajorVersion()
|
||||
switch majorVersion {
|
||||
case 5:
|
||||
return []string{
|
||||
"yum-utils",
|
||||
"yum-security",
|
||||
"yum-changelog",
|
||||
}
|
||||
case 6:
|
||||
return []string{
|
||||
"yum-utils",
|
||||
"yum-plugin-security",
|
||||
"yum-plugin-changelog",
|
||||
//TODO
|
||||
// return []string{"yum-plugin-ps"}
|
||||
}
|
||||
default:
|
||||
return []string{
|
||||
"yum-utils",
|
||||
"yum-plugin-changelog",
|
||||
//TODO
|
||||
// return []string{"yum-plugin-ps"}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
cmds := []cmd{{"needs-restarting", exitStatusZero}}
|
||||
if o.getServerInfo().Mode.IsOffline() {
|
||||
return cmds
|
||||
}
|
||||
|
||||
majorVersion, _ := o.Distro.MajorVersion()
|
||||
if majorVersion < 6 {
|
||||
return []cmd{
|
||||
{"yum repolist --color=never", exitStatusZero},
|
||||
{"yum list-security --security --color=never", exitStatusZero},
|
||||
{"yum info-security --color=never", exitStatusZero},
|
||||
{"repoquery -h", exitStatusZero},
|
||||
}
|
||||
}
|
||||
return append(cmds,
|
||||
cmd{"yum repolist --color=never", exitStatusZero},
|
||||
cmd{"yum updateinfo list updates --security --color=never", exitStatusZero},
|
||||
cmd{"yum updateinfo updates --security --color=never", exitStatusZero},
|
||||
cmd{"repoquery -h", exitStatusZero})
|
||||
}
|
||||
|
||||
func (o *oracle) sudoNoPasswdCmdsDeep() []cmd {
|
||||
return o.sudoNoPasswdCmdsFastRoot()
|
||||
}
|
||||
|
||||
type rootPrivOracle struct{}
|
||||
|
||||
func (o rootPrivOracle) repoquery() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (o rootPrivOracle) yumRepolist() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (o rootPrivOracle) yumUpdateInfo() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// root privilege isn't needed
|
||||
func (o rootPrivOracle) yumChangelog() bool {
|
||||
return false
|
||||
}
|
||||
@@ -48,11 +48,15 @@ func newPseudo(c config.ServerInfo) *pseudo {
|
||||
return d
|
||||
}
|
||||
|
||||
func (o *pseudo) checkScanMode() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *pseudo) checkIfSudoNoPasswd() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *pseudo) checkDependencies() error {
|
||||
func (o *pseudo) checkDeps() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -68,6 +72,10 @@ 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
|
||||
|
||||
@@ -31,34 +31,11 @@ import (
|
||||
ver "github.com/knqyf263/go-rpm-version"
|
||||
)
|
||||
|
||||
// inherit OsTypeInterface
|
||||
type redhat struct {
|
||||
base
|
||||
}
|
||||
|
||||
// NewRedhat is constructor
|
||||
func newRedhat(c config.ServerInfo) *redhat {
|
||||
r := &redhat{
|
||||
base: base{
|
||||
osPackages: osPackages{
|
||||
Packages: models.Packages{},
|
||||
VulnInfos: models.VulnInfos{},
|
||||
},
|
||||
},
|
||||
}
|
||||
r.log = util.NewCustomLogger(c)
|
||||
r.setServerInfo(c)
|
||||
return r
|
||||
}
|
||||
|
||||
// https://github.com/serverspec/specinfra/blob/master/lib/specinfra/helper/detect_os/redhat.rb
|
||||
func detectRedhat(c config.ServerInfo) (itsMe bool, red osTypeInterface) {
|
||||
red = newRedhat(c)
|
||||
|
||||
func detectRedhat(c config.ServerInfo) (bool, osTypeInterface) {
|
||||
if r := exec(c, "ls /etc/fedora-release", noSudo); r.isSuccess() {
|
||||
red.setDistro(config.Fedora, "unknown")
|
||||
util.Log.Warnf("Fedora not tested yet: %s", r)
|
||||
return true, red
|
||||
return true, &unknown{}
|
||||
}
|
||||
|
||||
if r := exec(c, "ls /etc/oracle-release", noSudo); r.isSuccess() {
|
||||
@@ -69,12 +46,13 @@ func detectRedhat(c config.ServerInfo) (itsMe bool, red osTypeInterface) {
|
||||
result := re.FindStringSubmatch(strings.TrimSpace(r.Stdout))
|
||||
if len(result) != 3 {
|
||||
util.Log.Warnf("Failed to parse Oracle Linux version: %s", r)
|
||||
return true, red
|
||||
return true, newOracle(c)
|
||||
}
|
||||
|
||||
ora := newOracle(c)
|
||||
release := result[2]
|
||||
red.setDistro(config.Oracle, release)
|
||||
return true, red
|
||||
ora.setDistro(config.Oracle, release)
|
||||
return true, ora
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,14 +64,15 @@ func detectRedhat(c config.ServerInfo) (itsMe bool, red osTypeInterface) {
|
||||
result := re.FindStringSubmatch(strings.TrimSpace(r.Stdout))
|
||||
if len(result) != 3 {
|
||||
util.Log.Warnf("Failed to parse CentOS version: %s", r)
|
||||
return true, red
|
||||
return true, newCentOS(c)
|
||||
}
|
||||
|
||||
release := result[2]
|
||||
switch strings.ToLower(result[1]) {
|
||||
case "centos", "centos linux":
|
||||
red.setDistro(config.CentOS, release)
|
||||
return true, red
|
||||
cent := newCentOS(c)
|
||||
cent.setDistro(config.CentOS, release)
|
||||
return true, cent
|
||||
default:
|
||||
util.Log.Warnf("Failed to parse CentOS: %s", r)
|
||||
}
|
||||
@@ -110,19 +89,22 @@ func detectRedhat(c config.ServerInfo) (itsMe bool, red osTypeInterface) {
|
||||
result := re.FindStringSubmatch(strings.TrimSpace(r.Stdout))
|
||||
if len(result) != 3 {
|
||||
util.Log.Warnf("Failed to parse RedHat/CentOS version: %s", r)
|
||||
return true, red
|
||||
return true, newCentOS(c)
|
||||
}
|
||||
|
||||
release := result[2]
|
||||
switch strings.ToLower(result[1]) {
|
||||
case "centos", "centos linux":
|
||||
red.setDistro(config.CentOS, release)
|
||||
cent := newCentOS(c)
|
||||
cent.setDistro(config.CentOS, release)
|
||||
return true, cent
|
||||
default:
|
||||
red.setDistro(config.RedHat, release)
|
||||
// RHEL
|
||||
rhel := newRHEL(c)
|
||||
rhel.setDistro(config.RedHat, release)
|
||||
return true, rhel
|
||||
}
|
||||
return true, red
|
||||
}
|
||||
return true, red
|
||||
}
|
||||
|
||||
if r := exec(c, "ls /etc/system-release", noSudo); r.isSuccess() {
|
||||
@@ -132,6 +114,9 @@ func detectRedhat(c config.ServerInfo) (itsMe bool, red osTypeInterface) {
|
||||
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 {
|
||||
@@ -139,57 +124,40 @@ func detectRedhat(c config.ServerInfo) (itsMe bool, red osTypeInterface) {
|
||||
}
|
||||
}
|
||||
}
|
||||
red.setDistro(family, release)
|
||||
return true, red
|
||||
amazon := newAmazon(c)
|
||||
amazon.setDistro(family, release)
|
||||
return true, amazon
|
||||
}
|
||||
|
||||
util.Log.Debugf("Not RedHat like Linux. servername: %s", c.ServerName)
|
||||
return false, red
|
||||
return false, &unknown{}
|
||||
}
|
||||
|
||||
func (o *redhat) checkIfSudoNoPasswd() error {
|
||||
if !config.Conf.Deep || !o.sudo() {
|
||||
o.log.Infof("sudo ... No need")
|
||||
return nil
|
||||
}
|
||||
// inherit OsTypeInterface
|
||||
type redhatBase struct {
|
||||
base
|
||||
sudo rootPriv
|
||||
}
|
||||
|
||||
type cmd struct {
|
||||
cmd string
|
||||
expectedStatusCodes []int
|
||||
}
|
||||
var cmds []cmd
|
||||
var zero = []int{0}
|
||||
type rootPriv interface {
|
||||
repoquery() bool
|
||||
yumRepolist() bool
|
||||
yumUpdateInfo() bool
|
||||
yumChangelog() bool
|
||||
}
|
||||
|
||||
switch o.Distro.Family {
|
||||
case config.RedHat, config.Oracle:
|
||||
majorVersion, err := o.Distro.MajorVersion()
|
||||
if err != nil {
|
||||
return fmt.Errorf("Not implemented yet: %s, err: %s", o.Distro, err)
|
||||
}
|
||||
type cmd struct {
|
||||
cmd string
|
||||
expectedStatusCodes []int
|
||||
}
|
||||
|
||||
if majorVersion < 6 {
|
||||
cmds = []cmd{
|
||||
{"yum --color=never repolist", zero},
|
||||
{"yum --color=never list-security --security", zero},
|
||||
{"yum --color=never info-security", zero},
|
||||
}
|
||||
} else {
|
||||
cmds = []cmd{
|
||||
{"yum --color=never repolist", zero},
|
||||
{"yum --color=never --security updateinfo list updates", zero},
|
||||
{"yum --color=never --security updateinfo updates", zero},
|
||||
}
|
||||
}
|
||||
|
||||
if o.Distro.Family == config.RedHat {
|
||||
cmds = append(cmds, cmd{"repoquery -h", zero})
|
||||
}
|
||||
}
|
||||
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), o.sudo())
|
||||
r := o.exec(util.PrependProxyEnv(cmd), sudo)
|
||||
if !r.isSuccess(c.expectedStatusCodes...) {
|
||||
o.log.Errorf("Check sudo or proxy settings: %s", r)
|
||||
return fmt.Errorf("Failed to sudo: %s", r)
|
||||
@@ -199,63 +167,7 @@ func (o *redhat) checkIfSudoNoPasswd() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// - Fast offline scan mode
|
||||
// Amazon ... yum-utils
|
||||
//
|
||||
// - Fast scan mode
|
||||
// All ... yum-utils
|
||||
//
|
||||
// - Deep scan mode
|
||||
// CentOS 6,7 ... yum-utils, yum-plugin-changelog
|
||||
// RHEL 5 (U1-) ... yum-utils, yum-security, yum-changelog
|
||||
// RHEL 6 ... yum-utils, yum-security, yum-plugin-changelog
|
||||
// RHEL 7 ... yum-utils, yum-plugin-changelog
|
||||
// Amazon ... yum-utils
|
||||
func (o *redhat) checkDependencies() error {
|
||||
majorVersion, err := o.Distro.MajorVersion()
|
||||
if err != nil {
|
||||
msg := fmt.Sprintf("Not implemented yet: %s, err: %s", o.Distro, err)
|
||||
o.log.Errorf(msg)
|
||||
return fmt.Errorf(msg)
|
||||
}
|
||||
|
||||
if o.Distro.Family == config.CentOS {
|
||||
if majorVersion < 6 {
|
||||
msg := fmt.Sprintf("CentOS %s is not supported", o.Distro.Release)
|
||||
o.log.Errorf(msg)
|
||||
return fmt.Errorf(msg)
|
||||
}
|
||||
}
|
||||
|
||||
packNames := []string{}
|
||||
if config.Conf.Fast {
|
||||
// Online fast scan needs yum-utils to issue repoquery cmd
|
||||
packNames = append(packNames, "yum-utils")
|
||||
} else if config.Conf.Offline {
|
||||
switch o.Distro.Family {
|
||||
case config.Amazon:
|
||||
// Offline scan doesn't support Amazon Linux
|
||||
packNames = append(packNames, "yum-utils")
|
||||
}
|
||||
} else {
|
||||
// Deep Scan
|
||||
switch o.Distro.Family {
|
||||
case config.CentOS, config.Amazon:
|
||||
packNames = append(packNames, "yum-utils", "yum-plugin-changelog")
|
||||
case config.RedHat, config.Oracle:
|
||||
switch majorVersion {
|
||||
case 5:
|
||||
packNames = append(packNames, "yum-utils", "yum-security", "yum-changelog")
|
||||
case 6:
|
||||
packNames = append(packNames, "yum-utils", "yum-plugin-security", "yum-plugin-changelog")
|
||||
default:
|
||||
packNames = append(packNames, "yum-utils", "yum-plugin-changelog")
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("Not implemented yet: %s", o.Distro)
|
||||
}
|
||||
}
|
||||
|
||||
func (o *redhatBase) execCheckDeps(packNames []string) error {
|
||||
for _, name := range packNames {
|
||||
cmd := "rpm -q " + name
|
||||
if r := o.exec(cmd, noSudo); !r.isSuccess() {
|
||||
@@ -268,7 +180,7 @@ func (o *redhat) checkDependencies() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *redhat) preCure() error {
|
||||
func (o *redhatBase) preCure() error {
|
||||
if err := o.detectIPAddr(); err != nil {
|
||||
o.log.Debugf("Failed to detect IP addresses: %s", err)
|
||||
}
|
||||
@@ -276,16 +188,27 @@ func (o *redhat) preCure() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *redhat) postScan() error {
|
||||
func (o *redhatBase) postScan() error {
|
||||
if o.isExecYumPS() {
|
||||
if err := o.yumPS(); err != nil {
|
||||
return fmt.Errorf("Failed to execute yum-ps: %s", err)
|
||||
}
|
||||
}
|
||||
if o.isExecNeedsRestarting() {
|
||||
if err := o.needsRestarting(); err != nil {
|
||||
return fmt.Errorf("Failed to execute need-restarting: %s", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *redhat) detectIPAddr() (err error) {
|
||||
func (o *redhatBase) detectIPAddr() (err error) {
|
||||
o.log.Infof("Scanning in %s", o.getServerInfo().Mode)
|
||||
o.ServerInfo.IPv4Addrs, o.ServerInfo.IPv6Addrs, err = o.ip()
|
||||
return err
|
||||
}
|
||||
|
||||
func (o *redhat) scanPackages() error {
|
||||
func (o *redhatBase) scanPackages() error {
|
||||
installed, err := o.scanInstalledPackages()
|
||||
if err != nil {
|
||||
o.log.Errorf("Failed to scan installed packages: %s", err)
|
||||
@@ -299,7 +222,7 @@ func (o *redhat) scanPackages() error {
|
||||
}
|
||||
o.Kernel.RebootRequired = rebootRequired
|
||||
|
||||
if config.Conf.Offline {
|
||||
if o.getServerInfo().Mode.IsOffline() {
|
||||
switch o.Distro.Family {
|
||||
case config.Amazon:
|
||||
// nop
|
||||
@@ -307,6 +230,11 @@ func (o *redhat) scanPackages() error {
|
||||
o.Packages = installed
|
||||
return nil
|
||||
}
|
||||
} else if o.Distro.Family == config.RedHat {
|
||||
if o.getServerInfo().Mode.IsFast() {
|
||||
o.Packages = installed
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
updatable, err := o.scanUpdatablePackages()
|
||||
@@ -326,7 +254,7 @@ func (o *redhat) scanPackages() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *redhat) rebootRequired() (bool, error) {
|
||||
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) {
|
||||
@@ -340,7 +268,7 @@ func (o *redhat) rebootRequired() (bool, error) {
|
||||
return running != lastInstalledKernelVer, nil
|
||||
}
|
||||
|
||||
func (o *redhat) scanInstalledPackages() (models.Packages, error) {
|
||||
func (o *redhatBase) scanInstalledPackages() (models.Packages, error) {
|
||||
release, version, err := o.runningKernel()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -350,19 +278,28 @@ func (o *redhat) scanInstalledPackages() (models.Packages, error) {
|
||||
Version: version,
|
||||
}
|
||||
|
||||
installed := models.Packages{}
|
||||
r := o.exec(rpmQa(o.Distro), noSudo)
|
||||
if !r.isSuccess() {
|
||||
return nil, fmt.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(r.Stdout, "\n")
|
||||
lines := strings.Split(stdout, "\n")
|
||||
for _, line := range lines {
|
||||
if trimed := strings.TrimSpace(line); len(trimed) != 0 {
|
||||
pack, err := o.parseInstalledPackagesLine(line)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// Kernel package may be isntalled multiple versions.
|
||||
@@ -370,19 +307,28 @@ func (o *redhat) scanInstalledPackages() (models.Packages, error) {
|
||||
// pay attention only to the running kernel
|
||||
isKernel, running := isRunningKernel(pack, o.Distro.Family, o.Kernel)
|
||||
if isKernel {
|
||||
if !running {
|
||||
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)
|
||||
}
|
||||
o.log.Debugf("Found a running kernel. pack: %#v, kernel: %#v", pack, o.Kernel)
|
||||
}
|
||||
installed[pack.Name] = pack
|
||||
}
|
||||
}
|
||||
return installed, nil
|
||||
return installed, nil, nil
|
||||
}
|
||||
|
||||
func (o *redhat) parseInstalledPackagesLine(line string) (models.Package, error) {
|
||||
func (o *redhatBase) parseInstalledPackagesLine(line string) (models.Package, error) {
|
||||
fields := strings.Fields(line)
|
||||
if len(fields) != 5 {
|
||||
return models.Package{},
|
||||
@@ -404,13 +350,13 @@ func (o *redhat) parseInstalledPackagesLine(line string) (models.Package, error)
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (o *redhat) scanUpdatablePackages() (models.Packages, error) {
|
||||
func (o *redhatBase) scanUpdatablePackages() (models.Packages, error) {
|
||||
cmd := `repoquery --all --pkgnarrow=updates --qf="%{NAME} %{EPOCH} %{VERSION} %{RELEASE} %{REPO}"`
|
||||
for _, repo := range o.getServerInfo().Enablerepo {
|
||||
cmd += " --enablerepo=" + repo
|
||||
}
|
||||
|
||||
r := o.exec(util.PrependProxyEnv(cmd), o.sudo())
|
||||
r := o.exec(util.PrependProxyEnv(cmd), o.sudo.repoquery())
|
||||
if !r.isSuccess() {
|
||||
return nil, fmt.Errorf("Failed to SSH: %s", r)
|
||||
}
|
||||
@@ -420,7 +366,7 @@ func (o *redhat) scanUpdatablePackages() (models.Packages, error) {
|
||||
}
|
||||
|
||||
// parseUpdatablePacksLines parse the stdout of repoquery to get package name, candidate version
|
||||
func (o *redhat) parseUpdatablePacksLines(stdout string) (models.Packages, error) {
|
||||
func (o *redhatBase) parseUpdatablePacksLines(stdout string) (models.Packages, error) {
|
||||
updatable := models.Packages{}
|
||||
lines := strings.Split(stdout, "\n")
|
||||
for _, line := range lines {
|
||||
@@ -444,7 +390,7 @@ func (o *redhat) parseUpdatablePacksLines(stdout string) (models.Packages, error
|
||||
return updatable, nil
|
||||
}
|
||||
|
||||
func (o *redhat) parseUpdatablePacksLine(line string) (models.Package, error) {
|
||||
func (o *redhatBase) parseUpdatablePacksLine(line string) (models.Package, error) {
|
||||
fields := strings.Fields(line)
|
||||
if len(fields) < 5 {
|
||||
return models.Package{}, fmt.Errorf("Unknown format: %s, fields: %s", line, fields)
|
||||
@@ -469,25 +415,109 @@ func (o *redhat) parseUpdatablePacksLine(line string) (models.Package, error) {
|
||||
return p, nil
|
||||
}
|
||||
|
||||
func (o *redhat) scanUnsecurePackages(updatable models.Packages) (models.VulnInfos, error) {
|
||||
if config.Conf.Deep && o.Distro.Family != config.Amazon {
|
||||
//TODO Cache changelogs to bolt
|
||||
func (o *redhatBase) isExecScanUsingYum() bool {
|
||||
if o.getServerInfo().Mode.IsOffline() {
|
||||
return false
|
||||
}
|
||||
if o.Distro.Family == config.CentOS {
|
||||
// CentOS doesn't have security channel
|
||||
return false
|
||||
}
|
||||
if o.getServerInfo().Mode.IsFastRoot() || o.getServerInfo().Mode.IsDeep() {
|
||||
return true
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (o *redhatBase) isExecFillChangelogs() bool {
|
||||
if o.getServerInfo().Mode.IsOffline() {
|
||||
return false
|
||||
}
|
||||
// Amazon linux has no changelos for updates
|
||||
return o.getServerInfo().Mode.IsDeep() &&
|
||||
o.Distro.Family != config.Amazon
|
||||
}
|
||||
|
||||
func (o *redhatBase) isExecScanChangelogs() bool {
|
||||
if o.getServerInfo().Mode.IsOffline() ||
|
||||
o.getServerInfo().Mode.IsFast() ||
|
||||
o.getServerInfo().Mode.IsFastRoot() {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (o *redhatBase) isExecYumPS() bool {
|
||||
// RedHat has no yum-ps
|
||||
switch o.Distro.Family {
|
||||
case config.RedHat,
|
||||
config.OpenSUSE,
|
||||
config.OpenSUSELeap,
|
||||
config.SUSEEnterpriseServer,
|
||||
config.SUSEEnterpriseDesktop,
|
||||
config.SUSEOpenstackCloud:
|
||||
return false
|
||||
}
|
||||
|
||||
// yum ps needs internet connection
|
||||
if o.getServerInfo().Mode.IsOffline() || o.getServerInfo().Mode.IsFast() {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (o *redhatBase) isExecNeedsRestarting() bool {
|
||||
switch o.Distro.Family {
|
||||
case config.OpenSUSE,
|
||||
config.OpenSUSELeap,
|
||||
config.SUSEEnterpriseServer,
|
||||
config.SUSEEnterpriseDesktop,
|
||||
config.SUSEOpenstackCloud:
|
||||
// TODO zypper ps
|
||||
// https://github.com/future-architect/vuls/issues/696
|
||||
return false
|
||||
case config.RedHat, config.CentOS, config.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) scanUnsecurePackages(updatable models.Packages) (models.VulnInfos, error) {
|
||||
if o.isExecFillChangelogs() {
|
||||
if err := o.fillChangelogs(updatable); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if o.Distro.Family != config.CentOS {
|
||||
// Amazon, RHEL, Oracle Linux has yum updateinfo as default
|
||||
// yum updateinfo can collenct vendor advisory information.
|
||||
return o.scanCveIDsByCommands(updatable)
|
||||
if o.isExecScanUsingYum() {
|
||||
return o.scanUsingYum(updatable)
|
||||
}
|
||||
|
||||
// Parse chnagelog because CentOS does not have security channel...
|
||||
return o.scanCveIDsInChangelog(updatable)
|
||||
if o.isExecScanChangelogs() {
|
||||
return o.scanChangelogs(updatable)
|
||||
}
|
||||
|
||||
return models.VulnInfos{}, nil
|
||||
}
|
||||
|
||||
func (o *redhat) fillChangelogs(updatables models.Packages) error {
|
||||
func (o *redhatBase) fillChangelogs(updatables models.Packages) error {
|
||||
names := []string{}
|
||||
for name := range updatables {
|
||||
names = append(names, name)
|
||||
@@ -516,7 +546,7 @@ func (o *redhat) fillChangelogs(updatables models.Packages) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *redhat) getAvailableChangelogs(packNames []string) (map[string]string, error) {
|
||||
func (o *redhatBase) getAvailableChangelogs(packNames []string) (map[string]string, error) {
|
||||
yumopts := ""
|
||||
if 0 < len(o.getServerInfo().Enablerepo) {
|
||||
yumopts = " --enablerepo=" + strings.Join(o.getServerInfo().Enablerepo, ",")
|
||||
@@ -527,10 +557,10 @@ func (o *redhat) getAvailableChangelogs(packNames []string) (map[string]string,
|
||||
if o.hasYumColorOption() {
|
||||
yumopts += " --color=never"
|
||||
}
|
||||
cmd := `yum changelog all %s updates %s | grep -A 1000000 "==================== Updated Packages ===================="`
|
||||
cmd := `yum changelog all updates %s %s | grep -A 1000000 "==================== Updated Packages ===================="`
|
||||
cmd = fmt.Sprintf(cmd, yumopts, strings.Join(packNames, " "))
|
||||
|
||||
r := o.exec(util.PrependProxyEnv(cmd), o.sudo())
|
||||
r := o.exec(util.PrependProxyEnv(cmd), o.sudo.yumChangelog())
|
||||
if !r.isSuccess(0, 1) {
|
||||
return nil, fmt.Errorf("Failed to SSH: %s", r)
|
||||
}
|
||||
@@ -539,7 +569,7 @@ func (o *redhat) getAvailableChangelogs(packNames []string) (map[string]string,
|
||||
}
|
||||
|
||||
// Divide available change logs of all updatable packages into each package's changelog
|
||||
func (o *redhat) divideChangelogsIntoEachPackages(stdout string) map[string]string {
|
||||
func (o *redhatBase) divideChangelogsIntoEachPackages(stdout string) map[string]string {
|
||||
changelogs := make(map[string]string)
|
||||
scanner := bufio.NewScanner(strings.NewReader(stdout))
|
||||
|
||||
@@ -580,7 +610,7 @@ func (o *redhat) divideChangelogsIntoEachPackages(stdout string) map[string]stri
|
||||
return changelogs
|
||||
}
|
||||
|
||||
func (o *redhat) fillDiffChangelogs(packNames []string) error {
|
||||
func (o *redhatBase) fillDiffChangelogs(packNames []string) error {
|
||||
changelogs, err := o.getAvailableChangelogs(packNames)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -593,19 +623,16 @@ func (o *redhat) fillDiffChangelogs(packNames []string) error {
|
||||
if index := strings.Index(p.NewVersion, ":"); 0 < index {
|
||||
epoch := p.NewVersion[0:index]
|
||||
ver := p.NewVersion[index+1 : len(p.NewVersion)]
|
||||
epochNameVerRel = fmt.Sprintf("%s:%s-%s",
|
||||
epoch, p.Name, ver)
|
||||
epochNameVerRel = fmt.Sprintf("%s:%s-%s", epoch, p.Name, ver)
|
||||
} else {
|
||||
epochNameVerRel = fmt.Sprintf("%s-%s",
|
||||
p.Name, p.NewVersion)
|
||||
epochNameVerRel = fmt.Sprintf("%s-%s", p.Name, p.NewVersion)
|
||||
}
|
||||
return strings.HasPrefix(s, epochNameVerRel)
|
||||
})
|
||||
|
||||
if found {
|
||||
diff, err := o.getDiffChangelog(pack, changelogs[s])
|
||||
var detectionMethod string
|
||||
|
||||
diff, err := o.getDiffChangelog(pack, changelogs[s])
|
||||
if err == nil {
|
||||
detectionMethod = models.ChangelogExactMatchStr
|
||||
} else {
|
||||
@@ -650,7 +677,7 @@ func (o *redhat) fillDiffChangelogs(packNames []string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *redhat) getDiffChangelog(pack models.Package, availableChangelog string) (string, error) {
|
||||
func (o *redhatBase) getDiffChangelog(pack models.Package, availableChangelog string) (string, error) {
|
||||
installedVer := ver.NewVersion(fmt.Sprintf("%s-%s", pack.Version, pack.Release))
|
||||
scanner := bufio.NewScanner(strings.NewReader(availableChangelog))
|
||||
diff := []string{}
|
||||
@@ -702,7 +729,7 @@ func (o *redhat) getDiffChangelog(pack models.Package, availableChangelog string
|
||||
return strings.TrimSpace(strings.Join(diff, "\n")), nil
|
||||
}
|
||||
|
||||
func (o *redhat) scanCveIDsInChangelog(updatable models.Packages) (models.VulnInfos, error) {
|
||||
func (o *redhatBase) scanChangelogs(updatable models.Packages) (models.VulnInfos, error) {
|
||||
packCveIDs := make(map[string][]string)
|
||||
for name := range updatable {
|
||||
cveIDs := []string{}
|
||||
@@ -739,7 +766,7 @@ func (o *redhat) scanCveIDsInChangelog(updatable models.Packages) (models.VulnIn
|
||||
vinfos[cid] = models.VulnInfo{
|
||||
CveID: cid,
|
||||
AffectedPackages: models.PackageStatuses{{Name: name}},
|
||||
Confidence: models.ChangelogExactMatch,
|
||||
Confidences: models.Confidences{models.ChangelogExactMatch},
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -754,7 +781,7 @@ type distroAdvisoryCveIDs struct {
|
||||
|
||||
// Scaning unsecure packages using yum-plugin-security.
|
||||
// Amazon, RHEL, Oracle Linux
|
||||
func (o *redhat) scanCveIDsByCommands(updatable models.Packages) (models.VulnInfos, error) {
|
||||
func (o *redhatBase) scanUsingYum(updatable models.Packages) (models.VulnInfos, error) {
|
||||
if o.Distro.Family == config.CentOS {
|
||||
// CentOS has no security channel.
|
||||
return nil, fmt.Errorf(
|
||||
@@ -769,8 +796,8 @@ func (o *redhat) scanCveIDsByCommands(updatable models.Packages) (models.VulnInf
|
||||
|
||||
var cmd string
|
||||
if (o.Distro.Family == config.RedHat || o.Distro.Family == config.Oracle) && major > 5 {
|
||||
cmd = "yum --color=never repolist"
|
||||
r := o.exec(util.PrependProxyEnv(cmd), o.sudo())
|
||||
cmd = "yum repolist --color=never"
|
||||
r := o.exec(util.PrependProxyEnv(cmd), o.sudo.yumRepolist())
|
||||
if !r.isSuccess() {
|
||||
return nil, fmt.Errorf("Failed to SSH: %s", r)
|
||||
}
|
||||
@@ -782,9 +809,9 @@ func (o *redhat) scanCveIDsByCommands(updatable models.Packages) (models.VulnInf
|
||||
cmd += " --color=never"
|
||||
}
|
||||
} else {
|
||||
cmd = "yum --color=never --security updateinfo list updates"
|
||||
cmd = "yum updateinfo list updates --security --color=never"
|
||||
}
|
||||
r := o.exec(util.PrependProxyEnv(cmd), o.sudo())
|
||||
r := o.exec(util.PrependProxyEnv(cmd), o.sudo.yumUpdateInfo())
|
||||
if !r.isSuccess() {
|
||||
return nil, fmt.Errorf("Failed to SSH: %s", r)
|
||||
}
|
||||
@@ -812,9 +839,9 @@ func (o *redhat) scanCveIDsByCommands(updatable models.Packages) (models.VulnInf
|
||||
cmd += " --color=never"
|
||||
}
|
||||
} else {
|
||||
cmd = "yum --color=never --security updateinfo updates"
|
||||
cmd = "yum updateinfo updates --security --color=never"
|
||||
}
|
||||
r = o.exec(util.PrependProxyEnv(cmd), o.sudo())
|
||||
r = o.exec(util.PrependProxyEnv(cmd), o.sudo.yumUpdateInfo())
|
||||
if !r.isSuccess() {
|
||||
return nil, fmt.Errorf("Failed to SSH: %s", r)
|
||||
}
|
||||
@@ -848,7 +875,7 @@ func (o *redhat) scanCveIDsByCommands(updatable models.Packages) (models.VulnInf
|
||||
CveID: cveID,
|
||||
DistroAdvisories: []models.DistroAdvisory{advIDCveIDs.DistroAdvisory},
|
||||
AffectedPackages: affected,
|
||||
Confidence: models.YumUpdateSecurityMatch,
|
||||
Confidences: models.Confidences{models.YumUpdateSecurityMatch},
|
||||
}
|
||||
}
|
||||
vinfos[cveID] = vinfo
|
||||
@@ -859,7 +886,7 @@ func (o *redhat) scanCveIDsByCommands(updatable models.Packages) (models.VulnInf
|
||||
|
||||
var horizontalRulePattern = regexp.MustCompile(`^=+$`)
|
||||
|
||||
func (o *redhat) parseYumUpdateinfo(stdout string) (result []distroAdvisoryCveIDs, err error) {
|
||||
func (o *redhatBase) parseYumUpdateinfo(stdout string) (result []distroAdvisoryCveIDs, err error) {
|
||||
sectionState := Outside
|
||||
lines := strings.Split(stdout, "\n")
|
||||
lines = append(lines, "=============")
|
||||
@@ -905,7 +932,7 @@ func (o *redhat) parseYumUpdateinfo(stdout string) (result []distroAdvisoryCveID
|
||||
case config.CentOS:
|
||||
// CentOS has no security channel.
|
||||
return result, fmt.Errorf(
|
||||
"yum updateinfo is not suppported on CentOS")
|
||||
"yum updateinfo is not suppported on CentOS")
|
||||
case config.RedHat, config.Amazon, config.Oracle:
|
||||
// nop
|
||||
}
|
||||
@@ -981,7 +1008,7 @@ const (
|
||||
Content = iota
|
||||
)
|
||||
|
||||
func (o *redhat) changeSectionState(state int) (newState int) {
|
||||
func (o *redhatBase) changeSectionState(state int) (newState int) {
|
||||
switch state {
|
||||
case Outside, Content:
|
||||
newState = Header
|
||||
@@ -991,19 +1018,19 @@ func (o *redhat) changeSectionState(state int) (newState int) {
|
||||
return newState
|
||||
}
|
||||
|
||||
func (o *redhat) isCvesHeaderLine(line string) bool {
|
||||
func (o *redhatBase) isCvesHeaderLine(line string) bool {
|
||||
return strings.Contains(line, "CVEs : ")
|
||||
}
|
||||
|
||||
var yumCveIDPattern = regexp.MustCompile(`(CVE-\d{4}-\d{4,})`)
|
||||
|
||||
func (o *redhat) parseYumUpdateinfoLineToGetCveIDs(line string) []string {
|
||||
func (o *redhatBase) parseYumUpdateinfoLineToGetCveIDs(line string) []string {
|
||||
return yumCveIDPattern.FindAllString(line, -1)
|
||||
}
|
||||
|
||||
var yumAdvisoryIDPattern = regexp.MustCompile(`^ *Update ID : (.*)$`)
|
||||
|
||||
func (o *redhat) parseYumUpdateinfoToGetAdvisoryID(line string) (advisoryID string, found bool) {
|
||||
func (o *redhatBase) parseYumUpdateinfoToGetAdvisoryID(line string) (advisoryID string, found bool) {
|
||||
result := yumAdvisoryIDPattern.FindStringSubmatch(line)
|
||||
if len(result) != 2 {
|
||||
return "", false
|
||||
@@ -1013,17 +1040,17 @@ func (o *redhat) parseYumUpdateinfoToGetAdvisoryID(line string) (advisoryID stri
|
||||
|
||||
var yumIssuedPattern = regexp.MustCompile(`^\s*Issued : (\d{4}-\d{2}-\d{2})`)
|
||||
|
||||
func (o *redhat) parseYumUpdateinfoLineToGetIssued(line string) (date time.Time, found bool) {
|
||||
func (o *redhatBase) parseYumUpdateinfoLineToGetIssued(line string) (date time.Time, found bool) {
|
||||
return o.parseYumUpdateinfoLineToGetDate(line, yumIssuedPattern)
|
||||
}
|
||||
|
||||
var yumUpdatedPattern = regexp.MustCompile(`^\s*Updated : (\d{4}-\d{2}-\d{2})`)
|
||||
|
||||
func (o *redhat) parseYumUpdateinfoLineToGetUpdated(line string) (date time.Time, found bool) {
|
||||
func (o *redhatBase) parseYumUpdateinfoLineToGetUpdated(line string) (date time.Time, found bool) {
|
||||
return o.parseYumUpdateinfoLineToGetDate(line, yumUpdatedPattern)
|
||||
}
|
||||
|
||||
func (o *redhat) parseYumUpdateinfoLineToGetDate(line string, regexpPattern *regexp.Regexp) (date time.Time, found bool) {
|
||||
func (o *redhatBase) parseYumUpdateinfoLineToGetDate(line string, regexpPattern *regexp.Regexp) (date time.Time, found bool) {
|
||||
result := regexpPattern.FindStringSubmatch(line)
|
||||
if len(result) != 2 {
|
||||
return date, false
|
||||
@@ -1037,13 +1064,13 @@ func (o *redhat) parseYumUpdateinfoLineToGetDate(line string, regexpPattern *reg
|
||||
|
||||
var yumDescriptionPattern = regexp.MustCompile(`^\s*Description : `)
|
||||
|
||||
func (o *redhat) isDescriptionLine(line string) bool {
|
||||
func (o *redhatBase) isDescriptionLine(line string) bool {
|
||||
return yumDescriptionPattern.MatchString(line)
|
||||
}
|
||||
|
||||
var yumSeverityPattern = regexp.MustCompile(`^ *Severity : (.*)$`)
|
||||
|
||||
func (o *redhat) parseYumUpdateinfoToGetSeverity(line string) (severity string, found bool) {
|
||||
func (o *redhatBase) parseYumUpdateinfoToGetSeverity(line string) (severity string, found bool) {
|
||||
result := yumSeverityPattern.FindStringSubmatch(line)
|
||||
if len(result) != 2 {
|
||||
return "", false
|
||||
@@ -1066,7 +1093,7 @@ func (list advisoryIDPacksList) find(advisoryID string) (advisoryIDPacks, bool)
|
||||
}
|
||||
return advisoryIDPacks{}, false
|
||||
}
|
||||
func (o *redhat) extractPackNameVerRel(nameVerRel string) (name, ver, rel string) {
|
||||
func (o *redhatBase) extractPackNameVerRel(nameVerRel string) (name, ver, rel string) {
|
||||
fields := strings.Split(nameVerRel, ".")
|
||||
archTrimed := strings.Join(fields[0:len(fields)-1], ".")
|
||||
|
||||
@@ -1078,7 +1105,7 @@ func (o *redhat) extractPackNameVerRel(nameVerRel string) (name, ver, rel string
|
||||
}
|
||||
|
||||
// parseYumUpdateinfoListAvailable collect AdvisorID(RHSA, ALAS, ELSA), packages
|
||||
func (o *redhat) parseYumUpdateinfoListAvailable(stdout string) (advisoryIDPacksList, error) {
|
||||
func (o *redhatBase) parseYumUpdateinfoListAvailable(stdout string) (advisoryIDPacksList, error) {
|
||||
result := []advisoryIDPacks{}
|
||||
lines := strings.Split(stdout, "\n")
|
||||
for _, line := range lines {
|
||||
@@ -1120,21 +1147,201 @@ func (o *redhat) parseYumUpdateinfoListAvailable(stdout string) (advisoryIDPacks
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (o *redhat) clone() osTypeInterface {
|
||||
return o
|
||||
}
|
||||
|
||||
func (o *redhat) sudo() bool {
|
||||
switch o.Distro.Family {
|
||||
case config.Amazon, config.CentOS:
|
||||
return false
|
||||
default:
|
||||
// RHEL, Oracle
|
||||
return config.Conf.Deep
|
||||
func (o *redhatBase) yumPS() error {
|
||||
cmd := "LANGUAGE=en_US.UTF-8 yum info yum"
|
||||
r := o.exec(util.PrependProxyEnv(cmd), noSudo)
|
||||
if !r.isSuccess() {
|
||||
return fmt.Errorf("Failed to SSH: %s", r)
|
||||
}
|
||||
if !o.checkYumPsInstalled(r.Stdout) {
|
||||
switch o.Distro.Family {
|
||||
case config.RedHat, config.Oracle:
|
||||
return nil
|
||||
default:
|
||||
return fmt.Errorf("yum-plugin-ps is not installed")
|
||||
}
|
||||
}
|
||||
|
||||
cmd = "LANGUAGE=en_US.UTF-8 yum -q ps all --color=never"
|
||||
r = o.exec(util.PrependProxyEnv(cmd), sudo)
|
||||
if !r.isSuccess() {
|
||||
return fmt.Errorf("Failed to SSH: %s", r)
|
||||
}
|
||||
packs := o.parseYumPS(r.Stdout)
|
||||
for name, pack := range packs {
|
||||
p := o.Packages[name]
|
||||
p.AffectedProcs = pack.AffectedProcs
|
||||
o.Packages[name] = p
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *redhat) hasYumColorOption() bool {
|
||||
func (o *redhatBase) checkYumPsInstalled(stdout string) bool {
|
||||
scanner := bufio.NewScanner(strings.NewReader(stdout))
|
||||
for scanner.Scan() {
|
||||
line := strings.TrimSpace(scanner.Text())
|
||||
if strings.HasPrefix(line, "Loaded plugins: ") {
|
||||
if strings.Contains(line, " ps,") || strings.HasSuffix(line, " ps") {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (o *redhatBase) parseYumPS(stdout string) models.Packages {
|
||||
packs := models.Packages{}
|
||||
scanner := bufio.NewScanner(strings.NewReader(stdout))
|
||||
isPackageLine, needToParseProcline := false, false
|
||||
currentPackName := ""
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
fields := strings.Fields(line)
|
||||
if len(fields) == 0 ||
|
||||
len(fields) == 1 && fields[0] == "ps" ||
|
||||
len(fields) == 6 && fields[0] == "pid" {
|
||||
continue
|
||||
}
|
||||
|
||||
isPackageLine = !strings.HasPrefix(line, " ")
|
||||
if isPackageLine {
|
||||
if 1 < len(fields) && fields[1] == "Upgrade" {
|
||||
needToParseProcline = true
|
||||
|
||||
// Search o.Packages to divide into name, version, release
|
||||
name, pack, found := o.Packages.FindOne(func(p models.Package) bool {
|
||||
var epochNameVerRel string
|
||||
if index := strings.Index(p.Version, ":"); 0 < index {
|
||||
epoch := p.Version[0:index]
|
||||
ver := p.Version[index+1 : len(p.Version)]
|
||||
epochNameVerRel = fmt.Sprintf("%s:%s-%s-%s.%s",
|
||||
epoch, p.Name, ver, p.Release, p.Arch)
|
||||
} else {
|
||||
epochNameVerRel = fmt.Sprintf("%s-%s-%s.%s",
|
||||
p.Name, p.Version, p.Release, p.Arch)
|
||||
}
|
||||
return strings.HasPrefix(fields[0], epochNameVerRel)
|
||||
})
|
||||
if !found {
|
||||
o.log.Errorf("`yum ps` package is not found: %s", line)
|
||||
continue
|
||||
}
|
||||
packs[name] = pack
|
||||
currentPackName = name
|
||||
} else {
|
||||
needToParseProcline = false
|
||||
}
|
||||
} else if needToParseProcline {
|
||||
if 6 < len(fields) {
|
||||
proc := models.AffectedProcess{
|
||||
PID: fields[0],
|
||||
Name: fields[1],
|
||||
}
|
||||
pack := packs[currentPackName]
|
||||
pack.AffectedProcs = append(pack.AffectedProcs, proc)
|
||||
packs[currentPackName] = pack
|
||||
} else {
|
||||
o.log.Errorf("`yum ps` Unknown Format: %s", line)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
return packs
|
||||
}
|
||||
|
||||
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 fmt.Errorf("Failed to SSH: %s", r)
|
||||
}
|
||||
procs := o.parseNeedsRestarting(r.Stdout)
|
||||
for _, proc := range procs {
|
||||
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.Warnf("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
|
||||
}
|
||||
|
||||
// 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}.%{ARCH}\n" ` + path
|
||||
r := o.exec(cmd, noSudo)
|
||||
if !r.isSuccess() {
|
||||
return "", fmt.Errorf("Failed to SSH: %s", r)
|
||||
}
|
||||
fqpn := strings.TrimSpace(r.Stdout)
|
||||
return strings.Replace(fqpn, "-(none):", "-", -1), nil
|
||||
}
|
||||
|
||||
func (o *redhatBase) hasYumColorOption() bool {
|
||||
cmd := "yum --help | grep color"
|
||||
r := o.exec(util.PrependProxyEnv(cmd), noSudo)
|
||||
return len(r.Stdout) > 0
|
||||
@@ -32,9 +32,90 @@ import (
|
||||
// return t
|
||||
// }
|
||||
|
||||
func TestParseScanedPackagesLineRedhat(t *testing.T) {
|
||||
func TestParseInstalledPackagesLinesRedhat(t *testing.T) {
|
||||
r := newRHEL(config.ServerInfo{})
|
||||
r.Distro = config.Distro{Family: config.RedHat}
|
||||
|
||||
r := newRedhat(config.ServerInfo{})
|
||||
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: 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",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
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 TestParseScanedPackagesLineRedhat(t *testing.T) {
|
||||
r := newRHEL(config.ServerInfo{})
|
||||
|
||||
var packagetests = []struct {
|
||||
in string
|
||||
@@ -74,7 +155,7 @@ func TestParseScanedPackagesLineRedhat(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestChangeSectionState(t *testing.T) {
|
||||
r := newRedhat(config.ServerInfo{})
|
||||
r := newRHEL(config.ServerInfo{})
|
||||
var tests = []struct {
|
||||
oldState int
|
||||
newState int
|
||||
@@ -92,7 +173,7 @@ func TestChangeSectionState(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestParseYumUpdateinfoLineToGetCveIDs(t *testing.T) {
|
||||
r := newRedhat(config.ServerInfo{})
|
||||
r := newRHEL(config.ServerInfo{})
|
||||
var tests = []struct {
|
||||
in string
|
||||
out []string
|
||||
@@ -122,7 +203,7 @@ func TestParseYumUpdateinfoLineToGetCveIDs(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestParseYumUpdateinfoToGetAdvisoryID(t *testing.T) {
|
||||
r := newRedhat(config.ServerInfo{})
|
||||
r := newRHEL(config.ServerInfo{})
|
||||
var tests = []struct {
|
||||
in string
|
||||
out string
|
||||
@@ -160,7 +241,7 @@ func TestParseYumUpdateinfoLineToGetIssued(t *testing.T) {
|
||||
|
||||
date, _ := time.Parse("2006-01-02", "2015-12-15")
|
||||
|
||||
r := newRedhat(config.ServerInfo{})
|
||||
r := newRHEL(config.ServerInfo{})
|
||||
var tests = []struct {
|
||||
in string
|
||||
out time.Time
|
||||
@@ -195,10 +276,9 @@ func TestParseYumUpdateinfoLineToGetIssued(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestParseYumUpdateinfoLineToGetUpdated(t *testing.T) {
|
||||
|
||||
date, _ := time.Parse("2006-01-02", "2015-12-15")
|
||||
|
||||
r := newRedhat(config.ServerInfo{})
|
||||
r := newRHEL(config.ServerInfo{})
|
||||
var tests = []struct {
|
||||
in string
|
||||
out time.Time
|
||||
@@ -234,7 +314,7 @@ func TestParseYumUpdateinfoLineToGetUpdated(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestIsDescriptionLine(t *testing.T) {
|
||||
r := newRedhat(config.ServerInfo{})
|
||||
r := newRHEL(config.ServerInfo{})
|
||||
var tests = []struct {
|
||||
in string
|
||||
found bool
|
||||
@@ -262,7 +342,7 @@ func TestIsDescriptionLine(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestParseYumUpdateinfoToGetSeverity(t *testing.T) {
|
||||
r := newRedhat(config.ServerInfo{})
|
||||
r := newRHEL(config.ServerInfo{})
|
||||
var tests = []struct {
|
||||
in string
|
||||
out string
|
||||
@@ -337,7 +417,7 @@ Description : kernel-uek
|
||||
`
|
||||
issued, _ := time.Parse("2006-01-02", "2017-02-15")
|
||||
|
||||
r := newRedhat(config.ServerInfo{})
|
||||
r := newRHEL(config.ServerInfo{})
|
||||
r.Distro = config.Distro{Family: "oraclelinux"}
|
||||
|
||||
var tests = []struct {
|
||||
@@ -458,7 +538,7 @@ Description : The sudo packages contain the sudo utility which allows system
|
||||
issued, _ := time.Parse("2006-01-02", "2015-09-03")
|
||||
updated, _ := time.Parse("2006-01-02", "2015-09-04")
|
||||
|
||||
r := newRedhat(config.ServerInfo{})
|
||||
r := newRHEL(config.ServerInfo{})
|
||||
r.Distro = config.Distro{Family: "redhat"}
|
||||
|
||||
var tests = []struct {
|
||||
@@ -532,8 +612,7 @@ Description : The sudo packages contain the sudo utility which allows system
|
||||
}
|
||||
|
||||
func TestParseYumUpdateinfoAmazon(t *testing.T) {
|
||||
|
||||
r := newRedhat(config.ServerInfo{})
|
||||
r := newAmazon(config.ServerInfo{})
|
||||
r.Distro = config.Distro{Family: "redhat"}
|
||||
|
||||
issued, _ := time.Parse("2006-01-02", "2015-12-15")
|
||||
@@ -617,7 +696,7 @@ Description : Package updates are available for Amazon Linux AMI that fix the
|
||||
}
|
||||
|
||||
func TestParseYumCheckUpdateLine(t *testing.T) {
|
||||
r := newRedhat(config.ServerInfo{})
|
||||
r := newCentOS(config.ServerInfo{})
|
||||
r.Distro = config.Distro{Family: "centos"}
|
||||
var tests = []struct {
|
||||
in string
|
||||
@@ -658,7 +737,7 @@ func TestParseYumCheckUpdateLine(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestParseYumCheckUpdateLines(t *testing.T) {
|
||||
r := newRedhat(config.ServerInfo{})
|
||||
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
|
||||
@@ -739,7 +818,7 @@ pytalloc 0 2.0.7 2.el6 @CentOS 6.5/6.5`
|
||||
}
|
||||
|
||||
func TestParseYumCheckUpdateLinesAmazon(t *testing.T) {
|
||||
r := newRedhat(config.ServerInfo{})
|
||||
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
|
||||
@@ -795,7 +874,7 @@ if-not-architecture 0 100 200 amzn-main`
|
||||
}
|
||||
|
||||
func TestParseYumUpdateinfoListAvailable(t *testing.T) {
|
||||
r := newRedhat(config.ServerInfo{})
|
||||
r := newRHEL(config.ServerInfo{})
|
||||
rhelStdout := `RHSA-2015:2315 Moderate/Sec. NetworkManager-1:1.0.6-27.el7.x86_64
|
||||
RHSA-2015:2315 Moderate/Sec. NetworkManager-config-server-1:1.0.6-27.el7.x86_64
|
||||
RHSA-2015:1705 Important/Sec. bind-libs-lite-32:9.9.4-18.el7_1.5.x86_64
|
||||
@@ -859,7 +938,7 @@ updateinfo list done`
|
||||
}
|
||||
|
||||
func TestExtractPackNameVerRel(t *testing.T) {
|
||||
r := newRedhat(config.ServerInfo{})
|
||||
r := newAmazon(config.ServerInfo{})
|
||||
var tests = []struct {
|
||||
in string
|
||||
out []string
|
||||
@@ -894,7 +973,7 @@ func TestExtractPackNameVerRel(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestGetDiffChangelog(t *testing.T) {
|
||||
r := newRedhat(config.ServerInfo{})
|
||||
r := newCentOS(config.ServerInfo{})
|
||||
type in struct {
|
||||
pack models.Package
|
||||
changelog string
|
||||
@@ -1245,7 +1324,7 @@ func TestGetDiffChangelog(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestDivideChangelogsIntoEachPackages(t *testing.T) {
|
||||
r := newRedhat(config.ServerInfo{})
|
||||
r := newRHEL(config.ServerInfo{})
|
||||
type in struct {
|
||||
pack models.Package
|
||||
changelog string
|
||||
@@ -1297,3 +1376,389 @@ func TestDivideChangelogsIntoEachPackages(t *testing.T) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestCheckYumPsInstalled(t *testing.T) {
|
||||
r := newCentOS(config.ServerInfo{})
|
||||
var tests = []struct {
|
||||
in string
|
||||
out bool
|
||||
}{
|
||||
{
|
||||
in: `Loaded plugins: changelog, fastestmirror, ps, remove-with-leaves, show-leaves
|
||||
Loading mirror speeds from cached hostfile
|
||||
* base: ftp.tsukuba.wide.ad.jp
|
||||
* extras: ftp.tsukuba.wide.ad.jp
|
||||
* updates: ftp.tsukuba.wide.ad.jp
|
||||
Installed Packages
|
||||
Name : yum
|
||||
Arch : noarch
|
||||
Version : 3.4.3
|
||||
Release : 150.el7.centos
|
||||
Size : 5.5 M
|
||||
Repo : installed
|
||||
From repo : anaconda
|
||||
Summary : RPM package installer/updater/manager
|
||||
URL : http://yum.baseurl.org/
|
||||
License : GPLv2+
|
||||
Description : Yum is a utility that can check for and automatically download and
|
||||
: install updated RPM packages. Dependencies are obtained and downloaded
|
||||
: automatically, prompting the user for permission as necessary.
|
||||
|
||||
Available Packages
|
||||
Name : yum
|
||||
Arch : noarch
|
||||
Version : 3.4.3
|
||||
Release : 154.el7.centos.1
|
||||
Size : 1.2 M
|
||||
Repo : updates/7/x86_64
|
||||
Summary : RPM package installer/updater/manager
|
||||
URL : http://yum.baseurl.org/
|
||||
License : GPLv2+
|
||||
Description : Yum is a utility that can check for and automatically download and
|
||||
: install updated RPM packages. Dependencies are obtained and downloaded
|
||||
: automatically, prompting the user for permission as necessary.`,
|
||||
out: true,
|
||||
},
|
||||
{
|
||||
in: `Failed to set locale, defaulting to C
|
||||
Loaded plugins: amazon-id, rhui-lb, search-disabled-repos
|
||||
Installed Packages
|
||||
Name : yum
|
||||
Arch : noarch
|
||||
Version : 3.4.3
|
||||
Release : 154.el7
|
||||
Size : 5.5 M
|
||||
Repo : installed
|
||||
From repo : rhui-REGION-rhel-server-releases
|
||||
Summary : RPM package installer/updater/manager
|
||||
URL : http://yum.baseurl.org/
|
||||
License : GPLv2+
|
||||
Description : Yum is a utility that can check for and automatically download and
|
||||
: install updated RPM packages. Dependencies are obtained and downloaded
|
||||
: automatically, prompting the user for permission as necessary.`,
|
||||
out: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
ok := r.checkYumPsInstalled(tt.in)
|
||||
if ok != tt.out {
|
||||
t.Errorf("expected: %v\nactual: %v", tt.out, ok)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseYumPS(t *testing.T) {
|
||||
r := newCentOS(config.ServerInfo{})
|
||||
r.Distro = config.Distro{Family: "centos"}
|
||||
r.Packages = models.NewPackages(
|
||||
models.Package{
|
||||
Name: "python",
|
||||
Version: "2.7.5",
|
||||
Release: "34.el7",
|
||||
Arch: "x86_64",
|
||||
},
|
||||
models.Package{
|
||||
Name: "util-linux",
|
||||
Version: "2.23.2",
|
||||
Release: "26.el7",
|
||||
Arch: "x86_64",
|
||||
},
|
||||
models.Package{
|
||||
Name: "wpa_supplicant",
|
||||
Version: "1:2.0",
|
||||
Release: "17.el7_1",
|
||||
Arch: "x86_64",
|
||||
},
|
||||
models.Package{
|
||||
Name: "yum",
|
||||
Version: "3.4.3",
|
||||
Release: "150.el7.centos",
|
||||
Arch: "noarch",
|
||||
},
|
||||
)
|
||||
|
||||
var tests = []struct {
|
||||
in string
|
||||
out models.Packages
|
||||
}{
|
||||
{
|
||||
` pid proc CPU RSS State uptime
|
||||
python-2.7.5-34.el7.x86_64 Upgrade 2.7.5-48.el7.x86_64
|
||||
741 tuned 1:54 16 MB Sleeping: 14 day(s) 21:52:32
|
||||
38755 yum 0:00 42 MB Running: 00:00
|
||||
util-linux-2.23.2-26.el7.x86_64 Upgrade 2.23.2-33.el7_3.2.x86_64
|
||||
626 agetty 0:00 848 kB Sleeping: 14 day(s) 21:52:37
|
||||
628 agetty 0:00 848 kB Sleeping: 14 day(s) 21:52:37
|
||||
1:wpa_supplicant-2.0-17.el7_1.x86_64 Upgrade 1:2.0-21.el7_3.x86_64
|
||||
638 wpa_supplicant 0:00 2.6 MB Sleeping: 14 day(s) 21:52:37
|
||||
yum-3.4.3-150.el7.centos.noarch
|
||||
38755 yum 0:00 42 MB Running: 00:00
|
||||
ps
|
||||
`,
|
||||
models.NewPackages(
|
||||
models.Package{
|
||||
Name: "python",
|
||||
Version: "2.7.5",
|
||||
Release: "34.el7",
|
||||
Arch: "x86_64",
|
||||
// NewVersion: "2.7.5-",
|
||||
// NewRelease: "48.el7.x86_64",
|
||||
AffectedProcs: []models.AffectedProcess{
|
||||
{
|
||||
PID: "741",
|
||||
Name: "tuned",
|
||||
},
|
||||
{
|
||||
PID: "38755",
|
||||
Name: "yum",
|
||||
},
|
||||
},
|
||||
},
|
||||
models.Package{
|
||||
Name: "util-linux",
|
||||
Version: "2.23.2",
|
||||
Release: "26.el7",
|
||||
Arch: "x86_64",
|
||||
// NewVersion: "2.7.5",
|
||||
// NewRelease: "48.el7.x86_64",
|
||||
AffectedProcs: []models.AffectedProcess{
|
||||
{
|
||||
PID: "626",
|
||||
Name: "agetty",
|
||||
},
|
||||
{
|
||||
PID: "628",
|
||||
Name: "agetty",
|
||||
},
|
||||
},
|
||||
},
|
||||
models.Package{
|
||||
Name: "wpa_supplicant",
|
||||
Version: "1:2.0",
|
||||
Release: "17.el7_1",
|
||||
Arch: "x86_64",
|
||||
// NewVersion: "1:2.0",
|
||||
// NewRelease: "21.el7_3.x86_64",
|
||||
AffectedProcs: []models.AffectedProcess{
|
||||
{
|
||||
PID: "638",
|
||||
Name: "wpa_supplicant",
|
||||
},
|
||||
},
|
||||
},
|
||||
),
|
||||
},
|
||||
{
|
||||
` pid proc CPU RSS State uptime
|
||||
acpid-2.0.19-6.7.amzn1.x86_64
|
||||
2388 acpid 0:00 1.4 MB Sleeping: 21:08
|
||||
at-3.1.10-48.15.amzn1.x86_64
|
||||
2546 atd 0:00 164 kB Sleeping: 21:06
|
||||
cronie-anacron-1.4.4-15.8.amzn1.x86_64
|
||||
2637 anacron 0:00 1.5 MB Sleeping: 13:14
|
||||
12:dhclient-4.1.1-51.P1.26.amzn1.x86_64
|
||||
2061 dhclient 0:00 1.4 MB Sleeping: 21:10
|
||||
2193 dhclient 0:00 2.1 MB Sleeping: 21:08
|
||||
mingetty-1.08-5.9.amzn1.x86_64
|
||||
2572 mingetty 0:00 1.4 MB Sleeping: 21:06
|
||||
2575 mingetty 0:00 1.4 MB Sleeping: 21:06
|
||||
2578 mingetty 0:00 1.5 MB Sleeping: 21:06
|
||||
2580 mingetty 0:00 1.4 MB Sleeping: 21:06
|
||||
2582 mingetty 0:00 1.4 MB Sleeping: 21:06
|
||||
2584 mingetty 0:00 1.4 MB Sleeping: 21:06
|
||||
openssh-server-6.6.1p1-33.66.amzn1.x86_64
|
||||
2481 sshd 0:00 2.6 MB Sleeping: 21:07
|
||||
python27-2.7.12-2.120.amzn1.x86_64
|
||||
2649 yum 0:00 35 MB Running: 00:01
|
||||
rsyslog-5.8.10-9.26.amzn1.x86_64
|
||||
2261 rsyslogd 0:00 2.6 MB Sleeping: 21:08
|
||||
udev-173-4.13.amzn1.x86_64
|
||||
1528 udevd 0:00 2.5 MB Sleeping: 21:12
|
||||
1652 udevd 0:00 2.1 MB Sleeping: 21:12
|
||||
1653 udevd 0:00 2.0 MB Sleeping: 21:12
|
||||
upstart-0.6.5-13.3.13.amzn1.x86_64
|
||||
1 init 0:00 2.5 MB Sleeping: 21:13
|
||||
util-linux-2.23.2-33.28.amzn1.x86_64
|
||||
2569 agetty 0:00 1.6 MB Sleeping: 21:06
|
||||
yum-3.4.3-150.70.amzn1.noarch
|
||||
2649 yum 0:00 35 MB Running: 00:01
|
||||
`,
|
||||
models.Packages{},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
packages := r.parseYumPS(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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseNeedsRestarting(t *testing.T) {
|
||||
r := newCentOS(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 21
|
||||
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 TestIsExecScanUsingYum(t *testing.T) {
|
||||
r := newRHEL(config.ServerInfo{})
|
||||
var tests = []struct {
|
||||
modes []byte
|
||||
family string
|
||||
out bool
|
||||
}{
|
||||
{
|
||||
modes: []byte{config.Offline},
|
||||
out: false,
|
||||
},
|
||||
{
|
||||
modes: []byte{},
|
||||
family: config.CentOS,
|
||||
out: false,
|
||||
},
|
||||
{
|
||||
family: config.Amazon,
|
||||
modes: []byte{config.FastRoot},
|
||||
out: true,
|
||||
},
|
||||
{
|
||||
family: config.Amazon,
|
||||
modes: []byte{config.Deep},
|
||||
out: true,
|
||||
},
|
||||
}
|
||||
|
||||
for i, tt := range tests {
|
||||
r.Distro.Family = tt.family
|
||||
mode := config.ScanMode{}
|
||||
for _, m := range tt.modes {
|
||||
mode.Set(m)
|
||||
}
|
||||
|
||||
si := r.getServerInfo()
|
||||
si.Mode = mode
|
||||
r.setServerInfo(si)
|
||||
|
||||
out := r.isExecScanUsingYum()
|
||||
if out != tt.out {
|
||||
t.Errorf("[%d] expected %#v, actual %#v", i, tt.out, out)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsExecFillChangelogs(t *testing.T) {
|
||||
r := newRHEL(config.ServerInfo{})
|
||||
var tests = []struct {
|
||||
modes []byte
|
||||
family string
|
||||
out bool
|
||||
}{
|
||||
{
|
||||
modes: []byte{config.Offline},
|
||||
out: false,
|
||||
},
|
||||
{
|
||||
modes: []byte{config.Deep},
|
||||
family: config.CentOS,
|
||||
out: true,
|
||||
},
|
||||
{
|
||||
family: config.Amazon,
|
||||
modes: []byte{config.Deep},
|
||||
out: false,
|
||||
},
|
||||
{
|
||||
modes: []byte{config.Deep},
|
||||
family: config.RedHat,
|
||||
out: true,
|
||||
},
|
||||
}
|
||||
|
||||
for i, tt := range tests {
|
||||
r.Distro.Family = tt.family
|
||||
mode := config.ScanMode{}
|
||||
for _, m := range tt.modes {
|
||||
mode.Set(m)
|
||||
}
|
||||
|
||||
si := r.getServerInfo()
|
||||
si.Mode = mode
|
||||
r.setServerInfo(si)
|
||||
out := r.isExecFillChangelogs()
|
||||
if out != tt.out {
|
||||
t.Errorf("[%d] expected %#v, actual %#v", i, tt.out, out)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsScanChangelogs(t *testing.T) {
|
||||
r := newCentOS(config.ServerInfo{})
|
||||
var tests = []struct {
|
||||
modes []byte
|
||||
family string
|
||||
out bool
|
||||
}{
|
||||
{
|
||||
modes: []byte{config.Offline},
|
||||
out: false,
|
||||
},
|
||||
{
|
||||
modes: []byte{config.Fast},
|
||||
out: false,
|
||||
},
|
||||
{
|
||||
modes: []byte{config.FastRoot},
|
||||
out: false,
|
||||
},
|
||||
{
|
||||
modes: []byte{config.Deep},
|
||||
family: config.RedHat,
|
||||
out: true,
|
||||
},
|
||||
}
|
||||
|
||||
for i, tt := range tests {
|
||||
r.Distro.Family = tt.family
|
||||
mode := config.ScanMode{}
|
||||
for _, m := range tt.modes {
|
||||
mode.Set(m)
|
||||
}
|
||||
|
||||
si := r.getServerInfo()
|
||||
si.Mode = mode
|
||||
r.setServerInfo(si)
|
||||
out := r.isExecScanChangelogs()
|
||||
if out != tt.out {
|
||||
t.Errorf("[%d] expected %#v, actual %#v", i, tt.out, out)
|
||||
}
|
||||
}
|
||||
}
|
||||
159
scan/rhel.go
Normal file
159
scan/rhel.go
Normal file
@@ -0,0 +1,159 @@
|
||||
package scan
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/future-architect/vuls/config"
|
||||
"github.com/future-architect/vuls/models"
|
||||
"github.com/future-architect/vuls/util"
|
||||
)
|
||||
|
||||
// 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 fmt.Errorf("Unknown scan mode")
|
||||
}
|
||||
|
||||
func (o *rhel) depsFast() []string {
|
||||
return []string{}
|
||||
}
|
||||
|
||||
func (o *rhel) depsFastRoot() []string {
|
||||
if o.getServerInfo().Mode.IsOffline() {
|
||||
return []string{}
|
||||
}
|
||||
|
||||
majorVersion, _ := o.Distro.MajorVersion()
|
||||
switch majorVersion {
|
||||
case 5:
|
||||
return []string{
|
||||
"yum-utils",
|
||||
"yum-security",
|
||||
}
|
||||
case 6:
|
||||
return []string{
|
||||
"yum-utils",
|
||||
"yum-plugin-security",
|
||||
}
|
||||
default:
|
||||
return []string{
|
||||
"yum-utils",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (o *rhel) depsDeep() []string {
|
||||
majorVersion, _ := o.Distro.MajorVersion()
|
||||
switch majorVersion {
|
||||
case 5:
|
||||
return []string{
|
||||
"yum-utils",
|
||||
"yum-security",
|
||||
"yum-changelog",
|
||||
}
|
||||
case 6:
|
||||
return []string{
|
||||
"yum-utils",
|
||||
"yum-plugin-security",
|
||||
"yum-plugin-changelog",
|
||||
}
|
||||
default:
|
||||
return []string{
|
||||
"yum-utils",
|
||||
"yum-plugin-changelog",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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.getServerInfo().Mode.IsOffline() {
|
||||
return []cmd{}
|
||||
}
|
||||
|
||||
majorVersion, _ := o.Distro.MajorVersion()
|
||||
if majorVersion < 6 {
|
||||
return []cmd{
|
||||
// {"needs-restarting", exitStatusZero},
|
||||
{"yum repolist --color=never", exitStatusZero},
|
||||
{"yum list-security --security --color=never", exitStatusZero},
|
||||
{"yum info-security --color=never", exitStatusZero},
|
||||
{"repoquery -h", exitStatusZero},
|
||||
}
|
||||
}
|
||||
return []cmd{
|
||||
{"yum repolist --color=never", exitStatusZero},
|
||||
{"yum updateinfo list updates --security --color=never", exitStatusZero},
|
||||
{"yum updateinfo updates --security --color=never ", exitStatusZero},
|
||||
{"repoquery -h", exitStatusZero},
|
||||
{"needs-restarting", exitStatusZero},
|
||||
}
|
||||
}
|
||||
|
||||
func (o *rhel) sudoNoPasswdCmdsDeep() []cmd {
|
||||
return append(o.sudoNoPasswdCmdsFastRoot(),
|
||||
cmd{"yum changelog all updates --color=never", exitStatusZero})
|
||||
}
|
||||
|
||||
type rootPrivRHEL struct{}
|
||||
|
||||
func (o rootPrivRHEL) repoquery() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (o rootPrivRHEL) yumRepolist() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (o rootPrivRHEL) yumUpdateInfo() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (o rootPrivRHEL) yumChangelog() bool {
|
||||
return true
|
||||
}
|
||||
@@ -18,7 +18,9 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
package scan
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
@@ -30,6 +32,13 @@ import (
|
||||
"github.com/future-architect/vuls/util"
|
||||
)
|
||||
|
||||
var (
|
||||
errOSFamilyHeader = errors.New("X-Vuls-OS-Family header is required")
|
||||
errOSReleaseHeader = errors.New("X-Vuls-OS-Release header is required")
|
||||
errKernelVersionHeader = errors.New("X-Vuls-Kernel-Version header is required")
|
||||
errServerNameHeader = errors.New("X-Vuls-Server-Name header is required")
|
||||
)
|
||||
|
||||
var servers, errServers []osTypeInterface
|
||||
|
||||
// Base Interface of redhat, debian, freebsd
|
||||
@@ -41,7 +50,8 @@ type osTypeInterface interface {
|
||||
detectPlatform()
|
||||
getPlatform() models.Platform
|
||||
|
||||
checkDependencies() error
|
||||
checkScanMode() error
|
||||
checkDeps() error
|
||||
checkIfSudoNoPasswd() error
|
||||
|
||||
preCure() error
|
||||
@@ -49,6 +59,8 @@ type osTypeInterface interface {
|
||||
scanPackages() error
|
||||
convertToModel() models.ScanResult
|
||||
|
||||
parseInstalledPackages(string) (models.Packages, models.SrcPackages, error)
|
||||
|
||||
runningContainers() ([]config.Container, error)
|
||||
exitedContainers() ([]config.Container, error)
|
||||
allContainers() ([]config.Container, error)
|
||||
@@ -119,7 +131,11 @@ func detectOS(c config.ServerInfo) (osType osTypeInterface) {
|
||||
}
|
||||
|
||||
// PrintSSHableServerNames print SSH-able servernames
|
||||
func PrintSSHableServerNames() {
|
||||
func PrintSSHableServerNames() bool {
|
||||
if len(servers) == 0 {
|
||||
util.Log.Error("No scannable servers")
|
||||
return false
|
||||
}
|
||||
util.Log.Info("Scannable servers are below...")
|
||||
for _, s := range servers {
|
||||
if s.getServerInfo().IsContainer() {
|
||||
@@ -132,6 +148,7 @@ func PrintSSHableServerNames() {
|
||||
}
|
||||
}
|
||||
fmt.Printf("\n")
|
||||
return true
|
||||
}
|
||||
|
||||
// InitServers detect the kind of OS distribution of target servers
|
||||
@@ -273,7 +290,7 @@ func detectContainerOSes(timeoutSec int) (actives, inactives []osTypeInterface)
|
||||
|
||||
func detectContainerOSesOnServer(containerHost osTypeInterface) (oses []osTypeInterface) {
|
||||
containerHostInfo := containerHost.getServerInfo()
|
||||
if len(containerHostInfo.Containers.Includes) == 0 {
|
||||
if len(containerHostInfo.ContainersIncluded) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -285,11 +302,10 @@ func detectContainerOSesOnServer(containerHost osTypeInterface) (oses []osTypeIn
|
||||
return append(oses, containerHost)
|
||||
}
|
||||
|
||||
if containerHostInfo.Containers.Includes[0] == "${running}" {
|
||||
if containerHostInfo.ContainersIncluded[0] == "${running}" {
|
||||
for _, containerInfo := range running {
|
||||
|
||||
found := false
|
||||
for _, ex := range containerHost.getServerInfo().Containers.Excludes {
|
||||
for _, ex := range containerHost.getServerInfo().ContainersExcluded {
|
||||
if containerInfo.Name == ex || containerInfo.ContainerID == ex {
|
||||
found = true
|
||||
}
|
||||
@@ -319,7 +335,7 @@ func detectContainerOSesOnServer(containerHost osTypeInterface) (oses []osTypeIn
|
||||
}
|
||||
|
||||
var exited, unknown []string
|
||||
for _, container := range containerHostInfo.Containers.Includes {
|
||||
for _, container := range containerHostInfo.ContainersIncluded {
|
||||
found := false
|
||||
for _, c := range running {
|
||||
if c.ContainerID == container || c.Name == container {
|
||||
@@ -355,10 +371,21 @@ func detectContainerOSesOnServer(containerHost osTypeInterface) (oses []osTypeIn
|
||||
return oses
|
||||
}
|
||||
|
||||
// CheckScanModes checks scan mode
|
||||
func CheckScanModes() error {
|
||||
for _, s := range servers {
|
||||
if err := s.checkScanMode(); err != nil {
|
||||
return fmt.Errorf("servers.%s.scanMode err: %s",
|
||||
s.getServerInfo().GetServerName(), err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CheckDependencies checks dependencies are installed on target servers.
|
||||
func CheckDependencies(timeoutSec int) {
|
||||
parallelExec(func(o osTypeInterface) error {
|
||||
return o.checkDependencies()
|
||||
return o.checkDeps()
|
||||
}, timeoutSec)
|
||||
return
|
||||
}
|
||||
@@ -420,15 +447,92 @@ func Scan(timeoutSec int) error {
|
||||
|
||||
util.Log.Info("Scanning vulnerable OS packages...")
|
||||
scannedAt := time.Now()
|
||||
dir, err := ensureResultDir(scannedAt)
|
||||
dir, err := EnsureResultDir(scannedAt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := scanVulns(dir, scannedAt, timeoutSec); err != nil {
|
||||
return err
|
||||
return scanVulns(dir, scannedAt, timeoutSec)
|
||||
}
|
||||
|
||||
// ViaHTTP scans servers by HTTP header and body
|
||||
func ViaHTTP(header http.Header, body string) (models.ScanResult, error) {
|
||||
family := header.Get("X-Vuls-OS-Family")
|
||||
if family == "" {
|
||||
return models.ScanResult{}, errOSFamilyHeader
|
||||
}
|
||||
|
||||
return nil
|
||||
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 == config.Debian && kernelVersion == "" {
|
||||
return models.ScanResult{}, errKernelVersionHeader
|
||||
}
|
||||
|
||||
serverName := header.Get("X-Vuls-Server-Name")
|
||||
if config.Conf.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 config.Debian, config.Ubuntu:
|
||||
osType = &debian{base: base}
|
||||
case config.RedHat:
|
||||
osType = &rhel{
|
||||
redhatBase: redhatBase{base: base},
|
||||
}
|
||||
case config.CentOS:
|
||||
osType = ¢os{
|
||||
redhatBase: redhatBase{base: base},
|
||||
}
|
||||
default:
|
||||
return models.ScanResult{}, fmt.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
|
||||
}
|
||||
|
||||
func setupChangelogCache() error {
|
||||
@@ -440,7 +544,7 @@ func setupChangelogCache() error {
|
||||
break
|
||||
case config.Ubuntu, config.Debian:
|
||||
//TODO changelopg cache for RedHat, Oracle, Amazon, CentOS is not implemented yet.
|
||||
if config.Conf.Deep {
|
||||
if s.getServerInfo().Mode.IsDeep() {
|
||||
needToSetupCache = true
|
||||
}
|
||||
break
|
||||
@@ -466,9 +570,13 @@ func scanVulns(jsonDir string, scannedAt time.Time, timeoutSec int) error {
|
||||
return o.postScan()
|
||||
}, timeoutSec)
|
||||
|
||||
hostname, _ := os.Hostname()
|
||||
for _, s := range append(servers, errServers...) {
|
||||
r := s.convertToModel()
|
||||
r.ScannedAt = scannedAt
|
||||
r.ScannedVersion = config.Version
|
||||
r.ScannedRevision = config.Revision
|
||||
r.ScannedBy = hostname
|
||||
r.Config.Scan = config.Conf
|
||||
results = append(results, r)
|
||||
}
|
||||
@@ -487,7 +595,8 @@ func scanVulns(jsonDir string, scannedAt time.Time, timeoutSec int) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func ensureResultDir(scannedAt time.Time) (currentDir string, err error) {
|
||||
// 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
|
||||
|
||||
@@ -1 +1,138 @@
|
||||
package scan
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/future-architect/vuls/config"
|
||||
"github.com/future-architect/vuls/models"
|
||||
)
|
||||
|
||||
func TestViaHTTP(t *testing.T) {
|
||||
r := newRHEL(config.ServerInfo{})
|
||||
r.Distro = config.Distro{Family: config.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)
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
34
scan/suse.go
34
scan/suse.go
@@ -13,13 +13,13 @@ import (
|
||||
|
||||
// inherit OsTypeInterface
|
||||
type suse struct {
|
||||
redhat
|
||||
redhatBase
|
||||
}
|
||||
|
||||
// NewRedhat is constructor
|
||||
func newSUSE(c config.ServerInfo) *suse {
|
||||
r := &suse{
|
||||
redhat: redhat{
|
||||
redhatBase: redhatBase{
|
||||
base: base{
|
||||
osPackages: osPackages{
|
||||
Packages: models.Packages{},
|
||||
@@ -98,7 +98,11 @@ func (o *suse) parseOSRelease(content string) (name string, ver string) {
|
||||
return name, result[1]
|
||||
}
|
||||
|
||||
func (o *suse) checkDependencies() error {
|
||||
func (o *suse) checkScanMode() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *suse) checkDeps() error {
|
||||
o.log.Infof("Dependencies... No need")
|
||||
return nil
|
||||
}
|
||||
@@ -122,6 +126,10 @@ func (o *suse) scanPackages() error {
|
||||
return err
|
||||
}
|
||||
o.Kernel.RebootRequired = rebootRequired
|
||||
if o.getServerInfo().Mode.IsOffline() {
|
||||
o.Packages = installed
|
||||
return nil
|
||||
}
|
||||
|
||||
updatable, err := o.scanUpdatablePackages()
|
||||
if err != nil {
|
||||
@@ -135,20 +143,20 @@ func (o *suse) scanPackages() error {
|
||||
}
|
||||
|
||||
func (o *suse) rebootRequired() (bool, error) {
|
||||
r := o.exec("rpm -q --last kernel-default | head -n1", noSudo)
|
||||
r := o.exec("rpm -q --last kernel-default", noSudo)
|
||||
if !r.isSuccess() {
|
||||
return false, fmt.Errorf("Failed to detect the last installed kernel : %v", r)
|
||||
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 := ""
|
||||
if v, _ := o.Distro.MajorVersion(); v < 12 {
|
||||
cmd = "zypper -q lu"
|
||||
} else {
|
||||
cmd = "zypper --no-color -q lu"
|
||||
cmd := "zypper -q lu"
|
||||
if o.hasZypperColorOption() {
|
||||
cmd = "zypper -q --no-color lu"
|
||||
}
|
||||
r := o.exec(cmd, noSudo)
|
||||
if !r.isSuccess() {
|
||||
@@ -188,3 +196,9 @@ func (o *suse) parseZypperLUOneLine(line string) (*models.Package, error) {
|
||||
Arch: fs[10],
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (o *suse) hasZypperColorOption() bool {
|
||||
cmd := "zypper --help | grep color"
|
||||
r := o.exec(util.PrependProxyEnv(cmd), noSudo)
|
||||
return len(r.Stdout) > 0
|
||||
}
|
||||
|
||||
@@ -17,16 +17,22 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package scan
|
||||
|
||||
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) checkDependencies() error {
|
||||
func (o *unknown) checkDeps() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -41,3 +47,7 @@ func (o *unknown) postScan() error {
|
||||
func (o *unknown) scanPackages() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *unknown) parseInstalledPackages(string) (models.Packages, models.SrcPackages, error) {
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
||||
@@ -70,7 +70,7 @@ func TestIsRunningKernelSUSE(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestIsRunningKernelRedHatLikeLinux(t *testing.T) {
|
||||
r := newRedhat(config.ServerInfo{})
|
||||
r := newAmazon(config.ServerInfo{})
|
||||
r.Distro = config.Distro{Family: config.Amazon}
|
||||
|
||||
kernel := models.Kernel{
|
||||
|
||||
Reference in New Issue
Block a user