diff --git a/client/internal/config.go b/client/internal/config.go index 725703c43..1df1e0547 100644 --- a/client/internal/config.go +++ b/client/internal/config.go @@ -117,6 +117,11 @@ type Config struct { // ReadConfig read config file and return with Config. If it is not exists create a new with default values func ReadConfig(configPath string) (*Config, error) { if configFileIsExists(configPath) { + err := util.EnforcePermission(configPath) + if err != nil { + log.Errorf("failed to enforce permission on config dir: %v", err) + } + config := &Config{} if _, err := util.ReadJson(configPath, config); err != nil { return nil, err @@ -159,13 +164,17 @@ func UpdateOrCreateConfig(input ConfigInput) (*Config, error) { if err != nil { return nil, err } - err = WriteOutConfig(input.ConfigPath, cfg) + err = util.WriteJsonWithRestrictedPermission(input.ConfigPath, cfg) return cfg, err } if isPreSharedKeyHidden(input.PreSharedKey) { input.PreSharedKey = nil } + err := util.EnforcePermission(input.ConfigPath) + if err != nil { + log.Errorf("failed to enforce permission on config dir: %v", err) + } return update(input) } diff --git a/util/file.go b/util/file.go index 2a6182556..8355488c9 100644 --- a/util/file.go +++ b/util/file.go @@ -10,51 +10,30 @@ import ( log "github.com/sirupsen/logrus" ) -// WriteJson writes JSON config object to a file creating parent directories if required -// The output JSON is pretty-formatted -func WriteJson(file string, obj interface{}) error { - +// WriteJsonWithRestrictedPermission writes JSON config object to a file. Enforces permission on the parent directory +func WriteJsonWithRestrictedPermission(file string, obj interface{}) error { configDir, configFileName, err := prepareConfigFileDir(file) if err != nil { return err } - // make it pretty - bs, err := json.MarshalIndent(obj, "", " ") + err = EnforcePermission(file) if err != nil { return err } - tempFile, err := os.CreateTemp(configDir, ".*"+configFileName) + return writeJson(file, obj, configDir, configFileName) +} + +// WriteJson writes JSON config object to a file creating parent directories if required +// The output JSON is pretty-formatted +func WriteJson(file string, obj interface{}) error { + configDir, configFileName, err := prepareConfigFileDir(file) if err != nil { return err } - tempFileName := tempFile.Name() - // closing file ops as windows doesn't allow to move it - err = tempFile.Close() - if err != nil { - return err - } - - defer func() { - _, err = os.Stat(tempFileName) - if err == nil { - os.Remove(tempFileName) - } - }() - - err = os.WriteFile(tempFileName, bs, 0600) - if err != nil { - return err - } - - err = os.Rename(tempFileName, file) - if err != nil { - return err - } - - return nil + return writeJson(file, obj, configDir, configFileName) } // DirectWriteJson writes JSON config object to a file creating parent directories if required without creating a temporary file @@ -96,6 +75,46 @@ func DirectWriteJson(ctx context.Context, file string, obj interface{}) error { return nil } +func writeJson(file string, obj interface{}, configDir string, configFileName string) error { + + // make it pretty + bs, err := json.MarshalIndent(obj, "", " ") + if err != nil { + return err + } + + tempFile, err := os.CreateTemp(configDir, ".*"+configFileName) + if err != nil { + return err + } + + tempFileName := tempFile.Name() + // closing file ops as windows doesn't allow to move it + err = tempFile.Close() + if err != nil { + return err + } + + defer func() { + _, err = os.Stat(tempFileName) + if err == nil { + os.Remove(tempFileName) + } + }() + + err = os.WriteFile(tempFileName, bs, 0600) + if err != nil { + return err + } + + err = os.Rename(tempFileName, file) + if err != nil { + return err + } + + return nil +} + func openOrCreateFile(file string) (*os.File, error) { s, err := os.Stat(file) if err == nil { @@ -172,5 +191,9 @@ func prepareConfigFileDir(file string) (string, string, error) { } err := os.MkdirAll(configDir, 0750) + if err != nil { + return "", "", err + } + return configDir, configFileName, err } diff --git a/util/permission.go b/util/permission.go new file mode 100644 index 000000000..666998cff --- /dev/null +++ b/util/permission.go @@ -0,0 +1,7 @@ +//go:build !windows + +package util + +func EnforcePermission(dirPath string) error { + return nil +} diff --git a/util/permission_windows.go b/util/permission_windows.go new file mode 100644 index 000000000..548fef824 --- /dev/null +++ b/util/permission_windows.go @@ -0,0 +1,86 @@ +package util + +import ( + "path/filepath" + + log "github.com/sirupsen/logrus" + "golang.org/x/sys/windows" +) + +const ( + securityFlags = windows.OWNER_SECURITY_INFORMATION | + windows.GROUP_SECURITY_INFORMATION | + windows.DACL_SECURITY_INFORMATION | + windows.PROTECTED_DACL_SECURITY_INFORMATION +) + +func EnforcePermission(file string) error { + dirPath := filepath.Dir(file) + + user, group, err := sids() + if err != nil { + return err + } + + adminGroupSid, err := windows.CreateWellKnownSid(windows.WinBuiltinAdministratorsSid) + if err != nil { + return err + } + + explicitAccess := []windows.EXPLICIT_ACCESS{ + { + AccessPermissions: windows.GENERIC_ALL, + AccessMode: windows.SET_ACCESS, + Inheritance: windows.SUB_CONTAINERS_AND_OBJECTS_INHERIT, + Trustee: windows.TRUSTEE{ + MultipleTrusteeOperation: windows.NO_MULTIPLE_TRUSTEE, + TrusteeForm: windows.TRUSTEE_IS_SID, + TrusteeType: windows.TRUSTEE_IS_USER, + TrusteeValue: windows.TrusteeValueFromSID(user), + }, + }, + { + AccessPermissions: windows.GENERIC_ALL, + AccessMode: windows.SET_ACCESS, + Inheritance: windows.SUB_CONTAINERS_AND_OBJECTS_INHERIT, + Trustee: windows.TRUSTEE{ + MultipleTrusteeOperation: windows.NO_MULTIPLE_TRUSTEE, + TrusteeForm: windows.TRUSTEE_IS_SID, + TrusteeType: windows.TRUSTEE_IS_WELL_KNOWN_GROUP, + TrusteeValue: windows.TrusteeValueFromSID(adminGroupSid), + }, + }, + } + + dacl, err := windows.ACLFromEntries(explicitAccess, nil) + if err != nil { + return err + } + + return windows.SetNamedSecurityInfo(dirPath, windows.SE_FILE_OBJECT, securityFlags, user, group, dacl, nil) +} + +func sids() (*windows.SID, *windows.SID, error) { + var token windows.Token + err := windows.OpenProcessToken(windows.CurrentProcess(), windows.TOKEN_QUERY, &token) + if err != nil { + return nil, nil, err + } + defer func() { + if err := token.Close(); err != nil { + log.Errorf("failed to close process token: %v", err) + } + }() + + tu, err := token.GetTokenUser() + if err != nil { + return nil, nil, err + } + + pg, err := token.GetTokenPrimaryGroup() + if err != nil { + return nil, nil, err + } + + return tu.User.Sid, pg.PrimaryGroup, nil +}