From 8d7b70cbd9983d7d656c2dca6a743fe42dcecab3 Mon Sep 17 00:00:00 2001 From: Christoph Hartmann Date: Sat, 2 Nov 2024 11:04:46 +0100 Subject: [PATCH] =?UTF-8?q?=E2=AD=90=EF=B8=8F=20support=20Windows=202025?= =?UTF-8?q?=20on=20arm=20via=20SSH?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../os/detector/detector_platform_test.go | 33 +++++++++++-- providers/os/detector/detector_win.go | 46 ++++++++++++++----- .../detector/testdata/detect-windows2019.toml | 11 +++-- .../detector/testdata/detect-windows2022.toml | 18 ++++++++ .../detector/testdata/detect-windows2025.toml | 13 ++++++ .../os/detector/windows/build_version.go | 24 +++++++--- .../os/detector/windows/build_version_test.go | 40 ++++++++++++---- providers/os/resources/powershell/encode.go | 5 +- 8 files changed, 153 insertions(+), 37 deletions(-) create mode 100644 providers/os/detector/testdata/detect-windows2022.toml create mode 100644 providers/os/detector/testdata/detect-windows2025.toml diff --git a/providers/os/detector/detector_platform_test.go b/providers/os/detector/detector_platform_test.go index 50c09e05f9..b669f4e101 100644 --- a/providers/os/detector/detector_platform_test.go +++ b/providers/os/detector/detector_platform_test.go @@ -7,9 +7,8 @@ import ( "errors" "testing" - "go.mondoo.com/cnquery/v11/providers-sdk/v1/inventory" - "github.com/stretchr/testify/assert" + "go.mondoo.com/cnquery/v11/providers-sdk/v1/inventory" "go.mondoo.com/cnquery/v11/providers/os/connection/mock" ) @@ -690,10 +689,34 @@ func TestWindows2019Detector(t *testing.T) { assert.Nil(t, err, "was able to create the provider") assert.Equal(t, "windows", di.Name, "os name should be identified") - assert.Equal(t, "Microsoft Windows Server 2019 Datacenter Evaluation", di.Title, "os title should be identified") + assert.Equal(t, "Windows Server 2019 Datacenter", di.Title, "os title should be identified") assert.Equal(t, "17763", di.Version, "os version should be identified") - assert.Equal(t, "720", di.Build, "os build version should be identified") - assert.Equal(t, "64-bit", di.Arch, "os arch should be identified") + assert.Equal(t, "6414", di.Build, "os build version should be identified") + assert.Equal(t, "AMD64", di.Arch, "os arch should be identified") + assert.Equal(t, []string{"windows", "os"}, di.Family) +} + +func TestWindows2022Detector(t *testing.T) { + di, err := detectPlatformFromMock("./testdata/detect-windows2022.toml") + assert.Nil(t, err, "was able to create the provider") + + assert.Equal(t, "windows", di.Name, "os name should be identified") + assert.Equal(t, "Windows Server 2022 Datacenter", di.Title, "os title should be identified") + assert.Equal(t, "20348", di.Version, "os version should be identified") + assert.Equal(t, "2762", di.Build, "os build version should be identified") + assert.Equal(t, "AMD64", di.Arch, "os arch should be identified") + assert.Equal(t, []string{"windows", "os"}, di.Family) +} + +func TestWindows2025Detector(t *testing.T) { + di, err := detectPlatformFromMock("./testdata/detect-windows2025.toml") + assert.Nil(t, err, "was able to create the provider") + + assert.Equal(t, "windows", di.Name, "os name should be identified") + assert.Equal(t, "Windows Server 2025 Datacenter", di.Title, "os title should be identified") + assert.Equal(t, "26311", di.Version, "os version should be identified") + assert.Equal(t, "5000", di.Build, "os build version should be identified") + assert.Equal(t, "ARM64", di.Arch, "os arch should be identified") assert.Equal(t, []string{"windows", "os"}, di.Family) } diff --git a/providers/os/detector/detector_win.go b/providers/os/detector/detector_win.go index 7deb4bd023..d184062910 100644 --- a/providers/os/detector/detector_win.go +++ b/providers/os/detector/detector_win.go @@ -13,7 +13,41 @@ import ( "go.mondoo.com/cnquery/v11/providers/os/registry" ) +// runtimeWindowsDetector uses powershell to gather information about the windows system func runtimeWindowsDetector(pf *inventory.Platform, conn shared.Connection) (bool, error) { + // most systems support wmi, but windows on arm does not ship with wmic, therefore we are trying to use windows + // builds from registry key first. If that fails, we try to use wmi + // see https://techcommunity.microsoft.com/t5/windows-it-pro-blog/wmi-command-line-wmic-utility-deprecation-next-steps/ba-p/4039242 + + if pf.Labels == nil { + pf.Labels = map[string]string{} + } + + // try to get build + ubr number (win 10+, 2019+) + current, err := win.GetWindowsOSBuild(conn) + if err == nil && current.UBR > 0 { + pf.Name = "windows" + pf.Title = current.ProductName + pf.Version = current.CurrentBuild + pf.Build = strconv.Itoa(current.UBR) + pf.Arch = current.Architecture + + var productType string + switch current.ProductType { + case "WinNT": + productType = "1" // Workstation + case "ServerNT": + productType = "3" // Server + case "LanmanNT": + productType = "2" // Domain Controller + } + + pf.Labels["windows.mondoo.com/product-type"] = productType + pf.Labels["windows.mondoo.com/display-version"] = current.DisplayVersion + return true, nil + } + + // fallback to wmi if the registry key is not available data, err := win.GetWmiInformation(conn) if err != nil { log.Debug().Err(err).Msg("could not gather wmi information") @@ -29,20 +63,8 @@ func runtimeWindowsDetector(pf *inventory.Platform, conn shared.Connection) (boo // FIXME: we need to ask wmic cpu get architecture pf.Arch = data.OSArchitecture - - if pf.Labels == nil { - pf.Labels = map[string]string{} - } pf.Labels["windows.mondoo.com/product-type"] = data.ProductType - // optional: try to get the ubr number (win 10 + 2019) - current, err := win.GetWindowsOSBuild(conn) - if err != nil { - log.Debug().Err(err).Msg("could not parse windows current version") - } else if current.UBR > 0 { - pf.Build = strconv.Itoa(current.UBR) - } - return true, nil } diff --git a/providers/os/detector/testdata/detect-windows2019.toml b/providers/os/detector/testdata/detect-windows2019.toml index e5936d5dfb..fbeeb151fa 100644 --- a/providers/os/detector/testdata/detect-windows2019.toml +++ b/providers/os/detector/testdata/detect-windows2019.toml @@ -3,11 +3,16 @@ stdout = """Node,BootDevice,BuildNumber,BuildType,Caption,CodeSet,CountryCode,Cr VAGRANT,\\Device\\HarddiskVolume1,17763,Multiprocessor Free,Microsoft Windows Server 2019 Datacenter Evaluation,1252,1,Win32_OperatingSystem,Win32_ComputerSystem,,VAGRANT,-420,TRUE,TRUE,TRUE,3,FALSE,,FALSE,256,2,721716,979372,1922780,20190906065515.000000-420,,20190908011749.580533-420,20190908042731.608000-420,0409,Microsoft Corporation,4294967295,137438953344,{en-US},Microsoft Windows Server 2019 Datacenter Evaluation|C:\\Windows|\\Device\\Harddisk0\\Partition2,0,69,1,80,Vagrant,64-bit,1033,400,18,,,,,FALSE,TRUE,3,,00431-20000-00000-AA838,0,0,1179648,OK,400,\\Device\\HarddiskVolume2,C:\\Windows\\system32,C:,,3276340,2096692,10.0.17763,C:\\Windows """ -[commands."powershell -c \"Get-ItemProperty -Path 'HKLM:\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion' -Name CurrentBuild, UBR, EditionID | ConvertTo-Json\""] +[commands."55dbc0e9b838caa11145eed07f6e73644bda27bf65a0c58a52291f9a18384481"] stdout=""" { "CurrentBuild": "17763", - "EditionID": "ServerDatacenterEval", - "UBR": 720 + "UBR": 6414, + "InstallationType": "Server", + "EditionID": "ServerDatacenter", + "ProductName": "Windows Server 2019 Datacenter", + "DisplayVersion": null, + "Architecture": "AMD64", + "ProductType": "ServerNT" } """ \ No newline at end of file diff --git a/providers/os/detector/testdata/detect-windows2022.toml b/providers/os/detector/testdata/detect-windows2022.toml new file mode 100644 index 0000000000..8b269008bc --- /dev/null +++ b/providers/os/detector/testdata/detect-windows2022.toml @@ -0,0 +1,18 @@ +[commands."wmic os get * /format:csv"] +stdout = """NNode,BootDevice,BuildNumber,BuildType,Caption,CodeSet,CountryCode,CreationClassName,CSCreationClassName,CSDVersion,CSName,CurrentTimeZone,DataExecutionPrevention_32BitApplications,DataExecutionPrevention_Available,DataExecutionPrevention_Drivers,DataExecutionPrevention_SupportPolicy,Debug,Description,Distributed,EncryptionLevel,ForegroundApplicationBoost,FreePhysicalMemory,FreeSpaceInPagingFiles,FreeVirtualMemory,InstallDate,LargeSystemCache,LastBootUpTime,LocalDateTime,Locale,Manufacturer,MaxNumberOfProcesses,MaxProcessMemorySize,MUILanguages,Name,NumberOfLicensedUsers,NumberOfProcesses,NumberOfUsers,OperatingSystemSKU,Organization,OSArchitecture,OSLanguage,OSProductSuite,OSType,OtherTypeDescription,PAEEnabled,PlusProductID,PlusVersionNumber,PortableOperatingSystem,Primary,ProductType,RegisteredUser,SerialNumber,ServicePackMajorVersion,ServicePackMinorVersion,SizeStoredInPagingFiles,Status,SuiteMask,SystemDevice,SystemDirectory,SystemDrive,TotalSwapSpaceSize,TotalVirtualMemorySize,TotalVisibleMemorySize,Version,WindowsDirectory +EC2AMAZ-EBJBV88,\\Device\\HarddiskVolume1,20348,Multiprocessor Free,Microsoft Windows Server 2022 Datacenter,1252,1,Win32_OperatingSystem,Win32_ComputerSystem,,EC2AMAZ-EBJBV88,0,TRUE,TRUE,TRUE,3,FALSE,,FALSE,256,2,2675412,1249808,4264104,20241030064719.000000+000,,20241030111529.500000+000,20241102105726.901000+000,0409,Microsoft Corporation,4294967295,137438953344,{en-US},Microsoft Windows Server 2022 Datacenter|C:\\Windows|\\Device\\Harddisk0\\Partition1,0,79,2,8,Amazon.com,64-bit,1033,400,18,,,,,FALSE,TRUE,3,EC2,00454-60000-00001-AA631,0,0,1441792,OK,400,\\Device\\HarddiskVolume1,C:\\Windows\\system32,C:,,5635696,4193904,10.0.20348,C:\\Windows +""" + +[commands."55dbc0e9b838caa11145eed07f6e73644bda27bf65a0c58a52291f9a18384481"] +stdout=""" +{ + "CurrentBuild": "20348", + "UBR": 2762, + "InstallationType": "Server", + "EditionID": "ServerDatacenter", + "ProductName": "Windows Server 2022 Datacenter", + "DisplayVersion": "21H2", + "Architecture": "AMD64", + "ProductType": "ServerNT" +} +""" \ No newline at end of file diff --git a/providers/os/detector/testdata/detect-windows2025.toml b/providers/os/detector/testdata/detect-windows2025.toml new file mode 100644 index 0000000000..63ecd406fa --- /dev/null +++ b/providers/os/detector/testdata/detect-windows2025.toml @@ -0,0 +1,13 @@ +[commands."55dbc0e9b838caa11145eed07f6e73644bda27bf65a0c58a52291f9a18384481"] +stdout=""" +{ + "CurrentBuild": "26311", + "UBR": 5000, + "InstallationType": "Server", + "EditionID": "ServerDatacenter", + "ProductName": "Windows Server 2025 Datacenter", + "DisplayVersion": "24H2", + "Architecture": "ARM64", + "ProductType": "ServerNT" +} +""" \ No newline at end of file diff --git a/providers/os/detector/windows/build_version.go b/providers/os/detector/windows/build_version.go index 5f381de068..ccd453f079 100644 --- a/providers/os/detector/windows/build_version.go +++ b/providers/os/detector/windows/build_version.go @@ -58,11 +58,16 @@ func (b BuildVersion) OSBuild() string { } type WindowsCurrentVersion struct { - CurrentBuild string `json:"CurrentBuild"` - EditionID string `json:"EditionID"` - ReleaseId string `json:"ReleaseId"` + CurrentBuild string `json:"CurrentBuild"` + EditionID string `json:"EditionID"` + ReleaseId string `json:"ReleaseId"` + InstallationType string `json:"InstallationType"` + ProductName string `json:"ProductName"` + DisplayVersion string `json:"DisplayVersion"` // Update Build Revision - UBR int `json:"UBR"` + UBR int `json:"UBR"` + Architecture string `json:"Architecture"` + ProductType string `json:"ProductType"` } func ParseWinRegistryCurrentVersion(r io.Reader) (*WindowsCurrentVersion, error) { @@ -82,8 +87,15 @@ func ParseWinRegistryCurrentVersion(r io.Reader) (*WindowsCurrentVersion, error) // powershellGetWindowsOSBuild runs a powershell script to retrieve the current version from windows func powershellGetWindowsOSBuild(conn shared.Connection) (*WindowsCurrentVersion, error) { - pscommand := "Get-ItemProperty -Path 'HKLM:\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion' -Name CurrentBuild, UBR, EditionID | ConvertTo-Json" - cmd, err := conn.RunCommand(powershell.Wrap(pscommand)) + pscommand := ` +$info = Get-ItemProperty -Path 'HKLM:\SYSTEM\CurrentControlSet\Control\ProductOptions' -Name ProductType +$sysInfo = Get-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion' -Name CurrentBuild, UBR, InstallationType, EditionID, ProductName, DisplayVersion +$sysInfo | Add-Member -MemberType NoteProperty -Name Architecture -Value $env:PROCESSOR_ARCHITECTURE +$sysInfo | Add-Member -MemberType NoteProperty -Name ProductType -Value $info.ProductType +$sysInfo | Select-Object CurrentBuild, UBR, InstallationType, EditionID, ProductName, DisplayVersion, Architecture, ProductType | ConvertTo-Json +` + + cmd, err := conn.RunCommand(powershell.Encode(pscommand)) if err != nil { return nil, err } diff --git a/providers/os/detector/windows/build_version_test.go b/providers/os/detector/windows/build_version_test.go index 2c42cc9c6f..8b6ab25ef2 100644 --- a/providers/os/detector/windows/build_version_test.go +++ b/providers/os/detector/windows/build_version_test.go @@ -12,17 +12,37 @@ import ( // UBR - Update Build Revision func TestParseWinRegistryCurrentVersion(t *testing.T) { - data := `{ - "CurrentBuild": "17763", - "UBR": 720, - "EditionID": "ServerDatacenterEval", - "ReleaseId": "1809" - }` + t.Run("parse windows version", func(t *testing.T) { + data := `{ + "CurrentBuild": "17763", + "UBR": 720, + "EditionID": "ServerDatacenterEval", + "ReleaseId": "1809" + }` - m, err := ParseWinRegistryCurrentVersion(strings.NewReader(data)) - assert.Nil(t, err) + m, err := ParseWinRegistryCurrentVersion(strings.NewReader(data)) + assert.Nil(t, err) - assert.Equal(t, "17763", m.CurrentBuild, "buildnumber should be parsed properly") - assert.Equal(t, 720, m.UBR, "ubr should be parsed properly") + assert.Equal(t, "17763", m.CurrentBuild, "buildnumber should be parsed properly") + assert.Equal(t, 720, m.UBR, "ubr should be parsed properly") + }) + t.Run("parse windows version with architecture", func(t *testing.T) { + data := `{ + "CurrentBuild": "26100", + "UBR": 2033, + "InstallationType": "Client", + "EditionID": "Enterprise", + "ProductName": "Windows 10 Enterprise", + "DisplayVersion": "24H2", + "Architecture": "ARM64", + "ProductType": "WinNT" + }` + m, err := ParseWinRegistryCurrentVersion(strings.NewReader(data)) + assert.Nil(t, err) + + assert.Equal(t, "26100", m.CurrentBuild, "buildnumber should be parsed properly") + assert.Equal(t, 2033, m.UBR, "ubr should be parsed properly") + assert.Equal(t, "ARM64", m.Architecture, "architecture should be parsed properly") + }) } diff --git a/providers/os/resources/powershell/encode.go b/providers/os/resources/powershell/encode.go index acd3ecd6e6..445f10d167 100644 --- a/providers/os/resources/powershell/encode.go +++ b/providers/os/resources/powershell/encode.go @@ -31,7 +31,7 @@ func Encode(cmd string) string { return fmt.Sprintf("powershell.exe -NoProfile -EncodedCommand %s", encodedScript) } -// The Encode equivalent for running powershell script in unix systems +// EncodeUnix is equivalent to Encode for running powershell script on unix systems func EncodeUnix(cmd string) string { // avoid messages to stderr that are not required in our execution script := "$ProgressPreference='SilentlyContinue';" + cmd @@ -60,6 +60,9 @@ func ToBase64String(script string) (string, error) { return base64.StdEncoding.EncodeToString([]byte(encoded)), nil } +// Wrap runs a powershell script by calling powershell. Note that this is not encoded and therefore does not support +// multiline scripts or special characters. You should use Encode for that or ensure the script is a single line and +// does use semicolons to separate commands. func Wrap(cmd string) string { return fmt.Sprintf("powershell -c \"%s\"", cmd) }