go.mondoo.com/cnquery@v0.0.0-20231005093811-59568235f6ea/providers/os/resources/registrykey.go (about)

     1  // Copyright (c) Mondoo, Inc.
     2  // SPDX-License-Identifier: BUSL-1.1
     3  
     4  package resources
     5  
     6  import (
     7  	"errors"
     8  	"runtime"
     9  	"strings"
    10  
    11  	"go.mondoo.com/cnquery/llx"
    12  	"go.mondoo.com/cnquery/providers-sdk/v1/plugin"
    13  	"go.mondoo.com/cnquery/providers/os/resources/powershell"
    14  	"go.mondoo.com/cnquery/providers/os/resources/windows"
    15  	"go.mondoo.com/ranger-rpc/codes"
    16  	"go.mondoo.com/ranger-rpc/status"
    17  )
    18  
    19  func (k *mqlRegistrykey) id() (string, error) {
    20  	return k.Path.Data, nil
    21  }
    22  
    23  func (k *mqlRegistrykey) exists() (bool, error) {
    24  	// if we are running locally on windows, we can use native api
    25  	if runtime.GOOS == "windows" {
    26  		items, err := windows.GetNativeRegistryKeyItems(k.Path.Data)
    27  		if err == nil && len(items) > 0 {
    28  			return true, nil
    29  		}
    30  		std, ok := status.FromError(err)
    31  		if ok && std.Code() == codes.NotFound {
    32  			return false, nil
    33  		}
    34  		if err != nil {
    35  			return false, err
    36  		}
    37  	}
    38  
    39  	script := powershell.Encode(windows.GetRegistryKeyItemScript(k.Path.Data))
    40  	o, err := CreateResource(k.MqlRuntime, "command", map[string]*llx.RawData{
    41  		"command": llx.StringData(script),
    42  	})
    43  	if err != nil {
    44  		return false, err
    45  	}
    46  	cmd := o.(*mqlCommand)
    47  
    48  	exit := cmd.GetExitcode()
    49  	if exit.Error != nil {
    50  		return false, exit.Error
    51  	}
    52  	if exit.Data != 0 {
    53  		stderr := cmd.GetStderr()
    54  		// this would be an expected error and would ensure that we do not throw an error on windows systems
    55  		// TODO: revisit how this is handled for non-english systems
    56  		if err == nil && (strings.Contains(stderr.Data, "not exist") || strings.Contains(stderr.Data, "ObjectNotFound")) {
    57  			return false, nil
    58  		}
    59  
    60  		return false, errors.New("could not retrieve registry key")
    61  	}
    62  	return true, nil
    63  }
    64  
    65  // GetEntries returns a list of registry key property resources
    66  func (k *mqlRegistrykey) getEntries() ([]windows.RegistryKeyItem, error) {
    67  	// if we are running locally on windows, we can use native api
    68  	if runtime.GOOS == "windows" {
    69  		return windows.GetNativeRegistryKeyItems(k.Path.Data)
    70  	}
    71  
    72  	// parse the output of the powershell script
    73  	script := powershell.Encode(windows.GetRegistryKeyItemScript(k.Path.Data))
    74  	o, err := CreateResource(k.MqlRuntime, "command", map[string]*llx.RawData{
    75  		"command": llx.StringData(script),
    76  	})
    77  	if err != nil {
    78  		return nil, err
    79  	}
    80  	cmd := o.(*mqlCommand)
    81  	exit := cmd.GetExitcode()
    82  	if exit.Error != nil {
    83  		return nil, exit.Error
    84  	}
    85  	if exit.Data != 0 {
    86  		return nil, errors.New("could not retrieve registry key")
    87  	}
    88  
    89  	stdout := cmd.GetStdout()
    90  	if stdout.Error != nil {
    91  		return nil, stdout.Error
    92  	}
    93  
    94  	return windows.ParsePowershellRegistryKeyItems(strings.NewReader(stdout.Data))
    95  }
    96  
    97  // Deprecated: properties returns the properties of a registry key
    98  // This function is deprecated and will be removed in a future release
    99  func (k *mqlRegistrykey) properties() (map[string]interface{}, error) {
   100  	res := map[string]interface{}{}
   101  
   102  	entries, err := k.getEntries()
   103  	if err != nil {
   104  		return nil, err
   105  	}
   106  
   107  	for i := range entries {
   108  		rkey := entries[i]
   109  		res[rkey.Key] = rkey.String()
   110  	}
   111  
   112  	return res, nil
   113  }
   114  
   115  // items returns a list of registry key property resources
   116  func (k *mqlRegistrykey) items() ([]interface{}, error) {
   117  	entries, err := k.getEntries()
   118  	if err != nil {
   119  		return nil, err
   120  	}
   121  
   122  	// create MQL mount entry resources for each mount
   123  	items := make([]interface{}, len(entries))
   124  	for i, entry := range entries {
   125  		o, err := CreateResource(k.MqlRuntime, "registrykey.property", map[string]*llx.RawData{
   126  			"path":   llx.StringData(k.Path.Data),
   127  			"name":   llx.StringData(entry.Key),
   128  			"value":  llx.StringData(entry.String()),
   129  			"type":   llx.StringData(entry.Kind()),
   130  			"data":   llx.DictData(entry.GetRawValue()),
   131  			"exists": llx.BoolData(true),
   132  		})
   133  		if err != nil {
   134  			return nil, err
   135  		}
   136  
   137  		items[i] = o.(*mqlRegistrykeyProperty)
   138  	}
   139  
   140  	return items, nil
   141  }
   142  
   143  func (k *mqlRegistrykey) children() ([]interface{}, error) {
   144  	res := []interface{}{}
   145  
   146  	var children []windows.RegistryKeyChild
   147  	if runtime.GOOS == "windows" {
   148  		var err error
   149  		children, err = windows.GetNativeRegistryKeyChildren(k.Path.Data)
   150  		if err != nil {
   151  			return nil, err
   152  		}
   153  	} else {
   154  		// parse powershell script
   155  		script := powershell.Encode(windows.GetRegistryKeyChildItemsScript(k.Path.Data))
   156  		o, err := CreateResource(k.MqlRuntime, "command", map[string]*llx.RawData{
   157  			"command": llx.StringData(script),
   158  		})
   159  		if err != nil {
   160  			return res, err
   161  		}
   162  		cmd := o.(*mqlCommand)
   163  		exitcode := cmd.GetExitcode()
   164  		if exitcode.Error != nil {
   165  			return nil, exitcode.Error
   166  		}
   167  		if exitcode.Data != 0 {
   168  			return nil, errors.New("could not retrieve registry key")
   169  		}
   170  
   171  		stdout := cmd.GetStdout()
   172  		if stdout.Error != nil {
   173  			return res, stdout.Error
   174  		}
   175  		children, err = windows.ParsePowershellRegistryKeyChildren(strings.NewReader(stdout.Data))
   176  		if err != nil {
   177  			return nil, err
   178  		}
   179  	}
   180  
   181  	for i := range children {
   182  		child := children[i]
   183  		res = append(res, child.Path)
   184  	}
   185  
   186  	return res, nil
   187  }
   188  
   189  func (p *mqlRegistrykeyProperty) id() (string, error) {
   190  	return p.Path.Data + " - " + p.Name.Data, nil
   191  }
   192  
   193  func initRegistrykeyProperty(runtime *plugin.Runtime, args map[string]*llx.RawData) (map[string]*llx.RawData, plugin.Resource, error) {
   194  	path := args["path"]
   195  	if path == nil {
   196  		return args, nil, nil
   197  	}
   198  
   199  	name := args["name"]
   200  	if name == nil {
   201  		return args, nil, nil
   202  	}
   203  
   204  	// if the data is set, we do not need to fetch the data first
   205  	dataRaw := args["data"]
   206  	if dataRaw != nil {
   207  		return args, nil, nil
   208  	}
   209  
   210  	// create resource here, but do not use it yet
   211  	obj, err := CreateResource(runtime, "registrykey", map[string]*llx.RawData{
   212  		"path": path,
   213  	})
   214  	if err != nil {
   215  		return nil, nil, err
   216  	}
   217  	key := obj.(*mqlRegistrykey)
   218  
   219  	exists := key.GetExists()
   220  	if err != nil {
   221  		return nil, nil, err
   222  	}
   223  
   224  	// set default values
   225  	args["exists"] = llx.BoolFalse
   226  	// NOTE: we do not set a value here so that MQL throws an error when a user try to gather the data for a
   227  	// non-existing key
   228  
   229  	// path exists
   230  	if exists.Data {
   231  		items := key.GetItems()
   232  		if items.Error != nil {
   233  			return nil, nil, items.Error
   234  		}
   235  
   236  		for i := range items.Data {
   237  			property := items.Data[i].(*mqlRegistrykeyProperty)
   238  			iname := property.GetName()
   239  			if iname.Error != nil {
   240  				return nil, nil, iname.Error
   241  			}
   242  
   243  			// property exists, return it
   244  			if strings.EqualFold(iname.Data, name.Value.(string)) {
   245  				return nil, property, nil
   246  			}
   247  		}
   248  	}
   249  	return args, nil, nil
   250  }
   251  
   252  func (p *mqlRegistrykeyProperty) exists() (bool, error) {
   253  	// NOTE: will not be called since it will always be set in init
   254  	return false, errors.New("could not determine if the property exists")
   255  }
   256  
   257  func (p *mqlRegistrykeyProperty) compute_type() (string, error) {
   258  	// NOTE: if we reach here the value has not been set in init, therefore we return an error
   259  	return "", errors.New("requested property does not exist")
   260  }
   261  
   262  func (p *mqlRegistrykeyProperty) data() (interface{}, error) {
   263  	// NOTE: if we reach here the value has not been set in init, therefore we return an error
   264  	return "", errors.New("requested property does not exist")
   265  }
   266  
   267  func (p *mqlRegistrykeyProperty) value() (string, error) {
   268  	// NOTE: if we reach here the value has not been set in init, therefore we return an error
   269  	return "", errors.New("requested property does not exist")
   270  }