feat(windows): support Windows
This commit is contained in:
@@ -60,6 +60,7 @@ type base struct {
|
||||
osPackages
|
||||
LibraryScanners []models.LibraryScanner
|
||||
WordPress models.WordPressPackages
|
||||
windowsKB *models.WindowsKB
|
||||
|
||||
log logging.Logger
|
||||
errs []error
|
||||
@@ -506,6 +507,7 @@ func (l *base) convertToModel() models.ScanResult {
|
||||
EnabledDnfModules: l.EnabledDnfModules,
|
||||
WordPressPackages: l.WordPress,
|
||||
LibraryScanners: l.LibraryScanners,
|
||||
WindowsKB: l.windowsKB,
|
||||
Optional: l.ServerInfo.Optional,
|
||||
Errors: errs,
|
||||
Warnings: warns,
|
||||
|
||||
@@ -42,16 +42,10 @@ func newDebian(c config.ServerInfo) *debian {
|
||||
|
||||
// Ubuntu, Debian, Raspbian
|
||||
// https://github.com/serverspec/specinfra/blob/master/lib/specinfra/helper/detect_os/debian.rb
|
||||
func detectDebian(c config.ServerInfo) (bool, osTypeInterface, error) {
|
||||
func detectDebian(c config.ServerInfo) (bool, osTypeInterface) {
|
||||
if r := exec(c, "ls /etc/debian_version", noSudo); !r.isSuccess() {
|
||||
if r.Error != nil {
|
||||
return false, nil, nil
|
||||
}
|
||||
if r.ExitStatus == 255 {
|
||||
return false, &unknown{base{ServerInfo: c}}, xerrors.Errorf("Unable to connect via SSH. Scan with -vvv option to print SSH debugging messages and check SSH settings.\n%s", r)
|
||||
}
|
||||
logging.Log.Debugf("Not Debian like Linux. %s", r)
|
||||
return false, nil, nil
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// Raspbian
|
||||
@@ -64,7 +58,7 @@ func detectDebian(c config.ServerInfo) (bool, osTypeInterface, error) {
|
||||
if len(result) > 2 && result[0] == constant.Raspbian {
|
||||
deb := newDebian(c)
|
||||
deb.setDistro(strings.ToLower(trim(result[0])), trim(result[2]))
|
||||
return true, deb, nil
|
||||
return true, deb
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,7 +78,7 @@ func detectDebian(c config.ServerInfo) (bool, osTypeInterface, error) {
|
||||
distro := strings.ToLower(trim(result[1]))
|
||||
deb.setDistro(distro, trim(result[2]))
|
||||
}
|
||||
return true, deb, nil
|
||||
return true, deb
|
||||
}
|
||||
|
||||
if r := exec(c, "cat /etc/lsb-release", noSudo); r.isSuccess() {
|
||||
@@ -104,7 +98,7 @@ func detectDebian(c config.ServerInfo) (bool, osTypeInterface, error) {
|
||||
distro := strings.ToLower(trim(result[1]))
|
||||
deb.setDistro(distro, trim(result[2]))
|
||||
}
|
||||
return true, deb, nil
|
||||
return true, deb
|
||||
}
|
||||
|
||||
// Debian
|
||||
@@ -112,11 +106,11 @@ func detectDebian(c config.ServerInfo) (bool, osTypeInterface, error) {
|
||||
if r := exec(c, cmd, noSudo); r.isSuccess() {
|
||||
deb := newDebian(c)
|
||||
deb.setDistro(constant.Debian, trim(r.Stdout))
|
||||
return true, deb, nil
|
||||
return true, deb
|
||||
}
|
||||
|
||||
logging.Log.Debugf("Not Debian like Linux: %s", c.ServerName)
|
||||
return false, nil, nil
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func trim(str string) string {
|
||||
|
||||
@@ -3,17 +3,24 @@ package scanner
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
ex "os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
homedir "github.com/mitchellh/go-homedir"
|
||||
"github.com/saintfish/chardet"
|
||||
"golang.org/x/text/encoding/japanese"
|
||||
"golang.org/x/text/encoding/unicode"
|
||||
"golang.org/x/text/transform"
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/future-architect/vuls/config"
|
||||
"github.com/future-architect/vuls/constant"
|
||||
"github.com/future-architect/vuls/logging"
|
||||
homedir "github.com/mitchellh/go-homedir"
|
||||
)
|
||||
|
||||
type execResult struct {
|
||||
@@ -152,15 +159,14 @@ func localExec(c config.ServerInfo, cmdstr string, sudo bool) (result execResult
|
||||
cmdstr = decorateCmd(c, cmdstr, sudo)
|
||||
var cmd *ex.Cmd
|
||||
switch c.Distro.Family {
|
||||
// case conf.FreeBSD, conf.Alpine, conf.Debian:
|
||||
// cmd = ex.Command("/bin/sh", "-c", cmdstr)
|
||||
case constant.Windows:
|
||||
cmd = ex.Command("powershell.exe", "-NoProfile", "-NonInteractive", cmdstr)
|
||||
default:
|
||||
cmd = ex.Command("/bin/sh", "-c", cmdstr)
|
||||
}
|
||||
var stdoutBuf, stderrBuf bytes.Buffer
|
||||
cmd.Stdout = &stdoutBuf
|
||||
cmd.Stderr = &stderrBuf
|
||||
|
||||
if err := cmd.Run(); err != nil {
|
||||
result.Error = err
|
||||
if exitError, ok := err.(*ex.ExitError); ok {
|
||||
@@ -172,42 +178,47 @@ func localExec(c config.ServerInfo, cmdstr string, sudo bool) (result execResult
|
||||
} else {
|
||||
result.ExitStatus = 0
|
||||
}
|
||||
|
||||
result.Stdout = stdoutBuf.String()
|
||||
result.Stderr = stderrBuf.String()
|
||||
result.Stdout = toUTF8(stdoutBuf.String())
|
||||
result.Stderr = toUTF8(stderrBuf.String())
|
||||
result.Cmd = strings.Replace(cmdstr, "\n", "", -1)
|
||||
return
|
||||
}
|
||||
|
||||
func sshExecExternal(c config.ServerInfo, cmd string, sudo bool) (result execResult) {
|
||||
func sshExecExternal(c config.ServerInfo, cmdstr string, sudo bool) (result execResult) {
|
||||
sshBinaryPath, err := ex.LookPath("ssh")
|
||||
if err != nil {
|
||||
return execResult{Error: err}
|
||||
}
|
||||
if runtime.GOOS == "windows" {
|
||||
sshBinaryPath = "ssh.exe"
|
||||
}
|
||||
|
||||
var args []string
|
||||
|
||||
if c.SSHConfigPath != "" {
|
||||
args = append(args, "-F", c.SSHConfigPath)
|
||||
} else {
|
||||
home, err := homedir.Dir()
|
||||
if err != nil {
|
||||
msg := fmt.Sprintf("Failed to get HOME directory: %s", err)
|
||||
result.Stderr = msg
|
||||
result.ExitStatus = 997
|
||||
return
|
||||
}
|
||||
controlPath := filepath.Join(home, ".vuls", `controlmaster-%r-`+c.ServerName+`.%p`)
|
||||
|
||||
args = append(args,
|
||||
"-o", "StrictHostKeyChecking=yes",
|
||||
"-o", "LogLevel=quiet",
|
||||
"-o", "ConnectionAttempts=3",
|
||||
"-o", "ConnectTimeout=10",
|
||||
"-o", "ControlMaster=auto",
|
||||
"-o", fmt.Sprintf("ControlPath=%s", controlPath),
|
||||
"-o", "Controlpersist=10m",
|
||||
)
|
||||
if runtime.GOOS != "windows" {
|
||||
home, err := homedir.Dir()
|
||||
if err != nil {
|
||||
msg := fmt.Sprintf("Failed to get HOME directory: %s", err)
|
||||
result.Stderr = msg
|
||||
result.ExitStatus = 997
|
||||
return
|
||||
}
|
||||
|
||||
controlPath := filepath.Join(home, ".vuls", `controlmaster-%r-`+c.ServerName+`.%p`)
|
||||
args = append(args,
|
||||
"-o", "ControlMaster=auto",
|
||||
"-o", fmt.Sprintf("ControlPath=%s", controlPath),
|
||||
"-o", "Controlpersist=10m")
|
||||
}
|
||||
}
|
||||
|
||||
if config.Conf.Vvv {
|
||||
@@ -228,16 +239,18 @@ func sshExecExternal(c config.ServerInfo, cmd string, sudo bool) (result execRes
|
||||
}
|
||||
args = append(args, c.Host)
|
||||
|
||||
cmd = decorateCmd(c, cmd, sudo)
|
||||
cmd = fmt.Sprintf("stty cols 1000; %s", cmd)
|
||||
|
||||
args = append(args, cmd)
|
||||
execCmd := ex.Command(sshBinaryPath, args...)
|
||||
|
||||
cmdstr = decorateCmd(c, cmdstr, sudo)
|
||||
var cmd *ex.Cmd
|
||||
switch c.Distro.Family {
|
||||
case constant.Windows:
|
||||
cmd = ex.Command(sshBinaryPath, append(args, "powershell.exe", "-NoProfile", "-NonInteractive", fmt.Sprintf(`"%s`, cmdstr))...)
|
||||
default:
|
||||
cmd = ex.Command(sshBinaryPath, append(args, fmt.Sprintf("stty cols 1000; %s", cmdstr))...)
|
||||
}
|
||||
var stdoutBuf, stderrBuf bytes.Buffer
|
||||
execCmd.Stdout = &stdoutBuf
|
||||
execCmd.Stderr = &stderrBuf
|
||||
if err := execCmd.Run(); err != nil {
|
||||
cmd.Stdout = &stdoutBuf
|
||||
cmd.Stderr = &stderrBuf
|
||||
if err := cmd.Run(); err != nil {
|
||||
if e, ok := err.(*ex.ExitError); ok {
|
||||
if s, ok := e.Sys().(syscall.WaitStatus); ok {
|
||||
result.ExitStatus = s.ExitStatus()
|
||||
@@ -250,9 +263,8 @@ func sshExecExternal(c config.ServerInfo, cmd string, sudo bool) (result execRes
|
||||
} else {
|
||||
result.ExitStatus = 0
|
||||
}
|
||||
|
||||
result.Stdout = stdoutBuf.String()
|
||||
result.Stderr = stderrBuf.String()
|
||||
result.Stdout = toUTF8(stdoutBuf.String())
|
||||
result.Stderr = toUTF8(stderrBuf.String())
|
||||
result.Servername = c.ServerName
|
||||
result.Container = c.Container
|
||||
result.Host = c.Host
|
||||
@@ -313,3 +325,33 @@ func decorateCmd(c config.ServerInfo, cmd string, sudo bool) string {
|
||||
// cmd = fmt.Sprintf("set -x; %s", cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func toUTF8(s string) string {
|
||||
d := chardet.NewTextDetector()
|
||||
res, err := d.DetectBest([]byte(s))
|
||||
if err != nil {
|
||||
return s
|
||||
}
|
||||
|
||||
var bs []byte
|
||||
switch res.Charset {
|
||||
case "UTF-8":
|
||||
bs, err = []byte(s), nil
|
||||
case "UTF-16LE":
|
||||
bs, err = io.ReadAll(transform.NewReader(strings.NewReader(s), unicode.UTF16(unicode.LittleEndian, unicode.UseBOM).NewDecoder()))
|
||||
case "UTF-16BE":
|
||||
bs, err = io.ReadAll(transform.NewReader(strings.NewReader(s), unicode.UTF16(unicode.BigEndian, unicode.UseBOM).NewDecoder()))
|
||||
case "Shift_JIS":
|
||||
bs, err = io.ReadAll(transform.NewReader(strings.NewReader(s), japanese.ShiftJIS.NewDecoder()))
|
||||
case "EUC-JP":
|
||||
bs, err = io.ReadAll(transform.NewReader(strings.NewReader(s), japanese.EUCJP.NewDecoder()))
|
||||
case "ISO-2022-JP":
|
||||
bs, err = io.ReadAll(transform.NewReader(strings.NewReader(s), japanese.ISO2022JP.NewDecoder()))
|
||||
default:
|
||||
bs, err = []byte(s), nil
|
||||
}
|
||||
if err != nil {
|
||||
return s
|
||||
}
|
||||
return string(bs)
|
||||
}
|
||||
|
||||
@@ -6,10 +6,12 @@ import (
|
||||
"net/http"
|
||||
"os"
|
||||
ex "os/exec"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
debver "github.com/knqyf263/go-deb-version"
|
||||
"golang.org/x/exp/maps"
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/future-architect/vuls/cache"
|
||||
@@ -149,64 +151,127 @@ func (s Scanner) Configtest() error {
|
||||
|
||||
// ViaHTTP scans servers by HTTP header and body
|
||||
func ViaHTTP(header http.Header, body string, toLocalFile bool) (models.ScanResult, error) {
|
||||
family := header.Get("X-Vuls-OS-Family")
|
||||
if family == "" {
|
||||
return models.ScanResult{}, errOSFamilyHeader
|
||||
}
|
||||
|
||||
release := header.Get("X-Vuls-OS-Release")
|
||||
if release == "" {
|
||||
return models.ScanResult{}, errOSReleaseHeader
|
||||
}
|
||||
|
||||
kernelRelease := header.Get("X-Vuls-Kernel-Release")
|
||||
if kernelRelease == "" {
|
||||
logging.Log.Warn("If X-Vuls-Kernel-Release is not specified, there is a possibility of false detection")
|
||||
}
|
||||
|
||||
kernelVersion := header.Get("X-Vuls-Kernel-Version")
|
||||
if family == constant.Debian {
|
||||
if kernelVersion == "" {
|
||||
logging.Log.Warn("X-Vuls-Kernel-Version is empty. skip kernel vulnerability detection.")
|
||||
} else {
|
||||
if _, err := debver.NewVersion(kernelVersion); err != nil {
|
||||
logging.Log.Warnf("X-Vuls-Kernel-Version is invalid. skip kernel vulnerability detection. actual kernelVersion: %s, err: %s", kernelVersion, err)
|
||||
kernelVersion = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
serverName := header.Get("X-Vuls-Server-Name")
|
||||
if toLocalFile && serverName == "" {
|
||||
return models.ScanResult{}, errServerNameHeader
|
||||
}
|
||||
|
||||
distro := config.Distro{
|
||||
Family: family,
|
||||
Release: release,
|
||||
family := header.Get("X-Vuls-OS-Family")
|
||||
if family == "" {
|
||||
return models.ScanResult{}, errOSFamilyHeader
|
||||
}
|
||||
|
||||
kernel := models.Kernel{
|
||||
Release: kernelRelease,
|
||||
Version: kernelVersion,
|
||||
}
|
||||
installedPackages, srcPackages, err := ParseInstalledPkgs(distro, kernel, body)
|
||||
if err != nil {
|
||||
return models.ScanResult{}, err
|
||||
}
|
||||
switch family {
|
||||
case constant.Windows:
|
||||
osInfo, hotfixs, err := parseSystemInfo(body)
|
||||
if err != nil {
|
||||
return models.ScanResult{}, xerrors.Errorf("Failed to parse systeminfo.exe. err: %w", err)
|
||||
}
|
||||
|
||||
return models.ScanResult{
|
||||
ServerName: serverName,
|
||||
Family: family,
|
||||
Release: release,
|
||||
RunningKernel: models.Kernel{
|
||||
release := header.Get("X-Vuls-OS-Release")
|
||||
if release == "" {
|
||||
release, err = detectOSName(osInfo)
|
||||
if err != nil {
|
||||
return models.ScanResult{}, xerrors.Errorf("Failed to detect os name. err: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
kernelVersion := header.Get("X-Vuls-Kernel-Version")
|
||||
if kernelVersion == "" {
|
||||
kernelVersion = formatKernelVersion(osInfo)
|
||||
}
|
||||
|
||||
w := &windows{
|
||||
base: base{
|
||||
Distro: config.Distro{Family: family, Release: release},
|
||||
osPackages: osPackages{
|
||||
Kernel: models.Kernel{Version: kernelVersion},
|
||||
},
|
||||
log: logging.Log,
|
||||
},
|
||||
}
|
||||
v, err := w.detectKernelVersion(hotfixs)
|
||||
if err != nil {
|
||||
return models.ScanResult{}, xerrors.Errorf("Failed to detect kernel version. err: %w", err)
|
||||
}
|
||||
w.Kernel = models.Kernel{Version: v}
|
||||
|
||||
kbs, err := w.detectKBsFromKernelVersion()
|
||||
if err != nil {
|
||||
return models.ScanResult{}, xerrors.Errorf("Failed to detect KBs from kernel version. err: %w", err)
|
||||
}
|
||||
|
||||
applied, unapplied := map[string]struct{}{}, map[string]struct{}{}
|
||||
for _, kb := range hotfixs {
|
||||
applied[kb] = struct{}{}
|
||||
}
|
||||
for _, kb := range kbs.Applied {
|
||||
applied[kb] = struct{}{}
|
||||
}
|
||||
for _, kb := range kbs.Unapplied {
|
||||
unapplied[kb] = struct{}{}
|
||||
}
|
||||
|
||||
return models.ScanResult{
|
||||
ServerName: serverName,
|
||||
Family: family,
|
||||
Release: release,
|
||||
RunningKernel: models.Kernel{
|
||||
Version: v,
|
||||
},
|
||||
WindowsKB: &models.WindowsKB{Applied: maps.Keys(applied), Unapplied: maps.Keys(unapplied)},
|
||||
ScannedCves: models.VulnInfos{},
|
||||
}, nil
|
||||
default:
|
||||
release := header.Get("X-Vuls-OS-Release")
|
||||
if release == "" {
|
||||
return models.ScanResult{}, errOSReleaseHeader
|
||||
}
|
||||
|
||||
kernelRelease := header.Get("X-Vuls-Kernel-Release")
|
||||
if kernelRelease == "" {
|
||||
logging.Log.Warn("If X-Vuls-Kernel-Release is not specified, there is a possibility of false detection")
|
||||
}
|
||||
|
||||
kernelVersion := header.Get("X-Vuls-Kernel-Version")
|
||||
if family == constant.Debian {
|
||||
if kernelVersion == "" {
|
||||
logging.Log.Warn("X-Vuls-Kernel-Version is empty. skip kernel vulnerability detection.")
|
||||
} else {
|
||||
if _, err := debver.NewVersion(kernelVersion); err != nil {
|
||||
logging.Log.Warnf("X-Vuls-Kernel-Version is invalid. skip kernel vulnerability detection. actual kernelVersion: %s, err: %s", kernelVersion, err)
|
||||
kernelVersion = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
distro := config.Distro{
|
||||
Family: family,
|
||||
Release: release,
|
||||
}
|
||||
|
||||
kernel := models.Kernel{
|
||||
Release: kernelRelease,
|
||||
Version: kernelVersion,
|
||||
},
|
||||
Packages: installedPackages,
|
||||
SrcPackages: srcPackages,
|
||||
ScannedCves: models.VulnInfos{},
|
||||
}, nil
|
||||
}
|
||||
installedPackages, srcPackages, err := ParseInstalledPkgs(distro, kernel, body)
|
||||
if err != nil {
|
||||
return models.ScanResult{}, err
|
||||
}
|
||||
|
||||
return models.ScanResult{
|
||||
ServerName: serverName,
|
||||
Family: family,
|
||||
Release: release,
|
||||
RunningKernel: models.Kernel{
|
||||
Release: kernelRelease,
|
||||
Version: kernelVersion,
|
||||
},
|
||||
Packages: installedPackages,
|
||||
SrcPackages: srcPackages,
|
||||
ScannedCves: models.VulnInfos{},
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
// ParseInstalledPkgs parses installed pkgs line
|
||||
@@ -342,7 +407,14 @@ func validateSSHConfig(c *config.ServerInfo) error {
|
||||
|
||||
logging.Log.Debugf("Validating SSH Settings for Server:%s ...", c.GetServerName())
|
||||
|
||||
sshBinaryPath, err := ex.LookPath("ssh")
|
||||
if runtime.GOOS == "windows" {
|
||||
c.Distro.Family = constant.Windows
|
||||
}
|
||||
defer func(c *config.ServerInfo) {
|
||||
c.Distro.Family = ""
|
||||
}(c)
|
||||
|
||||
sshBinaryPath, err := lookpath(c.Distro.Family, "ssh")
|
||||
if err != nil {
|
||||
return xerrors.Errorf("Failed to lookup ssh binary path. err: %w", err)
|
||||
}
|
||||
@@ -381,7 +453,7 @@ func validateSSHConfig(c *config.ServerInfo) error {
|
||||
return xerrors.New("Failed to find any known_hosts to use. Please check the UserKnownHostsFile and GlobalKnownHostsFile settings for SSH")
|
||||
}
|
||||
|
||||
sshKeyscanBinaryPath, err := ex.LookPath("ssh-keyscan")
|
||||
sshKeyscanBinaryPath, err := lookpath(c.Distro.Family, "ssh-keyscan")
|
||||
if err != nil {
|
||||
return xerrors.Errorf("Failed to lookup ssh-keyscan binary path. err: %w", err)
|
||||
}
|
||||
@@ -392,7 +464,7 @@ func validateSSHConfig(c *config.ServerInfo) error {
|
||||
}
|
||||
serverKeys := parseSSHScan(r.Stdout)
|
||||
|
||||
sshKeygenBinaryPath, err := ex.LookPath("ssh-keygen")
|
||||
sshKeygenBinaryPath, err := lookpath(c.Distro.Family, "ssh-keygen")
|
||||
if err != nil {
|
||||
return xerrors.Errorf("Failed to lookup ssh-keygen binary path. err: %w", err)
|
||||
}
|
||||
@@ -428,6 +500,19 @@ func validateSSHConfig(c *config.ServerInfo) error {
|
||||
buildSSHKeyScanCmd(sshKeyscanBinaryPath, c.Port, knownHostsPaths[0], sshConfig))
|
||||
}
|
||||
|
||||
func lookpath(family, file string) (string, error) {
|
||||
switch family {
|
||||
case constant.Windows:
|
||||
return fmt.Sprintf("%s.exe", strings.TrimPrefix(file, ".exe")), nil
|
||||
default:
|
||||
p, err := ex.LookPath(file)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return p, nil
|
||||
}
|
||||
}
|
||||
|
||||
func buildSSHBaseCmd(sshBinaryPath string, c *config.ServerInfo, options []string) []string {
|
||||
cmd := []string{sshBinaryPath}
|
||||
if len(options) > 0 {
|
||||
@@ -483,6 +568,7 @@ type sshConfiguration struct {
|
||||
func parseSSHConfiguration(stdout string) sshConfiguration {
|
||||
sshConfig := sshConfiguration{}
|
||||
for _, line := range strings.Split(stdout, "\n") {
|
||||
line = strings.TrimSuffix(line, "\r")
|
||||
switch {
|
||||
case strings.HasPrefix(line, "user "):
|
||||
sshConfig.user = strings.TrimPrefix(line, "user ")
|
||||
@@ -512,6 +598,7 @@ func parseSSHConfiguration(stdout string) sshConfiguration {
|
||||
func parseSSHScan(stdout string) map[string]string {
|
||||
keys := map[string]string{}
|
||||
for _, line := range strings.Split(stdout, "\n") {
|
||||
line = strings.TrimSuffix(line, "\r")
|
||||
if line == "" || strings.HasPrefix(line, "# ") {
|
||||
continue
|
||||
}
|
||||
@@ -524,6 +611,7 @@ func parseSSHScan(stdout string) map[string]string {
|
||||
|
||||
func parseSSHKeygen(stdout string) (string, string, error) {
|
||||
for _, line := range strings.Split(stdout, "\n") {
|
||||
line = strings.TrimSuffix(line, "\r")
|
||||
if line == "" || strings.HasPrefix(line, "# ") {
|
||||
continue
|
||||
}
|
||||
@@ -669,10 +757,20 @@ func (s Scanner) detectOS(c config.ServerInfo) osTypeInterface {
|
||||
return osType
|
||||
}
|
||||
|
||||
if itsMe, osType, fatalErr := s.detectDebianWithRetry(c); fatalErr != nil {
|
||||
osType.setErrs([]error{xerrors.Errorf("Failed to detect OS: %w", fatalErr)})
|
||||
if !isLocalExec(c.Port, c.Host) {
|
||||
if err := testFirstSSHConnection(c); err != nil {
|
||||
osType := &unknown{base{ServerInfo: c}}
|
||||
osType.setErrs([]error{xerrors.Errorf("Failed to test first SSH Connection. err: %w", err)})
|
||||
return osType
|
||||
}
|
||||
}
|
||||
|
||||
if itsMe, osType := detectWindows(c); itsMe {
|
||||
logging.Log.Debugf("Windows. Host: %s:%s", c.Host, c.Port)
|
||||
return osType
|
||||
} else if itsMe {
|
||||
}
|
||||
|
||||
if itsMe, osType := detectDebian(c); itsMe {
|
||||
logging.Log.Debugf("Debian based Linux. Host: %s:%s", c.Host, c.Port)
|
||||
return osType
|
||||
}
|
||||
@@ -702,28 +800,23 @@ func (s Scanner) detectOS(c config.ServerInfo) osTypeInterface {
|
||||
return osType
|
||||
}
|
||||
|
||||
// Retry as it may stall on the first SSH connection
|
||||
// https://github.com/future-architect/vuls/pull/753
|
||||
func (s Scanner) detectDebianWithRetry(c config.ServerInfo) (itsMe bool, deb osTypeInterface, err error) {
|
||||
type Response struct {
|
||||
itsMe bool
|
||||
deb osTypeInterface
|
||||
err error
|
||||
}
|
||||
resChan := make(chan Response, 1)
|
||||
go func(c config.ServerInfo) {
|
||||
itsMe, osType, fatalErr := detectDebian(c)
|
||||
resChan <- Response{itsMe, osType, fatalErr}
|
||||
}(c)
|
||||
|
||||
timeout := time.After(time.Duration(3) * time.Second)
|
||||
select {
|
||||
case res := <-resChan:
|
||||
return res.itsMe, res.deb, res.err
|
||||
case <-timeout:
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
return detectDebian(c)
|
||||
func testFirstSSHConnection(c config.ServerInfo) error {
|
||||
for i := 3; i > 0; i-- {
|
||||
rChan := make(chan execResult, 1)
|
||||
go func() {
|
||||
rChan <- exec(c, "exit", noSudo)
|
||||
}()
|
||||
select {
|
||||
case r := <-rChan:
|
||||
if r.ExitStatus == 255 {
|
||||
return xerrors.Errorf("Unable to connect via SSH. Scan with -vvv option to print SSH debugging messages and check SSH settings.\n%s", r)
|
||||
}
|
||||
return nil
|
||||
case <-time.After(time.Duration(3) * time.Second):
|
||||
}
|
||||
}
|
||||
logging.Log.Warnf("First SSH Connection to Host: %s:%s timeout", c.Host, c.Port)
|
||||
return nil
|
||||
}
|
||||
|
||||
// checkScanModes checks scan mode
|
||||
|
||||
@@ -5,6 +5,8 @@ import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"golang.org/x/exp/slices"
|
||||
|
||||
"github.com/future-architect/vuls/config"
|
||||
"github.com/future-architect/vuls/constant"
|
||||
"github.com/future-architect/vuls/models"
|
||||
@@ -104,6 +106,74 @@ func TestViaHTTP(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
header: map[string]string{
|
||||
"X-Vuls-OS-Family": "windows",
|
||||
},
|
||||
body: `
|
||||
Host Name: DESKTOP
|
||||
OS Name: Microsoft Windows 10 Pro
|
||||
OS Version: 10.0.19044 N/A Build 19044
|
||||
OS Manufacturer: Microsoft Corporation
|
||||
OS Configuration: Member Workstation
|
||||
OS Build Type: Multiprocessor Free
|
||||
Registered Owner: Windows User
|
||||
Registered Organization:
|
||||
Product ID: 00000-00000-00000-AA000
|
||||
Original Install Date: 2022/04/13, 12:25:41
|
||||
System Boot Time: 2022/06/06, 16:43:45
|
||||
System Manufacturer: HP
|
||||
System Model: HP EliteBook 830 G7 Notebook PC
|
||||
System Type: x64-based PC
|
||||
Processor(s): 1 Processor(s) Installed.
|
||||
[01]: Intel64 Family 6 Model 142 Stepping 12 GenuineIntel ~1803 Mhz
|
||||
BIOS Version: HP S70 Ver. 01.05.00, 2021/04/26
|
||||
Windows Directory: C:\WINDOWS
|
||||
System Directory: C:\WINDOWS\system32
|
||||
Boot Device: \Device\HarddiskVolume2
|
||||
System Locale: en-us;English (United States)
|
||||
Input Locale: en-us;English (United States)
|
||||
Time Zone: (UTC-08:00) Pacific Time (US & Canada)
|
||||
Total Physical Memory: 15,709 MB
|
||||
Available Physical Memory: 12,347 MB
|
||||
Virtual Memory: Max Size: 18,141 MB
|
||||
Virtual Memory: Available: 14,375 MB
|
||||
Virtual Memory: In Use: 3,766 MB
|
||||
Page File Location(s): C:\pagefile.sys
|
||||
Domain: WORKGROUP
|
||||
Logon Server: \\DESKTOP
|
||||
Hotfix(s): 7 Hotfix(s) Installed.
|
||||
[01]: KB5012117
|
||||
[02]: KB4562830
|
||||
[03]: KB5003791
|
||||
[04]: KB5007401
|
||||
[05]: KB5012599
|
||||
[06]: KB5011651
|
||||
[07]: KB5005699
|
||||
Network Card(s): 1 NIC(s) Installed.
|
||||
[01]: Intel(R) Wi-Fi 6 AX201 160MHz
|
||||
Connection Name: Wi-Fi
|
||||
DHCP Enabled: Yes
|
||||
DHCP Server: 192.168.0.1
|
||||
IP address(es)
|
||||
[01]: 192.168.0.205
|
||||
Hyper-V Requirements: VM Monitor Mode Extensions: Yes
|
||||
Virtualization Enabled In Firmware: Yes
|
||||
Second Level Address Translation: Yes
|
||||
Data Execution Prevention Available: Yes
|
||||
`,
|
||||
expectedResult: models.ScanResult{
|
||||
Family: "windows",
|
||||
Release: "Windows 10 Version 21H2 for x64-based Systems",
|
||||
RunningKernel: models.Kernel{
|
||||
Version: "10.0.19044.1645",
|
||||
},
|
||||
WindowsKB: &models.WindowsKB{
|
||||
Applied: []string{"5009543", "5011487", "5007401", "5011651", "5008212", "5012117", "4562830", "5005699", "5011543", "5012599", "5007253", "5010793", "5010415", "5003791", "5009596", "5010342"},
|
||||
Unapplied: []string{"5021233", "5019275", "5015020", "5014023", "5014666", "5017380", "5020435", "5020030", "5011831", "5014699", "5017308", "5018482", "5022834", "5016139", "5016688", "5018410", "5022282", "5013942", "5015807", "5015878", "5016616", "5020953", "5019959", "5022906"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
@@ -144,6 +214,18 @@ func TestViaHTTP(t *testing.T) {
|
||||
t.Errorf("release: expected %s, actual %s", expectedPack.Release, pack.Release)
|
||||
}
|
||||
}
|
||||
|
||||
if tt.expectedResult.WindowsKB != nil {
|
||||
slices.Sort(tt.expectedResult.WindowsKB.Applied)
|
||||
slices.Sort(tt.expectedResult.WindowsKB.Unapplied)
|
||||
}
|
||||
if result.WindowsKB != nil {
|
||||
slices.Sort(result.WindowsKB.Applied)
|
||||
slices.Sort(result.WindowsKB.Unapplied)
|
||||
}
|
||||
if !reflect.DeepEqual(tt.expectedResult.WindowsKB, result.WindowsKB) {
|
||||
t.Errorf("windows KB: expected %s, actual %s", tt.expectedResult.WindowsKB, result.WindowsKB)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ func isRunningKernel(pack models.Package, family string, kernel models.Kernel) (
|
||||
|
||||
// EnsureResultDir ensures the directory for scan results
|
||||
func EnsureResultDir(resultsDir string, scannedAt time.Time) (currentDir string, err error) {
|
||||
jsonDirName := scannedAt.Format(time.RFC3339)
|
||||
jsonDirName := scannedAt.Format("2006-01-02T15-04-05-0700")
|
||||
if resultsDir == "" {
|
||||
wd, _ := os.Getwd()
|
||||
resultsDir = filepath.Join(wd, "results")
|
||||
@@ -51,19 +51,6 @@ func EnsureResultDir(resultsDir string, scannedAt time.Time) (currentDir string,
|
||||
if err := os.MkdirAll(jsonDir, 0700); err != nil {
|
||||
return "", xerrors.Errorf("Failed to create dir: %w", err)
|
||||
}
|
||||
|
||||
symlinkPath := filepath.Join(resultsDir, "current")
|
||||
if _, err := os.Lstat(symlinkPath); err == nil {
|
||||
if err := os.Remove(symlinkPath); err != nil {
|
||||
return "", xerrors.Errorf(
|
||||
"Failed to remove symlink. path: %s, err: %w", symlinkPath, err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := os.Symlink(jsonDir, symlinkPath); err != nil {
|
||||
return "", xerrors.Errorf(
|
||||
"Failed to create symlink: path: %s, err: %w", symlinkPath, err)
|
||||
}
|
||||
return jsonDir, nil
|
||||
}
|
||||
|
||||
|
||||
4408
scanner/windows.go
Normal file
4408
scanner/windows.go
Normal file
File diff suppressed because it is too large
Load Diff
736
scanner/windows_test.go
Normal file
736
scanner/windows_test.go
Normal file
@@ -0,0 +1,736 @@
|
||||
package scanner
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/future-architect/vuls/config"
|
||||
"github.com/future-architect/vuls/models"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
func Test_parseSystemInfo(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
args string
|
||||
osInfo osInfo
|
||||
kbs []string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "happy",
|
||||
args: `
|
||||
Host Name: DESKTOP
|
||||
OS Name: Microsoft Windows 10 Pro
|
||||
OS Version: 10.0.19044 N/A Build 19044
|
||||
OS Manufacturer: Microsoft Corporation
|
||||
OS Configuration: Member Workstation
|
||||
OS Build Type: Multiprocessor Free
|
||||
Registered Owner: Windows User
|
||||
Registered Organization:
|
||||
Product ID: 00000-00000-00000-AA000
|
||||
Original Install Date: 2022/04/13, 12:25:41
|
||||
System Boot Time: 2022/06/06, 16:43:45
|
||||
System Manufacturer: HP
|
||||
System Model: HP EliteBook 830 G7 Notebook PC
|
||||
System Type: x64-based PC
|
||||
Processor(s): 1 Processor(s) Installed.
|
||||
[01]: Intel64 Family 6 Model 142 Stepping 12 GenuineIntel ~1803 Mhz
|
||||
BIOS Version: HP S70 Ver. 01.05.00, 2021/04/26
|
||||
Windows Directory: C:\WINDOWS
|
||||
System Directory: C:\WINDOWS\system32
|
||||
Boot Device: \Device\HarddiskVolume2
|
||||
System Locale: en-us;English (United States)
|
||||
Input Locale: en-us;English (United States)
|
||||
Time Zone: (UTC-08:00) Pacific Time (US & Canada)
|
||||
Total Physical Memory: 15,709 MB
|
||||
Available Physical Memory: 12,347 MB
|
||||
Virtual Memory: Max Size: 18,141 MB
|
||||
Virtual Memory: Available: 14,375 MB
|
||||
Virtual Memory: In Use: 3,766 MB
|
||||
Page File Location(s): C:\pagefile.sys
|
||||
Domain: WORKGROUP
|
||||
Logon Server: \\DESKTOP
|
||||
Hotfix(s): 7 Hotfix(s) Installed.
|
||||
[01]: KB5012117
|
||||
[02]: KB4562830
|
||||
[03]: KB5003791
|
||||
[04]: KB5007401
|
||||
[05]: KB5012599
|
||||
[06]: KB5011651
|
||||
[07]: KB5005699
|
||||
Network Card(s): 1 NIC(s) Installed.
|
||||
[01]: Intel(R) Wi-Fi 6 AX201 160MHz
|
||||
Connection Name: Wi-Fi
|
||||
DHCP Enabled: Yes
|
||||
DHCP Server: 192.168.0.1
|
||||
IP address(es)
|
||||
[01]: 192.168.0.205
|
||||
Hyper-V Requirements: VM Monitor Mode Extensions: Yes
|
||||
Virtualization Enabled In Firmware: Yes
|
||||
Second Level Address Translation: Yes
|
||||
Data Execution Prevention Available: Yes
|
||||
`,
|
||||
osInfo: osInfo{
|
||||
productName: "Microsoft Windows 10 Pro",
|
||||
version: "10.0",
|
||||
build: "19044",
|
||||
revision: "",
|
||||
edition: "",
|
||||
servicePack: "",
|
||||
arch: "x64-based",
|
||||
installationType: "Client",
|
||||
},
|
||||
kbs: []string{"5012117", "4562830", "5003791", "5007401", "5012599", "5011651", "5005699"},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
osInfo, kbs, err := parseSystemInfo(tt.args)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("parseSystemInfo() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if osInfo != tt.osInfo {
|
||||
t.Errorf("parseSystemInfo() got = %v, want %v", osInfo, tt.osInfo)
|
||||
}
|
||||
if !reflect.DeepEqual(kbs, tt.kbs) {
|
||||
t.Errorf("parseSystemInfo() got = %v, want %v", kbs, tt.kbs)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_parseGetComputerInfo(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
args string
|
||||
want osInfo
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "happy",
|
||||
args: `
|
||||
WindowsProductName : Windows 10 Pro
|
||||
OsVersion : 10.0.19044
|
||||
WindowsEditionId : Professional
|
||||
OsCSDVersion :
|
||||
CsSystemType : x64-based PC
|
||||
WindowsInstallationType : Client
|
||||
`,
|
||||
want: osInfo{
|
||||
productName: "Windows 10 Pro",
|
||||
version: "10.0",
|
||||
build: "19044",
|
||||
revision: "",
|
||||
edition: "Professional",
|
||||
servicePack: "",
|
||||
arch: "x64-based",
|
||||
installationType: "Client",
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := parseGetComputerInfo(tt.args)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("parseGetComputerInfo() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if got != tt.want {
|
||||
t.Errorf("parseGetComputerInfo() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_parseWmiObject(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
args string
|
||||
want osInfo
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "happy",
|
||||
args: `
|
||||
Caption : Microsoft Windows 10 Pro
|
||||
Version : 10.0.19044
|
||||
OperatingSystemSKU : 48
|
||||
CSDVersion :
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
DomainRole : 1
|
||||
SystemType : x64-based PC`,
|
||||
want: osInfo{
|
||||
productName: "Microsoft Windows 10 Pro",
|
||||
version: "10.0",
|
||||
build: "19044",
|
||||
revision: "",
|
||||
edition: "Professional",
|
||||
servicePack: "",
|
||||
arch: "x64-based",
|
||||
installationType: "Client",
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := parseWmiObject(tt.args)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("parseWmiObject() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if got != tt.want {
|
||||
t.Errorf("parseWmiObject() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_parseRegistry(t *testing.T) {
|
||||
type args struct {
|
||||
stdout string
|
||||
arch string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want osInfo
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "happy",
|
||||
args: args{
|
||||
stdout: `
|
||||
ProductName : Windows 10 Pro
|
||||
CurrentVersion : 6.3
|
||||
CurrentMajorVersionNumber : 10
|
||||
CurrentMinorVersionNumber : 0
|
||||
CurrentBuildNumber : 19044
|
||||
UBR : 2364
|
||||
EditionID : Professional
|
||||
InstallationType : Client`,
|
||||
arch: "AMD64",
|
||||
},
|
||||
want: osInfo{
|
||||
productName: "Windows 10 Pro",
|
||||
version: "10.0",
|
||||
build: "19044",
|
||||
revision: "2364",
|
||||
edition: "Professional",
|
||||
servicePack: "",
|
||||
arch: "x64-based",
|
||||
installationType: "Client",
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := parseRegistry(tt.args.stdout, tt.args.arch)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("parseRegistry() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("parseRegistry() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_detectOSName(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
args osInfo
|
||||
want string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "Windows 10 for x64-based Systems",
|
||||
args: osInfo{
|
||||
productName: "Microsoft Windows 10 Pro",
|
||||
version: "10.0",
|
||||
build: "10585",
|
||||
revision: "",
|
||||
edition: "Professional",
|
||||
servicePack: "",
|
||||
arch: "x64-based",
|
||||
installationType: "Client",
|
||||
},
|
||||
want: "Windows 10 for x64-based Systems",
|
||||
},
|
||||
{
|
||||
name: "Windows 10 Version 21H2 for x64-based Systems",
|
||||
args: osInfo{
|
||||
productName: "Microsoft Windows 10 Pro",
|
||||
version: "10.0",
|
||||
build: "19044",
|
||||
revision: "",
|
||||
edition: "Professional",
|
||||
servicePack: "",
|
||||
arch: "x64-based",
|
||||
installationType: "Client",
|
||||
},
|
||||
want: "Windows 10 Version 21H2 for x64-based Systems",
|
||||
},
|
||||
{
|
||||
name: "Windows Server 2022",
|
||||
args: osInfo{
|
||||
productName: "Windows Server",
|
||||
version: "10.0",
|
||||
build: "30000",
|
||||
revision: "",
|
||||
edition: "",
|
||||
servicePack: "",
|
||||
arch: "x64-based",
|
||||
installationType: "Server",
|
||||
},
|
||||
want: "Windows Server 2022",
|
||||
},
|
||||
{
|
||||
name: "err",
|
||||
args: osInfo{
|
||||
productName: "Microsoft Windows 10 Pro",
|
||||
version: "10.0",
|
||||
build: "build",
|
||||
revision: "",
|
||||
edition: "Professional",
|
||||
servicePack: "",
|
||||
arch: "x64-based",
|
||||
installationType: "Client",
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := detectOSName(tt.args)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("detectOSName() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if got != tt.want {
|
||||
t.Errorf("detectOSName() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_formatKernelVersion(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
args osInfo
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "major.minor.build.revision",
|
||||
args: osInfo{
|
||||
version: "10.0",
|
||||
build: "19045",
|
||||
revision: "2130",
|
||||
},
|
||||
want: "10.0.19045.2130",
|
||||
},
|
||||
{
|
||||
name: "major.minor.build",
|
||||
args: osInfo{
|
||||
version: "10.0",
|
||||
build: "19045",
|
||||
},
|
||||
want: "10.0.19045",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := formatKernelVersion(tt.args); got != tt.want {
|
||||
t.Errorf("formatKernelVersion() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_parseInstalledPackages(t *testing.T) {
|
||||
type args struct {
|
||||
stdout string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want models.Packages
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "happy",
|
||||
args: args{
|
||||
stdout: `
|
||||
Name : Git
|
||||
Version : 2.35.1.2
|
||||
ProviderName : Programs
|
||||
|
||||
Name : Oracle Database 11g Express Edition
|
||||
Version : 11.2.0
|
||||
ProviderName : msi
|
||||
|
||||
Name : 2022-12 x64 ベース システム用 Windows 10 Version 21H2 の累積更新プログラム (KB5021233)
|
||||
Version :
|
||||
ProviderName : msu
|
||||
`,
|
||||
},
|
||||
want: models.Packages{
|
||||
"Git": {
|
||||
Name: "Git",
|
||||
Version: "2.35.1.2",
|
||||
},
|
||||
"Oracle Database 11g Express Edition": {
|
||||
Name: "Oracle Database 11g Express Edition",
|
||||
Version: "11.2.0",
|
||||
},
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
o := &windows{}
|
||||
got, _, err := o.parseInstalledPackages(tt.args.stdout)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("windows.parseInstalledPackages() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("windows.parseInstalledPackages() got = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_parseGetHotfix(t *testing.T) {
|
||||
type args struct {
|
||||
stdout string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want []string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "happy",
|
||||
args: args{
|
||||
stdout: `
|
||||
HotFixID : KB5020872
|
||||
|
||||
HotFixID : KB4562830
|
||||
`,
|
||||
},
|
||||
want: []string{"5020872", "4562830"},
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
o := &windows{}
|
||||
got, err := o.parseGetHotfix(tt.args.stdout)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("windows.parseGetHotfix() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("windows.parseGetHotfix() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_parseGetPackageMSU(t *testing.T) {
|
||||
type args struct {
|
||||
stdout string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want []string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "happy",
|
||||
args: args{
|
||||
stdout: `
|
||||
Name : Git
|
||||
Version : 2.35.1.2
|
||||
ProviderName : Programs
|
||||
|
||||
Name : Oracle Database 11g Express Edition
|
||||
Version : 11.2.0
|
||||
ProviderName : msi
|
||||
|
||||
Name : 2022-12 x64 ベース システム用 Windows 10 Version 21H2 の累積更新プログラム (KB5021233)
|
||||
Version :
|
||||
ProviderName : msu
|
||||
`,
|
||||
},
|
||||
want: []string{"5021233"},
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
o := &windows{}
|
||||
got, err := o.parseGetPackageMSU(tt.args.stdout)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("windows.parseGetPackageMSU() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("windows.parseGetPackageMSU() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_parseWindowsUpdaterSearch(t *testing.T) {
|
||||
type args struct {
|
||||
stdout string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want []string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "happy",
|
||||
args: args{
|
||||
stdout: `5012170
|
||||
5021233
|
||||
5021088
|
||||
`,
|
||||
},
|
||||
want: []string{"5012170", "5021233", "5021088"},
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
o := &windows{}
|
||||
got, err := o.parseWindowsUpdaterSearch(tt.args.stdout)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("windows.parseWindowsUpdaterSearch() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("windows.parseWindowsUpdaterSearch() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_parseWindowsUpdateHistory(t *testing.T) {
|
||||
type args struct {
|
||||
stdout string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want []string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "happy",
|
||||
args: args{
|
||||
stdout: `
|
||||
Title : 2022-10 x64 ベース システム用 Windows 10 Version 21H2 の累積更新プログラム (KB5020435)
|
||||
Operation : 1
|
||||
ResultCode : 2
|
||||
|
||||
Title : 2022-10 x64 ベース システム用 Windows 10 Version 21H2 の累積更新プログラム (KB5020435)
|
||||
Operation : 2
|
||||
ResultCode : 2
|
||||
|
||||
Title : 2022-12 x64 (KB5021088) 向け Windows 10 Version 21H2 用 .NET Framework 3.5、4.8 および 4.8.1 の累積的な更新プログラム
|
||||
Operation : 1
|
||||
ResultCode : 2
|
||||
|
||||
Title : 2022-12 x64 ベース システム用 Windows 10 Version 21H2 の累積更新プログラム (KB5021233)
|
||||
Operation : 1
|
||||
ResultCode : 2
|
||||
`,
|
||||
},
|
||||
want: []string{"5021088", "5021233"},
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
o := &windows{}
|
||||
got, err := o.parseWindowsUpdateHistory(tt.args.stdout)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("windows.parseWindowsUpdateHistory() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
slices.Sort(got)
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("windows.parseWindowsUpdateHistory() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_windows_detectKernelVersion(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
base base
|
||||
args []string
|
||||
want string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "major.minor.build, applied on 10",
|
||||
base: base{
|
||||
Distro: config.Distro{Release: "Windows 10 Version 22H2 for x64-based Systems"},
|
||||
osPackages: osPackages{Kernel: models.Kernel{Version: "10.0.19045"}},
|
||||
},
|
||||
args: []string{"5020030", "5019275"},
|
||||
want: "10.0.19045.2546",
|
||||
},
|
||||
{
|
||||
name: "major.minor.build, zero applied on 10",
|
||||
base: base{
|
||||
Distro: config.Distro{Release: "Windows 10 Version 22H2 for x64-based Systems"},
|
||||
osPackages: osPackages{Kernel: models.Kernel{Version: "10.0.19045"}},
|
||||
},
|
||||
args: []string{},
|
||||
want: "10.0.19045",
|
||||
},
|
||||
{
|
||||
name: "major.minor.build.revision",
|
||||
base: base{
|
||||
Distro: config.Distro{Release: "Windows 10 Version 22H2 for x64-based Systems"},
|
||||
osPackages: osPackages{Kernel: models.Kernel{Version: "10.0.19045.2130"}},
|
||||
},
|
||||
want: "10.0.19045.2130",
|
||||
},
|
||||
{
|
||||
name: "major.minor.build, applied on 11",
|
||||
base: base{
|
||||
Distro: config.Distro{Release: "Windows 11 Version 22H2 for x64-based Systems"},
|
||||
osPackages: osPackages{Kernel: models.Kernel{Version: "10.0.22621"}},
|
||||
},
|
||||
args: []string{"5017389", "5022303"},
|
||||
want: "10.0.22621.1105",
|
||||
},
|
||||
{
|
||||
name: "major.minor.build, applied on server 2022",
|
||||
base: base{
|
||||
Distro: config.Distro{Release: "Windows Server 2022"},
|
||||
osPackages: osPackages{Kernel: models.Kernel{Version: "10.0.20348"}},
|
||||
},
|
||||
args: []string{"5022842"},
|
||||
want: "10.0.20348.1547",
|
||||
},
|
||||
{
|
||||
name: "major.minor",
|
||||
base: base{
|
||||
Distro: config.Distro{Release: "Windows 10 Version 22H2 for x64-based Systems"},
|
||||
osPackages: osPackages{Kernel: models.Kernel{Version: "10.0"}},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
o := &windows{
|
||||
base: tt.base,
|
||||
}
|
||||
got, err := o.detectKernelVersion(tt.args)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("windows.detectKernelVersion() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if got != tt.want {
|
||||
t.Errorf("windows.detectKernelVersion() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_windows_detectKBsFromKernelVersion(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
base base
|
||||
want models.WindowsKB
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "10.0.19045.2129",
|
||||
base: base{
|
||||
Distro: config.Distro{Release: "Windows 10 Version 22H2 for x64-based Systems"},
|
||||
osPackages: osPackages{Kernel: models.Kernel{Version: "10.0.19045.2129"}},
|
||||
},
|
||||
want: models.WindowsKB{
|
||||
Applied: nil,
|
||||
Unapplied: []string{"5020953", "5019959", "5020030", "5021233", "5022282", "5019275", "5022834", "5022906"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "10.0.19045.2130",
|
||||
base: base{
|
||||
Distro: config.Distro{Release: "Windows 10 Version 22H2 for x64-based Systems"},
|
||||
osPackages: osPackages{Kernel: models.Kernel{Version: "10.0.19045.2130"}},
|
||||
},
|
||||
want: models.WindowsKB{
|
||||
Applied: nil,
|
||||
Unapplied: []string{"5020953", "5019959", "5020030", "5021233", "5022282", "5019275", "5022834", "5022906"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "10.0.22621.1105",
|
||||
base: base{
|
||||
Distro: config.Distro{Release: "Windows 11 Version 22H2 for x64-based Systems"},
|
||||
osPackages: osPackages{Kernel: models.Kernel{Version: "10.0.22621.1105"}},
|
||||
},
|
||||
want: models.WindowsKB{
|
||||
Applied: []string{"5019311", "5017389", "5018427", "5019509", "5018496", "5019980", "5020044", "5021255", "5022303"},
|
||||
Unapplied: []string{"5022360", "5022845"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "10.0.20348.1547",
|
||||
base: base{
|
||||
Distro: config.Distro{Release: "Windows Server 2022"},
|
||||
osPackages: osPackages{Kernel: models.Kernel{Version: "10.0.20348.1547"}},
|
||||
},
|
||||
want: models.WindowsKB{
|
||||
Applied: []string{"5005575", "5005619", "5006699", "5006745", "5007205", "5007254", "5008223", "5010197", "5009555", "5010796", "5009608", "5010354", "5010421", "5011497", "5011558", "5012604", "5012637", "5013944", "5015013", "5014021", "5014678", "5014665", "5015827", "5015879", "5016627", "5016693", "5017316", "5017381", "5018421", "5020436", "5018485", "5019081", "5021656", "5020032", "5021249", "5022553", "5022291", "5022842"},
|
||||
Unapplied: nil,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "err",
|
||||
base: base{
|
||||
Distro: config.Distro{Release: "Windows 10 Version 22H2 for x64-based Systems"},
|
||||
osPackages: osPackages{Kernel: models.Kernel{Version: "10.0"}},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
o := &windows{
|
||||
base: tt.base,
|
||||
}
|
||||
got, err := o.detectKBsFromKernelVersion()
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("windows.detectKBsFromKernelVersion() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("windows.detectKBsFromKernelVersion() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user