package scanner import ( "reflect" "testing" "github.com/future-architect/vuls/config" "github.com/future-architect/vuls/constant" "github.com/future-architect/vuls/models" "github.com/k0kubun/pp" ) // func unixtimeNoerr(s string) time.Time { // t, _ := unixtime(s) // return t // } func TestParseInstalledPackagesLinesRedhat(t *testing.T) { r := newRHEL(config.ServerInfo{}) var packagetests = []struct { in string distro config.Distro 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`, distro: config.Distro{Family: constant.RedHat}, kernel: models.Kernel{}, packages: models.Packages{ "openssl": models.Package{ Name: "openssl", Version: "1.0.1e", Release: "30.el6.11", }, "Percona-Server-shared-56": models.Package{ Name: "Percona-Server-shared-56", Version: "1:5.6.19", Release: "rel67.0.el6", }, "kernel": models.Package{ Name: "kernel", Version: "2.6.32", Release: "696.20.3.el6", }, }, }, { in: `openssl 0 1.0.1e 30.el6.11 x86_64 Percona-Server-shared-56 1 5.6.19 rel67.0.el6 x84_64 kernel 0 2.6.32 696.20.1.el6 x86_64 kernel 0 2.6.32 696.20.3.el6 x86_64 kernel 0 2.6.32 695.20.3.el6 x86_64 kernel-devel 0 2.6.32 696.20.1.el6 x86_64 kernel-devel 0 2.6.32 696.20.3.el6 x86_64 kernel-devel 0 2.6.32 695.20.3.el6 x86_64`, distro: config.Distro{Family: constant.RedHat}, kernel: models.Kernel{Release: "2.6.32-696.20.3.el6.x86_64"}, packages: models.Packages{ "openssl": models.Package{ Name: "openssl", Version: "1.0.1e", Release: "30.el6.11", }, "Percona-Server-shared-56": models.Package{ Name: "Percona-Server-shared-56", Version: "1:5.6.19", Release: "rel67.0.el6", }, "kernel": models.Package{ Name: "kernel", Version: "2.6.32", Release: "696.20.3.el6", }, "kernel-devel": models.Package{ Name: "kernel-devel", Version: "2.6.32", Release: "696.20.3.el6", }, }, }, { in: `openssl 0 1.0.1e 30.el6.11 x86_64 Percona-Server-shared-56 1 5.6.19 rel67.0.el6 x84_64 kernel 0 2.6.32 696.20.1.el6 x86_64 kernel 0 2.6.32 696.20.3.el6 x86_64 kernel 0 2.6.32 695.20.3.el6 x86_64 kernel-devel 0 2.6.32 696.20.1.el6 x86_64 kernel-devel 0 2.6.32 696.20.3.el6 x86_64 kernel-devel 0 2.6.32 695.20.3.el6 x86_64`, distro: config.Distro{Family: constant.RedHat}, kernel: models.Kernel{Release: "2.6.32-695.20.3.el6.x86_64"}, packages: models.Packages{ "openssl": models.Package{ Name: "openssl", Version: "1.0.1e", Release: "30.el6.11", }, "Percona-Server-shared-56": models.Package{ Name: "Percona-Server-shared-56", Version: "1:5.6.19", Release: "rel67.0.el6", }, "kernel": models.Package{ Name: "kernel", Version: "2.6.32", Release: "695.20.3.el6", }, "kernel-devel": models.Package{ Name: "kernel-devel", Version: "2.6.32", Release: "695.20.3.el6", }, }, }, { 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`, distro: config.Distro{Family: constant.Amazon, Release: "2 (Karoo)"}, 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: `yum-utils 0 1.1.31 46.amzn2.0.1 noarch @amzn2-core zlib 0 1.2.7 19.amzn2.0.1 x86_64 installed java-1.8.0-amazon-corretto 1 1.8.0_192.b12 1.amzn2 x86_64 @amzn2extra-corretto8`, distro: config.Distro{Family: constant.Amazon, Release: "2 (Karoo)"}, packages: models.Packages{ "yum-utils": models.Package{ Name: "yum-utils", Version: "1.1.31", Release: "46.amzn2.0.1", Arch: "noarch", Repository: "amzn2-core", }, "zlib": models.Package{ Name: "zlib", Version: "1.2.7", Release: "19.amzn2.0.1", Arch: "x86_64", Repository: "amzn2-core", }, "java-1.8.0-amazon-corretto": models.Package{ Name: "java-1.8.0-amazon-corretto", Version: "1:1.8.0_192.b12", Release: "1.amzn2", Arch: "x86_64", Repository: "amzn2extra-corretto8", }, }, }, } for _, tt := range packagetests { r.Distro = tt.distro r.Kernel = tt.kernel packages, _, err := r.parseInstalledPackages(tt.in) if err != nil { t.Errorf("Unexpected error: %s", err) } for name, expectedPack := range tt.packages { pack := packages[name] if pack.Name != expectedPack.Name { t.Errorf("name: expected %s, actual %s", expectedPack.Name, pack.Name) } if pack.Version != expectedPack.Version { t.Errorf("version: expected %s, actual %s", expectedPack.Version, pack.Version) } if pack.Release != expectedPack.Release { t.Errorf("release: expected %s, actual %s", expectedPack.Release, pack.Release) } } } } func TestParseInstalledPackagesLine(t *testing.T) { r := newRHEL(config.ServerInfo{}) var packagetests = []struct { in string pack models.Package err bool }{ { "openssl 0 1.0.1e 30.el6.11 x86_64", models.Package{ Name: "openssl", Version: "1.0.1e", Release: "30.el6.11", }, false, }, { "Percona-Server-shared-56 1 5.6.19 rel67.0.el6 x84_64", models.Package{ Name: "Percona-Server-shared-56", Version: "1:5.6.19", Release: "rel67.0.el6", }, false, }, { "community-mysql 0 8.0.26 1.module_f35+12627+b26747dd x86_64 mysql:8.0:3520210817160118:f27b74a8", models.Package{ Name: "community-mysql", Version: "8.0.26", Release: "1.module_f35+12627+b26747dd", ModularityLabel: "mysql:8.0:3520210817160118:f27b74a8", }, false, }, { "dnf-utils 0 4.0.24 1.fc35 noarch (none)", models.Package{ Name: "dnf-utils", Version: "4.0.24", Release: "1.fc35", ModularityLabel: "", }, false, }, } for i, tt := range packagetests { p, err := r.parseInstalledPackagesLine(tt.in) if err == nil && tt.err { t.Errorf("Expected err not occurred: %d", i) } if err != nil && !tt.err { t.Errorf("UnExpected err not occurred: %d", i) } if p.Name != tt.pack.Name { t.Errorf("name: expected %s, actual %s", tt.pack.Name, p.Name) } if p.Version != tt.pack.Version { t.Errorf("version: expected %s, actual %s", tt.pack.Version, p.Version) } if p.Release != tt.pack.Release { t.Errorf("release: expected %s, actual %s", tt.pack.Release, p.Release) } if p.ModularityLabel != tt.pack.ModularityLabel { t.Errorf("modularitylabel: expected %s, actual %s", tt.pack.ModularityLabel, p.ModularityLabel) } } } func TestParseInstalledPackagesLineFromRepoquery(t *testing.T) { r := newRHEL(config.ServerInfo{}) var packagetests = []struct { in string pack models.Package err bool }{ { in: "yum-utils 0 1.1.31 46.amzn2.0.1 noarch @amzn2-core", pack: models.Package{ Name: "yum-utils", Version: "1.1.31", Release: "46.amzn2.0.1", Arch: "noarch", Repository: "amzn2-core", }, }, { in: "zlib 0 1.2.7 19.amzn2.0.1 x86_64 installed", pack: models.Package{ Name: "zlib", Version: "1.2.7", Release: "19.amzn2.0.1", Arch: "x86_64", Repository: "amzn2-core", }, }, { in: "java-1.8.0-amazon-corretto 1 1.8.0_192.b12 1.amzn2 x86_64 @amzn2extra-corretto8", pack: models.Package{ Name: "java-1.8.0-amazon-corretto", Version: "1:1.8.0_192.b12", Release: "1.amzn2", Arch: "x86_64", Repository: "amzn2extra-corretto8", }, }, } for i, tt := range packagetests { p, err := r.parseInstalledPackagesLineFromRepoquery(tt.in) if err == nil && tt.err { t.Errorf("Expected err not occurred: %d", i) } if err != nil && !tt.err { t.Errorf("UnExpected err not occurred: %d", i) } if p.Name != tt.pack.Name { t.Errorf("name: expected %s, actual %s", tt.pack.Name, p.Name) } if p.Version != tt.pack.Version { t.Errorf("version: expected %s, actual %s", tt.pack.Version, p.Version) } if p.Release != tt.pack.Release { t.Errorf("release: expected %s, actual %s", tt.pack.Release, p.Release) } if p.Arch != tt.pack.Arch { t.Errorf("arch: expected %s, actual %s", tt.pack.Arch, p.Arch) } if p.Repository != tt.pack.Repository { t.Errorf("repository: expected %s, actual %s", tt.pack.Repository, p.Repository) } } } func TestParseYumCheckUpdateLine(t *testing.T) { r := newCentOS(config.ServerInfo{}) r.Distro = config.Distro{Family: "centos"} var tests = []struct { in string out models.Package }{ { "zlib 0 1.2.7 17.el7 rhui-REGION-rhel-server-releases", models.Package{ Name: "zlib", NewVersion: "1.2.7", NewRelease: "17.el7", Repository: "rhui-REGION-rhel-server-releases", }, }, { "shadow-utils 2 4.1.5.1 24.el7 rhui-REGION-rhel-server-releases", models.Package{ Name: "shadow-utils", NewVersion: "2:4.1.5.1", NewRelease: "24.el7", Repository: "rhui-REGION-rhel-server-releases", }, }, } for _, tt := range tests { aPack, err := r.parseUpdatablePacksLine(tt.in) if err != nil { t.Errorf("Error has occurred, err: %+v\ntt.in: %v", err, tt.in) return } if !reflect.DeepEqual(tt.out, aPack) { e := pp.Sprintf("%v", tt.out) a := pp.Sprintf("%v", aPack) t.Errorf("expected %s, actual %s", e, a) } } } func TestParseYumCheckUpdateLines(t *testing.T) { r := newCentOS(config.ServerInfo{}) r.Distro = config.Distro{Family: "centos"} stdout := `audit-libs 0 2.3.7 5.el6 base bash 0 4.1.2 33.el6_7.1 updates python-libs 0 2.6.6 64.el6 rhui-REGION-rhel-server-releases python-ordereddict 0 1.1 3.el6ev installed bind-utils 30 9.3.6 25.P1.el5_11.8 updates pytalloc 0 2.0.7 2.el6 @CentOS 6.5/6.5` r.Packages = models.NewPackages( models.Package{Name: "audit-libs"}, models.Package{Name: "bash"}, models.Package{Name: "python-libs"}, models.Package{Name: "python-ordereddict"}, models.Package{Name: "bind-utils"}, models.Package{Name: "pytalloc"}, ) var tests = []struct { in string out models.Packages }{ { stdout, models.NewPackages( models.Package{ Name: "audit-libs", NewVersion: "2.3.7", NewRelease: "5.el6", Repository: "base", }, models.Package{ Name: "bash", NewVersion: "4.1.2", NewRelease: "33.el6_7.1", Repository: "updates", }, models.Package{ Name: "python-libs", NewVersion: "2.6.6", NewRelease: "64.el6", Repository: "rhui-REGION-rhel-server-releases", }, models.Package{ Name: "python-ordereddict", NewVersion: "1.1", NewRelease: "3.el6ev", Repository: "installed", }, models.Package{ Name: "bind-utils", NewVersion: "30:9.3.6", NewRelease: "25.P1.el5_11.8", Repository: "updates", }, models.Package{ Name: "pytalloc", NewVersion: "2.0.7", NewRelease: "2.el6", Repository: "@CentOS 6.5/6.5", }, ), }, } for _, tt := range tests { packages, err := r.parseUpdatablePacksLines(tt.in) if err != nil { t.Errorf("Error has occurred, err: %+v\ntt.in: %v", err, tt.in) return } for name, ePack := range tt.out { if !reflect.DeepEqual(ePack, packages[name]) { e := pp.Sprintf("%v", ePack) a := pp.Sprintf("%v", packages[name]) t.Errorf("expected %s, actual %s", e, a) } } } } func TestParseYumCheckUpdateLinesAmazon(t *testing.T) { r := newAmazon(config.ServerInfo{}) r.Distro = config.Distro{Family: "amazon"} stdout := `bind-libs 32 9.8.2 0.37.rc1.45.amzn1 amzn-main java-1.7.0-openjdk 0 1.7.0.95 2.6.4.0.65.amzn1 amzn-main if-not-architecture 0 100 200 amzn-main` r.Packages = models.NewPackages( models.Package{Name: "bind-libs"}, models.Package{Name: "java-1.7.0-openjdk"}, models.Package{Name: "if-not-architecture"}, ) var tests = []struct { in string out models.Packages }{ { stdout, models.NewPackages( models.Package{ Name: "bind-libs", NewVersion: "32:9.8.2", NewRelease: "0.37.rc1.45.amzn1", Repository: "amzn-main", }, models.Package{ Name: "java-1.7.0-openjdk", NewVersion: "1.7.0.95", NewRelease: "2.6.4.0.65.amzn1", Repository: "amzn-main", }, models.Package{ Name: "if-not-architecture", NewVersion: "100", NewRelease: "200", Repository: "amzn-main", }, ), }, } for _, tt := range tests { packages, err := r.parseUpdatablePacksLines(tt.in) if err != nil { t.Errorf("Error has occurred, err: %+v\ntt.in: %v", err, tt.in) return } for name, ePack := range tt.out { if !reflect.DeepEqual(ePack, packages[name]) { e := pp.Sprintf("%v", ePack) a := pp.Sprintf("%v", packages[name]) t.Errorf("[%s] expected %s, actual %s", name, e, a) } } } } func TestParseNeedsRestarting(t *testing.T) { r := newRHEL(config.ServerInfo{}) r.Distro = config.Distro{Family: "centos"} var tests = []struct { in string out []models.NeedRestartProcess }{ { `1 : /usr/lib/systemd/systemd --switched-root --system --deserialize 21kk 30170 : 437 : /usr/sbin/NetworkManager --no-daemon`, []models.NeedRestartProcess{ { PID: "30170", Path: "", HasInit: true, }, { PID: "437", Path: "/usr/sbin/NetworkManager --no-daemon", HasInit: true, }, }, }, } for _, tt := range tests { procs := r.parseNeedsRestarting(tt.in) if !reflect.DeepEqual(tt.out, procs) { t.Errorf("expected %#v, actual %#v", tt.out, procs) } } } func Test_redhatBase_parseRpmQfLine(t *testing.T) { type fields struct { base base sudo rootPriv } type args struct { line string } tests := []struct { name string fields fields args args wantPkg *models.Package wantIgnored bool wantErr bool }{ { name: "permission denied will be ignored", fields: fields{base: base{}}, args: args{line: "/tmp/hogehoge Permission denied"}, wantPkg: nil, wantIgnored: true, wantErr: false, }, { name: "is not owned by any package", fields: fields{base: base{}}, args: args{line: "/tmp/hogehoge is not owned by any package"}, wantPkg: nil, wantIgnored: true, wantErr: false, }, { name: "No such file or directory will be ignored", fields: fields{base: base{}}, args: args{line: "/tmp/hogehoge No such file or directory"}, wantPkg: nil, wantIgnored: true, wantErr: false, }, { name: "valid line", fields: fields{base: base{}}, args: args{line: "Percona-Server-shared-56 1 5.6.19 rel67.0.el6 x86_64"}, wantPkg: &models.Package{ Name: "Percona-Server-shared-56", Version: "1:5.6.19", Release: "rel67.0.el6", Arch: "x86_64", }, wantIgnored: false, wantErr: false, }, { name: "err", fields: fields{base: base{}}, args: args{line: "/tmp/hogehoge something unknown format"}, wantPkg: nil, wantIgnored: false, wantErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { o := &redhatBase{ base: tt.fields.base, sudo: tt.fields.sudo, } gotPkg, gotIgnored, err := o.parseRpmQfLine(tt.args.line) if (err != nil) != tt.wantErr { t.Errorf("redhatBase.parseRpmQfLine() error = %v, wantErr %v", err, tt.wantErr) return } if !reflect.DeepEqual(gotPkg, tt.wantPkg) { t.Errorf("redhatBase.parseRpmQfLine() gotPkg = %v, want %v", gotPkg, tt.wantPkg) } if gotIgnored != tt.wantIgnored { t.Errorf("redhatBase.parseRpmQfLine() gotIgnored = %v, want %v", gotIgnored, tt.wantIgnored) } }) } } func Test_redhatBase_rebootRequired(t *testing.T) { type fields struct { base base sudo rootPriv } type args struct { fn func(s string) execResult } tests := []struct { name string fields fields args args want bool wantErr bool }{ { name: "uek kernel no-reboot", fields: fields{ base: base{ osPackages: osPackages{ Kernel: models.Kernel{ Release: "5.4.17-2102.200.13.el7uek.x86_64", }, }, }, }, args: args{ fn: func(s string) execResult { return execResult{ Stdout: `kernel-uek-5.4.17-2102.200.13.el7uek.x86_64 Mon 05 Apr 2021 04:52:06 PM UTC kernel-uek-4.14.35-2047.501.2.el7uek.x86_64 Mon 05 Apr 2021 04:49:39 PM UTC kernel-uek-4.14.35-1902.10.2.1.el7uek.x86_64 Wed 29 Jan 2020 05:04:52 PM UTC`, } }, }, want: false, wantErr: false, }, { name: "uek kernel needs-reboot", fields: fields{ base: base{ osPackages: osPackages{ Kernel: models.Kernel{ Release: "4.14.35-2047.501.2.el7uek.x86_64", }, }, }, }, args: args{ fn: func(s string) execResult { return execResult{ Stdout: `kernel-uek-5.4.17-2102.200.13.el7uek.x86_64 Mon 05 Apr 2021 04:52:06 PM UTC kernel-uek-4.14.35-2047.501.2.el7uek.x86_64 Mon 05 Apr 2021 04:49:39 PM UTC kernel-uek-4.14.35-1902.10.2.1.el7uek.x86_64 Wed 29 Jan 2020 05:04:52 PM UTC`, } }, }, want: true, wantErr: false, }, { name: "kerne needs-reboot", fields: fields{ base: base{ osPackages: osPackages{ Kernel: models.Kernel{ Release: "3.10.0-1062.12.1.el7.x86_64", }, }, }, }, args: args{ fn: func(s string) execResult { return execResult{ Stdout: `kernel-3.10.0-1160.24.1.el7.x86_64 Mon 26 Apr 2021 10:13:54 AM UTC kernel-3.10.0-1062.12.1.el7.x86_64 Sat 29 Feb 2020 12:09:00 PM UTC`, } }, }, want: true, wantErr: false, }, { name: "kerne no-reboot", fields: fields{ base: base{ osPackages: osPackages{ Kernel: models.Kernel{ Release: "3.10.0-1160.24.1.el7.x86_64", }, }, }, }, args: args{ fn: func(s string) execResult { return execResult{ Stdout: `kernel-3.10.0-1160.24.1.el7.x86_64 Mon 26 Apr 2021 10:13:54 AM UTC kernel-3.10.0-1062.12.1.el7.x86_64 Sat 29 Feb 2020 12:09:00 PM UTC`, } }, }, want: false, wantErr: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { o := &redhatBase{ base: tt.fields.base, sudo: tt.fields.sudo, } got, err := o.rebootRequired(tt.args.fn) if (err != nil) != tt.wantErr { t.Errorf("redhatBase.rebootRequired() error = %v, wantErr %v", err, tt.wantErr) return } if got != tt.want { t.Errorf("redhatBase.rebootRequired() = %v, want %v", got, tt.want) } }) } }