feat(fast-root): get running procs for each pkgs (all RHEL, CentOS, AmazonLinux, Ubuntu, Debian) (#855)
* fix(scan): exec yum-plugin-ps on RHEL6 and 7 * feat(yumps): get affected procs on RHEL6 and RHEL8 * feat(scan): get affected processes for each packages * tuning * feat(scan): get running procs for each pkgs on Debian, Ubuntu
This commit is contained in:
@@ -80,10 +80,11 @@ func (o *amazon) sudoNoPasswdCmdsFast() []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},
|
||||
{"stat /proc/1/exe", exitStatusZero},
|
||||
{"ls -l /proc/1/exe", exitStatusZero},
|
||||
{"cat /proc/1/maps", exitStatusZero},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,3 +101,7 @@ func (o rootPrivAmazon) repoquery() bool {
|
||||
func (o rootPrivAmazon) yumMakeCache() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (o rootPrivAmazon) yumPS() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
59
scan/base.go
59
scan/base.go
@@ -32,6 +32,7 @@ import (
|
||||
|
||||
"github.com/future-architect/vuls/config"
|
||||
"github.com/future-architect/vuls/models"
|
||||
"github.com/future-architect/vuls/util"
|
||||
"github.com/sirupsen/logrus"
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
@@ -722,3 +723,61 @@ func (l *base) detectWpPlugins() ([]models.WpPackage, error) {
|
||||
}
|
||||
return plugins, nil
|
||||
}
|
||||
|
||||
func (l *base) ps() (stdout string, err error) {
|
||||
cmd := `LANGUAGE=en_US.UTF-8 ps --no-headers --ppid 2 -p 2 --deselect -o pid,comm | awk '{print $1,$2}'`
|
||||
r := l.exec(util.PrependProxyEnv(cmd), noSudo)
|
||||
if !r.isSuccess() {
|
||||
return "", xerrors.Errorf("Failed to SSH: %s", r)
|
||||
}
|
||||
return r.Stdout, nil
|
||||
}
|
||||
|
||||
func (l *base) parsePs(stdout string) map[string]string {
|
||||
pidNames := map[string]string{}
|
||||
scanner := bufio.NewScanner(strings.NewReader(stdout))
|
||||
for scanner.Scan() {
|
||||
line := strings.TrimSpace(scanner.Text())
|
||||
ss := strings.Fields(line)
|
||||
if len(ss) < 2 {
|
||||
continue
|
||||
}
|
||||
pidNames[ss[0]] = ss[1]
|
||||
}
|
||||
return pidNames
|
||||
}
|
||||
|
||||
func (l *base) lsProcExe(pid string) (stdout string, err error) {
|
||||
cmd := fmt.Sprintf("ls -l /proc/%s/exe", pid)
|
||||
r := l.exec(util.PrependProxyEnv(cmd), sudo)
|
||||
if !r.isSuccess() {
|
||||
return "", xerrors.Errorf("Failed to SSH: %s", r)
|
||||
}
|
||||
return r.Stdout, nil
|
||||
}
|
||||
|
||||
func (l *base) parseLsProcExe(stdout string) (string, error) {
|
||||
ss := strings.Fields(stdout)
|
||||
if len(ss) < 11 {
|
||||
return "", xerrors.Errorf("Unknown format: %s", stdout)
|
||||
}
|
||||
return ss[10], nil
|
||||
}
|
||||
|
||||
func (l *base) grepProcMap(pid string) (stdout string, err error) {
|
||||
cmd := fmt.Sprintf(`cat /proc/%s/maps 2>/dev/null | grep -v " 00:00 " | awk '{print $6}' | sort -n | uniq`, pid)
|
||||
r := l.exec(util.PrependProxyEnv(cmd), sudo)
|
||||
if !r.isSuccess() {
|
||||
return "", xerrors.Errorf("Failed to SSH: %s", r)
|
||||
}
|
||||
return r.Stdout, nil
|
||||
}
|
||||
|
||||
func (l *base) parseGrepProcMap(stdout string) (soPaths []string) {
|
||||
scanner := bufio.NewScanner(strings.NewReader(stdout))
|
||||
for scanner.Scan() {
|
||||
line := strings.TrimSpace(scanner.Text())
|
||||
soPaths = append(soPaths, line)
|
||||
}
|
||||
return soPaths
|
||||
}
|
||||
|
||||
@@ -22,6 +22,13 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/future-architect/vuls/config"
|
||||
_ "github.com/knqyf263/fanal/analyzer/library/bundler"
|
||||
_ "github.com/knqyf263/fanal/analyzer/library/cargo"
|
||||
_ "github.com/knqyf263/fanal/analyzer/library/composer"
|
||||
_ "github.com/knqyf263/fanal/analyzer/library/npm"
|
||||
_ "github.com/knqyf263/fanal/analyzer/library/pipenv"
|
||||
_ "github.com/knqyf263/fanal/analyzer/library/poetry"
|
||||
_ "github.com/knqyf263/fanal/analyzer/library/yarn"
|
||||
)
|
||||
|
||||
func TestParseDockerPs(t *testing.T) {
|
||||
@@ -178,3 +185,70 @@ func TestParseSystemctlStatus(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func Test_base_parseLsProcExe(t *testing.T) {
|
||||
type args struct {
|
||||
stdout string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "systemd",
|
||||
args: args{
|
||||
stdout: "lrwxrwxrwx 1 root root 0 Jun 29 17:13 /proc/1/exe -> /lib/systemd/systemd",
|
||||
},
|
||||
want: "/lib/systemd/systemd",
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
l := &base{}
|
||||
got, err := l.parseLsProcExe(tt.args.stdout)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("base.parseLsProcExe() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if got != tt.want {
|
||||
t.Errorf("base.parseLsProcExe() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_base_parseGrepProcMap(t *testing.T) {
|
||||
type args struct {
|
||||
stdout string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantSoPaths []string
|
||||
}{
|
||||
{
|
||||
name: "systemd",
|
||||
args: args{
|
||||
`/etc/selinux/targeted/contexts/files/file_contexts.bin
|
||||
/etc/selinux/targeted/contexts/files/file_contexts.homedirs.bin
|
||||
/usr/lib64/libdl-2.28.so`,
|
||||
},
|
||||
wantSoPaths: []string{
|
||||
"/etc/selinux/targeted/contexts/files/file_contexts.bin",
|
||||
"/etc/selinux/targeted/contexts/files/file_contexts.homedirs.bin",
|
||||
"/usr/lib64/libdl-2.28.so",
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
l := &base{}
|
||||
if gotSoPaths := l.parseGrepProcMap(tt.args.stdout); !reflect.DeepEqual(gotSoPaths, tt.wantSoPaths) {
|
||||
t.Errorf("base.parseGrepProcMap() = %v, want %v", gotSoPaths, tt.wantSoPaths)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,19 +88,19 @@ func (o *centos) sudoNoPasswdCmdsFast() []cmd {
|
||||
}
|
||||
|
||||
func (o *centos) sudoNoPasswdCmdsFastRoot() []cmd {
|
||||
if o.getServerInfo().Mode.IsOffline() {
|
||||
// yum ps needs internet connection
|
||||
if !o.ServerInfo.IsContainer() {
|
||||
return []cmd{
|
||||
{"stat /proc/1/exe", exitStatusZero},
|
||||
{"repoquery -h", exitStatusZero},
|
||||
{"needs-restarting", exitStatusZero},
|
||||
{"which which", exitStatusZero},
|
||||
{"stat /proc/1/exe", exitStatusZero},
|
||||
{"ls -l /proc/1/exe", exitStatusZero},
|
||||
{"cat /proc/1/maps", exitStatusZero},
|
||||
}
|
||||
}
|
||||
return []cmd{
|
||||
{"yum -q ps all --color=never", exitStatusZero},
|
||||
{"stat /proc/1/exe", exitStatusZero},
|
||||
{"repoquery -h", exitStatusZero},
|
||||
{"needs-restarting", exitStatusZero},
|
||||
{"which which", exitStatusZero},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -117,3 +117,7 @@ func (o rootPrivCentos) repoquery() bool {
|
||||
func (o rootPrivCentos) yumMakeCache() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (o rootPrivCentos) yumPS() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -149,6 +149,8 @@ func (o *debian) checkIfSudoNoPasswd() error {
|
||||
cmds := []string{
|
||||
"checkrestart",
|
||||
"stat /proc/1/exe",
|
||||
"ls -l /proc/1/exe",
|
||||
"cat /proc/1/maps",
|
||||
}
|
||||
|
||||
if !o.getServerInfo().Mode.IsOffline() {
|
||||
@@ -262,6 +264,15 @@ func (o *debian) preCure() error {
|
||||
}
|
||||
|
||||
func (o *debian) postScan() error {
|
||||
if o.getServerInfo().Mode.IsDeep() || o.getServerInfo().Mode.IsFastRoot() {
|
||||
if err := o.dpkgPs(); err != nil {
|
||||
err = xerrors.Errorf("Failed to dpkg-ps: %w", err)
|
||||
o.log.Warnf("err: %+v", err)
|
||||
o.warns = append(o.warns, err)
|
||||
// Only warning this error
|
||||
}
|
||||
}
|
||||
|
||||
if o.getServerInfo().Mode.IsDeep() || o.getServerInfo().Mode.IsFastRoot() {
|
||||
if err := o.checkrestart(); err != nil {
|
||||
err = xerrors.Errorf("Failed to scan need-restarting processes: %w", err)
|
||||
@@ -1109,3 +1120,90 @@ func (o *debian) parseCheckRestart(stdout string) (models.Packages, []string) {
|
||||
}
|
||||
return packs, unknownServices
|
||||
}
|
||||
|
||||
func (o *debian) dpkgPs() error {
|
||||
stdout, err := o.ps()
|
||||
if err != nil {
|
||||
return xerrors.Errorf("Failed to ps: %w", err)
|
||||
}
|
||||
pidNames := o.parsePs(stdout)
|
||||
pidLoadedFiles := map[string][]string{}
|
||||
// for pid, name := range pidNames {
|
||||
for pid := range pidNames {
|
||||
stdout := ""
|
||||
stdout, err = o.lsProcExe(pid)
|
||||
if err != nil {
|
||||
o.log.Debugf("Failed to exec /proc/%s/exe err: %s", pid, err)
|
||||
continue
|
||||
}
|
||||
s, err := o.parseLsProcExe(stdout)
|
||||
if err != nil {
|
||||
o.log.Debugf("Failed to parse /proc/%s/exe: %s", pid, err)
|
||||
continue
|
||||
}
|
||||
pidLoadedFiles[pid] = append(pidLoadedFiles[pid], s)
|
||||
|
||||
stdout, err = o.grepProcMap(pid)
|
||||
if err != nil {
|
||||
o.log.Debugf("Failed to exec /proc/%s/maps: %s", pid, err)
|
||||
continue
|
||||
}
|
||||
ss := o.parseGrepProcMap(stdout)
|
||||
pidLoadedFiles[pid] = append(pidLoadedFiles[pid], ss...)
|
||||
}
|
||||
|
||||
for pid, loadedFiles := range pidLoadedFiles {
|
||||
o.log.Debugf("dpkg -S %#v", loadedFiles)
|
||||
pkgNames, err := o.getPkgName(loadedFiles)
|
||||
if err != nil {
|
||||
o.log.Debugf("Failed to get package name by file path: %s, err: %s", pkgNames, err)
|
||||
continue
|
||||
}
|
||||
|
||||
procName := ""
|
||||
if _, ok := pidNames[pid]; ok {
|
||||
procName = pidNames[pid]
|
||||
}
|
||||
proc := models.AffectedProcess{
|
||||
PID: pid,
|
||||
Name: procName,
|
||||
}
|
||||
|
||||
for _, n := range pkgNames {
|
||||
p, ok := o.Packages[n]
|
||||
if !ok {
|
||||
return xerrors.Errorf("pkg not found %s", n)
|
||||
}
|
||||
p.AffectedProcs = append(p.AffectedProcs, proc)
|
||||
o.Packages[p.Name] = p
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *debian) getPkgName(paths []string) (pkgNames []string, err error) {
|
||||
cmd := "dpkg -S " + strings.Join(paths, " ")
|
||||
r := o.exec(util.PrependProxyEnv(cmd), noSudo)
|
||||
if !r.isSuccess(0, 1) {
|
||||
return nil, xerrors.Errorf("Failed to SSH: %s", r)
|
||||
}
|
||||
return o.parseGetPkgName(r.Stdout), nil
|
||||
}
|
||||
|
||||
func (o *debian) parseGetPkgName(stdout string) (pkgNames []string) {
|
||||
uniq := map[string]struct{}{}
|
||||
scanner := bufio.NewScanner(strings.NewReader(stdout))
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
ss := strings.Fields(line)
|
||||
if len(ss) < 2 || ss[1] == "no" {
|
||||
continue
|
||||
}
|
||||
s := strings.Split(ss[0], ":")[0]
|
||||
uniq[s] = struct{}{}
|
||||
}
|
||||
for n := range uniq {
|
||||
pkgNames = append(pkgNames, n)
|
||||
}
|
||||
return pkgNames
|
||||
}
|
||||
|
||||
@@ -726,3 +726,38 @@ util-linux:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func Test_debian_parseGetPkgName(t *testing.T) {
|
||||
type args struct {
|
||||
stdout string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantPkgNames []string
|
||||
}{
|
||||
{
|
||||
name: "success",
|
||||
args: args{
|
||||
stdout: `udev: /lib/systemd/systemd-udevd
|
||||
dpkg-query: no path found matching pattern /lib/modules/3.16.0-6-amd64/modules.alias.bin
|
||||
udev: /lib/systemd/systemd-udevd
|
||||
dpkg-query: no path found matching pattern /lib/udev/hwdb.bin
|
||||
libuuid1:amd64: /lib/x86_64-linux-gnu/libuuid.so.1.3.0`,
|
||||
},
|
||||
wantPkgNames: []string{
|
||||
"udev",
|
||||
"libuuid1",
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
o := &debian{}
|
||||
gotPkgNames := o.parseGetPkgName(tt.args.stdout)
|
||||
if !reflect.DeepEqual(gotPkgNames, tt.wantPkgNames) {
|
||||
t.Errorf("debian.parseGetPkgName() = %v, want %v", gotPkgNames, tt.wantPkgNames)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,11 +74,20 @@ func (o *oracle) sudoNoPasswdCmdsFast() []cmd {
|
||||
}
|
||||
|
||||
func (o *oracle) sudoNoPasswdCmdsFastRoot() []cmd {
|
||||
cmds := []cmd{{"needs-restarting", exitStatusZero}}
|
||||
if o.getServerInfo().Mode.IsOffline() {
|
||||
return cmds
|
||||
if !o.ServerInfo.IsContainer() {
|
||||
return []cmd{
|
||||
{"repoquery -h", exitStatusZero},
|
||||
{"needs-restarting", exitStatusZero},
|
||||
{"which which", exitStatusZero},
|
||||
{"stat /proc/1/exe", exitStatusZero},
|
||||
{"ls -l /proc/1/exe", exitStatusZero},
|
||||
{"cat /proc/1/maps", exitStatusZero},
|
||||
}
|
||||
}
|
||||
return []cmd{
|
||||
{"repoquery -h", exitStatusZero},
|
||||
{"needs-restarting", exitStatusZero},
|
||||
}
|
||||
return append(cmds, cmd{"repoquery -h", exitStatusZero})
|
||||
}
|
||||
|
||||
func (o *oracle) sudoNoPasswdCmdsDeep() []cmd {
|
||||
@@ -94,3 +103,7 @@ func (o rootPrivOracle) repoquery() bool {
|
||||
func (o rootPrivOracle) yumMakeCache() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (o rootPrivOracle) yumPS() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -142,6 +142,7 @@ type redhatBase struct {
|
||||
type rootPriv interface {
|
||||
repoquery() bool
|
||||
yumMakeCache() bool
|
||||
yumPS() bool
|
||||
}
|
||||
|
||||
type cmd struct {
|
||||
@@ -189,7 +190,7 @@ func (o *redhatBase) preCure() error {
|
||||
|
||||
func (o *redhatBase) postScan() error {
|
||||
if o.isExecYumPS() {
|
||||
if err := o.yumPS(); err != nil {
|
||||
if err := o.yumPs(); err != nil {
|
||||
err = xerrors.Errorf("Failed to execute yum-ps: %w", err)
|
||||
o.log.Warnf("err: %+v", err)
|
||||
o.warns = append(o.warns, err)
|
||||
@@ -431,24 +432,9 @@ func (o *redhatBase) parseUpdatablePacksLine(line string) (models.Package, error
|
||||
return p, nil
|
||||
}
|
||||
|
||||
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) isExecYumPS() bool {
|
||||
// RedHat has no yum-ps
|
||||
switch o.Distro.Family {
|
||||
case config.RedHat,
|
||||
case config.Oracle,
|
||||
config.OpenSUSE,
|
||||
config.OpenSUSELeap,
|
||||
config.SUSEEnterpriseServer,
|
||||
@@ -456,12 +442,7 @@ func (o *redhatBase) isExecYumPS() bool {
|
||||
config.SUSEOpenstackCloud:
|
||||
return false
|
||||
}
|
||||
|
||||
// yum ps needs internet connection
|
||||
if o.getServerInfo().Mode.IsOffline() || o.getServerInfo().Mode.IsFast() {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
return !o.getServerInfo().Mode.IsFast()
|
||||
}
|
||||
|
||||
func (o *redhatBase) isExecNeedsRestarting() bool {
|
||||
@@ -496,107 +477,69 @@ func (o *redhatBase) isExecNeedsRestarting() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
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 xerrors.Errorf("Failed to SSH: %s", r)
|
||||
func (o *redhatBase) yumPs() error {
|
||||
stdout, err := o.ps()
|
||||
if err != nil {
|
||||
return xerrors.Errorf("Failed to yum ps: %w", err)
|
||||
}
|
||||
if !o.checkYumPsInstalled(r.Stdout) {
|
||||
switch o.Distro.Family {
|
||||
case config.RedHat, config.Oracle:
|
||||
return nil
|
||||
default:
|
||||
return xerrors.New("yum-plugin-ps is not installed")
|
||||
pidNames := o.parsePs(stdout)
|
||||
pidLoadedFiles := map[string][]string{}
|
||||
// for pid, name := range pidNames {
|
||||
for pid := range pidNames {
|
||||
stdout := ""
|
||||
stdout, err = o.lsProcExe(pid)
|
||||
if err != nil {
|
||||
o.log.Debugf("Failed to exec /proc/%s/exe err: %s", pid, err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
cmd = "LANGUAGE=en_US.UTF-8 yum -q ps all --color=never"
|
||||
r = o.exec(util.PrependProxyEnv(cmd), sudo)
|
||||
if !r.isSuccess() {
|
||||
return xerrors.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 *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
|
||||
s, err := o.parseLsProcExe(stdout)
|
||||
if err != nil {
|
||||
o.log.Debugf("Failed to parse /proc/%s/exe: %s", pid, err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
pidLoadedFiles[pid] = append(pidLoadedFiles[pid], s)
|
||||
|
||||
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" {
|
||||
stdout, err = o.grepProcMap(pid)
|
||||
if err != nil {
|
||||
o.log.Debugf("Failed to exec /proc/%s/maps: %s", pid, err)
|
||||
continue
|
||||
}
|
||||
ss := o.parseGrepProcMap(stdout)
|
||||
pidLoadedFiles[pid] = append(pidLoadedFiles[pid], ss...)
|
||||
}
|
||||
|
||||
for pid, loadedFiles := range pidLoadedFiles {
|
||||
o.log.Debugf("rpm -qf: %#v", loadedFiles)
|
||||
pkgNames, err := o.getPkgName(loadedFiles)
|
||||
if err != nil {
|
||||
o.log.Debugf("Failed to get package name by file path: %s, err: %s", pkgNames, err)
|
||||
continue
|
||||
}
|
||||
|
||||
isPackageLine = !strings.HasPrefix(line, " ")
|
||||
if isPackageLine {
|
||||
if 1 < len(fields) && fields[1] == "Upgrade" {
|
||||
needToParseProcline = true
|
||||
uniq := map[string]struct{}{}
|
||||
for _, name := range pkgNames {
|
||||
uniq[name] = struct{}{}
|
||||
}
|
||||
|
||||
// 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
|
||||
procName := ""
|
||||
if _, ok := pidNames[pid]; ok {
|
||||
procName = pidNames[pid]
|
||||
}
|
||||
proc := models.AffectedProcess{
|
||||
PID: pid,
|
||||
Name: procName,
|
||||
}
|
||||
|
||||
for fqpn := range uniq {
|
||||
p, err := o.Packages.FindByFQPN(fqpn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p.AffectedProcs = append(p.AffectedProcs, proc)
|
||||
o.Packages[p.Name] = *p
|
||||
}
|
||||
}
|
||||
return packs
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *redhatBase) needsRestarting() error {
|
||||
@@ -690,8 +633,22 @@ func (o *redhatBase) procPathToFQPN(execCommand string) (string, error) {
|
||||
return strings.Replace(fqpn, "-(none):", "-", -1), nil
|
||||
}
|
||||
|
||||
func (o *redhatBase) hasYumColorOption() bool {
|
||||
cmd := "yum --help | grep color"
|
||||
func (o *redhatBase) getPkgName(paths []string) (pkgNames []string, err error) {
|
||||
cmd := rpmQf(o.Distro) + strings.Join(paths, " ")
|
||||
r := o.exec(util.PrependProxyEnv(cmd), noSudo)
|
||||
return len(r.Stdout) > 0
|
||||
if !r.isSuccess() {
|
||||
return nil, xerrors.Errorf("Failed to SSH: %s", r)
|
||||
}
|
||||
|
||||
scanner := bufio.NewScanner(strings.NewReader(r.Stdout))
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
pack, err := o.parseInstalledPackagesLine(line)
|
||||
if err != nil {
|
||||
o.log.Debugf("Failed to parse rpm -qf line: %s, r: %s. continue", line, r)
|
||||
continue
|
||||
}
|
||||
pkgNames = append(pkgNames, pack.FQPN())
|
||||
}
|
||||
return pkgNames, nil
|
||||
}
|
||||
|
||||
@@ -329,229 +329,6 @@ if-not-architecture 0 100 200 amzn-main`
|
||||
}
|
||||
}
|
||||
|
||||
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"}
|
||||
@@ -580,49 +357,3 @@ func TestParseNeedsRestarting(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
16
scan/rhel.go
16
scan/rhel.go
@@ -81,14 +81,14 @@ func (o *rhel) sudoNoPasswdCmdsFast() []cmd {
|
||||
}
|
||||
|
||||
func (o *rhel) sudoNoPasswdCmdsFastRoot() []cmd {
|
||||
if o.getServerInfo().Mode.IsOffline() {
|
||||
return []cmd{}
|
||||
}
|
||||
|
||||
majorVersion, _ := o.Distro.MajorVersion()
|
||||
if majorVersion < 6 {
|
||||
if !o.ServerInfo.IsContainer() {
|
||||
return []cmd{
|
||||
{"repoquery -h", exitStatusZero},
|
||||
{"needs-restarting", exitStatusZero},
|
||||
{"which which", exitStatusZero},
|
||||
{"stat /proc/1/exe", exitStatusZero},
|
||||
{"ls -l /proc/1/exe", exitStatusZero},
|
||||
{"cat /proc/1/maps", exitStatusZero},
|
||||
}
|
||||
}
|
||||
return []cmd{
|
||||
@@ -110,3 +110,7 @@ func (o rootPrivRHEL) repoquery() bool {
|
||||
func (o rootPrivRHEL) yumMakeCache() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (o rootPrivRHEL) yumPS() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -52,8 +52,25 @@ func isRunningKernel(pack models.Package, family string, kernel models.Kernel) (
|
||||
}
|
||||
|
||||
func rpmQa(distro config.Distro) string {
|
||||
const old = "rpm -qa --queryformat \"%{NAME} %{EPOCH} %{VERSION} %{RELEASE} %{ARCH}\n\""
|
||||
const new = "rpm -qa --queryformat \"%{NAME} %{EPOCHNUM} %{VERSION} %{RELEASE} %{ARCH}\n\""
|
||||
const old = `rpm -qa --queryformat "%{NAME} %{EPOCH} %{VERSION} %{RELEASE} %{ARCH}\n"`
|
||||
const new = `rpm -qa --queryformat "%{NAME} %{EPOCHNUM} %{VERSION} %{RELEASE} %{ARCH}\n"`
|
||||
switch distro.Family {
|
||||
case config.SUSEEnterpriseServer:
|
||||
if v, _ := distro.MajorVersion(); v < 12 {
|
||||
return old
|
||||
}
|
||||
return new
|
||||
default:
|
||||
if v, _ := distro.MajorVersion(); v < 6 {
|
||||
return old
|
||||
}
|
||||
return new
|
||||
}
|
||||
}
|
||||
|
||||
func rpmQf(distro config.Distro) string {
|
||||
const old = `rpm -qf --queryformat "%{NAME} %{EPOCH} %{VERSION} %{RELEASE} %{ARCH}\n" `
|
||||
const new = `rpm -qf --queryformat "%{NAME} %{EPOCHNUM} %{VERSION} %{RELEASE} %{ARCH}\n" `
|
||||
switch distro.Family {
|
||||
case config.SUSEEnterpriseServer:
|
||||
if v, _ := distro.MajorVersion(); v < 12 {
|
||||
|
||||
Reference in New Issue
Block a user