Skip to content

Commit

Permalink
Updates to file permissions commit
Browse files Browse the repository at this point in the history
  • Loading branch information
claudiubelu committed Apr 17, 2024
1 parent 1b6ed5a commit ee104b3
Show file tree
Hide file tree
Showing 3 changed files with 69 additions and 169 deletions.
2 changes: 1 addition & 1 deletion pkg/volume/util/util_others.go
Expand Up @@ -2,7 +2,7 @@
// +build !windows

/*
Copyright 2023 The Kubernetes Authors.
Copyright 2024 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
Expand Down
197 changes: 38 additions & 159 deletions pkg/volume/util/util_windows.go
Expand Up @@ -2,7 +2,7 @@
// +build windows

/*
Copyright 2023 The Kubernetes Authors.
Copyright 2024 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
Expand All @@ -21,7 +21,6 @@ package util

import (
"os"
"unsafe"

"golang.org/x/sys/windows"
)
Expand All @@ -37,47 +36,13 @@ const (
// execute = read data | file execute
EXECUTE_PERMISSIONS = 0x0001 | 0x0020

// Accounts
EVERYONE = "Everyone"
NONE = "None"

// Well-known SID Strings
// https://support.microsoft.com/en-us/help/243330/well-known-security-identifiers-in-windows-operating-systems
CREATOR_SID_STR = "S-1-3-0"
GROUP_SID_STR = "S-1-3-1"
CREATOR_SID_STR = "S-1-3-2"
GROUP_SID_STR = "S-1-3-3"
EVERYONE_SID_STR = "S-1-1-0"

// Constants for AceType
// https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-ace_header
ACCESS_ALLOWED_ACE_TYPE = 0
ACCESS_DENIED_ACE_TYPE = 1
)

var (
advapi32 = windows.MustLoadDLL("advapi32.dll")
procSetEntriesInAclW = advapi32.MustFindProc("SetEntriesInAclW")
procGetAce = advapi32.MustFindProc("GetAce")
procGetNamedSecurityInfoW = advapi32.MustFindProc("GetNamedSecurityInfoW")
)

// https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-acl
type ACL struct {
aclRevision byte
sbz1 byte
aclSize uint16
aceCount uint16
sbz2 uint16
}

// https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-access_allowed_ace
type accessAllowedAce struct {
aceType byte
aceFlags byte
aceSize uint16
mask windows.ACCESS_MASK
sid windows.SID
}

// Change the permissions of the specified file. Only the nine
// least-significant bytes are used, allowing access by the file's owner, the
// file's group, and everyone else to be explicitly controlled.
Expand All @@ -98,52 +63,23 @@ func Chmod(name string, fileMode os.FileMode) error {
mode := windows.ACCESS_MASK(fileMode)
return apply(
name,
true,
false,
grantSid(((mode&0700)<<23)|((mode&0200)<<9), creatorOwnerSID),
grantSid(((mode&0070)<<26)|((mode&0020)<<12), creatorGroupSID),
grantSid(((mode&0007)<<29)|((mode&0002)<<15), everyoneSID),
)
}

// apply the provided access control entries to a file. If the replace
// parameter is true, existing entries will be overwritten. If the inherit
// parameter is true, the file will inherit ACEs from its parent.
func apply(name string, replace, inherit bool, entries ...windows.EXPLICIT_ACCESS) error {
var oldAcl windows.Handle
if !replace {
var secDesc windows.Handle
getNamedSecurityInfo(
name,
windows.SE_FILE_OBJECT,
windows.DACL_SECURITY_INFORMATION,
nil,
nil,
&oldAcl,
nil,
&secDesc,
)
defer windows.LocalFree(secDesc)
}
var acl *windows.ACL
if err := setEntriesInAcl(
entries,
oldAcl,
&acl,
); err != nil {
// apply the provided access control entries to a file.
func apply(name string, entries ...windows.EXPLICIT_ACCESS) error {
acl, err := windows.ACLFromEntries(entries, nil)
if err != nil {
return err
}
defer windows.LocalFree((windows.Handle)(unsafe.Pointer(acl)))
var secInfo windows.SECURITY_INFORMATION
if !inherit {
secInfo = windows.PROTECTED_DACL_SECURITY_INFORMATION
} else {
secInfo = windows.UNPROTECTED_DACL_SECURITY_INFORMATION
}

return windows.SetNamedSecurityInfo(
name,
windows.SE_FILE_OBJECT,
windows.DACL_SECURITY_INFORMATION|secInfo,
windows.DACL_SECURITY_INFORMATION|windows.PROTECTED_DACL_SECURITY_INFORMATION,
nil,
nil,
acl,
Expand All @@ -153,70 +89,68 @@ func apply(name string, replace, inherit bool, entries ...windows.EXPLICIT_ACCES

// GetFileMode returns the mode of the given file.
func GetFileMode(file string) (os.FileMode, error) {
var (
daclHandle, secDesc windows.Handle
owner, group *windows.SID
)
err := getNamedSecurityInfo(
descriptor, err := windows.GetNamedSecurityInfo(
file,
windows.SE_FILE_OBJECT,
windows.OWNER_SECURITY_INFORMATION|windows.GROUP_SECURITY_INFORMATION|windows.DACL_SECURITY_INFORMATION|windows.UNPROTECTED_DACL_SECURITY_INFORMATION,
&owner,
&group,
&daclHandle,
nil,
&secDesc,
)
if err != nil {
return 0, err
}

defer windows.LocalFree(secDesc)
dacl, _, err := descriptor.DACL()
if err != nil {
return 0, err
}

owner, _, err := descriptor.Owner()
if err != nil {
return 0, err
}

group, _, err := descriptor.Group()
if err != nil {
return 0, err
}

everyone, err := windows.StringToSid(EVERYONE_SID_STR)
if err != nil {
return 0, err
}

dacl := (*ACL)(unsafe.Pointer(daclHandle))
aces, err := getACEs(dacl)
aces, err := windows.GetEntriesFromACL(dacl)
if err != nil {
return 0, err
}

allowMode := 0
denyMode := 0
for _, ace := range aces {
accountName, _, _, err := ace.sid.LookupAccount("")
if err != nil {
return 0, err
}

// LookupAccount may return an empty string.
if accountName == "" {
accountName = NONE
}

perms := 0
if (ace.mask & READ_PERMISSIONS) == READ_PERMISSIONS {
if (ace.Mask & READ_PERMISSIONS) == READ_PERMISSIONS {
perms = 0x4
}
if (ace.mask & WRITE_PERMISSIONS) == WRITE_PERMISSIONS {
if (ace.Mask & WRITE_PERMISSIONS) == WRITE_PERMISSIONS {
perms |= 0x2
}
if (ace.mask & EXECUTE_PERMISSIONS) == EXECUTE_PERMISSIONS {
if (ace.Mask & EXECUTE_PERMISSIONS) == EXECUTE_PERMISSIONS {
perms |= 0x1
}

mode := 0
if owner.Equals(&ace.sid) {
if owner.Equals(&ace.Sid) {
mode = perms << 6
}
if group.Equals(&ace.sid) {
if group.Equals(&ace.Sid) {
mode |= perms << 3
}
if accountName == EVERYONE {
if everyone.Equals(&ace.Sid) {
mode |= perms
}

if ace.aceType == ACCESS_ALLOWED_ACE_TYPE {
if ace.Header.AceType == windows.ACCESS_ALLOWED_ACE_TYPE {
allowMode |= mode
} else if ace.aceType == ACCESS_DENIED_ACE_TYPE {
} else if ace.Header.AceType == windows.ACCESS_DENIED_ACE_TYPE {
denyMode |= mode
}
}
Expand All @@ -225,61 +159,6 @@ func GetFileMode(file string) (os.FileMode, error) {
return os.FileMode(allowMode & ^denyMode), nil
}

// https://docs.microsoft.com/en-us/windows/win32/api/aclapi/nf-aclapi-setentriesinaclw
func setEntriesInAcl(entries []windows.EXPLICIT_ACCESS, oldAcl windows.Handle, newAcl **windows.ACL) error {
ret, _, _ := procSetEntriesInAclW.Call(
uintptr(len(entries)),
uintptr(unsafe.Pointer(&entries[0])),
uintptr(oldAcl),
uintptr(unsafe.Pointer(newAcl)),
)
if ret != 0 {
return windows.Errno(ret)
}
return nil
}

// https://learn.microsoft.com/en-us/windows/win32/api/securitybaseapi/nf-securitybaseapi-getace
func getACEs(acl *ACL) ([]*accessAllowedAce, error) {
aces := make([]*accessAllowedAce, acl.aceCount)
var ace *accessAllowedAce

for i := uint16(0); i < acl.aceCount; i++ {
ret, _, _ := procGetAce.Call(
uintptr(unsafe.Pointer(acl)),
uintptr(i),
uintptr(unsafe.Pointer(&ace)),
)
if ret == 0 {
return []*accessAllowedAce{}, windows.GetLastError()
}

aceBytes := make([]byte, ace.aceSize)
copy(aceBytes, (*[(1 << 31) - 1]byte)(unsafe.Pointer(ace))[:len(aceBytes)])
aces[i] = (*accessAllowedAce)(unsafe.Pointer(&aceBytes[0]))
}

return aces, nil
}

// https://docs.microsoft.com/en-us/windows/win32/api/aclapi/nf-aclapi-getnamedsecurityinfow
func getNamedSecurityInfo(objectName string, objectType windows.SE_OBJECT_TYPE, secInfo windows.SECURITY_INFORMATION, owner, group **windows.SID, dacl, sacl, secDesc *windows.Handle) error {
ret, _, _ := procGetNamedSecurityInfoW.Call(
uintptr(unsafe.Pointer(windows.StringToUTF16Ptr(objectName))),
uintptr(objectType),
uintptr(secInfo),
uintptr(unsafe.Pointer(owner)),
uintptr(unsafe.Pointer(group)),
uintptr(unsafe.Pointer(dacl)),
uintptr(unsafe.Pointer(sacl)),
uintptr(unsafe.Pointer(secDesc)),
)
if ret != 0 {
return windows.Errno(ret)
}
return nil
}

// Create an EXPLICIT_ACCESS instance granting permissions to the provided SID.
func grantSid(accessPermissions windows.ACCESS_MASK, sid *windows.SID) windows.EXPLICIT_ACCESS {
return accessSid(accessPermissions, windows.GRANT_ACCESS, sid)
Expand Down
39 changes: 30 additions & 9 deletions pkg/volume/util/util_windows_test.go
Expand Up @@ -2,7 +2,7 @@
// +build windows

/*
Copyright 2023 The Kubernetes Authors.
Copyright 2024 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
Expand All @@ -20,7 +20,10 @@ limitations under the License.
package util

import (
"bytes"
"fmt"
"os"
"os/exec"
"strings"
"testing"

Expand All @@ -29,17 +32,18 @@ import (
)

func TestWindowsFileMode(t *testing.T) {
// Create a temp file and set read only permissions on it.
// Create a temp file, and later on remove all permissions from it.
f, err := os.CreateTemp("", "permissions_test")
require.NoError(t, err)

// Write a sample string into the file, and then expect to be able to read it later.
// Write a sample string into the file.
_, err = f.WriteString("hello!")
require.NoError(t, err)

f.Close()
defer os.Remove(f.Name())

// Remove all permissions from the file.
err = Chmod(f.Name(), 0000)
require.NoError(t, err)

Expand All @@ -49,14 +53,32 @@ func TestWindowsFileMode(t *testing.T) {
assert.Equal(t, 0000, int(mode))

// Assert that we cannot read the file, as we don't have read permissions.
_, err = os.Open(f.Name())
expectedErrMsg := "Access is denied."
if err == nil || !strings.Contains(err.Error(), expectedErrMsg) {
// There shouldn't be any ACEs on the file, so os.Open should end up with a "Access is denied." error.
// However, that doesn't happen. Interestingly, it does happen in other languages, or even Powershell.
// We test using Powershell instead.
cmd := exec.Command("powershell.exe", "-NonInteractive", "cat", f.Name())
var errOut bytes.Buffer
cmd.Stderr = &errOut
err = cmd.Run()
expectedErrMsg := fmt.Sprintf("Access to the path '%s' is denied.", f.Name())
if err == nil || !strings.Contains(errOut.String(), expectedErrMsg) {
t.Fatalf("Unexpected error message while opening the file for reading. Got: %v, expected: %v", err, expectedErrMsg)
}

// We can still open the file in golang and read from it.
f, err = os.Open(f.Name())
require.NoError(t, err)

bytes := make([]byte, 64)
n, err := f.Read(bytes)
require.NoError(t, err)
assert.Equal(t, "hello!", string(bytes[:n]))

f.Close()

// Assert that we cannot write in the file, as we do not have write permissions.
_, err = os.Create(f.Name())
expectedErrMsg = "Access is denied."
if err == nil || !strings.Contains(err.Error(), expectedErrMsg) {
t.Fatalf("Unexpected error message while opening the file for writing. Got: %v, expected: %v", err, expectedErrMsg)
}
Expand All @@ -83,10 +105,9 @@ func TestWindowsFileMode(t *testing.T) {
f, err = os.Open(f.Name())
require.NoError(t, err)

bytes := make([]byte, len(expectedContent))
_, err = f.Read(bytes)
n, err = f.Read(bytes)
require.NoError(t, err)
assert.Equal(t, expectedContent, string(bytes))
assert.Equal(t, expectedContent, string(bytes[:n]))

f.Close()

Expand Down

0 comments on commit ee104b3

Please sign in to comment.