feat: add registry resource type for Windows - #1053
Conversation
7ac5f7b to
eb7a099
Compare
| return goss.AddResources(c.GlobalString("gossfile"), resource.InterfaceResourceName, c.Args(), newRuntimeConfigFromCLI(c)) | ||
| }, | ||
| }, | ||
| { |
There was a problem hiding this comment.
Just an FYI: #1060 will break this if/when it's merged. The fix is easy though: it's just an indentation change and Action: func(c *cli.Context) error { needs to become Action: func(_ context.Context, c *cli.Context) error {.
|
It was merged just a few hours ago, so you'll need to rebase or merge from master to resolve the issues. It should be a relatively straightforward merge conflict to resolve. |
Add a new registry resource type that validates Windows registry keys
natively using the golang.org/x/sys/windows/registry API. This replaces
the need to shell out to PowerShell for registry checks, providing
significant performance improvements (0.02s vs 44s for 250 checks).
Gossfile syntax:
registry:
HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProductName:
exists: true
value: "Windows Server 2025 Datacenter"
type: REG_SZ
For value names containing backslashes (e.g. HardenedPaths UNC entries),
use "::" as an explicit separator:
registry:
HKLM\...\HardenedPaths::\\*\NETLOGON:
exists: true
Supported hives: HKLM, HKCU, HKCR, HKU, HKCC.
Supported types: REG_SZ, REG_EXPAND_SZ, REG_DWORD, REG_QWORD,
REG_BINARY, REG_MULTI_SZ.
On non-Windows platforms, the resource returns an error indicating
it is only supported on Windows, following the NullPackage pattern.
eb7a099 to
0cc113f
Compare
|
Rebased on top of current
Verified locally in
Diff shape unchanged: 15 files, +711/-2 (was +726/-3; the delta is entirely from the tighter The dependency note on #1055 stays as-is — this PR doesn't touch any file that #1055 touches, so the two rebase independently, but #1055 is still recommended to land first per the original description. |
| type RegistryMap map[string]*Registry | ||
|
|
||
| func (r RegistryMap) AppendSysResource(sr string, sys *system.System, config util.Config) (*Registry, error) { | ||
| ctx := context.WithValue(context.Background(), idKey{}, sr) |
There was a problem hiding this comment.
This has put something on my to-do list: we should really be passing the context down from main(). I'll look into doing that later.
There was a problem hiding this comment.
#1090 is there as a reminder for me to fix up context handling.
kgaughan
left a comment
There was a problem hiding this comment.
Looks good! I've some suggestions regarding some tweaks to the error handling: I want to be able to turn on the err113 linter at some point.
| // ValueName="\\*\NETLOGON" | ||
| func parseRegistryKey(key string) (registryPathParts, error) { | ||
| if key == "" { | ||
| return registryPathParts{}, errors.New("empty registry key") |
There was a problem hiding this comment.
Could you lift this error out so it's defined once? Something like this:
var errInvalidRegistryKey = errors.New("invalid registry key")
// ...
if key == "" {
return registryPathParts{}, fmt.Errorf("%w: empty key", errInvalidRegistryKey)|
|
||
| parts := strings.SplitN(key, `\`, 2) | ||
| if len(parts) < 2 { | ||
| return registryPathParts{}, errors.New("invalid registry key: missing subkey path") |
There was a problem hiding this comment.
Ditto: could you lift this error out so it's defined once? Going off the above, this would become:
| return registryPathParts{}, errors.New("invalid registry key: missing subkey path") | |
| return registryPathParts{}, fmt.Errorf("%w: missing subkey path", errInvalidRegistryKey) |
| switch hive { | ||
| case "HKLM", "HKCU", "HKCR", "HKU", "HKCC": | ||
| default: | ||
| return registryPathParts{}, errors.New("invalid registry hive: " + parts[0]) |
There was a problem hiding this comment.
Same with this, though I'd suggest something like this:
var errInvalidRegistryHive = errors.New("invalid registry hive")
// ...
default:
return registryPathParts{}, fmt.Errorf("%w: %s", errInvalidRegistryHive, parts[0])|
|
||
| rest := parts[1] | ||
| if rest == "" { | ||
| return registryPathParts{}, errors.New("invalid registry key: empty subkey path") |
There was a problem hiding this comment.
Same again:
| return registryPathParts{}, errors.New("invalid registry key: empty subkey path") | |
| return registryPathParts{}, fmt.Errorf("%w: empty subkey path", errInvalidRegistryKey) |
| type RegistryMap map[string]*Registry | ||
|
|
||
| func (r RegistryMap) AppendSysResource(sr string, sys *system.System, config util.Config) (*Registry, error) { | ||
| ctx := context.WithValue(context.Background(), idKey{}, sr) |
There was a problem hiding this comment.
#1090 is there as a reminder for me to fix up context handling.
Summary
Adds a new
registryresource type that validates Windows registry keys natively using thegolang.org/x/sys/windows/registryAPI. This replaces the need to shell out to PowerShell for registry checks.Closes #616
Motivation
The ansible-lockdown Windows CIS audit repos (Windows 2016/2019/2025) currently rely entirely on the
commandresource shelling out to PowerShell for ~300+ registry checks per benchmark. This is slow (~180ms per check due to PowerShell process spawn) and verbose.With the native
registryresource, the same checks run in milliseconds via direct Windows API calls.Benchmark results (250 identical checks):
registryresourcecommand+ PowerShellGossfile syntax
For value names containing backslashes (e.g. HardenedPaths UNC entries), use
::as an explicit separator:Details
QUERY_VALUEaccess (read-only, least privilege)NullPackagepatternFiles changed
New files:
system/registry.go-- Registry interface + key path parsersystem/registry_test.go-- 17 unit tests for the parsersystem/registry_notwindows.go-- Non-Windows stubsystem/registry_windows.go-- Windows implementationresource/registry.go-- Resource type definitionintegration-tests/goss/windows/tests/registry.goss.yaml-- Integration testsdocs/windows-parity-progress.md-- Progress trackerModified files:
system/system.go-- AddedNewRegistryfactoryresource/resource_list_genny.go+resource/resource_list.go-- Added RegistryMapgoss_config.go-- Wired into configadd.go-- Added to AddResource switchcmd/goss/goss.go-- Added CLI subcommanddocs/schema.yaml-- Added schema definitiondocs/platforms.md-- Updated platform matrixTest plan
go vet ./...passesgo test ./...passes (17 new parser tests + all existing)GOOS=windows GOARCH=amd64 go buildcross-compiles successfullygosec ./system/ ./resource/-- zero findings in new codecommandresource still works