diff --git a/providers/os/connection/fs/filesystem.go b/providers/os/connection/fs/filesystem.go index fbd4ff4c9b..1ce8c746da 100644 --- a/providers/os/connection/fs/filesystem.go +++ b/providers/os/connection/fs/filesystem.go @@ -5,6 +5,7 @@ package fs import ( "errors" + "path/filepath" "github.com/rs/zerolog/log" "github.com/spf13/afero" @@ -85,6 +86,7 @@ func (c *FileSystemConnection) FileInfo(path string) (shared.FileInfoDetails, er Size: stat.Size(), Uid: uid, Gid: gid, + Path: filepath.Join(c.MountedDir, path), }, nil } diff --git a/providers/os/connection/shared/shared.go b/providers/os/connection/shared/shared.go index 7729cc4c6c..90d7770889 100644 --- a/providers/os/connection/shared/shared.go +++ b/providers/os/connection/shared/shared.go @@ -150,6 +150,7 @@ type FileInfoDetails struct { Mode FileModeDetails Uid int64 Gid int64 + Path string } type FileModeDetails struct { diff --git a/providers/os/detector/detector_all.go b/providers/os/detector/detector_all.go index c5d32373e9..b3a9550452 100644 --- a/providers/os/detector/detector_all.go +++ b/providers/os/detector/detector_all.go @@ -7,14 +7,12 @@ import ( "bytes" "io" "regexp" - "strconv" "strings" "github.com/rs/zerolog/log" "github.com/spf13/afero" "go.mondoo.com/cnquery/v11/providers-sdk/v1/inventory" "go.mondoo.com/cnquery/v11/providers/os/connection/shared" - win "go.mondoo.com/cnquery/v11/providers/os/detector/windows" ) const ( @@ -703,36 +701,14 @@ var windows = &PlatformResolver{ Name: "windows", IsFamily: false, Detect: func(r *PlatformResolver, pf *inventory.Platform, conn shared.Connection) (bool, error) { - data, err := win.GetWmiInformation(conn) - if err != nil { - log.Debug().Err(err).Msg("could not gather wmi information") - return false, nil + if conn.Capabilities().Has(shared.Capability_RunCommand) { + return runtimeWindowsDetector(pf, conn) } - pf.Name = "windows" - pf.Title = data.Caption - - // instead of using windows major.minor.build.ubr we just use build.ubr since - // major and minor can be derived from the build version - pf.Version = data.BuildNumber - - // FIXME: we need to ask wmic cpu get architecture - pf.Arch = data.OSArchitecture - - if pf.Labels == nil { - pf.Labels = map[string]string{} + if conn.Capabilities().Has(shared.Capability_FileSearch) { + return staticWindowsDetector(pf, conn) } - 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 && current.UBR > 0 { - pf.Build = strconv.Itoa(current.UBR) - } else { - log.Debug().Err(err).Msg("could not parse windows current version") - } - - return true, nil + return false, nil }, } diff --git a/providers/os/detector/detector_win.go b/providers/os/detector/detector_win.go new file mode 100644 index 0000000000..7deb4bd023 --- /dev/null +++ b/providers/os/detector/detector_win.go @@ -0,0 +1,93 @@ +// Copyright (c) Mondoo, Inc. +// SPDX-License-Identifier: BUSL-1.1 + +package detector + +import ( + "strconv" + + "github.com/rs/zerolog/log" + "go.mondoo.com/cnquery/v11/providers-sdk/v1/inventory" + "go.mondoo.com/cnquery/v11/providers/os/connection/shared" + win "go.mondoo.com/cnquery/v11/providers/os/detector/windows" + "go.mondoo.com/cnquery/v11/providers/os/registry" +) + +func runtimeWindowsDetector(pf *inventory.Platform, conn shared.Connection) (bool, error) { + data, err := win.GetWmiInformation(conn) + if err != nil { + log.Debug().Err(err).Msg("could not gather wmi information") + return false, nil + } + + pf.Name = "windows" + pf.Title = data.Caption + + // instead of using windows major.minor.build.ubr we just use build.ubr since + // major and minor can be derived from the build version + pf.Version = data.BuildNumber + + // 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 +} + +func staticWindowsDetector(pf *inventory.Platform, conn shared.Connection) (bool, error) { + rh := registry.NewRegistryHandler() + defer func() { + err := rh.UnloadSubkeys() + if err != nil { + log.Debug().Err(err).Msg("could not unload registry subkeys") + } + }() + fi, err := conn.FileInfo(registry.SoftwareRegPath) + if err != nil { + log.Debug().Err(err).Msg("could not find SOFTWARE registry key file") + return false, nil + } + err = rh.LoadSubkey(registry.Software, fi.Path) + if err != nil { + log.Debug().Err(err).Msg("could not load SOFTWARE registry key file") + return false, nil + } + + pf.Name = "windows" + productName, err := rh.GetRegistryItemValue(registry.Software, "Microsoft\\Windows NT\\CurrentVersion", "ProductName") + if err == nil { + log.Debug().Str("productName", productName.Value.String).Msg("found productName") + pf.Title = productName.Value.String + } + + ubr, err := rh.GetRegistryItemValue(registry.Software, "Microsoft\\Windows NT\\CurrentVersion", "UBR") + if err == nil && ubr.Value.String != "" { + log.Debug().Str("ubr", ubr.Value.String).Msg("found ubr") + pf.Build = ubr.Value.String + } + // we try both CurrentBuild and CurrentBuildNumber for the version number + currentBuild, err := rh.GetRegistryItemValue(registry.Software, "Microsoft\\Windows NT\\CurrentVersion", "CurrentBuild") + if err == nil && currentBuild.Value.String != "" { + log.Debug().Str("currentBuild", currentBuild.Value.String).Msg("found currentBuild") + pf.Version = currentBuild.Value.String + } else { + currentBuildNumber, err := rh.GetRegistryItemValue(registry.Software, "Microsoft\\Windows NT\\CurrentVersion", "CurrentBuildNumber") + if err == nil && currentBuildNumber.Value.String != "" { + log.Debug().Str("currentBuildNumber", currentBuildNumber.Value.String).Msg("found currentBuildNumber") + pf.Version = currentBuildNumber.Value.String + } + } + return true, nil +} diff --git a/providers/os/id/hostname/hostname.go b/providers/os/id/hostname/hostname.go index 2109988bee..313df2a108 100644 --- a/providers/os/id/hostname/hostname.go +++ b/providers/os/id/hostname/hostname.go @@ -11,6 +11,7 @@ import ( "github.com/spf13/afero" "go.mondoo.com/cnquery/v11/providers-sdk/v1/inventory" "go.mondoo.com/cnquery/v11/providers/os/connection/shared" + "go.mondoo.com/cnquery/v11/providers/os/registry" ) // Hostname returns the hostname of the system. @@ -65,5 +66,38 @@ func Hostname(conn shared.Connection, pf *inventory.Platform) (string, bool) { } } + // Fallback for windows systems to using registry for static analysis + if pf.IsFamily(inventory.FAMILY_WINDOWS) && conn.Capabilities().Has(shared.Capability_FileSearch) { + fi, err := conn.FileInfo(registry.SystemRegPath) + if err != nil { + log.Debug().Err(err).Msg("could not find SYSTEM registry file, cannot perform hostname lookup") + return "", false + } + + rh := registry.NewRegistryHandler() + defer func() { + err := rh.UnloadSubkeys() + if err != nil { + log.Debug().Err(err).Msg("could not unload registry subkeys") + } + }() + err = rh.LoadSubkey(registry.System, fi.Path) + if err != nil { + log.Debug().Err(err).Msg("could not load SYSTEM registry key file") + return "", false + } + key, err := rh.GetRegistryItemValue(registry.System, "ControlSet001\\Control\\ComputerName\\ComputerName", "ComputerName") + if err == nil { + return key.Value.String, true + } + + // we also can try ControlSet002 as a fallback + log.Debug().Err(err).Msg("unable to read windows registry, trying ControlSet002 fallback") + key, err = rh.GetRegistryItemValue(registry.System, "ControlSet002\\Control\\ComputerName\\ComputerName", "ComputerName") + if err == nil { + return key.Value.String, true + } + } + return "", false } diff --git a/providers/os/registry/registry.go b/providers/os/registry/registry.go new file mode 100644 index 0000000000..ebeeee84fb --- /dev/null +++ b/providers/os/registry/registry.go @@ -0,0 +1,28 @@ +// Copyright (c) Mondoo, Inc. +// SPDX-License-Identifier: BUSL-1.1 + +package registry + +const ( + // According to https://learn.microsoft.com/en-gb/windows/win32/sysinfo/structure-of-the-registry + // we have the following registry keys + Software = "SOFTWARE" + System = "SYSTEM" + Security = "SECURITY" + Default = "DEFAULT" + Sam = "SAM" + + SoftwareRegPath = "Windows\\System32\\config\\SOFTWARE" + SystemRegPath = "Windows\\System32\\config\\SYSTEM" + SecurityRegPath = "Windows\\System32\\config\\SECURITY" + DefaultRegPath = "Windows\\System32\\config\\DEFAULT" + SamRegPath = "Windows\\System32\\config\\SAM" +) + +var KnownRegistryFiles = map[string]string{ + Software: SoftwareRegPath, + System: SystemRegPath, + Security: SecurityRegPath, + Default: DefaultRegPath, + Sam: SamRegPath, +} diff --git a/providers/os/registry/registryhandler.go b/providers/os/registry/registryhandler.go new file mode 100644 index 0000000000..a98d7a3f25 --- /dev/null +++ b/providers/os/registry/registryhandler.go @@ -0,0 +1,103 @@ +// Copyright (c) Mondoo, Inc. +// SPDX-License-Identifier: BUSL-1.1 + +package registry + +import ( + "errors" + "fmt" + "runtime" + "sync" +) + +// RegistryHandler allows for loading and unloading of registry keys from files. It also allows for looking up registry keys and values. +type RegistryHandler struct { + // a map of currently loaded registries. + // the id is the registry's id (e.g. "SOFTWARE") and the value is the path to the loaded key (e.g. "TmpReg_SOFTWARE") + registries map[string]string + lock sync.Mutex +} + +const ( + subkeyPrefix = "TMPREG" +) + +func NewRegistryHandler() *RegistryHandler { + return &RegistryHandler{ + registries: make(map[string]string), + } +} + +// Loads the given registry file into the registry handler under a subkey, generated by buildSubKeyPath. +// The subkey file is indicated by the filepath parameter. +// Only known registry files (see KnownRegistryFiles) can be loaded. +func (r *RegistryHandler) LoadSubkey(registryId, filepath string) error { + if runtime.GOOS != "windows" { + return errors.New("loading of registry subkeys is only supported on windows") + } + + r.lock.Lock() + defer r.lock.Unlock() + // sanity check, make sure we only try and load registry files we know of + if _, ok := KnownRegistryFiles[registryId]; !ok { + return errors.New("invalid registry id") + } + if _, ok := r.registries[registryId]; ok { + return nil + } + // format the registry path + key := buildSubKeyPath(registryId) + err := LoadRegistrySubkey(key, filepath) + if err != nil { + return err + } + r.registries[registryId] = key + return nil +} + +// we use the name of the registry file as an id, e.g. "SOFTWARE" +// we combine this with the prefix to get a subkey like "TmpReg_SOFTWARE" +func buildSubKeyPath(registryId string) string { + return fmt.Sprintf("%s_%s", subkeyPrefix, registryId) +} + +// Unloads all the subkeys, that were loaded by the handler. +func (r *RegistryHandler) UnloadSubkeys() error { + r.lock.Lock() + defer r.lock.Unlock() + for id, regPath := range r.registries { + err := UnloadRegistrySubkey(regPath) + if err != nil { + return err + } + delete(r.registries, id) + } + return nil +} + +// Gets a fully qualified registry path for a given registry id, including the root (HKLM). +func (r *RegistryHandler) getRegistryPath(id string) (string, error) { + if path, ok := r.registries[id]; ok { + // we always point the syscalls to HKEY_LOCAL_MACHINE so we can safely prefix it here + return fmt.Sprintf("HKLM\\%s", path), nil + } + return "", fmt.Errorf("registry %s not loaded", id) +} + +// Gets a fully qualified registry key path for a given registry id and key. +// E.g. if the registry id is "SOFTWARE" and the key is "Microsoft", the result will be "HKLM\TmpReg_SOFTWARE\Microsoft". +func (r *RegistryHandler) getRegistryKeyPath(id string, key string) (string, error) { + root, err := r.getRegistryPath(id) + if err != nil { + return "", err + } + return fmt.Sprintf("%s\\%s", root, key), nil +} + +func (r *RegistryHandler) GetRegistryItemValue(registryId string, path, key string) (RegistryKeyItem, error) { + regPath, err := r.getRegistryKeyPath(registryId, path) + if err != nil { + return RegistryKeyItem{}, err + } + return GetNativeRegistryKeyItem(regPath, key) +} diff --git a/providers/os/registry/registryhandler_test.go b/providers/os/registry/registryhandler_test.go new file mode 100644 index 0000000000..be6991b0de --- /dev/null +++ b/providers/os/registry/registryhandler_test.go @@ -0,0 +1,44 @@ +// Copyright (c) Mondoo, Inc. +// SPDX-License-Identifier: BUSL-1.1 + +package registry + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestBuildSubKey(t *testing.T) { + require.Equal(t, "TMPREG_T", buildSubKeyPath("T")) +} + +func TestGetRegistryPath(t *testing.T) { + t.Run("get registry path for an registry that has not been loaded yet", func(t *testing.T) { + rh := NewRegistryHandler() + _, err := rh.getRegistryPath("TMPREG_T") + require.Error(t, err) + }) + t.Run("get registry path for an registry that has been loaded", func(t *testing.T) { + rh := NewRegistryHandler() + rh.registries["SOFTWARE"] = "TMPREG_SOFTWARE" + path, err := rh.getRegistryPath("SOFTWARE") + require.NoError(t, err) + require.Equal(t, "HKLM\\TMPREG_SOFTWARE", path) + }) +} + +func TestGetRegistryKeyPath(t *testing.T) { + t.Run("get registry key path for an registry that has not been loaded yet", func(t *testing.T) { + rh := NewRegistryHandler() + _, err := rh.getRegistryKeyPath("TMPREG_T", "Microsoft\\Windows") + require.Error(t, err) + }) + t.Run("get registry key path for an registry that has been loaded", func(t *testing.T) { + rh := NewRegistryHandler() + rh.registries["SOFTWARE"] = "TMPREG_SOFTWARE" + path, err := rh.getRegistryKeyPath("SOFTWARE", "Microsoft\\Windows") + require.NoError(t, err) + require.Equal(t, "HKLM\\TMPREG_SOFTWARE\\Microsoft\\Windows", path) + }) +} diff --git a/providers/os/registry/registryhandler_unix.go b/providers/os/registry/registryhandler_unix.go new file mode 100644 index 0000000000..461151dd68 --- /dev/null +++ b/providers/os/registry/registryhandler_unix.go @@ -0,0 +1,19 @@ +// Copyright (c) Mondoo, Inc. +// SPDX-License-Identifier: BUSL-1.1 + +//go:build !windows +// +build !windows + +package registry + +import ( + "errors" +) + +func LoadRegistrySubkey(key, path string) error { + return errors.New("LoadRegistrySubkey is not supported on non-windows platforms") +} + +func UnloadRegistrySubkey(key string) error { + return errors.New("UnloadRegistrySubkey is not supported on non-windows platforms") +} diff --git a/providers/os/registry/registryhandler_windows.go b/providers/os/registry/registryhandler_windows.go new file mode 100644 index 0000000000..b52a03e902 --- /dev/null +++ b/providers/os/registry/registryhandler_windows.go @@ -0,0 +1,50 @@ +// Copyright (c) Mondoo, Inc. +// SPDX-License-Identifier: BUSL-1.1 + +//go:build windows +// +build windows + +package registry + +import ( + "syscall" + "unsafe" +) + +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") +) + +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 + } + ret, _, 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 { + return err + } + return nil +} + +func UnloadRegistrySubkey(key string) error { + keyPtr, err := syscall.UTF16PtrFromString(key) + if err != nil { + return err + } + + ret, _, 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 { + return err + } + return nil +} diff --git a/providers/os/resources/windows/registrykey_powershell.go b/providers/os/registry/registrykey.go similarity index 58% rename from providers/os/resources/windows/registrykey_powershell.go rename to providers/os/registry/registrykey.go index bc1ba4b93c..6a10d26799 100644 --- a/providers/os/resources/windows/registrykey_powershell.go +++ b/providers/os/registry/registrykey.go @@ -1,73 +1,119 @@ // Copyright (c) Mondoo, Inc. // SPDX-License-Identifier: BUSL-1.1 -package windows +package registry import ( "encoding/binary" "encoding/json" "fmt" - "io" "math" "strconv" "strings" "github.com/rs/zerolog/log" + "go.mondoo.com/cnquery/v11/providers-sdk/v1/util/convert" ) -// RegistryKeyItem represents a registry key item and its properties -const getRegistryKeyItemScript = ` -$path = '%s' -$reg = Get-Item ('Registry::' + $path) -if ($reg -eq $null) { - Write-Error "Could not find registry key" - exit 1 +// derived from "golang.org/x/sys/windows/registry" +// see https://github.com/golang/sys/blob/master/windows/registry/value.go#L17-L31 +const ( + NONE = 0 + SZ = 1 + EXPAND_SZ = 2 + BINARY = 3 + DWORD = 4 + DWORD_BIG_ENDIAN = 5 + LINK = 6 + MULTI_SZ = 7 + RESOURCE_LIST = 8 + FULL_RESOURCE_DESCRIPTOR = 9 + RESOURCE_REQUIREMENTS_LIST = 10 + QWORD = 11 +) + +type RegistryKeyItem struct { + Key string + Value RegistryKeyValue } -$properties = @() -$reg.Property | ForEach-Object { - $fetchKeyValue = $_ - if ("(default)".Equals($_)) { $fetchKeyValue = '' } - $data = $(Get-ItemProperty ('Registry::' + $path)).$_; - $kind = $reg.GetValueKind($fetchKeyValue); - if ($kind -eq 7) { - $data = $(Get-ItemProperty ('Registry::' + $path)) | Select-Object -ExpandProperty $_ + +func (k RegistryKeyItem) Kind() string { + switch k.Value.Kind { + case NONE: + return "bone" + case SZ: + return "string" + case EXPAND_SZ: + return "expandstring" + case BINARY: + return "binary" + case DWORD: + return "dword" + case DWORD_BIG_ENDIAN: + return "dword" + case LINK: + return "link" + case MULTI_SZ: + return "multistring" + case RESOURCE_LIST: + return "" + case FULL_RESOURCE_DESCRIPTOR: + return "" + case RESOURCE_REQUIREMENTS_LIST: + return "" + case QWORD: + return "qword" } - $entry = New-Object psobject -Property @{ - "key" = $_ - "value" = New-Object psobject -Property @{ - "data" = $data; - "kind" = $kind; - } - } - $properties += $entry + return "" } -ConvertTo-Json -Depth 3 -Compress $properties -` -func GetRegistryKeyItemScript(path string) string { - return fmt.Sprintf(getRegistryKeyItemScript, path) +func (k RegistryKeyItem) GetRawValue() interface{} { + switch k.Value.Kind { + case NONE: + return nil + case SZ: + return k.Value.String + case EXPAND_SZ: + return k.Value.String + case BINARY: + return k.Value.Binary + case DWORD: + return k.Value.Number + case DWORD_BIG_ENDIAN: + return nil + case LINK: + return nil + case MULTI_SZ: + return convert.SliceAnyToInterface(k.Value.MultiString) + case RESOURCE_LIST: + return nil + case FULL_RESOURCE_DESCRIPTOR: + return nil + case RESOURCE_REQUIREMENTS_LIST: + return nil + case QWORD: + return k.Value.Number + } + return nil } -// getRegistryKeyChildItemsScript represents a registry key item and its children -const getRegistryKeyChildItemsScript = ` -$path = '%s' -$children = Get-ChildItem -Path ('Registry::' + $path) -rec -ea SilentlyContinue +// String returns a string representation of the registry key value +func (k RegistryKeyItem) String() string { + return k.Value.String // conversion to string is handled in UnmarshalJSON +} -$properties = @() -$children | ForEach-Object { - $entry = New-Object psobject -Property @{ - "name" = $_.PSChildName - "path" = $_.Name - "properties" = $_.Property - "children" = $_.SubKeyCount - } - $properties += $entry +type RegistryKeyValue struct { + Kind int + Binary []byte + Number int64 + String string + MultiString []string } -ConvertTo-Json -compress $properties -` -func GetRegistryKeyChildItemsScript(path string) string { - return fmt.Sprintf(getRegistryKeyChildItemsScript, path) +type RegistryKeyChild struct { + Name string + Path string + Properties []string } type keyKindRaw struct { @@ -167,33 +213,3 @@ func (k *RegistryKeyValue) UnmarshalJSON(b []byte) error { } return nil } - -func ParsePowershellRegistryKeyItems(r io.Reader) ([]RegistryKeyItem, error) { - data, err := io.ReadAll(r) - if err != nil { - return nil, err - } - - var items []RegistryKeyItem - err = json.Unmarshal(data, &items) - if err != nil { - return nil, err - } - - return items, nil -} - -func ParsePowershellRegistryKeyChildren(r io.Reader) ([]RegistryKeyChild, error) { - data, err := io.ReadAll(r) - if err != nil { - return nil, err - } - - var children []RegistryKeyChild - err = json.Unmarshal(data, &children) - if err != nil { - return nil, err - } - - return children, nil -} diff --git a/providers/os/registry/registrykey_powershell.go b/providers/os/registry/registrykey_powershell.go new file mode 100644 index 0000000000..f8d3fd1f42 --- /dev/null +++ b/providers/os/registry/registrykey_powershell.go @@ -0,0 +1,87 @@ +// Copyright (c) Mondoo, Inc. +// SPDX-License-Identifier: BUSL-1.1 + +package registry + +import ( + "encoding/json" + "fmt" + "io" +) + +func ParsePowershellRegistryKeyItems(r io.Reader) ([]RegistryKeyItem, error) { + data, err := io.ReadAll(r) + if err != nil { + return nil, err + } + + var items []RegistryKeyItem + err = json.Unmarshal(data, &items) + return items, err +} + +func ParsePowershellRegistryKeyChildren(r io.Reader) ([]RegistryKeyChild, error) { + data, err := io.ReadAll(r) + if err != nil { + return nil, err + } + + var children []RegistryKeyChild + err = json.Unmarshal(data, &children) + return children, err +} + +// RegistryKeyItem represents a registry key item and its properties +const getRegistryKeyItemScript = ` +$path = '%s' +$reg = Get-Item ('Registry::' + $path) +if ($reg -eq $null) { + Write-Error "Could not find registry key" + exit 1 +} +$properties = @() +$reg.Property | ForEach-Object { + $fetchKeyValue = $_ + if ("(default)".Equals($_)) { $fetchKeyValue = '' } + $data = $(Get-ItemProperty ('Registry::' + $path)).$_; + $kind = $reg.GetValueKind($fetchKeyValue); + if ($kind -eq 7) { + $data = $(Get-ItemProperty ('Registry::' + $path)) | Select-Object -ExpandProperty $_ + } + $entry = New-Object psobject -Property @{ + "key" = $_ + "value" = New-Object psobject -Property @{ + "data" = $data; + "kind" = $kind; + } + } + $properties += $entry +} +ConvertTo-Json -Depth 3 -Compress $properties +` + +func GetRegistryKeyItemScript(path string) string { + return fmt.Sprintf(getRegistryKeyItemScript, path) +} + +// getRegistryKeyChildItemsScript represents a registry key item and its children +const getRegistryKeyChildItemsScript = ` +$path = '%s' +$children = Get-ChildItem -Path ('Registry::' + $path) -rec -ea SilentlyContinue + +$properties = @() +$children | ForEach-Object { + $entry = New-Object psobject -Property @{ + "name" = $_.PSChildName + "path" = $_.Name + "properties" = $_.Property + "children" = $_.SubKeyCount + } + $properties += $entry +} +ConvertTo-Json -compress $properties +` + +func GetRegistryKeyChildItemsScript(path string) string { + return fmt.Sprintf(getRegistryKeyChildItemsScript, path) +} diff --git a/providers/os/resources/windows/registrykey_powershell_test.go b/providers/os/registry/registrykey_powershell_test.go similarity index 98% rename from providers/os/resources/windows/registrykey_powershell_test.go rename to providers/os/registry/registrykey_powershell_test.go index 9d9002c0cc..ff07953658 100644 --- a/providers/os/resources/windows/registrykey_powershell_test.go +++ b/providers/os/registry/registrykey_powershell_test.go @@ -1,7 +1,7 @@ // Copyright (c) Mondoo, Inc. // SPDX-License-Identifier: BUSL-1.1 -package windows +package registry import ( "os" diff --git a/providers/os/resources/windows/registrykey_unix.go b/providers/os/registry/registrykey_unix.go similarity index 70% rename from providers/os/resources/windows/registrykey_unix.go rename to providers/os/registry/registrykey_unix.go index 9dbed5b5b2..110d3a093a 100644 --- a/providers/os/resources/windows/registrykey_unix.go +++ b/providers/os/registry/registrykey_unix.go @@ -4,7 +4,7 @@ //go:build !windows // +build !windows -package windows +package registry import "errors" @@ -16,3 +16,7 @@ func GetNativeRegistryKeyItems(path string) ([]RegistryKeyItem, error) { func GetNativeRegistryKeyChildren(path string) ([]RegistryKeyChild, error) { return nil, errors.New("native registry key children not supported on non-windows platforms") } + +func GetNativeRegistryKeyItem(path, key string) (RegistryKeyItem, error) { + return RegistryKeyItem{}, errors.New("native registry key item not supported on non-windows platforms") +} diff --git a/providers/os/resources/windows/registrykey_windows.go b/providers/os/registry/registrykey_windows.go similarity index 91% rename from providers/os/resources/windows/registrykey_windows.go rename to providers/os/registry/registrykey_windows.go index 11f883ecc8..320dc25e22 100644 --- a/providers/os/resources/windows/registrykey_windows.go +++ b/providers/os/registry/registrykey_windows.go @@ -4,10 +4,11 @@ //go:build windows // +build windows -package windows +package registry import ( "errors" + "fmt" "strconv" "strings" @@ -143,3 +144,16 @@ func GetNativeRegistryKeyChildren(path string) ([]RegistryKeyChild, error) { return res, nil } + +func GetNativeRegistryKeyItem(path, key string) (RegistryKeyItem, error) { + values, err := GetNativeRegistryKeyItems(path) + if err != nil { + return RegistryKeyItem{}, err + } + for _, value := range values { + if value.Key == key { + return value, nil + } + } + return RegistryKeyItem{}, status.Error(codes.NotFound, fmt.Sprintf("registry value %s not found under %s", key, path)) +} diff --git a/providers/os/resources/windows/testdata/registrykey-children.json b/providers/os/registry/testdata/registrykey-children.json similarity index 100% rename from providers/os/resources/windows/testdata/registrykey-children.json rename to providers/os/registry/testdata/registrykey-children.json diff --git a/providers/os/resources/windows/testdata/registrykey.json b/providers/os/registry/testdata/registrykey.json similarity index 100% rename from providers/os/resources/windows/testdata/registrykey.json rename to providers/os/registry/testdata/registrykey.json diff --git a/providers/os/resources/windows/testdata/registrykey_multistring.json b/providers/os/registry/testdata/registrykey_multistring.json similarity index 100% rename from providers/os/resources/windows/testdata/registrykey_multistring.json rename to providers/os/registry/testdata/registrykey_multistring.json diff --git a/providers/os/resources/registrykey.go b/providers/os/resources/registrykey.go index 2ab296829f..01ca2d05ce 100644 --- a/providers/os/resources/registrykey.go +++ b/providers/os/resources/registrykey.go @@ -12,8 +12,8 @@ import ( "go.mondoo.com/cnquery/v11/providers-sdk/v1/plugin" "go.mondoo.com/cnquery/v11/providers/os/connection/mock" "go.mondoo.com/cnquery/v11/providers/os/connection/shared" + "go.mondoo.com/cnquery/v11/providers/os/registry" "go.mondoo.com/cnquery/v11/providers/os/resources/powershell" - "go.mondoo.com/cnquery/v11/providers/os/resources/windows" "go.mondoo.com/ranger-rpc/codes" "go.mondoo.com/ranger-rpc/status" ) @@ -26,7 +26,7 @@ func (k *mqlRegistrykey) exists() (bool, error) { conn := k.MqlRuntime.Connection.(shared.Connection) // if we are running locally on windows, we can use native api if conn.Type() == shared.Type_Local && runtime.GOOS == "windows" { - items, err := windows.GetNativeRegistryKeyItems(k.Path.Data) + items, err := registry.GetNativeRegistryKeyItems(k.Path.Data) if err == nil && len(items) > 0 { return true, nil } @@ -39,7 +39,7 @@ func (k *mqlRegistrykey) exists() (bool, error) { } } - script := powershell.Encode(windows.GetRegistryKeyItemScript(k.Path.Data)) + script := powershell.Encode(registry.GetRegistryKeyItemScript(k.Path.Data)) o, err := CreateResource(k.MqlRuntime, "command", map[string]*llx.RawData{ "command": llx.StringData(script), }) @@ -69,15 +69,15 @@ func (k *mqlRegistrykey) exists() (bool, error) { } // GetEntries returns a list of registry key property resources -func (k *mqlRegistrykey) getEntries() ([]windows.RegistryKeyItem, error) { +func (k *mqlRegistrykey) getEntries() ([]registry.RegistryKeyItem, error) { // if we are running locally on windows, we can use native api conn := k.MqlRuntime.Connection.(shared.Connection) if conn.Type() == shared.Type_Local && runtime.GOOS == "windows" { - return windows.GetNativeRegistryKeyItems(k.Path.Data) + return registry.GetNativeRegistryKeyItems(k.Path.Data) } // parse the output of the powershell script - script := powershell.Encode(windows.GetRegistryKeyItemScript(k.Path.Data)) + script := powershell.Encode(registry.GetRegistryKeyItemScript(k.Path.Data)) o, err := CreateResource(k.MqlRuntime, "command", map[string]*llx.RawData{ "command": llx.StringData(script), }) @@ -108,7 +108,7 @@ func (k *mqlRegistrykey) getEntries() ([]windows.RegistryKeyItem, error) { return nil, stdout.Error } - return windows.ParsePowershellRegistryKeyItems(strings.NewReader(stdout.Data)) + return registry.ParsePowershellRegistryKeyItems(strings.NewReader(stdout.Data)) } // Deprecated: properties returns the properties of a registry key @@ -167,16 +167,16 @@ func (k *mqlRegistrykey) items() ([]interface{}, error) { func (k *mqlRegistrykey) children() ([]interface{}, error) { conn := k.MqlRuntime.Connection.(shared.Connection) res := []interface{}{} - var children []windows.RegistryKeyChild + var children []registry.RegistryKeyChild if conn.Type() == shared.Type_Local && runtime.GOOS == "windows" { var err error - children, err = windows.GetNativeRegistryKeyChildren(k.Path.Data) + children, err = registry.GetNativeRegistryKeyChildren(k.Path.Data) if err != nil { return nil, err } } else { // parse powershell script - script := powershell.Encode(windows.GetRegistryKeyChildItemsScript(k.Path.Data)) + script := powershell.Encode(registry.GetRegistryKeyChildItemsScript(k.Path.Data)) o, err := CreateResource(k.MqlRuntime, "command", map[string]*llx.RawData{ "command": llx.StringData(script), }) @@ -196,7 +196,7 @@ func (k *mqlRegistrykey) children() ([]interface{}, error) { if stdout.Error != nil { return res, stdout.Error } - children, err = windows.ParsePowershellRegistryKeyChildren(strings.NewReader(stdout.Data)) + children, err = registry.ParsePowershellRegistryKeyChildren(strings.NewReader(stdout.Data)) if err != nil { return nil, err } diff --git a/providers/os/resources/windows/registrykey.go b/providers/os/resources/windows/registrykey.go deleted file mode 100644 index fe414d4f91..0000000000 --- a/providers/os/resources/windows/registrykey.go +++ /dev/null @@ -1,107 +0,0 @@ -// Copyright (c) Mondoo, Inc. -// SPDX-License-Identifier: BUSL-1.1 - -package windows - -import "go.mondoo.com/cnquery/v11/providers-sdk/v1/util/convert" - -// derived from "golang.org/x/sys/windows/registry" -// see https://github.com/golang/sys/blob/master/windows/registry/value.go#L17-L31 -const ( - NONE = 0 - SZ = 1 - EXPAND_SZ = 2 - BINARY = 3 - DWORD = 4 - DWORD_BIG_ENDIAN = 5 - LINK = 6 - MULTI_SZ = 7 - RESOURCE_LIST = 8 - FULL_RESOURCE_DESCRIPTOR = 9 - RESOURCE_REQUIREMENTS_LIST = 10 - QWORD = 11 -) - -type RegistryKeyItem struct { - Key string - Value RegistryKeyValue -} - -func (k RegistryKeyItem) Kind() string { - switch k.Value.Kind { - case NONE: - return "bone" - case SZ: - return "string" - case EXPAND_SZ: - return "expandstring" - case BINARY: - return "binary" - case DWORD: - return "dword" - case DWORD_BIG_ENDIAN: - return "dword" - case LINK: - return "link" - case MULTI_SZ: - return "multistring" - case RESOURCE_LIST: - return "" - case FULL_RESOURCE_DESCRIPTOR: - return "" - case RESOURCE_REQUIREMENTS_LIST: - return "" - case QWORD: - return "qword" - } - return "" -} - -func (k RegistryKeyItem) GetRawValue() interface{} { - switch k.Value.Kind { - case NONE: - return nil - case SZ: - return k.Value.String - case EXPAND_SZ: - return k.Value.String - case BINARY: - return k.Value.Binary - case DWORD: - return k.Value.Number - case DWORD_BIG_ENDIAN: - return nil - case LINK: - return nil - case MULTI_SZ: - return convert.SliceAnyToInterface(k.Value.MultiString) - case RESOURCE_LIST: - return nil - case FULL_RESOURCE_DESCRIPTOR: - return nil - case RESOURCE_REQUIREMENTS_LIST: - return nil - case QWORD: - return k.Value.Number - } - return nil -} - -// String returns a string representation of the registry key value -func (k RegistryKeyItem) String() string { - return k.Value.String // conversion to string is handled in UnmarshalJSON -} - -type RegistryKeyValue struct { - Kind int - Binary []byte - Number int64 - String string - MultiString []string -} - -type RegistryKeyChild struct { - Name string - Path string - Properties []string -}