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:
Kota Kanbe
2018-08-27 13:51:09 +09:00
committed by GitHub
parent d785fc2a54
commit 44fa2c5800
82 changed files with 14019 additions and 2485 deletions

View File

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

View File

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

View File

@@ -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
View 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 := &centos{
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
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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 = &centos{
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

View File

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

View File

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

View File

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

View File

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