1
2
3 package jobcontainers
4
5 import (
6 "context"
7 "fmt"
8 "strings"
9 "unsafe"
10
11 "github.com/Microsoft/go-winio/pkg/guid"
12 "github.com/Microsoft/hcsshim/internal/log"
13 "github.com/Microsoft/hcsshim/internal/winapi"
14 "github.com/pkg/errors"
15 "golang.org/x/sys/windows"
16 )
17
18 func randomPswd() (*uint16, error) {
19 g, err := guid.NewV4()
20 if err != nil {
21 return nil, err
22 }
23 return windows.UTF16PtrFromString(g.String())
24 }
25
26 func groupExists(groupName string) bool {
27 var p *byte
28 if err := winapi.NetLocalGroupGetInfo(
29 "",
30 groupName,
31 1,
32 &p,
33 ); err != nil {
34 return false
35 }
36 defer func() {
37 _ = windows.NetApiBufferFree(p)
38 }()
39 return true
40 }
41
42
43
44
45 func makeLocalAccount(ctx context.Context, user, groupName string) (_ *uint16, err error) {
46
47 pswd, err := randomPswd()
48 if err != nil {
49 return nil, fmt.Errorf("failed to generate random password: %w", err)
50 }
51
52 userUTF16, err := windows.UTF16PtrFromString(user)
53 if err != nil {
54 return nil, fmt.Errorf("failed to encode username to UTF16: %w", err)
55 }
56
57 usr1 := &winapi.UserInfo1{
58 Name: userUTF16,
59 Password: pswd,
60 Priv: winapi.USER_PRIV_USER,
61 Flags: winapi.UF_NORMAL_ACCOUNT | winapi.UF_DONT_EXPIRE_PASSWD,
62 }
63 if err := winapi.NetUserAdd(
64 "",
65 1,
66 (*byte)(unsafe.Pointer(usr1)),
67 nil,
68 ); err != nil {
69 return nil, fmt.Errorf("failed to create user %s: %w", user, err)
70 }
71 defer func() {
72 if err != nil {
73 _ = winapi.NetUserDel("", user)
74 }
75 }()
76
77 log.G(ctx).WithField("username", user).Debug("Created local user account for job container")
78
79 sid, _, _, err := windows.LookupSID("", user)
80 if err != nil {
81 return nil, fmt.Errorf("failed to lookup SID for user %q: %w", user, err)
82 }
83
84 sids := []winapi.LocalGroupMembersInfo0{{Sid: sid}}
85 if err := winapi.NetLocalGroupAddMembers(
86 "",
87 groupName,
88 0,
89 (*byte)(unsafe.Pointer(&sids[0])),
90 1,
91 ); err != nil {
92 return nil, fmt.Errorf("failed to add user %q to the %q group: %w", user, groupName, err)
93 }
94
95 return pswd, nil
96 }
97
98
99
100
101 func (c *JobContainer) processToken(ctx context.Context, userOrGroup string) (windows.Token, error) {
102 var (
103 domain string
104 userName string
105 token windows.Token
106 )
107
108 if userOrGroup == "" {
109 return 0, errors.New("empty username or group name passed")
110 }
111
112 if groupExists(userOrGroup) {
113 userName = c.id[:winapi.UserNameCharLimit]
114 pswd, err := makeLocalAccount(ctx, userName, userOrGroup)
115 if err != nil {
116 return 0, fmt.Errorf("failed to create local account for container: %w", err)
117 }
118 if err := winapi.LogonUser(
119 windows.StringToUTF16Ptr(userName),
120 nil,
121 pswd,
122 winapi.LOGON32_LOGON_INTERACTIVE,
123 winapi.LOGON32_PROVIDER_DEFAULT,
124 &token,
125 ); err != nil {
126 return 0, fmt.Errorf("failed to logon user: %w", err)
127 }
128 c.localUserAccount = userName
129 return token, nil
130 }
131
132
133 split := strings.Split(userOrGroup, "\\")
134 if len(split) == 2 {
135 domain = split[0]
136 userName = split[1]
137 } else if len(split) == 1 {
138 userName = split[0]
139 } else {
140 return 0, fmt.Errorf("invalid user string `%s`", userOrGroup)
141 }
142
143 logonType := winapi.LOGON32_LOGON_INTERACTIVE
144
145 if domain == "NT AUTHORITY" {
146 logonType = winapi.LOGON32_LOGON_SERVICE
147 }
148
149 if err := winapi.LogonUser(
150 windows.StringToUTF16Ptr(userName),
151 windows.StringToUTF16Ptr(domain),
152 nil,
153 logonType,
154 winapi.LOGON32_PROVIDER_DEFAULT,
155 &token,
156 ); err != nil {
157 return 0, fmt.Errorf("failed to logon user: %w", err)
158 }
159 return token, nil
160 }
161
162 func openCurrentProcessToken() (windows.Token, error) {
163 var token windows.Token
164 if err := windows.OpenProcessToken(windows.CurrentProcess(), windows.TOKEN_ALL_ACCESS, &token); err != nil {
165 return 0, errors.Wrap(err, "failed to open current process token")
166 }
167 return token, nil
168 }
169
View as plain text