Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

⭐️ support Windows 2025 on arm via SSH #4808

Merged
merged 1 commit into from
Nov 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 28 additions & 5 deletions providers/os/detector/detector_platform_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)

Expand Down Expand Up @@ -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)
}

Expand Down
46 changes: 34 additions & 12 deletions providers/os/detector/detector_win.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be nice to log this error even though we are falling back to wmi

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Feel free to add this to the PR.

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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is there any reason why we use the digits here and not Workstation, Server, etc?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it makes sense to change this, but this way it is compatible to the current implementation. We need to check and adjust policies like https://github.com/mondoohq/cnspec-policies/blob/main/core/mondoo-windows-11-compatibility.mql.yaml#L22-L23 to use a new label based approach.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same question, specially since PS command returns a string as a product type and here we are using a number as a string? 🤔

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's do this with #4815

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")
Expand All @@ -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
}

Expand Down
11 changes: 8 additions & 3 deletions providers/os/detector/testdata/detect-windows2019.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"]

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why are we replacing the verbose subsection name with a sha?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because this is a longer script now, we use the script hash.

stdout="""
{
"CurrentBuild": "17763",
"EditionID": "ServerDatacenterEval",
"UBR": 720
"UBR": 6414,
"InstallationType": "Server",
"EditionID": "ServerDatacenter",
"ProductName": "Windows Server 2019 Datacenter",
"DisplayVersion": null,
"Architecture": "AMD64",
"ProductType": "ServerNT"
}
"""
18 changes: 18 additions & 0 deletions providers/os/detector/testdata/detect-windows2022.toml
Original file line number Diff line number Diff line change
@@ -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"
}
"""
13 changes: 13 additions & 0 deletions providers/os/detector/testdata/detect-windows2025.toml
Original file line number Diff line number Diff line change
@@ -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"
}
"""
24 changes: 18 additions & 6 deletions providers/os/detector/windows/build_version.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: why not use our existing way of fetching registry keys to build this? the only thing thats extra here seems to be the processor architecture which can be a separate smaller pwsh cmd to fetch

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was just adjusting the current implementation with minimal change. I agree, that we should use the new registry key implementation for this. Since I have no time to do this, I recommend we do this in a follow up PR.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's do this with #4815

$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
}
Expand Down
40 changes: 30 additions & 10 deletions providers/os/detector/windows/build_version_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
})
}
5 changes: 4 additions & 1 deletion providers/os/resources/powershell/encode.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
}
Loading