diff --git a/commands_test.go b/commands_test.go index b833622c..96c2ec50 100644 --- a/commands_test.go +++ b/commands_test.go @@ -310,6 +310,7 @@ func TestShowSchedules(t *testing.T) { schedule backup@default: at: daily permission: auto + run-level: auto command-output: auto priority: standard lock-mode: default @@ -318,6 +319,7 @@ schedule backup@default: schedule check@default: at: weekly permission: auto + run-level: auto command-output: auto priority: standard lock-mode: default diff --git a/config/profile.go b/config/profile.go index 81885001..1b4ca445 100644 --- a/config/profile.go +++ b/config/profile.go @@ -312,6 +312,7 @@ type ScheduleBaseSection struct { scheduleConfig *ScheduleConfig Schedule any `mapstructure:"schedule" show:"noshow" examples:"hourly;daily;weekly;monthly;10:00,14:00,18:00,22:00;Wed,Fri 17:48;*-*-15 02:45;Mon..Fri 00:30" description:"Configures the scheduled execution of this profile section. Can be times in systemd timer format or a config structure"` SchedulePermission string `mapstructure:"schedule-permission" show:"noshow" default:"auto" enum:"auto;system;user;user_logged_on" description:"Specify whether the schedule runs with system or user privileges - see https://creativeprojects.github.io/resticprofile/schedules/configuration/"` + ScheduleRunLevel string `mapstructure:"schedule-run-level" show:"noshow" default:"auto" enum:"auto;lowest;highest" description:"Specify the schedule privilege level (for Windows Task Scheduler only)"` ScheduleLog string `mapstructure:"schedule-log" show:"noshow" examples:"/resticprofile.log;syslog-tcp://syslog-server:514;syslog:server;syslog:" description:"Redirect the output into a log file or to syslog when running on schedule"` SchedulePriority string `mapstructure:"schedule-priority" show:"noshow" default:"standard" enum:"background;standard" description:"Set the priority at which the schedule is run"` ScheduleLockMode string `mapstructure:"schedule-lock-mode" show:"noshow" default:"default" enum:"default;fail;ignore" description:"Specify how locks are used when running on schedule - see https://creativeprojects.github.io/resticprofile/schedules/configuration/"` diff --git a/config/schedule.go b/config/schedule.go index 9d44a312..44615f7b 100644 --- a/config/schedule.go +++ b/config/schedule.go @@ -35,6 +35,7 @@ const ( // ScheduleBaseConfig is the base user configuration that could be shared across all schedules. type ScheduleBaseConfig struct { Permission string `mapstructure:"permission" default:"auto" enum:"auto;system;user;user_logged_on" description:"Specify whether the schedule runs with system or user privileges - see https://creativeprojects.github.io/resticprofile/schedules/configuration/"` + RunLevel string `mapstructure:"run-level" default:"auto" enum:"auto;lowest;highest" description:"Specify the schedule privilege level (for Windows Task Scheduler only)"` Log string `mapstructure:"log" examples:"/resticprofile.log;syslog-tcp://syslog-server:514;syslog:server;syslog:" description:"Redirect the output into a log file or to syslog when running on schedule - see https://creativeprojects.github.io/resticprofile/configuration/logs/"` CommandOutput string `mapstructure:"command-output" default:"auto" enum:"auto;log;console;all" description:"Sets the destination for command output (stderr/stdout). \"log\" sends output to the log file (if specified), \"console\" sends it to the console instead. \"auto\" sends it to \"both\" if console is a terminal otherwise to \"log\" only - see https://creativeprojects.github.io/resticprofile/configuration/logs/"` Priority string `mapstructure:"priority" default:"standard" enum:"background;standard" description:"Set the priority at which the schedule is run"` @@ -50,6 +51,7 @@ type ScheduleBaseConfig struct { // scheduleBaseConfigDefaults declares built-in scheduling defaults var scheduleBaseConfigDefaults = ScheduleBaseConfig{ Permission: "auto", + RunLevel: "auto", CommandOutput: constants.DefaultCommandOutput, Priority: "standard", LockMode: "default", @@ -64,6 +66,9 @@ func (s *ScheduleBaseConfig) init(defaults *ScheduleBaseConfig) { if s.Permission == "" { s.Permission = defaults.Permission } + if s.RunLevel == "" { + s.RunLevel = defaults.RunLevel + } if s.Log == "" { s.Log = defaults.Log } @@ -98,6 +103,7 @@ func (s *ScheduleBaseConfig) applyOverrides(section *ScheduleBaseSection) { defaults := *s // applying the settings of the section s.Permission = section.SchedulePermission + s.RunLevel = section.ScheduleRunLevel s.Log = section.ScheduleLog s.Priority = section.SchedulePriority s.LockMode = section.ScheduleLockMode diff --git a/docs/content/schedules/configuration.md b/docs/content/schedules/configuration.md index 8723d301..eedb6d44 100644 --- a/docs/content/schedules/configuration.md +++ b/docs/content/schedules/configuration.md @@ -91,6 +91,14 @@ Follow this order: - Change your permission (user to system, or system to user). - Schedule your updated profile. +## schedule-run-level + +In Windows Task Manager, you can also specify a scheduled task privilege level. + +The `schedule-run-level` parameter accepts three values: +- `lowest`: Runs the task with the least user privileges. +- `highest`: Runs the task with the highest user privileges available. +- `auto`: Uses `highest` if `schedule-permission` is set to `system`. Otherwise, defaults to `lowest`. ## schedule-lock-mode diff --git a/schedule/config.go b/schedule/config.go index 8a303004..d46f1502 100644 --- a/schedule/config.go +++ b/schedule/config.go @@ -12,6 +12,7 @@ type Config struct { CommandName string // restic command Schedules []string Permission string + RunLevel string WorkingDirectory string Command string // path to resticprofile executable Arguments CommandArguments diff --git a/schedule/handler_windows.go b/schedule/handler_windows.go index 8189080b..bcbe7dbb 100644 --- a/schedule/handler_windows.go +++ b/schedule/handler_windows.go @@ -62,6 +62,7 @@ func (h *HandlerWindows) CreateJob(job *Config, schedules []*calendar.Event, per Arguments: job.Arguments.String(), WorkingDirectory: job.WorkingDirectory, JobDescription: job.JobDescription, + RunLevel: job.RunLevel, } err := schtasks.Create(jobConfig, schedules, perm) if err != nil { diff --git a/schedule_jobs.go b/schedule_jobs.go index f193913d..539a27fa 100644 --- a/schedule_jobs.go +++ b/schedule_jobs.go @@ -226,6 +226,7 @@ func scheduleToConfig(sched *config.Schedule) *schedule.Config { CommandName: origin.Command, Schedules: sched.Schedules, Permission: sched.Permission, + RunLevel: sched.RunLevel, WorkingDirectory: "", Command: "", Arguments: schedule.NewCommandArguments(nil), diff --git a/schtasks/config.go b/schtasks/config.go index c1f27fd8..ff234aa4 100644 --- a/schtasks/config.go +++ b/schtasks/config.go @@ -9,4 +9,5 @@ type Config struct { Arguments string WorkingDirectory string JobDescription string + RunLevel string } diff --git a/schtasks/taskscheduler.go b/schtasks/taskscheduler.go index b6b8e912..8be5b3dc 100644 --- a/schtasks/taskscheduler.go +++ b/schtasks/taskscheduler.go @@ -54,10 +54,24 @@ func Create(config *Config, schedules []*calendar.Event, permission Permission) task := createTaskDefinition(config, schedules) task.RegistrationInfo.URI = taskPath + switch config.RunLevel { + case "lowest": + task.Principals.Principal.RunLevel = RunLevelLeastPrivilege + + case "highest": + task.Principals.Principal.RunLevel = RunLevelHighest + + default: + if permission == SystemAccount { + task.Principals.Principal.RunLevel = RunLevelHighest + } else { + task.Principals.Principal.RunLevel = RunLevelDefault + } + } + switch permission { case SystemAccount: task.Principals.Principal.LogonType = LogonTypeServiceForUser - task.Principals.Principal.RunLevel = RunLevelHighest task.Principals.Principal.UserId = serviceAccount task.RegistrationInfo.SecurityDescriptor = securityDescriptor // allow authenticated users to read the task status diff --git a/schtasks/taskscheduler_test.go b/schtasks/taskscheduler_test.go index 4b68520b..6c4d0359 100644 --- a/schtasks/taskscheduler_test.go +++ b/schtasks/taskscheduler_test.go @@ -279,3 +279,11 @@ func TestTaskSchedulerIntegration(t *testing.T) { }) } } + +func TestRunLevelOption(t *testing.T) { + // atm it's impossible to test `run-level` option + // due to lack info about task `run-level` in schtasks output + // such info only present in xml format, we are currently using csv + // see related: https://github.com/creativeprojects/resticprofile/issues/545 + // TODO: implement test when possible +}