From f4c1f13225c4689399a171721fd7f17243d686fd Mon Sep 17 00:00:00 2001 From: Preslav Date: Fri, 31 May 2024 09:00:32 +0200 Subject: [PATCH 1/3] =?UTF-8?q?=E2=AD=90=EF=B8=8F=20Add=20support=20for=20?= =?UTF-8?q?static=20analysis=20of=20Windows=20systems=20by=20using=20regis?= =?UTF-8?q?try=20files,=20located=20on=20a=20filesystem?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Preslav --- providers/os/connection/fs/filesystem.go | 2 + providers/os/connection/shared/shared.go | 1 + providers/os/detector/detector_all.go | 94 ++++++-- providers/os/id/hostname/hostname.go | 33 +++ providers/os/registry/registry.go | 28 +++ providers/os/registry/registryhandler.go | 103 +++++++++ providers/os/registry/registryhandler_test.go | 44 ++++ providers/os/registry/registryhandler_unix.go | 19 ++ .../os/registry/registryhandler_windows.go | 50 ++++ providers/os/registry/registrykey.go | 215 ++++++++++++++++++ .../os/registry/registrykey_powershell.go | 39 ++++ .../registrykey_powershell_test.go | 2 +- .../windows => registry}/registrykey_unix.go | 6 +- .../registrykey_windows.go | 16 +- .../testdata/registrykey-children.json | 0 .../testdata/registrykey.json | 0 .../testdata/registrykey_multistring.json | 0 providers/os/resources/packages/packages.go | 1 + providers/os/resources/registrykey.go | 15 +- providers/os/resources/windows/registrykey.go | 107 --------- .../windows/registrykey_powershell.go | 136 ----------- 21 files changed, 635 insertions(+), 276 deletions(-) create mode 100644 providers/os/registry/registry.go create mode 100644 providers/os/registry/registryhandler.go create mode 100644 providers/os/registry/registryhandler_test.go create mode 100644 providers/os/registry/registryhandler_unix.go create mode 100644 providers/os/registry/registryhandler_windows.go create mode 100644 providers/os/registry/registrykey.go create mode 100644 providers/os/registry/registrykey_powershell.go rename providers/os/{resources/windows => registry}/registrykey_powershell_test.go (98%) rename providers/os/{resources/windows => registry}/registrykey_unix.go (70%) rename providers/os/{resources/windows => registry}/registrykey_windows.go (91%) rename providers/os/{resources/windows => registry}/testdata/registrykey-children.json (100%) rename providers/os/{resources/windows => registry}/testdata/registrykey.json (100%) rename providers/os/{resources/windows => registry}/testdata/registrykey_multistring.json (100%) delete mode 100644 providers/os/resources/windows/registrykey.go 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..a4e8cadea9 100644 --- a/providers/os/detector/detector_all.go +++ b/providers/os/detector/detector_all.go @@ -15,6 +15,7 @@ import ( "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" ) const ( @@ -703,36 +704,83 @@ 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) { + data, err := win.GetWmiInformation(conn) + if err != nil { + log.Debug().Err(err).Msg("could not gather wmi information") + } - pf.Name = "windows" - pf.Title = data.Caption + 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 + // 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 + // 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 + 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 && current.UBR > 0 { + pf.Build = strconv.Itoa(current.UBR) + } else { + log.Debug().Err(err).Msg("could not parse windows current version") + } - // 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 true, nil + if conn.Capabilities().Has(shared.Capability_FileSearch) { + 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 { + 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 + } + return false, nil }, } diff --git a/providers/os/id/hostname/hostname.go b/providers/os/id/hostname/hostname.go index 2109988bee..026f65a2a3 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,37 @@ 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 + } else { + // we also can try ControlSet002 as a 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/registry/registrykey.go b/providers/os/registry/registrykey.go new file mode 100644 index 0000000000..6a10d26799 --- /dev/null +++ b/providers/os/registry/registrykey.go @@ -0,0 +1,215 @@ +// Copyright (c) Mondoo, Inc. +// SPDX-License-Identifier: BUSL-1.1 + +package registry + +import ( + "encoding/binary" + "encoding/json" + "fmt" + "math" + "strconv" + "strings" + + "github.com/rs/zerolog/log" + "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 +} + +type keyKindRaw struct { + Kind int + Data interface{} +} + +func (k *RegistryKeyValue) UnmarshalJSON(b []byte) error { + var raw keyKindRaw + + // try to unmarshal the type + err := json.Unmarshal(b, &raw) + if err != nil { + return err + } + k.Kind = raw.Kind + + if raw.Data == nil { + return nil + } + + // see https://docs.microsoft.com/en-us/powershell/scripting/samples/working-with-registry-entries?view=powershell-7 + switch raw.Kind { + case NONE: + // ignore + case SZ: // Any string value + value, ok := raw.Data.(string) + if !ok { + return fmt.Errorf("registry key value is not a string: %v", raw.Data) + } + k.String = value + case EXPAND_SZ: // A string that can contain environment variables that are dynamically expanded + value, ok := raw.Data.(string) + if !ok { + return fmt.Errorf("registry key value is not a string: %v", raw.Data) + } + k.String = value + case BINARY: // Binary data + rawData, ok := raw.Data.([]interface{}) + if !ok { + return fmt.Errorf("registry key value is not a byte array: %v", raw.Data) + } + data := make([]byte, len(rawData)) + for i, v := range rawData { + val, ok := v.(float64) + if !ok { + return fmt.Errorf("registry key value is not a byte array: %v", raw.Data) + } + data[i] = byte(val) + } + k.Binary = data + case DWORD: // A number that is a valid UInt32 + data, ok := raw.Data.(float64) + if !ok { + return fmt.Errorf("registry key value is not a number: %v", raw.Data) + } + number := int64(data) + // string fallback + k.Number = number + k.String = strconv.FormatInt(number, 10) + case DWORD_BIG_ENDIAN: + log.Warn().Msg("DWORD_BIG_ENDIAN for registry key is not supported") + case LINK: + log.Warn().Msg("LINK for registry key is not supported") + case MULTI_SZ: // A multiline string + switch value := raw.Data.(type) { + case string: + k.String = value + if value != "" { + k.MultiString = []string{value} + } + case []interface{}: + if len(value) > 0 { + var multiString []string + for _, v := range value { + multiString = append(multiString, v.(string)) + } + // NOTE: this is to be consistent with the output before we moved to multi-datatype support for registry keys + k.String = strings.Join(multiString, " ") + k.MultiString = multiString + } + } + case RESOURCE_LIST: + log.Warn().Msg("RESOURCE_LIST for registry key is not supported") + case FULL_RESOURCE_DESCRIPTOR: + log.Warn().Msg("FULL_RESOURCE_DESCRIPTOR for registry key is not supported") + case RESOURCE_REQUIREMENTS_LIST: + log.Warn().Msg("RESOURCE_REQUIREMENTS_LIST for registry key is not supported") + case QWORD: // 8 bytes of binary data + f, ok := raw.Data.(float64) + if !ok { + return fmt.Errorf("registry key value is not a number: %v", raw.Data) + } + buf := make([]byte, 8) + binary.LittleEndian.PutUint64(buf[:], math.Float64bits(f)) + k.Binary = buf + } + return nil +} diff --git a/providers/os/registry/registrykey_powershell.go b/providers/os/registry/registrykey_powershell.go new file mode 100644 index 0000000000..7c27c2ce6d --- /dev/null +++ b/providers/os/registry/registrykey_powershell.go @@ -0,0 +1,39 @@ +// Copyright (c) Mondoo, Inc. +// SPDX-License-Identifier: BUSL-1.1 + +package registry + +import ( + "encoding/json" + "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) + 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/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/packages/packages.go b/providers/os/resources/packages/packages.go index 020bf01849..e249f38c60 100644 --- a/providers/os/resources/packages/packages.go +++ b/providers/os/resources/packages/packages.go @@ -109,6 +109,7 @@ func ResolveSystemPkgManager(conn shared.Connection) (OperatingSystemPkgManager, pm = &AlpinePkgManager{conn: conn, platform: asset.Platform} case asset.Platform.Name == "macos": // mac os family pm = &MacOSPkgManager{conn: conn} + // TODO: add a new manager here for fs conn that lists registry directly case asset.Platform.Name == "windows": pm = &WinPkgManager{conn: conn, platform: asset.Platform} case asset.Platform.Name == "scratch" || asset.Platform.Name == "coreos": diff --git a/providers/os/resources/registrykey.go b/providers/os/resources/registrykey.go index 2ab296829f..912e62dddc 100644 --- a/providers/os/resources/registrykey.go +++ b/providers/os/resources/registrykey.go @@ -12,6 +12,7 @@ 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" @@ -26,7 +27,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 } @@ -69,11 +70,11 @@ 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 @@ -108,7 +109,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,10 +168,10 @@ 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 } @@ -196,7 +197,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 -} diff --git a/providers/os/resources/windows/registrykey_powershell.go b/providers/os/resources/windows/registrykey_powershell.go index bc1ba4b93c..b395058448 100644 --- a/providers/os/resources/windows/registrykey_powershell.go +++ b/providers/os/resources/windows/registrykey_powershell.go @@ -4,15 +4,7 @@ package windows import ( - "encoding/binary" - "encoding/json" "fmt" - "io" - "math" - "strconv" - "strings" - - "github.com/rs/zerolog/log" ) // RegistryKeyItem represents a registry key item and its properties @@ -69,131 +61,3 @@ ConvertTo-Json -compress $properties func GetRegistryKeyChildItemsScript(path string) string { return fmt.Sprintf(getRegistryKeyChildItemsScript, path) } - -type keyKindRaw struct { - Kind int - Data interface{} -} - -func (k *RegistryKeyValue) UnmarshalJSON(b []byte) error { - var raw keyKindRaw - - // try to unmarshal the type - err := json.Unmarshal(b, &raw) - if err != nil { - return err - } - k.Kind = raw.Kind - - if raw.Data == nil { - return nil - } - - // see https://docs.microsoft.com/en-us/powershell/scripting/samples/working-with-registry-entries?view=powershell-7 - switch raw.Kind { - case NONE: - // ignore - case SZ: // Any string value - value, ok := raw.Data.(string) - if !ok { - return fmt.Errorf("registry key value is not a string: %v", raw.Data) - } - k.String = value - case EXPAND_SZ: // A string that can contain environment variables that are dynamically expanded - value, ok := raw.Data.(string) - if !ok { - return fmt.Errorf("registry key value is not a string: %v", raw.Data) - } - k.String = value - case BINARY: // Binary data - rawData, ok := raw.Data.([]interface{}) - if !ok { - return fmt.Errorf("registry key value is not a byte array: %v", raw.Data) - } - data := make([]byte, len(rawData)) - for i, v := range rawData { - val, ok := v.(float64) - if !ok { - return fmt.Errorf("registry key value is not a byte array: %v", raw.Data) - } - data[i] = byte(val) - } - k.Binary = data - case DWORD: // A number that is a valid UInt32 - data, ok := raw.Data.(float64) - if !ok { - return fmt.Errorf("registry key value is not a number: %v", raw.Data) - } - number := int64(data) - // string fallback - k.Number = number - k.String = strconv.FormatInt(number, 10) - case DWORD_BIG_ENDIAN: - log.Warn().Msg("DWORD_BIG_ENDIAN for registry key is not supported") - case LINK: - log.Warn().Msg("LINK for registry key is not supported") - case MULTI_SZ: // A multiline string - switch value := raw.Data.(type) { - case string: - k.String = value - if value != "" { - k.MultiString = []string{value} - } - case []interface{}: - if len(value) > 0 { - var multiString []string - for _, v := range value { - multiString = append(multiString, v.(string)) - } - // NOTE: this is to be consistent with the output before we moved to multi-datatype support for registry keys - k.String = strings.Join(multiString, " ") - k.MultiString = multiString - } - } - case RESOURCE_LIST: - log.Warn().Msg("RESOURCE_LIST for registry key is not supported") - case FULL_RESOURCE_DESCRIPTOR: - log.Warn().Msg("FULL_RESOURCE_DESCRIPTOR for registry key is not supported") - case RESOURCE_REQUIREMENTS_LIST: - log.Warn().Msg("RESOURCE_REQUIREMENTS_LIST for registry key is not supported") - case QWORD: // 8 bytes of binary data - f, ok := raw.Data.(float64) - if !ok { - return fmt.Errorf("registry key value is not a number: %v", raw.Data) - } - buf := make([]byte, 8) - binary.LittleEndian.PutUint64(buf[:], math.Float64bits(f)) - k.Binary = buf - } - 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 -} From 8cc29e4062e20484677695cb86dff0e699dff890 Mon Sep 17 00:00:00 2001 From: Preslav Date: Thu, 6 Jun 2024 18:41:33 +0300 Subject: [PATCH 2/3] process review feedback. --- providers/os/detector/detector_all.go | 76 +--------------- providers/os/detector/detector_win.go | 91 +++++++++++++++++++ .../os/registry/registrykey_powershell.go | 56 ++++++++++++ providers/os/resources/registrykey.go | 7 +- .../windows/registrykey_powershell.go | 63 ------------- 5 files changed, 152 insertions(+), 141 deletions(-) create mode 100644 providers/os/detector/detector_win.go delete mode 100644 providers/os/resources/windows/registrykey_powershell.go diff --git a/providers/os/detector/detector_all.go b/providers/os/detector/detector_all.go index a4e8cadea9..b3a9550452 100644 --- a/providers/os/detector/detector_all.go +++ b/providers/os/detector/detector_all.go @@ -7,15 +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" - "go.mondoo.com/cnquery/v11/providers/os/registry" ) const ( @@ -705,80 +702,11 @@ var windows = &PlatformResolver{ IsFamily: false, Detect: func(r *PlatformResolver, pf *inventory.Platform, conn shared.Connection) (bool, error) { if conn.Capabilities().Has(shared.Capability_RunCommand) { - data, err := win.GetWmiInformation(conn) - if err != nil { - log.Debug().Err(err).Msg("could not gather wmi information") - } - - 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 && 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 runtimeWindowsDetector(pf, conn) } if conn.Capabilities().Has(shared.Capability_FileSearch) { - 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 { - 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 + return staticWindowsDetector(pf, conn) } 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..3a20039aa7 --- /dev/null +++ b/providers/os/detector/detector_win.go @@ -0,0 +1,91 @@ +// 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") + } + + 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 && current.UBR > 0 { + pf.Build = strconv.Itoa(current.UBR) + } else { + log.Debug().Err(err).Msg("could not parse windows current version") + } + + 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 { + 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/registry/registrykey_powershell.go b/providers/os/registry/registrykey_powershell.go index 7c27c2ce6d..29a1e712c6 100644 --- a/providers/os/registry/registrykey_powershell.go +++ b/providers/os/registry/registrykey_powershell.go @@ -5,6 +5,7 @@ package registry import ( "encoding/json" + "fmt" "io" ) @@ -37,3 +38,58 @@ func ParsePowershellRegistryKeyChildren(r io.Reader) ([]RegistryKeyChild, error) return children, nil } + +// 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/registrykey.go b/providers/os/resources/registrykey.go index 912e62dddc..01ca2d05ce 100644 --- a/providers/os/resources/registrykey.go +++ b/providers/os/resources/registrykey.go @@ -14,7 +14,6 @@ import ( "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" ) @@ -40,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), }) @@ -78,7 +77,7 @@ func (k *mqlRegistrykey) getEntries() ([]registry.RegistryKeyItem, error) { } // 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), }) @@ -177,7 +176,7 @@ func (k *mqlRegistrykey) children() ([]interface{}, error) { } } 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), }) diff --git a/providers/os/resources/windows/registrykey_powershell.go b/providers/os/resources/windows/registrykey_powershell.go deleted file mode 100644 index b395058448..0000000000 --- a/providers/os/resources/windows/registrykey_powershell.go +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright (c) Mondoo, Inc. -// SPDX-License-Identifier: BUSL-1.1 - -package windows - -import ( - "fmt" -) - -// 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) -} From edcfb47c07e4a4bcdaf751d4f603c52f8746e7f8 Mon Sep 17 00:00:00 2001 From: Preslav Date: Thu, 6 Jun 2024 21:18:59 +0300 Subject: [PATCH 3/3] process review feedback. --- providers/os/detector/detector_win.go | 8 +++++--- providers/os/id/hostname/hostname.go | 13 +++++++------ providers/os/registry/registrykey_powershell.go | 12 ++---------- providers/os/resources/packages/packages.go | 1 - 4 files changed, 14 insertions(+), 20 deletions(-) diff --git a/providers/os/detector/detector_win.go b/providers/os/detector/detector_win.go index 3a20039aa7..7deb4bd023 100644 --- a/providers/os/detector/detector_win.go +++ b/providers/os/detector/detector_win.go @@ -17,6 +17,7 @@ func runtimeWindowsDetector(pf *inventory.Platform, conn shared.Connection) (boo data, err := win.GetWmiInformation(conn) if err != nil { log.Debug().Err(err).Msg("could not gather wmi information") + return false, nil } pf.Name = "windows" @@ -36,10 +37,10 @@ func runtimeWindowsDetector(pf *inventory.Platform, conn shared.Connection) (boo // 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 { + 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 @@ -67,6 +68,7 @@ func staticWindowsDetector(pf *inventory.Platform, conn shared.Connection) (bool 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 } diff --git a/providers/os/id/hostname/hostname.go b/providers/os/id/hostname/hostname.go index 026f65a2a3..313df2a108 100644 --- a/providers/os/id/hostname/hostname.go +++ b/providers/os/id/hostname/hostname.go @@ -89,12 +89,13 @@ func Hostname(conn shared.Connection, pf *inventory.Platform) (string, bool) { key, err := rh.GetRegistryItemValue(registry.System, "ControlSet001\\Control\\ComputerName\\ComputerName", "ComputerName") if err == nil { return key.Value.String, true - } else { - // we also can try ControlSet002 as a fallback - key, err := rh.GetRegistryItemValue(registry.System, "ControlSet002\\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 } } diff --git a/providers/os/registry/registrykey_powershell.go b/providers/os/registry/registrykey_powershell.go index 29a1e712c6..f8d3fd1f42 100644 --- a/providers/os/registry/registrykey_powershell.go +++ b/providers/os/registry/registrykey_powershell.go @@ -17,11 +17,7 @@ func ParsePowershellRegistryKeyItems(r io.Reader) ([]RegistryKeyItem, error) { var items []RegistryKeyItem err = json.Unmarshal(data, &items) - if err != nil { - return nil, err - } - - return items, nil + return items, err } func ParsePowershellRegistryKeyChildren(r io.Reader) ([]RegistryKeyChild, error) { @@ -32,11 +28,7 @@ func ParsePowershellRegistryKeyChildren(r io.Reader) ([]RegistryKeyChild, error) var children []RegistryKeyChild err = json.Unmarshal(data, &children) - if err != nil { - return nil, err - } - - return children, nil + return children, err } // RegistryKeyItem represents a registry key item and its properties diff --git a/providers/os/resources/packages/packages.go b/providers/os/resources/packages/packages.go index e249f38c60..020bf01849 100644 --- a/providers/os/resources/packages/packages.go +++ b/providers/os/resources/packages/packages.go @@ -109,7 +109,6 @@ func ResolveSystemPkgManager(conn shared.Connection) (OperatingSystemPkgManager, pm = &AlpinePkgManager{conn: conn, platform: asset.Platform} case asset.Platform.Name == "macos": // mac os family pm = &MacOSPkgManager{conn: conn} - // TODO: add a new manager here for fs conn that lists registry directly case asset.Platform.Name == "windows": pm = &WinPkgManager{conn: conn, platform: asset.Platform} case asset.Platform.Name == "scratch" || asset.Platform.Name == "coreos":