forked from taskcluster/taskcluster
-
Notifications
You must be signed in to change notification settings - Fork 0
/
multiuser.go
220 lines (205 loc) · 6.72 KB
/
multiuser.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
// +build multiuser
package main
import (
"encoding/json"
"errors"
"fmt"
"log"
"os"
"path/filepath"
"strings"
"time"
"github.com/taskcluster/taskcluster/v42/workers/generic-worker/fileutil"
"github.com/taskcluster/taskcluster/v42/workers/generic-worker/process"
"github.com/taskcluster/taskcluster/v42/workers/generic-worker/runtime"
)
const (
engine = "multiuser"
)
func secure(configFile string) {
if !config.RunTasksAsCurrentUser {
secureError := fileutil.SecureFiles(configFile)
exitOnError(CANT_SECURE_CONFIG, secureError, "Not able to secure config file %q", configFile)
}
}
func PlatformTaskEnvironmentSetup(taskDirName string) (reboot bool) {
reboot = true
_, err := os.Stat("next-task-user.json")
if err == nil {
_, err = fileutil.Copy("current-task-user.json", "next-task-user.json")
if err != nil {
panic(err)
}
err = fileutil.SecureFiles("current-task-user.json")
if err != nil {
panic(err)
}
taskUserCredentials, err := StoredUserCredentials()
if err != nil {
panic(err)
}
err = runtime.WaitForLoginCompletion(5 * time.Minute)
if err != nil {
panic(err)
}
interactiveUsername, err := runtime.InteractiveUsername()
if err != nil {
panic(err)
}
if taskUserCredentials.Name != interactiveUsername {
panic(fmt.Errorf("Interactive username %v does not match task user %v from next-task-user.json file", interactiveUsername, taskUserCredentials.Name))
}
reboot = false
pd, err := process.NewPlatformData(config.RunTasksAsCurrentUser)
if err != nil {
panic(err)
}
taskContext = &TaskContext{
User: taskUserCredentials,
TaskDir: filepath.Join(config.TasksDir, interactiveUsername),
pd: pd,
}
// At this point, we know we have already booted into the new task user, and the user
// is logged in.
// Note we don't create task directory before logging in, since
// if the task directory is also the user profile home, this
// would mess up the windows logon process.
err = os.MkdirAll(taskContext.TaskDir, 0777) // note: 0777 is mostly ignored on windows
if err != nil {
panic(err)
}
// Make sure task user has full control of task directory. Due to
// https://bugzilla.mozilla.org/show_bug.cgi?id=1439588#c38 we can't
// assume previous MkdirAll has granted this permission.
log.Printf("Granting %v control of %v", taskContext.User.Name, taskContext.TaskDir)
err = makeFileOrDirReadWritableForUser(false, taskContext.TaskDir, taskContext.User)
if err != nil {
panic(err)
}
if script := config.RunAfterUserCreation; script != "" {
// See https://bugzil.la/1559210
// Regardless of whether we are running tasks as current user or
// not, task initialisation steps should be run as task user.
pdTaskUser, err := process.TaskUserPlatformData()
if err != nil {
panic(err)
}
command, err := process.NewCommand([]string{script}, taskContext.TaskDir, nil, pdTaskUser)
if err != nil {
panic(err)
}
command.DirectOutput(os.Stdout)
result := command.Execute()
log.Printf("%v", result)
switch {
case result.Failed():
panic(result.FailureCause())
case result.Crashed():
panic(result.CrashCause())
}
}
// If there is precisely one more task to run, no need to create a
// future task user, as we already have a task user created for the
// current task, which we found in the Windows registry settings for
// auto-logon.
//
// This also protects against generic-worker tests creating task users,
// since in tests we always set NumberOfTasksToRun to 1. We don't want
// tests to create OS users since the new users can only be used after
// a reboot and we can't reboot mid-task in a CI test. Therefore we
// allow the hosting generic-worker to create a single task user for
// the CI task run, and the tests for the current CI task all use this
// task user, whose credentials they find in the Windows logon regsitry
// settings.
if config.NumberOfTasksToRun == 1 {
return
}
}
// Bug 1533694
//
// Create user for subsequent task run already, before we've run current
// task, in case worker restarts unexpectedly during current task, due to
// e.g. Blue Screen of Death.
// Regardless of whether we run tasks as current user or not, we should
// make sure there is a task user created - since runTasksAsCurrentUser is
// now only something for CI so on Windows a generic-worker test can
// execute in the context of a Windows Service running under LocalSystem
// account. Username can only be 20 chars, uuids are too long, therefore
// use prefix (5 chars) plus seconds since epoch (10 chars).
nextTaskUser := &runtime.OSUser{
Name: taskDirName,
Password: runtime.GeneratePassword(),
}
err = nextTaskUser.CreateNew(false)
if err != nil {
panic(err)
}
PreRebootSetup(nextTaskUser)
// configure worker to auto-login to this newly generated user account
err = runtime.SetAutoLogin(nextTaskUser)
if err != nil {
panic(err)
}
err = fileutil.WriteToFileAsJSON(nextTaskUser, "next-task-user.json")
if err != nil {
panic(err)
}
err = fileutil.SecureFiles("next-task-user.json")
if err != nil {
panic(err)
}
return
}
// Only return critical errors
func purgeOldTasks() error {
if !config.CleanUpTaskDirs {
log.Printf("WARNING: Not purging previous task directories/users since config setting cleanUpTaskDirs is false")
return nil
}
deleteTaskDirs(runtime.UserHomeDirectoriesParent(), taskContext.User.Name, runtime.AutoLogonUser())
deleteTaskDirs(config.TasksDir, taskContext.User.Name, runtime.AutoLogonUser())
// regardless of whether we are running as current user or not, we should purge old task users
err := deleteExistingOSUsers()
if err != nil {
log.Printf("Could not delete old task users:\n%v", err)
}
return nil
}
func deleteExistingOSUsers() (err error) {
log.Print("Looking for existing task users to delete...")
userAccounts, err := runtime.ListUserAccounts()
if err != nil {
return
}
allErrors := []string{}
for _, username := range userAccounts {
if strings.HasPrefix(username, "task_") && username != taskContext.User.Name && username != runtime.AutoLogonUser() {
log.Print("Attempting to remove user " + username + "...")
err2 := runtime.DeleteUser(username)
if err2 != nil {
allErrors = append(allErrors, fmt.Sprintf("Could not remove user account %v: %v", username, err2))
}
}
}
if len(allErrors) > 0 {
err = errors.New(strings.Join(allErrors, "\n"))
}
return
}
func StoredUserCredentials() (*runtime.OSUser, error) {
credsFile, err := os.Open("current-task-user.json")
if err != nil {
return nil, err
}
defer func() {
credsFile.Close()
}()
decoder := json.NewDecoder(credsFile)
decoder.DisallowUnknownFields()
var user runtime.OSUser
err = decoder.Decode(&user)
if err != nil {
panic(err)
}
return &user, nil
}