Skip to content

Commit

Permalink
test(scripts/install.ps1): check Windows ACLs
Browse files Browse the repository at this point in the history
  • Loading branch information
swiatekm committed Mar 12, 2024
1 parent 287f96f commit c30c232
Show file tree
Hide file tree
Showing 2 changed files with 109 additions and 13 deletions.
99 changes: 92 additions & 7 deletions pkg/scripts_test/check_windows.go
Original file line number Diff line number Diff line change
@@ -1,16 +1,33 @@
//go:build windows

package sumologic_scripts_tests

import (
"os"
"path/filepath"
"strings"
"syscall"
"testing"
"unsafe"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"golang.org/x/sys/windows"
)

var (
modAdvapi32 = syscall.NewLazyDLL("advapi32.dll")
procGetExplicitEntriesFromACL = modAdvapi32.NewProc("GetExplicitEntriesFromAclW")
)

// A Windows ACL record. Windows represents these as windows.EXPLICIT_ACCESS, which comes with an impractical
// representation of trustees. Instead, we just use a string representation of SIDs.
type ACLRecord struct {
SID string
AccessPermissions windows.ACCESS_MASK
AccessMode windows.ACCESS_MODE
}

func checkAbortedDueToNoToken(c check) {
require.Greater(c.test, len(c.output), 1)
require.Greater(c.test, len(c.errorOutput), 1)
Expand Down Expand Up @@ -69,23 +86,22 @@ func checkConfigFilesOwnershipAndPermissions(ownerSid string) func(c check) {
paths, err := filepath.Glob(glob)
require.NoError(c.test, err)
for _, path := range paths {
var permissions uint32
var aclRecords []ACLRecord
info, err := os.Stat(path)
require.NoError(c.test, err)
if info.IsDir() {
if path == opampDPath {
permissions = opampDPermissions
aclRecords = opampDPermissions
} else {
permissions = configPathDirPermissions
aclRecords = configPathDirPermissions
}
} else {
permissions = configPathFilePermissions
aclRecords = configPathFilePermissions
}
PathHasPermissions(c.test, path, permissions)
PathHasOwner(c.test, configPath, ownerSid)
PathHasWindowsACLs(c.test, path, aclRecords)
PathHasOwner(c.test, path, ownerSid)
}
}
PathHasPermissions(c.test, configPath, configPathFilePermissions)
}
}

Expand All @@ -103,3 +119,72 @@ func PathHasOwner(t *testing.T, path string, ownerSID string) {

require.Equal(t, ownerSID, owner.String(), "%s should be owned by user '%s'", path, ownerSID)
}

func PathHasWindowsACLs(t *testing.T, path string, expectedACLs []ACLRecord) {
securityDescriptor, err := windows.GetNamedSecurityInfo(
path,
windows.SE_FILE_OBJECT,
windows.DACL_SECURITY_INFORMATION,
)
require.NoError(t, err)

// get the ACL entries
acl, _, err := securityDescriptor.DACL()
require.NoError(t, err)
require.NotNil(t, acl)
entries, err := GetExplicitEntriesFromACL(acl)
require.NoError(t, err)
aclRecords := []ACLRecord{}
for _, entry := range entries {
aclRecord := ExplicitEntryToACLRecord(entry)
if aclRecord != nil {
aclRecords = append(aclRecords, *aclRecord)
}
}
assert.Equal(t, expectedACLs, aclRecords, "invalid ACLs for %s", path)
}

// GetExplicitEntriesFromACL gets a list of explicit entries from an ACL
// This doesn't exist in golang.org/x/sys/windows so we need to define it ourselves.
func GetExplicitEntriesFromACL(acl *windows.ACL) ([]windows.EXPLICIT_ACCESS, error) {
var pExplicitEntries *windows.EXPLICIT_ACCESS
var explicitEntriesSize uint64
// Get dacl
r1, _, err := procGetExplicitEntriesFromACL.Call(
uintptr(unsafe.Pointer(acl)),
uintptr(unsafe.Pointer(&explicitEntriesSize)),
uintptr(unsafe.Pointer(&pExplicitEntries)),
)
if r1 != 0 {
return nil, err
}
if pExplicitEntries == nil {
return []windows.EXPLICIT_ACCESS{}, nil
}

// convert the pointer we got from Windows to a Go slice by doing some gnarly looking pointer arithmetic
explicitEntries := make([]windows.EXPLICIT_ACCESS, explicitEntriesSize)
for i := 0; i < int(explicitEntriesSize); i++ {
elementPtr := unsafe.Pointer(
uintptr(unsafe.Pointer(pExplicitEntries)) +
uintptr(i)*unsafe.Sizeof(pExplicitEntries),
)
explicitEntries[i] = *(*windows.EXPLICIT_ACCESS)(elementPtr)
}
return explicitEntries, nil
}

// ExplicitEntryToACLRecord converts a windows.EXPLICIT_ACCESS to a ACLRecord. If the trustee type is not SID,
// we return nil.
func ExplicitEntryToACLRecord(entry windows.EXPLICIT_ACCESS) *ACLRecord {
trustee := entry.Trustee
if trustee.TrusteeType != windows.TRUSTEE_IS_SID {
return nil
}
trusteeSid := (*windows.SID)(unsafe.Pointer(entry.Trustee.TrusteeValue))
return &ACLRecord{
SID: trusteeSid.String(),
AccessMode: entry.AccessMode,
AccessPermissions: entry.AccessPermissions,
}
}
23 changes: 17 additions & 6 deletions pkg/scripts_test/consts_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

package sumologic_scripts_tests

import "golang.org/x/sys/windows"

const (
// See: https://learn.microsoft.com/en-us/windows-server/identity/ad-ds/manage/understand-security-identifiers
localSystemSID string = "S-1-5-18"
Expand All @@ -23,10 +25,19 @@ const (
installTokenEnv string = "SUMOLOGIC_INSTALLATION_TOKEN"
apiBaseURL string = "https://open-collectors.sumologic.com"

commonConfigPathFilePermissions uint32 = 0777
configPathDirPermissions uint32 = 0777
configPathFilePermissions uint32 = 0666
confDPathFilePermissions uint32 = 0777
etcPathPermissions uint32 = 0777
opampDPermissions uint32 = 0777
allFilePermissions = windows.STANDARD_RIGHTS_ALL | windows.FILE_GENERIC_READ | windows.FILE_GENERIC_WRITE | windows.FILE_GENERIC_EXECUTE | 0x00000040
)

var (
pathPermissions = []ACLRecord{
{
SID: localSystemSID,
AccessPermissions: allFilePermissions,
AccessMode: windows.GRANT_ACCESS,
},
}
filePermissions = []ACLRecord{}
opampDPermissions = pathPermissions
configPathDirPermissions = pathPermissions
configPathFilePermissions = filePermissions
)

0 comments on commit c30c232

Please sign in to comment.