From fc3369bdf8629daf562cb3abf51eb302e16b7d06 Mon Sep 17 00:00:00 2001 From: Preslav Date: Fri, 7 Jun 2024 17:36:56 +0300 Subject: [PATCH 1/7] =?UTF-8?q?=E2=9C=A8=20Add=20basic=20support=20for=20s?= =?UTF-8?q?canning=20windows=20disk=20drives=20via=20device=20conn.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Preslav --- .../os/connection/device/device_connection.go | 8 +- .../device/windows/device_manager.go | 90 +++++++++++++++ .../connection/device/windows/disk_drive.go | 107 ++++++++++++++++++ .../os/registry/registryhandler_windows.go | 12 +- .../os/resources/packages/windows_packages.go | 2 +- 5 files changed, 211 insertions(+), 8 deletions(-) create mode 100644 providers/os/connection/device/windows/device_manager.go create mode 100644 providers/os/connection/device/windows/disk_drive.go diff --git a/providers/os/connection/device/device_connection.go b/providers/os/connection/device/device_connection.go index 5f35e3a671..89c6fd9a3a 100644 --- a/providers/os/connection/device/device_connection.go +++ b/providers/os/connection/device/device_connection.go @@ -13,6 +13,8 @@ import ( "go.mondoo.com/cnquery/v11/providers-sdk/v1/inventory" "go.mondoo.com/cnquery/v11/providers-sdk/v1/plugin" "go.mondoo.com/cnquery/v11/providers/os/connection/device/linux" + "go.mondoo.com/cnquery/v11/providers/os/connection/device/windows" + "go.mondoo.com/cnquery/v11/providers/os/connection/fs" "go.mondoo.com/cnquery/v11/providers/os/connection/shared" "go.mondoo.com/cnquery/v11/providers/os/detector" @@ -35,8 +37,8 @@ func getDeviceManager(conf *inventory.Config) (DeviceManager, error) { return nil, errors.New("device manager not implemented for darwin") } if runtime.GOOS == "windows" { - // shell = []string{"powershell", "-c"} - return nil, errors.New("device manager not implemented for windows") + shell = []string{"powershell", "-c"} + return windows.NewWindowsDeviceManager(shell, conf.Options) } return linux.NewLinuxDeviceManager(shell, conf.Options) } @@ -148,7 +150,7 @@ func (p *DeviceConnection) UpdateAsset(asset *inventory.Asset) { } func (p *DeviceConnection) Capabilities() shared.Capabilities { - return shared.Capability_File + return p.FileSystemConnection.Capabilities() } func (p *DeviceConnection) RunCommand(command string) (*shared.Command, error) { diff --git a/providers/os/connection/device/windows/device_manager.go b/providers/os/connection/device/windows/device_manager.go new file mode 100644 index 0000000000..7c9e00235e --- /dev/null +++ b/providers/os/connection/device/windows/device_manager.go @@ -0,0 +1,90 @@ +// Copyright (c) Mondoo, Inc. +// SPDX-License-Identifier: BUSL-1.1 + +package windows + +import ( + "errors" + "strconv" + "strings" + + "github.com/rs/zerolog/log" + "go.mondoo.com/cnquery/v11/providers/os/connection/snapshot" +) + +const ( + LunOption = "lun" +) + +type WindowsDeviceManager struct { + cmdRunner *snapshot.LocalCommandRunner + opts map[string]string +} + +func NewWindowsDeviceManager(shell []string, opts map[string]string) (*WindowsDeviceManager, error) { + if err := validateOpts(opts); err != nil { + return nil, err + } + return &WindowsDeviceManager{ + cmdRunner: &snapshot.LocalCommandRunner{Shell: shell}, + opts: opts, + }, nil +} + +func (d *WindowsDeviceManager) Name() string { + return "windows" +} + +func (d *WindowsDeviceManager) IdentifyMountTargets(opts map[string]string) ([]*snapshot.PartitionInfo, error) { + lun := opts[LunOption] + lunInt, err := strconv.Atoi(lun) + if err != nil { + return nil, err + } + log.Debug().Str("lun", lun).Msg("device connection> identifying mount targets") + diskDrives, err := d.IdentifyDiskDrives() + if err != nil { + return nil, err + } + + targetDrive, err := filterDiskDrives(diskDrives, lunInt) + if err != nil { + return nil, err + } + partitions, err := d.identifyPartitions(targetDrive.Index) + if err != nil { + return nil, err + } + partition, err := filterPartitions(partitions) + if err != nil { + return nil, err + } + partitionInfo := &snapshot.PartitionInfo{ + Name: partition.DriveLetter, + FsType: "Windows", + } + return []*snapshot.PartitionInfo{partitionInfo}, nil +} + +// validates the options provided to the device manager +func validateOpts(opts map[string]string) error { + lun := opts[LunOption] + if lun == "" { + return errors.New("lun is required for a windows device connection") + } + + return nil +} + +func (d *WindowsDeviceManager) Mount(pi *snapshot.PartitionInfo) (string, error) { + // note: we do not (yet) do the mounting in windows. for now, we simply return the drive letter + // as that means the drive is already mounted + if strings.HasSuffix(pi.Name, ":") { + return pi.Name, nil + } + return pi.Name + ":", nil +} + +func (d *WindowsDeviceManager) UnmountAndClose() { + log.Debug().Msg("closing windows device manager") +} diff --git a/providers/os/connection/device/windows/disk_drive.go b/providers/os/connection/device/windows/disk_drive.go new file mode 100644 index 0000000000..bf40911eca --- /dev/null +++ b/providers/os/connection/device/windows/disk_drive.go @@ -0,0 +1,107 @@ +// Copyright (c) Mondoo, Inc. +// SPDX-License-Identifier: BUSL-1.1 + +package windows + +import ( + "encoding/json" + "errors" + "fmt" + "io" + "slices" + + "go.mondoo.com/cnquery/v11/providers/os/resources/powershell" +) + +const ( + identifyDiskDrivesPwshScript = `Get-WmiObject -Class Win32_DiskDrive | Select-Object Name,SCSILogicalUnit,Index | ConvertTo-Json` + identifyPartitionPwshScript = `Get-Disk -Number %d | Get-Partition | Select DriveLetter, Size, Type | ConvertTo-Json` +) + +type diskDrive struct { + Name string `json:"Name"` + SCSILogicalUnit int `json:"SCSILogicalUnit"` + Index int `json:"Index"` +} + +type diskPartition struct { + DriveLetter string `json:"DriveLetter"` + Size uint64 `json:"Size"` + Type string `json:"Type"` +} + +func (d *WindowsDeviceManager) IdentifyDiskDrives() ([]*diskDrive, error) { + cmd, err := d.cmdRunner.RunCommand(powershell.Encode(identifyDiskDrivesPwshScript)) + if err != nil { + return nil, err + } + + if cmd.ExitStatus != 0 { + outErr, err := io.ReadAll(cmd.Stderr) + if err != nil { + return nil, err + } + return nil, fmt.Errorf("failed to run powershell script: %s", outErr) + } + + stdout, err := io.ReadAll(cmd.Stdout) + if err != nil { + return nil, err + } + + var drives []*diskDrive + err = json.Unmarshal(stdout, &drives) + if err != nil { + return nil, err + } + + return drives, nil +} + +func (d *WindowsDeviceManager) identifyPartitions(diskNumber int) ([]*diskPartition, error) { + script := fmt.Sprintf(identifyPartitionPwshScript, diskNumber) + cmd, err := d.cmdRunner.RunCommand(powershell.Encode(script)) + if err != nil { + return nil, err + } + + if cmd.ExitStatus != 0 { + outErr, err := io.ReadAll(cmd.Stderr) + if err != nil { + return nil, err + } + return nil, fmt.Errorf("failed to run powershell script: %s", outErr) + } + + stdout, err := io.ReadAll(cmd.Stdout) + if err != nil { + return nil, err + } + + var partitions []*diskPartition + err = json.Unmarshal(stdout, &partitions) + if err != nil { + return nil, err + } + + return partitions, nil +} + +func filterDiskDrives(drives []*diskDrive, lun int) (*diskDrive, error) { + for _, d := range drives { + if lun == d.SCSILogicalUnit { + return d, nil + } + } + return nil, errors.New("no disk drive with matching LUN found") +} + +func filterPartitions(partitions []*diskPartition) (*diskPartition, error) { + allowed := []string{"Basic", "Windows"} + for _, p := range partitions { + if slices.Contains(allowed, p.Type) && p.DriveLetter != "" { + return p, nil + } + } + return nil, errors.New("no basic partition with assigned drive letter found") +} diff --git a/providers/os/registry/registryhandler_windows.go b/providers/os/registry/registryhandler_windows.go index b52a03e902..4a17e0f295 100644 --- a/providers/os/registry/registryhandler_windows.go +++ b/providers/os/registry/registryhandler_windows.go @@ -9,6 +9,8 @@ package registry import ( "syscall" "unsafe" + + "github.com/rs/zerolog/log" ) var ( @@ -27,9 +29,10 @@ func LoadRegistrySubkey(key, path string) error { if err != nil { return err } - ret, _, err := regLoadKey.Call(syscall.HKEY_LOCAL_MACHINE, uintptr(unsafe.Pointer(keyPtr)), uintptr(unsafe.Pointer(pathPtr))) + _, _, err = regLoadKey.Call(syscall.HKEY_LOCAL_MACHINE, uintptr(unsafe.Pointer(keyPtr)), uintptr(unsafe.Pointer(pathPtr))) // the Microsoft docs indicate that the return value is 0 on success - if ret != 0 { + if syserr, ok := err.(syscall.Errno); ok && syserr != 0 { + log.Debug().Err(syserr).Msg("could not load registry subkey") return err } return nil @@ -41,9 +44,10 @@ func UnloadRegistrySubkey(key string) error { return err } - ret, _, err := regUnloadKey.Call(syscall.HKEY_LOCAL_MACHINE, uintptr(unsafe.Pointer(keyPtr))) + _, _, err = regUnloadKey.Call(syscall.HKEY_LOCAL_MACHINE, uintptr(unsafe.Pointer(keyPtr))) // the Microsoft docs indicate that the return value is 0 on success - if ret != 0 { + if syserr, ok := err.(syscall.Errno); ok && syserr != 0 { + log.Debug().Err(syserr).Msg("could not unload registry subkey") return err } return nil diff --git a/providers/os/resources/packages/windows_packages.go b/providers/os/resources/packages/windows_packages.go index 75b5de7098..ca5223bfa7 100644 --- a/providers/os/resources/packages/windows_packages.go +++ b/providers/os/resources/packages/windows_packages.go @@ -247,7 +247,7 @@ func (w *WinPkgManager) getInstalledApps() ([]Package, error) { return w.getLocalInstalledApps() } - if w.conn.Type() == shared.Type_FileSystem { + if w.conn.Type() == shared.Type_FileSystem || w.conn.Type() == shared.Type_Device { return w.getFsInstalledApps() } From 228f95fbb6d2ed70d1d17fec3c3f68369e081c15 Mon Sep 17 00:00:00 2001 From: Preslav Date: Thu, 13 Jun 2024 16:59:48 +0300 Subject: [PATCH 2/7] Set disk to online if offline. Signed-off-by: Preslav --- .../device/windows/device_manager.go | 25 +++++++- .../connection/device/windows/disk_drive.go | 61 ++++++++++++++++++- 2 files changed, 83 insertions(+), 3 deletions(-) diff --git a/providers/os/connection/device/windows/device_manager.go b/providers/os/connection/device/windows/device_manager.go index 7c9e00235e..2b8c919fac 100644 --- a/providers/os/connection/device/windows/device_manager.go +++ b/providers/os/connection/device/windows/device_manager.go @@ -17,8 +17,10 @@ const ( ) type WindowsDeviceManager struct { - cmdRunner *snapshot.LocalCommandRunner - opts map[string]string + cmdRunner *snapshot.LocalCommandRunner + opts map[string]string + diskSetToOnline bool + diskIndex int } func NewWindowsDeviceManager(shell []string, opts map[string]string) (*WindowsDeviceManager, error) { @@ -51,6 +53,19 @@ func (d *WindowsDeviceManager) IdentifyMountTargets(opts map[string]string) ([]* if err != nil { return nil, err } + + diskOnline, err := d.identifyDiskOnline(targetDrive.Index) + if err != nil { + return nil, err + } + if diskOnline.IsOffline { + err = d.setDiskOnlineState(targetDrive.Index, true) + if err != nil { + return nil, err + } + d.diskSetToOnline = true + d.diskIndex = targetDrive.Index + } partitions, err := d.identifyPartitions(targetDrive.Index) if err != nil { return nil, err @@ -87,4 +102,10 @@ func (d *WindowsDeviceManager) Mount(pi *snapshot.PartitionInfo) (string, error) func (d *WindowsDeviceManager) UnmountAndClose() { log.Debug().Msg("closing windows device manager") + if d.diskSetToOnline { + err := d.setDiskOnlineState(d.diskIndex, false) + if err != nil { + log.Debug().Err(err).Msg("could not set disk offline") + } + } } diff --git a/providers/os/connection/device/windows/disk_drive.go b/providers/os/connection/device/windows/disk_drive.go index bf40911eca..99b607c845 100644 --- a/providers/os/connection/device/windows/disk_drive.go +++ b/providers/os/connection/device/windows/disk_drive.go @@ -10,12 +10,16 @@ import ( "io" "slices" + "github.com/rs/zerolog/log" + "go.mondoo.com/cnquery/v11/providers/os/resources/powershell" ) const ( - identifyDiskDrivesPwshScript = `Get-WmiObject -Class Win32_DiskDrive | Select-Object Name,SCSILogicalUnit,Index | ConvertTo-Json` + identifyDiskDrivesPwshScript = `Get-WmiObject -Class Win32_DiskDrive | Select-Object Name, SCSILogicalUnit, Index | ConvertTo-Json` + identifyDiskOnlinePwshScript = `Get-Disk -Number %d | Select-Object Number, IsOffline | ConvertTo-Json` identifyPartitionPwshScript = `Get-Disk -Number %d | Get-Partition | Select DriveLetter, Size, Type | ConvertTo-Json` + setDiskOnlinePwshScript = `Set-Disk -Number %d -IsOffline %s` ) type diskDrive struct { @@ -24,12 +28,66 @@ type diskDrive struct { Index int `json:"Index"` } +type diskOnlineStatus struct { + Number int `json:"Number"` + IsOffline bool `json:"IsOffline"` +} + type diskPartition struct { DriveLetter string `json:"DriveLetter"` Size uint64 `json:"Size"` Type string `json:"Type"` } +func (d *WindowsDeviceManager) setDiskOnlineState(diskNumber int, online bool) error { + str := "$true" + if online { + str = "$false" + } + log.Debug().Int("diskNumber", diskNumber).Bool("online", online).Msg("setting disk online state") + script := fmt.Sprintf(setDiskOnlinePwshScript, diskNumber, str) + cmd, err := d.cmdRunner.RunCommand(powershell.Encode(script)) + if err != nil { + return err + } + if cmd.ExitStatus != 0 { + outErr, err := io.ReadAll(cmd.Stderr) + if err != nil { + return err + } + return fmt.Errorf("failed to run powershell script: %s", outErr) + } + + return nil +} + +func (d *WindowsDeviceManager) identifyDiskOnline(diskNumber int) (*diskOnlineStatus, error) { + cmd, err := d.cmdRunner.RunCommand(powershell.Encode(fmt.Sprintf(identifyDiskOnlinePwshScript, diskNumber))) + if err != nil { + return nil, err + } + if cmd.ExitStatus != 0 { + outErr, err := io.ReadAll(cmd.Stderr) + if err != nil { + return nil, err + } + return nil, fmt.Errorf("failed to run powershell script: %s", outErr) + } + + stdout, err := io.ReadAll(cmd.Stdout) + if err != nil { + return nil, err + } + + var status *diskOnlineStatus + err = json.Unmarshal(stdout, &status) + if err != nil { + return nil, err + } + + return status, nil +} + func (d *WindowsDeviceManager) IdentifyDiskDrives() ([]*diskDrive, error) { cmd, err := d.cmdRunner.RunCommand(powershell.Encode(identifyDiskDrivesPwshScript)) if err != nil { @@ -90,6 +148,7 @@ func (d *WindowsDeviceManager) identifyPartitions(diskNumber int) ([]*diskPartit func filterDiskDrives(drives []*diskDrive, lun int) (*diskDrive, error) { for _, d := range drives { if lun == d.SCSILogicalUnit { + log.Debug().Int("lun", lun).Str("name", d.Name).Int("index", d.Index).Msg("found disk drive with matching LUN") return d, nil } } From b658ce469bd0fefc02fbec3674f176ca86b4ceee Mon Sep 17 00:00:00 2001 From: Preslav Date: Thu, 13 Jun 2024 19:00:35 +0300 Subject: [PATCH 3/7] add comments. --- providers/os/connection/device/windows/device_manager.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/providers/os/connection/device/windows/device_manager.go b/providers/os/connection/device/windows/device_manager.go index 2b8c919fac..3aed0f8372 100644 --- a/providers/os/connection/device/windows/device_manager.go +++ b/providers/os/connection/device/windows/device_manager.go @@ -17,10 +17,12 @@ const ( ) type WindowsDeviceManager struct { - cmdRunner *snapshot.LocalCommandRunner - opts map[string]string + cmdRunner *snapshot.LocalCommandRunner + opts map[string]string + // indicates if the disk we've targeted has been set to online. we use this to know if we need to put it back offline once we're done diskSetToOnline bool - diskIndex int + // if we've set the disk online, we need to know the index to set it back offline + diskIndex int } func NewWindowsDeviceManager(shell []string, opts map[string]string) (*WindowsDeviceManager, error) { From 382a7ac35c23417fcdca2ef68d1d94b1a1841a81 Mon Sep 17 00:00:00 2001 From: Preslav Date: Thu, 13 Jun 2024 19:53:06 +0300 Subject: [PATCH 4/7] Add new --serial-number arg. Add support for filtering disks by serial number. --- providers/os/config/config.go | 10 ++++- .../device/windows/device_manager.go | 23 ++++++----- .../connection/device/windows/disk_drive.go | 40 +++++++++++++++++-- providers/os/provider/provider.go | 4 ++ 4 files changed, 60 insertions(+), 17 deletions(-) diff --git a/providers/os/config/config.go b/providers/os/config/config.go index 1b4cfbd53c..d2f8d9568a 100644 --- a/providers/os/config/config.go +++ b/providers/os/config/config.go @@ -273,13 +273,19 @@ var Config = plugin.Provider{ { Long: "lun", Type: plugin.FlagType_String, - Desc: "The logical unit number of the block device that should be scanned. Do not use together with --device-name", + Desc: "The logical unit number of the block device that should be scanned. Do not use together with --device-name or --serial-number", Option: plugin.FlagOption_Hidden, }, { Long: "device-name", Type: plugin.FlagType_String, - Desc: "The target device to scan, e.g. /dev/sda. Do not use together with --lun", + Desc: "The target device to scan, e.g. /dev/sda. Do not use together with --lun or --serial-number", + Option: plugin.FlagOption_Hidden, + }, + { + Long: "serial-number", + Type: plugin.FlagType_String, + Desc: "The serial number of the block device that should be scanned. Do not use together with --device-name or --lun", Option: plugin.FlagOption_Hidden, }, { diff --git a/providers/os/connection/device/windows/device_manager.go b/providers/os/connection/device/windows/device_manager.go index 3aed0f8372..f32a4261c9 100644 --- a/providers/os/connection/device/windows/device_manager.go +++ b/providers/os/connection/device/windows/device_manager.go @@ -5,7 +5,6 @@ package windows import ( "errors" - "strconv" "strings" "github.com/rs/zerolog/log" @@ -13,7 +12,8 @@ import ( ) const ( - LunOption = "lun" + LunOption = "lun" + SerialNumberOption = "serial-number" ) type WindowsDeviceManager struct { @@ -40,18 +40,13 @@ func (d *WindowsDeviceManager) Name() string { } func (d *WindowsDeviceManager) IdentifyMountTargets(opts map[string]string) ([]*snapshot.PartitionInfo, error) { - lun := opts[LunOption] - lunInt, err := strconv.Atoi(lun) - if err != nil { - return nil, err - } - log.Debug().Str("lun", lun).Msg("device connection> identifying mount targets") + log.Debug().Msg("device connection> identifying mount targets") diskDrives, err := d.IdentifyDiskDrives() if err != nil { return nil, err } - targetDrive, err := filterDiskDrives(diskDrives, lunInt) + targetDrive, err := filterDiskDrives(diskDrives, opts) if err != nil { return nil, err } @@ -86,8 +81,14 @@ func (d *WindowsDeviceManager) IdentifyMountTargets(opts map[string]string) ([]* // validates the options provided to the device manager func validateOpts(opts map[string]string) error { lun := opts[LunOption] - if lun == "" { - return errors.New("lun is required for a windows device connection") + serialNumber := opts[SerialNumberOption] + + if lun != "" && serialNumber != "" { + return errors.New("lun and serial-number are mutually exclusive options") + } + + if lun == "" && serialNumber == "" { + return errors.New("either lun or serial-number must be provided") } return nil diff --git a/providers/os/connection/device/windows/disk_drive.go b/providers/os/connection/device/windows/disk_drive.go index 99b607c845..aa2b1a48f2 100644 --- a/providers/os/connection/device/windows/disk_drive.go +++ b/providers/os/connection/device/windows/disk_drive.go @@ -9,6 +9,7 @@ import ( "fmt" "io" "slices" + "strconv" "github.com/rs/zerolog/log" @@ -16,7 +17,7 @@ import ( ) const ( - identifyDiskDrivesPwshScript = `Get-WmiObject -Class Win32_DiskDrive | Select-Object Name, SCSILogicalUnit, Index | ConvertTo-Json` + identifyDiskDrivesPwshScript = `Get-WmiObject -Class Win32_DiskDrive | Select-Object Name, SCSILogicalUnit, Index, SerialNumber | ConvertTo-Json` identifyDiskOnlinePwshScript = `Get-Disk -Number %d | Select-Object Number, IsOffline | ConvertTo-Json` identifyPartitionPwshScript = `Get-Disk -Number %d | Get-Partition | Select DriveLetter, Size, Type | ConvertTo-Json` setDiskOnlinePwshScript = `Set-Disk -Number %d -IsOffline %s` @@ -26,6 +27,7 @@ type diskDrive struct { Name string `json:"Name"` SCSILogicalUnit int `json:"SCSILogicalUnit"` Index int `json:"Index"` + SerialNumber string `json:"SerialNumber"` } type diskOnlineStatus struct { @@ -139,13 +141,43 @@ func (d *WindowsDeviceManager) identifyPartitions(diskNumber int) ([]*diskPartit var partitions []*diskPartition err = json.Unmarshal(stdout, &partitions) if err != nil { - return nil, err + // fallback, if only one partition is found, the output is not an array + var partition *diskPartition + err = json.Unmarshal(stdout, &partition) + if err != nil { + return nil, err + } + return []*diskPartition{partition}, nil } return partitions, nil } -func filterDiskDrives(drives []*diskDrive, lun int) (*diskDrive, error) { +func filterDiskDrives(drives []*diskDrive, opts map[string]string) (*diskDrive, error) { + serialNumber := opts[SerialNumberOption] + lun := opts[LunOption] + if serialNumber != "" { + return filterDiskDrivesBySerialNumber(drives, serialNumber) + } + + lunInt, err := strconv.Atoi(lun) + if err != nil { + return nil, err + } + return filterDiskDrivesByLun(drives, lunInt) +} + +func filterDiskDrivesBySerialNumber(drives []*diskDrive, serialNumber string) (*diskDrive, error) { + for _, d := range drives { + if serialNumber == d.SerialNumber { + log.Debug().Str("serialNumber", serialNumber).Str("name", d.Name).Int("index", d.Index).Msg("found disk drive with matching serial number") + return d, nil + } + } + return nil, errors.New("no disk drive with matching serial number found") +} + +func filterDiskDrivesByLun(drives []*diskDrive, lun int) (*diskDrive, error) { for _, d := range drives { if lun == d.SCSILogicalUnit { log.Debug().Int("lun", lun).Str("name", d.Name).Int("index", d.Index).Msg("found disk drive with matching LUN") @@ -156,7 +188,7 @@ func filterDiskDrives(drives []*diskDrive, lun int) (*diskDrive, error) { } func filterPartitions(partitions []*diskPartition) (*diskPartition, error) { - allowed := []string{"Basic", "Windows"} + allowed := []string{"Basic", "Windows", "IFS"} for _, p := range partitions { if slices.Contains(allowed, p.Type) && p.DriveLetter != "" { return p, nil diff --git a/providers/os/provider/provider.go b/providers/os/provider/provider.go index dfc51ba43a..694f5d0556 100644 --- a/providers/os/provider/provider.go +++ b/providers/os/provider/provider.go @@ -230,6 +230,10 @@ func (s *Service) ParseCLI(req *plugin.ParseCLIReq) (*plugin.ParseCLIRes, error) if deviceName, ok := flags["device-name"]; ok { conf.Options["device-name"] = deviceName.RawData().Value.(string) } + if serialNumber, ok := flags["serial-number"]; ok { + conf.Options["serial-number"] = serialNumber.RawData().Value.(string) + } + if platformIDs, ok := flags["platform-ids"]; ok { platformIDs := platformIDs.Array strs := []string{} From 5c15b4c03577213a3f88af55087e3a896d964a18 Mon Sep 17 00:00:00 2001 From: Preslav Date: Thu, 13 Jun 2024 19:53:53 +0300 Subject: [PATCH 5/7] clarify which params are only for windows/linux. Signed-off-by: Preslav --- providers/os/config/config.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/providers/os/config/config.go b/providers/os/config/config.go index d2f8d9568a..c55072b7ea 100644 --- a/providers/os/config/config.go +++ b/providers/os/config/config.go @@ -279,13 +279,13 @@ var Config = plugin.Provider{ { Long: "device-name", Type: plugin.FlagType_String, - Desc: "The target device to scan, e.g. /dev/sda. Do not use together with --lun or --serial-number", + Desc: "The target device to scan, e.g. /dev/sda. Supported only for Linux scanning. Do not use together with --lun or --serial-number", Option: plugin.FlagOption_Hidden, }, { Long: "serial-number", Type: plugin.FlagType_String, - Desc: "The serial number of the block device that should be scanned. Do not use together with --device-name or --lun", + Desc: "The serial number of the block device that should be scanned. Supported only for Windows scanning. Do not use together with --device-name or --lun", Option: plugin.FlagOption_Hidden, }, { From 0fd2301526882746d66d04bcc786833f5e2386b4 Mon Sep 17 00:00:00 2001 From: Salim Afiune Maya Date: Thu, 13 Jun 2024 09:57:08 -0700 Subject: [PATCH 6/7] =?UTF-8?q?=F0=9F=94=A5=F0=9F=AA=9F=20use=20reg.exe=20?= =?UTF-8?q?to=20(un)load=20sub=20keys=20(#4226)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Proof-of-concept that we can use the `reg` command instead of the syscall that doesn't seem to work when running with `SYSTEM` user. https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/reg Signed-off-by: Salim Afiune Maya --- .../os/registry/registryhandler_windows.go | 42 ++----------------- 1 file changed, 4 insertions(+), 38 deletions(-) diff --git a/providers/os/registry/registryhandler_windows.go b/providers/os/registry/registryhandler_windows.go index 4a17e0f295..9713af1e0d 100644 --- a/providers/os/registry/registryhandler_windows.go +++ b/providers/os/registry/registryhandler_windows.go @@ -7,48 +7,14 @@ package registry import ( - "syscall" - "unsafe" - - "github.com/rs/zerolog/log" -) - -var ( - advapi32 = syscall.NewLazyDLL("advapi32.dll") - // note: we're using the W (RegLoadKeyW and NOT RegLoadKeyA) versions of these functions to work with UTF16 strings - regLoadKey = advapi32.NewProc("RegLoadKeyW") - regUnloadKey = advapi32.NewProc("RegUnLoadKeyW") + "fmt" + "os/exec" ) func LoadRegistrySubkey(key, path string) error { - keyPtr, err := syscall.UTF16PtrFromString(key) - if err != nil { - return err - } - pathPtr, err := syscall.UTF16PtrFromString(path) - if err != nil { - return err - } - _, _, err = regLoadKey.Call(syscall.HKEY_LOCAL_MACHINE, uintptr(unsafe.Pointer(keyPtr)), uintptr(unsafe.Pointer(pathPtr))) - // the Microsoft docs indicate that the return value is 0 on success - if syserr, ok := err.(syscall.Errno); ok && syserr != 0 { - log.Debug().Err(syserr).Msg("could not load registry subkey") - return err - } - return nil + return exec.Command("cmd", "/C", "reg", "load", fmt.Sprintf(`HKEY_LOCAL_MACHINE\%s`, key), path).Run() } func UnloadRegistrySubkey(key string) error { - keyPtr, err := syscall.UTF16PtrFromString(key) - if err != nil { - return err - } - - _, _, err = regUnloadKey.Call(syscall.HKEY_LOCAL_MACHINE, uintptr(unsafe.Pointer(keyPtr))) - // the Microsoft docs indicate that the return value is 0 on success - if syserr, ok := err.(syscall.Errno); ok && syserr != 0 { - log.Debug().Err(syserr).Msg("could not unload registry subkey") - return err - } - return nil + return exec.Command("cmd", "/C", "reg", "unload", fmt.Sprintf(`HKEY_LOCAL_MACHINE\%s`, key)).Run() } From 7a7872ff7faa712e887bf4e5ec25a4316729cd8b Mon Sep 17 00:00:00 2001 From: Preslav Date: Thu, 13 Jun 2024 21:16:59 +0300 Subject: [PATCH 7/7] Add tests. Signed-off-by: Preslav --- .../device/windows/device_manager_test.go | 43 +++++ .../device/windows/disk_drive_test.go | 152 ++++++++++++++++++ 2 files changed, 195 insertions(+) create mode 100644 providers/os/connection/device/windows/device_manager_test.go create mode 100644 providers/os/connection/device/windows/disk_drive_test.go diff --git a/providers/os/connection/device/windows/device_manager_test.go b/providers/os/connection/device/windows/device_manager_test.go new file mode 100644 index 0000000000..e61a151276 --- /dev/null +++ b/providers/os/connection/device/windows/device_manager_test.go @@ -0,0 +1,43 @@ +// Copyright (c) Mondoo, Inc. +// SPDX-License-Identifier: BUSL-1.1 + +package windows + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestValidateOpts(t *testing.T) { + t.Run("valid (only LUN)", func(t *testing.T) { + opts := map[string]string{ + LunOption: "0", + } + err := validateOpts(opts) + require.NoError(t, err) + }) + + t.Run("valid (only serial number)", func(t *testing.T) { + opts := map[string]string{ + SerialNumberOption: "0", + } + err := validateOpts(opts) + require.NoError(t, err) + }) + + t.Run("invalid (both LUN and serial number are provided", func(t *testing.T) { + opts := map[string]string{ + SerialNumberOption: "1234", + LunOption: "1", + } + err := validateOpts(opts) + require.Error(t, err) + }) + + t.Run("invalid (neither LUN nor serial number are provided", func(t *testing.T) { + opts := map[string]string{} + err := validateOpts(opts) + require.Error(t, err) + }) +} diff --git a/providers/os/connection/device/windows/disk_drive_test.go b/providers/os/connection/device/windows/disk_drive_test.go new file mode 100644 index 0000000000..9cf3d9418b --- /dev/null +++ b/providers/os/connection/device/windows/disk_drive_test.go @@ -0,0 +1,152 @@ +// Copyright (c) Mondoo, Inc. +// SPDX-License-Identifier: BUSL-1.1 + +package windows + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestFilterDrives(t *testing.T) { + t.Run("filter by serial number", func(t *testing.T) { + opts := map[string]string{ + SerialNumberOption: "1234", + } + drives := []*diskDrive{ + { + SerialNumber: "1234", + SCSILogicalUnit: 0, + Index: 0, + Name: "a", + }, + { + SerialNumber: "5678", + SCSILogicalUnit: 1, + Index: 1, + Name: "b", + }, + } + filtered, err := filterDiskDrives(drives, opts) + require.NoError(t, err) + expected := &diskDrive{ + SerialNumber: "1234", + SCSILogicalUnit: 0, + Index: 0, + Name: "a", + } + require.Equal(t, expected, filtered) + }) + + t.Run("filter by LUN", func(t *testing.T) { + opts := map[string]string{ + LunOption: "1", + } + drives := []*diskDrive{ + { + SerialNumber: "1234", + SCSILogicalUnit: 0, + Index: 0, + Name: "a", + }, + { + SerialNumber: "5678", + SCSILogicalUnit: 1, + Index: 1, + Name: "b", + }, + } + filtered, err := filterDiskDrives(drives, opts) + require.NoError(t, err) + expected := &diskDrive{ + SerialNumber: "5678", + SCSILogicalUnit: 1, + Index: 1, + Name: "b", + } + require.Equal(t, expected, filtered) + }) + t.Run("filter by invalid LUN", func(t *testing.T) { + opts := map[string]string{ + LunOption: "a", + } + drives := []*diskDrive{ + { + SerialNumber: "1234", + SCSILogicalUnit: 0, + Index: 0, + Name: "a", + }, + { + SerialNumber: "5678", + SCSILogicalUnit: 1, + Index: 1, + Name: "b", + }, + } + _, err := filterDiskDrives(drives, opts) + require.Error(t, err) + }) +} + +func TestFilterPartitions(t *testing.T) { + t.Run("find a partition (Basic type)", func(t *testing.T) { + parts := []*diskPartition{ + { + DriveLetter: "A", + Size: 123, + Type: "Basic", + }, + { + Size: 123, + Type: "Basic", + }, + } + part, err := filterPartitions(parts) + require.NoError(t, err) + expected := &diskPartition{ + DriveLetter: "A", + Size: 123, + Type: "Basic", + } + require.Equal(t, expected, part) + }) + + t.Run("find a partition (IFS type)", func(t *testing.T) { + parts := []*diskPartition{ + { + DriveLetter: "A", + Size: 123, + Type: "IFS", + }, + { + Size: 123, + Type: "Basic", + }, + } + part, err := filterPartitions(parts) + require.NoError(t, err) + expected := &diskPartition{ + DriveLetter: "A", + Size: 123, + Type: "IFS", + } + require.Equal(t, expected, part) + }) + + t.Run("no applicable partition", func(t *testing.T) { + parts := []*diskPartition{ + { + Size: 123, + Type: "IFS", + }, + { + Size: 123, + Type: "Basic", + }, + } + _, err := filterPartitions(parts) + require.Error(t, err) + }) +}