mirror of
https://github.com/tailscale/tailscale.git
synced 2024-11-25 19:15:34 +00:00
9ebb5d4205
ProgramData has a permissive ACL. For us to safely store machine-wide state information, we must set a more restrictive ACL on our state directory. We set the ACL so that only talescaled's user (ie, LocalSystem) and the Administrators group may access our directory. We must include Administrators to ensure that logs continue to be easily accessible; omitting that group would force users to use special tools to log in interactively as LocalSystem, which is not ideal. (Note that the ACL we apply matches the ACL that was used for LocalSystem's AppData\Local). There are two cases where we need to reset perms: One is during migration from the old location to the new. The second case is for clean installations where we are creating the file store for the first time. Updates #2856 Signed-off-by: Aaron Klotz <aaron@tailscale.com>
146 lines
4.4 KiB
Go
146 lines
4.4 KiB
Go
// Copyright (c) 2021 Tailscale Inc & AUTHORS All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package paths
|
|
|
|
import (
|
|
"os"
|
|
"unsafe"
|
|
|
|
"golang.org/x/sys/windows"
|
|
)
|
|
|
|
func getTokenInfo(token windows.Token, infoClass uint32) ([]byte, error) {
|
|
var desiredLen uint32
|
|
err := windows.GetTokenInformation(token, infoClass, nil, 0, &desiredLen)
|
|
if err != nil && err != windows.ERROR_INSUFFICIENT_BUFFER {
|
|
return nil, err
|
|
}
|
|
|
|
buf := make([]byte, desiredLen)
|
|
actualLen := desiredLen
|
|
err = windows.GetTokenInformation(token, infoClass, &buf[0], desiredLen, &actualLen)
|
|
return buf, err
|
|
}
|
|
|
|
func getTokenUserInfo(token windows.Token) (*windows.Tokenuser, error) {
|
|
buf, err := getTokenInfo(token, windows.TokenUser)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return (*windows.Tokenuser)(unsafe.Pointer(&buf[0])), nil
|
|
}
|
|
|
|
func getTokenPrimaryGroupInfo(token windows.Token) (*windows.Tokenprimarygroup, error) {
|
|
buf, err := getTokenInfo(token, windows.TokenPrimaryGroup)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return (*windows.Tokenprimarygroup)(unsafe.Pointer(&buf[0])), nil
|
|
}
|
|
|
|
type userSids struct {
|
|
User *windows.SID
|
|
PrimaryGroup *windows.SID
|
|
}
|
|
|
|
func getCurrentUserSids() (*userSids, error) {
|
|
token, err := windows.OpenCurrentProcessToken()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer token.Close()
|
|
|
|
userInfo, err := getTokenUserInfo(token)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
primaryGroup, err := getTokenPrimaryGroupInfo(token)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &userSids{userInfo.User.Sid, primaryGroup.PrimaryGroup}, nil
|
|
}
|
|
|
|
// ensureStateDirPerms applies a restrictive ACL to the directory specified by dirPath.
|
|
// It sets the following security attributes on the directory:
|
|
// Owner: The user for the current process;
|
|
// Primary Group: The primary group for the current process;
|
|
// DACL: Full control to the current user and to the Administrators group.
|
|
// (We include Administrators so that admin users may still access logs;
|
|
// granting access exclusively to LocalSystem would require admins to use
|
|
// special tools to access the Log directory)
|
|
// Inheritance: The directory does not inherit the ACL from its parent.
|
|
// However, any directories and/or files created within this
|
|
// directory *do* inherit the ACL that we are setting.
|
|
func ensureStateDirPerms(dirPath string) error {
|
|
fi, err := os.Stat(dirPath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !fi.IsDir() {
|
|
return os.ErrInvalid
|
|
}
|
|
|
|
// We need the info for our current user as SIDs
|
|
sids, err := getCurrentUserSids()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// We also need the SID for the Administrators group so that admins may
|
|
// easily access logs.
|
|
adminGroupSid, err := windows.CreateWellKnownSid(windows.WinBuiltinAdministratorsSid)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Munge the SIDs into the format required by EXPLICIT_ACCESS.
|
|
userTrustee := windows.TRUSTEE{nil, windows.NO_MULTIPLE_TRUSTEE,
|
|
windows.TRUSTEE_IS_SID, windows.TRUSTEE_IS_USER,
|
|
windows.TrusteeValueFromSID(sids.User)}
|
|
|
|
adminTrustee := windows.TRUSTEE{nil, windows.NO_MULTIPLE_TRUSTEE,
|
|
windows.TRUSTEE_IS_SID, windows.TRUSTEE_IS_WELL_KNOWN_GROUP,
|
|
windows.TrusteeValueFromSID(adminGroupSid)}
|
|
|
|
// We declare our access rights via this array of EXPLICIT_ACCESS structures.
|
|
// We set full access to our user and to Administrators.
|
|
// We configure the DACL such that any files or directories created within
|
|
// dirPath will also inherit this DACL.
|
|
explicitAccess := []windows.EXPLICIT_ACCESS{
|
|
windows.EXPLICIT_ACCESS{
|
|
windows.GENERIC_ALL,
|
|
windows.SET_ACCESS,
|
|
windows.SUB_CONTAINERS_AND_OBJECTS_INHERIT,
|
|
userTrustee,
|
|
},
|
|
windows.EXPLICIT_ACCESS{
|
|
windows.GENERIC_ALL,
|
|
windows.SET_ACCESS,
|
|
windows.SUB_CONTAINERS_AND_OBJECTS_INHERIT,
|
|
adminTrustee,
|
|
},
|
|
}
|
|
|
|
dacl, err := windows.ACLFromEntries(explicitAccess, nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// We now reset the file's owner, primary group, and DACL.
|
|
// We also must pass PROTECTED_DACL_SECURITY_INFORMATION so that our new ACL
|
|
// does not inherit any ACL entries from the parent directory.
|
|
const flags = windows.OWNER_SECURITY_INFORMATION |
|
|
windows.GROUP_SECURITY_INFORMATION |
|
|
windows.DACL_SECURITY_INFORMATION |
|
|
windows.PROTECTED_DACL_SECURITY_INFORMATION
|
|
return windows.SetNamedSecurityInfo(dirPath, windows.SE_FILE_OBJECT, flags,
|
|
sids.User, sids.PrimaryGroup, dacl, nil)
|
|
}
|