mirror of https://github.com/restic/restic.git
Fix review comments
This commit is contained in:
parent
3f76b902e5
commit
08c6945d61
|
@ -1,10 +1,9 @@
|
||||||
Enhancement: Back up and restore SecurityDescriptors on Windows
|
Enhancement: Back up and restore SecurityDescriptors on Windows
|
||||||
|
|
||||||
Restic did not back up SecurityDescriptors of files on Windows.
|
Restic now backs up and restores SecurityDescriptors when backing up files and folders
|
||||||
Restic now backs up and restores SecurityDescriptors (which includes owner, group,
|
on Windows which includes owner, group, discretionary access control list (DACL),
|
||||||
discretionary access control list (DACL), system access control list (SACL))
|
system access control list (SACL). This requires the user to be a member of backup
|
||||||
when backing up files and folders on Windows. This requires the user to be
|
operators or the application must be run as admin.
|
||||||
a member of backup operators or the application must be run as admin.
|
|
||||||
If that is not the case, only the current user's owner, group and DACL will be backed up
|
If that is not the case, only the current user's owner, group and DACL will be backed up
|
||||||
and during restore only the DACL of the backed file will be restored while the current
|
and during restore only the DACL of the backed file will be restored while the current
|
||||||
user's owner and group will be set during the restore.
|
user's owner and group will be set during the restore.
|
||||||
|
|
|
@ -514,9 +514,9 @@ written, and the next backup needs to write new metadata again. If you really
|
||||||
want to save the access time for files and directories, you can pass the
|
want to save the access time for files and directories, you can pass the
|
||||||
``--with-atime`` option to the ``backup`` command.
|
``--with-atime`` option to the ``backup`` command.
|
||||||
|
|
||||||
Backing up full security descriptors on windows is only possible when the user
|
Backing up full security descriptors on Windows is only possible when the user
|
||||||
has ``SeBackupPrivilege``privilege or is running as admin. This is a restriction
|
has ``SeBackupPrivilege``privilege or is running as admin. This is a restriction
|
||||||
of windows not restic.
|
of Windows not restic.
|
||||||
If either of these conditions are not met, only the owner, group and DACL will
|
If either of these conditions are not met, only the owner, group and DACL will
|
||||||
be backed up.
|
be backed up.
|
||||||
|
|
||||||
|
|
|
@ -72,9 +72,9 @@ Restoring symbolic links on windows is only possible when the user has
|
||||||
``SeCreateSymbolicLinkPrivilege`` privilege or is running as admin. This is a
|
``SeCreateSymbolicLinkPrivilege`` privilege or is running as admin. This is a
|
||||||
restriction of windows not restic.
|
restriction of windows not restic.
|
||||||
|
|
||||||
Restoring full security descriptors on windows is only possible when the user has
|
Restoring full security descriptors on Windows is only possible when the user has
|
||||||
``SeRestorePrivilege``, ``SeSecurityPrivilege`` and ``SeTakeOwnershipPrivilege``
|
``SeRestorePrivilege``, ``SeSecurityPrivilege`` and ``SeTakeOwnershipPrivilege``
|
||||||
privilege or is running as admin. This is a restriction of windows not restic.
|
privilege or is running as admin. This is a restriction of Windows not restic.
|
||||||
If either of these conditions are not met, only the DACL will be restored.
|
If either of these conditions are not met, only the DACL will be restored.
|
||||||
|
|
||||||
By default, restic does not restore files as sparse. Use ``restore --sparse`` to
|
By default, restic does not restore files as sparse. Use ``restore --sparse`` to
|
||||||
|
|
|
@ -8,8 +8,6 @@ import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/restic/restic/internal/fs"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var opts struct {
|
var opts struct {
|
||||||
|
@ -46,7 +44,7 @@ func initDebugLogger() {
|
||||||
|
|
||||||
fmt.Fprintf(os.Stderr, "debug log file %v\n", debugfile)
|
fmt.Fprintf(os.Stderr, "debug log file %v\n", debugfile)
|
||||||
|
|
||||||
f, err := fs.OpenFile(debugfile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0600)
|
f, err := os.OpenFile(debugfile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0600)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "unable to open debug log file: %v\n", err)
|
fmt.Fprintf(os.Stderr, "unable to open debug log file: %v\n", err)
|
||||||
os.Exit(2)
|
os.Exit(2)
|
||||||
|
|
|
@ -9,7 +9,7 @@ import (
|
||||||
"unicode/utf16"
|
"unicode/utf16"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
"github.com/restic/restic/internal/errors"
|
"github.com/restic/restic/internal/debug"
|
||||||
"golang.org/x/sys/windows"
|
"golang.org/x/sys/windows"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -26,9 +26,7 @@ var (
|
||||||
// SeTakeOwnershipPrivilege allows the application to take ownership of files and directories, regardless of the permissions set on them.
|
// SeTakeOwnershipPrivilege allows the application to take ownership of files and directories, regardless of the permissions set on them.
|
||||||
SeTakeOwnershipPrivilege = "SeTakeOwnershipPrivilege"
|
SeTakeOwnershipPrivilege = "SeTakeOwnershipPrivilege"
|
||||||
|
|
||||||
backupPrivilegeError error
|
lowerPrivileges bool
|
||||||
restorePrivilegeError error
|
|
||||||
lowerPrivileges bool
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Flags for backup and restore with admin permissions
|
// Flags for backup and restore with admin permissions
|
||||||
|
@ -49,14 +47,14 @@ func GetSecurityDescriptor(filePath string) (securityDescriptor *[]byte, err err
|
||||||
var sd *windows.SECURITY_DESCRIPTOR
|
var sd *windows.SECURITY_DESCRIPTOR
|
||||||
|
|
||||||
if lowerPrivileges {
|
if lowerPrivileges {
|
||||||
sd, err = getNamedSecurityInfoLow(sd, err, filePath)
|
sd, err = getNamedSecurityInfoLow(filePath)
|
||||||
} else {
|
} else {
|
||||||
sd, err = getNamedSecurityInfoHigh(sd, err, filePath)
|
sd, err = getNamedSecurityInfoHigh(filePath)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if isHandlePrivilegeNotHeldError(err) {
|
if isHandlePrivilegeNotHeldError(err) {
|
||||||
lowerPrivileges = true
|
lowerPrivileges = true
|
||||||
sd, err = getNamedSecurityInfoLow(sd, err, filePath)
|
sd, err = getNamedSecurityInfoLow(filePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("get low-level named security info failed with: %w", err)
|
return nil, fmt.Errorf("get low-level named security info failed with: %w", err)
|
||||||
}
|
}
|
||||||
|
@ -128,12 +126,12 @@ func SetSecurityDescriptor(filePath string, securityDescriptor *[]byte) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// getNamedSecurityInfoHigh gets the higher level SecurityDescriptor which requires admin permissions.
|
// getNamedSecurityInfoHigh gets the higher level SecurityDescriptor which requires admin permissions.
|
||||||
func getNamedSecurityInfoHigh(sd *windows.SECURITY_DESCRIPTOR, err error, filePath string) (*windows.SECURITY_DESCRIPTOR, error) {
|
func getNamedSecurityInfoHigh(filePath string) (*windows.SECURITY_DESCRIPTOR, error) {
|
||||||
return windows.GetNamedSecurityInfo(filePath, windows.SE_FILE_OBJECT, highSecurityFlags)
|
return windows.GetNamedSecurityInfo(filePath, windows.SE_FILE_OBJECT, highSecurityFlags)
|
||||||
}
|
}
|
||||||
|
|
||||||
// getNamedSecurityInfoLow gets the lower level SecurityDescriptor which requires no admin permissions.
|
// getNamedSecurityInfoLow gets the lower level SecurityDescriptor which requires no admin permissions.
|
||||||
func getNamedSecurityInfoLow(sd *windows.SECURITY_DESCRIPTOR, err error, filePath string) (*windows.SECURITY_DESCRIPTOR, error) {
|
func getNamedSecurityInfoLow(filePath string) (*windows.SECURITY_DESCRIPTOR, error) {
|
||||||
return windows.GetNamedSecurityInfo(filePath, windows.SE_FILE_OBJECT, lowBackupSecurityFlags)
|
return windows.GetNamedSecurityInfo(filePath, windows.SE_FILE_OBJECT, lowBackupSecurityFlags)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -151,7 +149,7 @@ func setNamedSecurityInfoLow(filePath string, dacl *windows.ACL) error {
|
||||||
func enableBackupPrivilege() {
|
func enableBackupPrivilege() {
|
||||||
err := enableProcessPrivileges([]string{SeBackupPrivilege})
|
err := enableProcessPrivileges([]string{SeBackupPrivilege})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
backupPrivilegeError = fmt.Errorf("error enabling backup privilege: %w", err)
|
debug.Log("error enabling backup privilege: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -159,26 +157,10 @@ func enableBackupPrivilege() {
|
||||||
func enableRestorePrivilege() {
|
func enableRestorePrivilege() {
|
||||||
err := enableProcessPrivileges([]string{SeRestorePrivilege, SeSecurityPrivilege, SeTakeOwnershipPrivilege})
|
err := enableProcessPrivileges([]string{SeRestorePrivilege, SeSecurityPrivilege, SeTakeOwnershipPrivilege})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
restorePrivilegeError = fmt.Errorf("error enabling restore/security privilege: %w", err)
|
debug.Log("error enabling restore/security privilege: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// DisableBackupPrivileges disables privileges that are needed for backup operations.
|
|
||||||
// They may be reenabled if GetSecurityDescriptor is called again.
|
|
||||||
func DisableBackupPrivileges() error {
|
|
||||||
//Reset the once so that backup privileges can be enabled again if needed.
|
|
||||||
onceBackup = sync.Once{}
|
|
||||||
return enableDisableProcessPrivilege([]string{SeBackupPrivilege}, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DisableRestorePrivileges disables privileges that are needed for restore operations.
|
|
||||||
// They may be reenabled if SetSecurityDescriptor is called again.
|
|
||||||
func DisableRestorePrivileges() error {
|
|
||||||
//Reset the once so that restore privileges can be enabled again if needed.
|
|
||||||
onceRestore = sync.Once{}
|
|
||||||
return enableDisableProcessPrivilege([]string{SeRestorePrivilege, SeSecurityPrivilege}, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// isHandlePrivilegeNotHeldError checks if the error is ERROR_PRIVILEGE_NOT_HELD
|
// isHandlePrivilegeNotHeldError checks if the error is ERROR_PRIVILEGE_NOT_HELD
|
||||||
func isHandlePrivilegeNotHeldError(err error) bool {
|
func isHandlePrivilegeNotHeldError(err error) bool {
|
||||||
// Use a type assertion to check if the error is of type syscall.Errno
|
// Use a type assertion to check if the error is of type syscall.Errno
|
||||||
|
@ -189,23 +171,26 @@ func isHandlePrivilegeNotHeldError(err error) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsAdmin checks if current user is an administrator.
|
// SecurityDescriptorBytesToStruct converts the security descriptor bytes representation
|
||||||
func IsAdmin() (isAdmin bool, err error) {
|
// into a pointer to windows SECURITY_DESCRIPTOR.
|
||||||
var sid *windows.SID
|
func SecurityDescriptorBytesToStruct(sd []byte) (*windows.SECURITY_DESCRIPTOR, error) {
|
||||||
err = windows.AllocateAndInitializeSid(&windows.SECURITY_NT_AUTHORITY, 2, windows.SECURITY_BUILTIN_DOMAIN_RID, windows.DOMAIN_ALIAS_RID_ADMINS,
|
if l := int(unsafe.Sizeof(windows.SECURITY_DESCRIPTOR{})); len(sd) < l {
|
||||||
0, 0, 0, 0, 0, 0, &sid)
|
return nil, fmt.Errorf("securityDescriptor (%d) smaller than expected (%d): %w", len(sd), l, windows.ERROR_INCORRECT_SIZE)
|
||||||
if err != nil {
|
|
||||||
return false, errors.Errorf("sid error: %s", err)
|
|
||||||
}
|
}
|
||||||
token := windows.Token(0)
|
s := (*windows.SECURITY_DESCRIPTOR)(unsafe.Pointer(&sd[0]))
|
||||||
member, err := token.IsMember(sid)
|
return s, nil
|
||||||
if err != nil {
|
|
||||||
return false, errors.Errorf("token membership error: %s", err)
|
|
||||||
}
|
|
||||||
return member, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// The code below was adapted from github.com/Microsoft/go-winio under MIT license.
|
// securityDescriptorStructToBytes converts the pointer to windows SECURITY_DESCRIPTOR
|
||||||
|
// into a security descriptor bytes representation.
|
||||||
|
func securityDescriptorStructToBytes(sd *windows.SECURITY_DESCRIPTOR) ([]byte, error) {
|
||||||
|
b := unsafe.Slice((*byte)(unsafe.Pointer(sd)), sd.Length())
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// The code below was adapted from
|
||||||
|
// https://github.com/microsoft/go-winio/blob/3c9576c9346a1892dee136329e7e15309e82fb4f/privilege.go
|
||||||
|
// under MIT license.
|
||||||
|
|
||||||
// The MIT License (MIT)
|
// The MIT License (MIT)
|
||||||
|
|
||||||
|
@ -262,23 +247,6 @@ type PrivilegeError struct {
|
||||||
privileges []uint64
|
privileges []uint64
|
||||||
}
|
}
|
||||||
|
|
||||||
// SecurityDescriptorBytesToStruct converts the security descriptor bytes representation
|
|
||||||
// into a pointer to windows SECURITY_DESCRIPTOR.
|
|
||||||
func SecurityDescriptorBytesToStruct(sd []byte) (*windows.SECURITY_DESCRIPTOR, error) {
|
|
||||||
if l := int(unsafe.Sizeof(windows.SECURITY_DESCRIPTOR{})); len(sd) < l {
|
|
||||||
return nil, fmt.Errorf("securityDescriptor (%d) smaller than expected (%d): %w", len(sd), l, windows.ERROR_INCORRECT_SIZE)
|
|
||||||
}
|
|
||||||
s := (*windows.SECURITY_DESCRIPTOR)(unsafe.Pointer(&sd[0]))
|
|
||||||
return s, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// securityDescriptorStructToBytes converts the pointer to windows SECURITY_DESCRIPTOR
|
|
||||||
// into a security descriptor bytes representation.
|
|
||||||
func securityDescriptorStructToBytes(sd *windows.SECURITY_DESCRIPTOR) ([]byte, error) {
|
|
||||||
b := unsafe.Slice((*byte)(unsafe.Pointer(sd)), sd.Length())
|
|
||||||
return b, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Error returns the string message for the error.
|
// Error returns the string message for the error.
|
||||||
func (e *PrivilegeError) Error() string {
|
func (e *PrivilegeError) Error() string {
|
||||||
s := "Could not enable privilege "
|
s := "Could not enable privilege "
|
||||||
|
@ -293,12 +261,6 @@ func (e *PrivilegeError) Error() string {
|
||||||
s += getPrivilegeName(p)
|
s += getPrivilegeName(p)
|
||||||
s += `"`
|
s += `"`
|
||||||
}
|
}
|
||||||
if backupPrivilegeError != nil {
|
|
||||||
s += " backupPrivilegeError:" + backupPrivilegeError.Error()
|
|
||||||
}
|
|
||||||
if restorePrivilegeError != nil {
|
|
||||||
s += " restorePrivilegeError:" + restorePrivilegeError.Error()
|
|
||||||
}
|
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,7 @@ import (
|
||||||
"github.com/restic/restic/internal/test"
|
"github.com/restic/restic/internal/test"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Test_SetGetFileSecurityDescriptors(t *testing.T) {
|
func TestSetGetFileSecurityDescriptors(t *testing.T) {
|
||||||
tempDir := t.TempDir()
|
tempDir := t.TempDir()
|
||||||
testfilePath := filepath.Join(tempDir, "testfile.txt")
|
testfilePath := filepath.Join(tempDir, "testfile.txt")
|
||||||
// create temp file
|
// create temp file
|
||||||
|
@ -31,7 +31,7 @@ func Test_SetGetFileSecurityDescriptors(t *testing.T) {
|
||||||
testSecurityDescriptors(t, TestFileSDs, testfilePath)
|
testSecurityDescriptors(t, TestFileSDs, testfilePath)
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test_SetGetFolderSecurityDescriptors(t *testing.T) {
|
func TestSetGetFolderSecurityDescriptors(t *testing.T) {
|
||||||
tempDir := t.TempDir()
|
tempDir := t.TempDir()
|
||||||
testfolderPath := filepath.Join(tempDir, "testfolder")
|
testfolderPath := filepath.Join(tempDir, "testfolder")
|
||||||
// create temp folder
|
// create temp folder
|
||||||
|
|
|
@ -23,6 +23,23 @@ var (
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// IsAdmin checks if current user is an administrator.
|
||||||
|
func IsAdmin() (isAdmin bool, err error) {
|
||||||
|
var sid *windows.SID
|
||||||
|
err = windows.AllocateAndInitializeSid(&windows.SECURITY_NT_AUTHORITY, 2, windows.SECURITY_BUILTIN_DOMAIN_RID, windows.DOMAIN_ALIAS_RID_ADMINS,
|
||||||
|
0, 0, 0, 0, 0, 0, &sid)
|
||||||
|
if err != nil {
|
||||||
|
return false, errors.Errorf("sid error: %s", err)
|
||||||
|
}
|
||||||
|
windows.GetCurrentProcessToken()
|
||||||
|
token := windows.Token(0)
|
||||||
|
member, err := token.IsMember(sid)
|
||||||
|
if err != nil {
|
||||||
|
return false, errors.Errorf("token membership error: %s", err)
|
||||||
|
}
|
||||||
|
return member, nil
|
||||||
|
}
|
||||||
|
|
||||||
// CompareSecurityDescriptors runs tests for comparing 2 security descriptors in []byte format.
|
// CompareSecurityDescriptors runs tests for comparing 2 security descriptors in []byte format.
|
||||||
func CompareSecurityDescriptors(t *testing.T, testPath string, sdInputBytes, sdOutputBytes []byte) {
|
func CompareSecurityDescriptors(t *testing.T, testPath string, sdInputBytes, sdOutputBytes []byte) {
|
||||||
sdInput, err := SecurityDescriptorBytesToStruct(sdInputBytes)
|
sdInput, err := SecurityDescriptorBytesToStruct(sdInputBytes)
|
||||||
|
|
|
@ -721,7 +721,7 @@ func (node *Node) fillExtra(path string, fi os.FileInfo, ignoreXattrListError bo
|
||||||
allowExtended, err := node.fillGenericAttributes(path, fi, stat)
|
allowExtended, err := node.fillGenericAttributes(path, fi, stat)
|
||||||
if allowExtended {
|
if allowExtended {
|
||||||
// Skip processing ExtendedAttributes if allowExtended is false.
|
// Skip processing ExtendedAttributes if allowExtended is false.
|
||||||
err = errors.CombineErrors(err, node.fillExtendedAttributes(path))
|
err = errors.CombineErrors(err, node.fillExtendedAttributes(path, ignoreXattrListError))
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,7 +24,7 @@ type WindowsAttributes struct {
|
||||||
// FileAttributes is used for storing file attributes for windows files.
|
// FileAttributes is used for storing file attributes for windows files.
|
||||||
FileAttributes *uint32 `generic:"file_attributes"`
|
FileAttributes *uint32 `generic:"file_attributes"`
|
||||||
// SecurityDescriptor is used for storing security descriptors which includes
|
// SecurityDescriptor is used for storing security descriptors which includes
|
||||||
// owner, group, discretionary access control list (DACL), system access control list (SACL))
|
// owner, group, discretionary access control list (DACL), system access control list (SACL)
|
||||||
SecurityDescriptor *[]byte `generic:"security_descriptor"`
|
SecurityDescriptor *[]byte `generic:"security_descriptor"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue