Add test cases for security descriptors
This commit is contained in:
parent
70cf8e3788
commit
90916f53de
3 changed files with 226 additions and 0 deletions
60
internal/fs/sd_windows_test.go
Normal file
60
internal/fs/sd_windows_test.go
Normal file
|
@ -0,0 +1,60 @@
|
|||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package fs
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/restic/restic/internal/errors"
|
||||
"github.com/restic/restic/internal/test"
|
||||
)
|
||||
|
||||
func Test_SetGetFileSecurityDescriptors(t *testing.T) {
|
||||
tempDir := t.TempDir()
|
||||
testfilePath := filepath.Join(tempDir, "testfile.txt")
|
||||
// create temp file
|
||||
testfile, err := os.Create(testfilePath)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create temporary file: %s", err)
|
||||
}
|
||||
defer func() {
|
||||
err := testfile.Close()
|
||||
if err != nil {
|
||||
t.Logf("Error closing file %s: %v\n", testfilePath, err)
|
||||
}
|
||||
}()
|
||||
|
||||
testSecurityDescriptors(t, TestFileSDs, testfilePath)
|
||||
}
|
||||
|
||||
func Test_SetGetFolderSecurityDescriptors(t *testing.T) {
|
||||
tempDir := t.TempDir()
|
||||
testfolderPath := filepath.Join(tempDir, "testfolder")
|
||||
// create temp folder
|
||||
err := os.Mkdir(testfolderPath, os.ModeDir)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create temporary file: %s", err)
|
||||
}
|
||||
|
||||
testSecurityDescriptors(t, TestDirSDs, testfolderPath)
|
||||
}
|
||||
|
||||
func testSecurityDescriptors(t *testing.T, testSDs []string, testPath string) {
|
||||
for _, testSD := range testSDs {
|
||||
sdInputBytes, err := base64.StdEncoding.DecodeString(testSD)
|
||||
test.OK(t, errors.Wrapf(err, "Error decoding SD: %s", testPath))
|
||||
|
||||
err = SetSecurityDescriptor(testPath, &sdInputBytes)
|
||||
test.OK(t, errors.Wrapf(err, "Error setting file security descriptor for: %s", testPath))
|
||||
|
||||
var sdOutputBytes *[]byte
|
||||
sdOutputBytes, err = GetSecurityDescriptor(testPath)
|
||||
test.OK(t, errors.Wrapf(err, "Error getting file security descriptor for: %s", testPath))
|
||||
|
||||
CompareSecurityDescriptors(t, testPath, sdInputBytes, *sdOutputBytes)
|
||||
}
|
||||
}
|
109
internal/fs/sd_windows_test_helpers.go
Normal file
109
internal/fs/sd_windows_test_helpers.go
Normal file
|
@ -0,0 +1,109 @@
|
|||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package fs
|
||||
|
||||
import (
|
||||
"os/user"
|
||||
"testing"
|
||||
|
||||
"github.com/restic/restic/internal/errors"
|
||||
"github.com/restic/restic/internal/test"
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
var (
|
||||
TestFileSDs = []string{"AQAUvBQAAAAwAAAAAAAAAEwAAAABBQAAAAAABRUAAACIn1iuVqCC6sy9JqvqAwAAAQUAAAAAAAUVAAAAiJ9YrlaggurMvSarAQIAAAIAfAAEAAAAAAAkAKkAEgABBQAAAAAABRUAAACIn1iuVqCC6sy9JqvtAwAAABAUAP8BHwABAQAAAAAABRIAAAAAEBgA/wEfAAECAAAAAAAFIAAAACACAAAAECQA/wEfAAEFAAAAAAAFFQAAAIifWK5WoILqzL0mq+oDAAA=",
|
||||
"AQAUvBQAAAAwAAAAAAAAAEwAAAABBQAAAAAABRUAAACIn1iuVqCC6sy9JqvqAwAAAQUAAAAAAAUVAAAAiJ9YrlaggurMvSarAQIAAAIAyAAHAAAAAAAUAKkAEgABAQAAAAAABQcAAAAAABQAiQASAAEBAAAAAAAFBwAAAAAAJACpABIAAQUAAAAAAAUVAAAAiJ9YrlaggurMvSar7QMAAAAAJAC/ARMAAQUAAAAAAAUVAAAAiJ9YrlaggurMvSar6gMAAAAAFAD/AR8AAQEAAAAAAAUSAAAAAAAYAP8BHwABAgAAAAAABSAAAAAgAgAAAAAkAP8BHwABBQAAAAAABRUAAACIn1iuVqCC6sy9JqvqAwAA",
|
||||
"AQAUvBQAAAAwAAAA7AAAAEwAAAABBQAAAAAABRUAAAAvr7t03PyHGk2FokNHCAAAAQUAAAAAAAUVAAAAiJ9YrlaggurMvSarAQIAAAIAoAAFAAAAAAAkAP8BHwABBQAAAAAABRUAAAAvr7t03PyHGk2FokNHCAAAAAAkAKkAEgABBQAAAAAABRUAAACIn1iuVqCC6sy9JqvtAwAAABAUAP8BHwABAQAAAAAABRIAAAAAEBgA/wEfAAECAAAAAAAFIAAAACACAAAAECQA/wEfAAEFAAAAAAAFFQAAAIifWK5WoILqzL0mq+oDAAACAHQAAwAAAAKAJAC/AQIAAQUAAAAAAAUVAAAAL6+7dNz8hxpNhaJDtgQAAALAJAC/AQMAAQUAAAAAAAUVAAAAL6+7dNz8hxpNhaJDPgkAAAJAJAD/AQ8AAQUAAAAAAAUVAAAAL6+7dNz8hxpNhaJDtQQAAA==",
|
||||
}
|
||||
TestDirSDs = []string{"AQAUvBQAAAAwAAAAAAAAAEwAAAABBQAAAAAABRUAAACIn1iuVqCC6sy9JqvqAwAAAQUAAAAAAAUVAAAAiJ9YrlaggurMvSarAQIAAAIAfAAEAAAAAAAkAKkAEgABBQAAAAAABRUAAACIn1iuVqCC6sy9JqvtAwAAABMUAP8BHwABAQAAAAAABRIAAAAAExgA/wEfAAECAAAAAAAFIAAAACACAAAAEyQA/wEfAAEFAAAAAAAFFQAAAIifWK5WoILqzL0mq+oDAAA=",
|
||||
"AQAUvBQAAAAwAAAAAAAAAEwAAAABBQAAAAAABRUAAACIn1iuVqCC6sy9JqvqAwAAAQUAAAAAAAUVAAAAiJ9YrlaggurMvSarAQIAAAIA3AAIAAAAAAIUAKkAEgABAQAAAAAABQcAAAAAAxQAiQASAAEBAAAAAAAFBwAAAAAAJACpABIAAQUAAAAAAAUVAAAAiJ9YrlaggurMvSar7QMAAAAAJAC/ARMAAQUAAAAAAAUVAAAAiJ9YrlaggurMvSar6gMAAAALFAC/ARMAAQEAAAAAAAMAAAAAABMUAP8BHwABAQAAAAAABRIAAAAAExgA/wEfAAECAAAAAAAFIAAAACACAAAAEyQA/wEfAAEFAAAAAAAFFQAAAIifWK5WoILqzL0mq+oDAAA=",
|
||||
"AQAUvBQAAAAwAAAA7AAAAEwAAAABBQAAAAAABRUAAAAvr7t03PyHGk2FokNHCAAAAQUAAAAAAAUVAAAAiJ9YrlaggurMvSarAQIAAAIAoAAFAAAAAAAkAP8BHwABBQAAAAAABRUAAAAvr7t03PyHGk2FokNHCAAAAAAkAKkAEgABBQAAAAAABRUAAACIn1iuVqCC6sy9JqvtAwAAABMUAP8BHwABAQAAAAAABRIAAAAAExgA/wEfAAECAAAAAAAFIAAAACACAAAAEyQA/wEfAAEFAAAAAAAFFQAAAIifWK5WoILqzL0mq+oDAAACAHQAAwAAAAKAJAC/AQIAAQUAAAAAAAUVAAAAL6+7dNz8hxpNhaJDtgQAAALAJAC/AQMAAQUAAAAAAAUVAAAAL6+7dNz8hxpNhaJDPgkAAAJAJAD/AQ8AAQUAAAAAAAUVAAAAL6+7dNz8hxpNhaJDtQQAAA==",
|
||||
}
|
||||
)
|
||||
|
||||
// CompareSecurityDescriptors runs tests for comparing 2 security descriptors in []byte format.
|
||||
func CompareSecurityDescriptors(t *testing.T, testPath string, sdInputBytes, sdOutputBytes []byte) {
|
||||
sdInput, err := SecurityDescriptorBytesToStruct(sdInputBytes)
|
||||
test.OK(t, errors.Wrapf(err, "Error converting SD to struct for: %s", testPath))
|
||||
|
||||
sdOutput, err := SecurityDescriptorBytesToStruct(sdOutputBytes)
|
||||
test.OK(t, errors.Wrapf(err, "Error converting SD to struct for: %s", testPath))
|
||||
|
||||
isAdmin, err := IsAdmin()
|
||||
test.OK(t, errors.Wrapf(err, "Error checking if user is admin: %s", testPath))
|
||||
|
||||
var ownerExpected *windows.SID
|
||||
var defaultedOwnerExpected bool
|
||||
var groupExpected *windows.SID
|
||||
var defaultedGroupExpected bool
|
||||
var daclExpected *windows.ACL
|
||||
var defaultedDaclExpected bool
|
||||
var saclExpected *windows.ACL
|
||||
var defaultedSaclExpected bool
|
||||
|
||||
// The Dacl is set correctly whether or not application is running as admin.
|
||||
daclExpected, defaultedDaclExpected, err = sdInput.DACL()
|
||||
test.OK(t, errors.Wrapf(err, "Error getting input dacl for: %s", testPath))
|
||||
|
||||
if isAdmin {
|
||||
// If application is running as admin, all sd values including owner, group, dacl, sacl are set correctly during restore.
|
||||
// Hence we will use the input values for comparison with the output values.
|
||||
ownerExpected, defaultedOwnerExpected, err = sdInput.Owner()
|
||||
test.OK(t, errors.Wrapf(err, "Error getting input owner for: %s", testPath))
|
||||
groupExpected, defaultedGroupExpected, err = sdInput.Group()
|
||||
test.OK(t, errors.Wrapf(err, "Error getting input group for: %s", testPath))
|
||||
saclExpected, defaultedSaclExpected, err = sdInput.SACL()
|
||||
test.OK(t, errors.Wrapf(err, "Error getting input sacl for: %s", testPath))
|
||||
} else {
|
||||
// If application is not running as admin, owner and group are set as current user's SID/GID during restore and sacl is empty.
|
||||
// Get the current user
|
||||
user, err := user.Current()
|
||||
test.OK(t, errors.Wrapf(err, "Could not get current user for: %s", testPath))
|
||||
// Get current user's SID
|
||||
currentUserSID, err := windows.StringToSid(user.Uid)
|
||||
test.OK(t, errors.Wrapf(err, "Error getting output group for: %s", testPath))
|
||||
// Get current user's Group SID
|
||||
currentGroupSID, err := windows.StringToSid(user.Gid)
|
||||
test.OK(t, errors.Wrapf(err, "Error getting output group for: %s", testPath))
|
||||
|
||||
// Set owner and group as current user's SID and GID during restore.
|
||||
ownerExpected = currentUserSID
|
||||
defaultedOwnerExpected = false
|
||||
groupExpected = currentGroupSID
|
||||
defaultedGroupExpected = false
|
||||
|
||||
// If application is not running as admin, SACL is returned empty.
|
||||
saclExpected = nil
|
||||
defaultedSaclExpected = false
|
||||
}
|
||||
// Now do all the comparisons
|
||||
// Get owner SID from output file
|
||||
ownerOut, defaultedOwnerOut, err := sdOutput.Owner()
|
||||
test.OK(t, errors.Wrapf(err, "Error getting output owner for: %s", testPath))
|
||||
// Compare owner SIDs. We must use the Equals method for comparison as a syscall is made for comparing SIDs.
|
||||
test.Assert(t, ownerExpected.Equals(ownerOut), "Owner from SDs read from test path don't match: %s, cur:%s, exp: %s", testPath, ownerExpected.String(), ownerOut.String())
|
||||
test.Equals(t, defaultedOwnerExpected, defaultedOwnerOut, "Defaulted for owner from SDs read from test path don't match: %s", testPath)
|
||||
|
||||
// Get group SID from output file
|
||||
groupOut, defaultedGroupOut, err := sdOutput.Group()
|
||||
test.OK(t, errors.Wrapf(err, "Error getting output group for: %s", testPath))
|
||||
// Compare group SIDs. We must use the Equals method for comparison as a syscall is made for comparing SIDs.
|
||||
test.Assert(t, groupExpected.Equals(groupOut), "Group from SDs read from test path don't match: %s, cur:%s, exp: %s", testPath, groupExpected.String(), groupOut.String())
|
||||
test.Equals(t, defaultedGroupExpected, defaultedGroupOut, "Defaulted for group from SDs read from test path don't match: %s", testPath)
|
||||
|
||||
// Get dacl from output file
|
||||
daclOut, defaultedDaclOut, err := sdOutput.DACL()
|
||||
test.OK(t, errors.Wrapf(err, "Error getting output dacl for: %s", testPath))
|
||||
// Compare dacls
|
||||
test.Equals(t, daclExpected, daclOut, "DACL from SDs read from test path don't match: %s", testPath)
|
||||
test.Equals(t, defaultedDaclExpected, defaultedDaclOut, "Defaulted for DACL from SDs read from test path don't match: %s", testPath)
|
||||
|
||||
// Get sacl from output file
|
||||
saclOut, defaultedSaclOut, err := sdOutput.SACL()
|
||||
test.OK(t, errors.Wrapf(err, "Error getting output sacl for: %s", testPath))
|
||||
// Compare sacls
|
||||
test.Equals(t, saclExpected, saclOut, "DACL from SDs read from test path don't match: %s", testPath)
|
||||
test.Equals(t, defaultedSaclExpected, defaultedSaclOut, "Defaulted for SACL from SDs read from test path don't match: %s", testPath)
|
||||
}
|
|
@ -4,6 +4,7 @@
|
|||
package restic
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
|
@ -12,10 +13,66 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/restic/restic/internal/errors"
|
||||
"github.com/restic/restic/internal/fs"
|
||||
"github.com/restic/restic/internal/test"
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
func TestRestoreSecurityDescriptors(t *testing.T) {
|
||||
t.Parallel()
|
||||
tempDir := t.TempDir()
|
||||
for i, sd := range fs.TestFileSDs {
|
||||
testRestoreSecurityDescriptor(t, sd, tempDir, "file", fmt.Sprintf("testfile%d", i))
|
||||
}
|
||||
for i, sd := range fs.TestDirSDs {
|
||||
testRestoreSecurityDescriptor(t, sd, tempDir, "dir", fmt.Sprintf("testdir%d", i))
|
||||
}
|
||||
}
|
||||
|
||||
func testRestoreSecurityDescriptor(t *testing.T, sd string, tempDir, fileType, fileName string) {
|
||||
// Decode the encoded string SD to get the security descriptor input in bytes.
|
||||
sdInputBytes, err := base64.StdEncoding.DecodeString(sd)
|
||||
test.OK(t, errors.Wrapf(err, "Error decoding SD for: %s", fileName))
|
||||
// Wrap the security descriptor bytes in windows attributes and convert to generic attributes.
|
||||
genericAttributes, err := WindowsAttrsToGenericAttributes(WindowsAttributes{CreationTime: nil, FileAttributes: nil, SecurityDescriptor: &sdInputBytes})
|
||||
test.OK(t, errors.Wrapf(err, "Error constructing windows attributes for: %s", fileName))
|
||||
// Construct a Node with the generic attributes.
|
||||
expectedNode := getNode(fileName, fileType, genericAttributes)
|
||||
|
||||
// Restore the file/dir and restore the meta data including the security descriptors.
|
||||
testPath, node := restoreAndGetNode(t, tempDir, expectedNode, false)
|
||||
// Get the security descriptor from the node constructed from the file info of the restored path.
|
||||
sdByteFromRestoredNode := getWindowsAttr(t, testPath, node).SecurityDescriptor
|
||||
|
||||
// Get the security descriptor for the test path after the restore.
|
||||
sdBytesFromRestoredPath, err := fs.GetSecurityDescriptor(testPath)
|
||||
test.OK(t, errors.Wrapf(err, "Error while getting the security descriptor for: %s", testPath))
|
||||
|
||||
// Compare the input SD and the SD got from the restored file.
|
||||
fs.CompareSecurityDescriptors(t, testPath, sdInputBytes, *sdBytesFromRestoredPath)
|
||||
// Compare the SD got from node constructed from the restored file info and the SD got directly from the restored file.
|
||||
fs.CompareSecurityDescriptors(t, testPath, *sdByteFromRestoredNode, *sdBytesFromRestoredPath)
|
||||
}
|
||||
|
||||
func getNode(name string, fileType string, genericAttributes map[GenericAttributeType]json.RawMessage) Node {
|
||||
return Node{
|
||||
Name: name,
|
||||
Type: fileType,
|
||||
Mode: 0644,
|
||||
ModTime: parseTime("2024-02-21 6:30:01.111"),
|
||||
AccessTime: parseTime("2024-02-22 7:31:02.222"),
|
||||
ChangeTime: parseTime("2024-02-23 8:32:03.333"),
|
||||
GenericAttributes: genericAttributes,
|
||||
}
|
||||
}
|
||||
|
||||
func getWindowsAttr(t *testing.T, testPath string, node *Node) WindowsAttributes {
|
||||
windowsAttributes, unknownAttribs, err := genericAttributesToWindowsAttrs(node.GenericAttributes)
|
||||
test.OK(t, errors.Wrapf(err, "Error getting windows attr from generic attr: %s", testPath))
|
||||
test.Assert(t, len(unknownAttribs) == 0, "Unkown attribs found: %s for: %s", unknownAttribs, testPath)
|
||||
return windowsAttributes
|
||||
}
|
||||
|
||||
func TestRestoreCreationTime(t *testing.T) {
|
||||
t.Parallel()
|
||||
path := t.TempDir()
|
||||
|
|
Loading…
Reference in a new issue