Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
gitpod-io
GitHub Repository: gitpod-io/gitpod
Path: blob/main/components/ws-daemon/pkg/iws/uidmap.go
2499 views
1
// Copyright (c) 2020 Gitpod GmbH. All rights reserved.
2
// Licensed under the GNU Affero General Public License (AGPL).
3
// See License.AGPL.txt in the project root for license information.
4
5
package iws
6
7
import (
8
"bufio"
9
"context"
10
"fmt"
11
"os"
12
"path/filepath"
13
"strconv"
14
"strings"
15
16
"golang.org/x/xerrors"
17
"google.golang.org/grpc/codes"
18
"google.golang.org/grpc/status"
19
"google.golang.org/protobuf/encoding/protojson"
20
21
"github.com/gitpod-io/gitpod/common-go/log"
22
"github.com/gitpod-io/gitpod/ws-daemon/api"
23
"github.com/gitpod-io/gitpod/ws-daemon/pkg/container"
24
)
25
26
// Uidmapper provides UID mapping services for creating Linux user namespaces
27
// from within a workspace.
28
type Uidmapper struct {
29
Config UidmapperConfig
30
Runtime container.Runtime
31
}
32
33
// UidmapperConfig configures the UID mapper
34
type UidmapperConfig struct {
35
// ProcLocation is the location of the node's proc filesystem
36
ProcLocation string `json:"procLocation"`
37
// RootRange is the range to which one can map the root (uid 0) user/group to
38
RootRange UIDRange `json:"rootUIDRange"`
39
// UserRange is the range to which any other user can be mapped to
40
UserRange []UIDRange `json:"userUIDRange"`
41
}
42
43
// UIDRange represents a range of UID/GID's
44
type UIDRange struct {
45
Start uint32 `json:"start"`
46
Size uint32 `json:"size"`
47
}
48
49
// Contains returns true if the other range is contained by this one
50
func (r UIDRange) Contains(start, size uint32) bool {
51
if start < r.Start {
52
return false
53
}
54
if size > r.Size {
55
return false
56
}
57
return true
58
}
59
60
// HandleUIDMappingRequest performs a UID mapping request
61
func (m *Uidmapper) HandleUIDMappingRequest(ctx context.Context, req *api.WriteIDMappingRequest, containerID container.ID, instanceID string) (err error) {
62
var reqjson []byte
63
reqjson, err = protojson.Marshal(req)
64
if err != nil {
65
return err
66
}
67
68
log := log.WithFields(map[string]interface{}{
69
"req": string(reqjson),
70
"containerID": containerID,
71
"instanceId": instanceID,
72
})
73
74
log.Debug("received UID mapping request")
75
76
err = m.validateMapping(req.Mapping)
77
if err != nil {
78
return err
79
}
80
81
containerPID, err := m.Runtime.ContainerPID(ctx, containerID)
82
if err != nil {
83
log.WithError(err).Error("handleUIDMappingRequest: cannot get containerPID")
84
return status.Error(codes.Internal, "cannot establish mapping")
85
}
86
87
log.WithField("containerPID", containerPID)
88
89
hostPID, err := m.findHostPID(uint64(containerPID), uint64(req.Pid))
90
if err != nil {
91
log.WithError(err).Error("handleUIDMappingRequest: cannot find PID on host")
92
return status.Error(codes.InvalidArgument, "cannot find PID")
93
}
94
95
log = log.WithField("hostPID", hostPID)
96
97
err = WriteMapping(hostPID, req.Gid, req.Mapping)
98
if err != nil {
99
log.WithError(err).Error("handleUIDMappingRequest: cannot write mapping")
100
return status.Error(codes.FailedPrecondition, "cannot write mapping")
101
}
102
103
log.Debug("established UID/GID mapping")
104
105
return nil
106
}
107
108
func (m *Uidmapper) validateMapping(mapping []*api.WriteIDMappingRequest_Mapping) error {
109
for _, mp := range mapping {
110
if mp.ContainerId == 0 && !m.Config.RootRange.Contains(mp.HostId, mp.Size) {
111
return status.Error(codes.InvalidArgument, "mapping for UID 0 is out of range")
112
}
113
if mp.ContainerId > 0 {
114
var found bool
115
for _, r := range m.Config.UserRange {
116
if r.Contains(mp.HostId, mp.Size) {
117
found = true
118
break
119
}
120
}
121
if !found {
122
return status.Errorf(codes.InvalidArgument, "mapping for UID %d is out of range", mp.ContainerId)
123
}
124
}
125
}
126
return nil
127
}
128
129
// WriteMapping writes uid_map and gid_map
130
func WriteMapping(hostPID uint64, gid bool, mapping []*api.WriteIDMappingRequest_Mapping) (err error) {
131
// Note: unlike shadow's newuidmap/newgidmap we do not set /proc/PID/setgroups to deny because:
132
// - we're writing from a privileged process, hence don't trip that restriction introduced in Linux 3.39
133
// - denying setgroups would prevent any meaningfull use of the NS mapped "root" user (e.g. breaks apt-get)
134
135
var fc string
136
for _, m := range mapping {
137
fc += fmt.Sprintf("%d %d %d\n", m.ContainerId, m.HostId, m.Size)
138
}
139
140
var fn string
141
if gid {
142
fn = "gid_map"
143
} else {
144
fn = "uid_map"
145
}
146
147
pth := fmt.Sprintf("/proc/%d/%s", hostPID, fn)
148
log.WithField("path", pth).WithField("fc", fc).Debug("attempting to write UID mapping")
149
150
err = os.WriteFile(pth, []byte(fc), 0644)
151
if err != nil {
152
return xerrors.Errorf("cannot write UID/GID mapping: %w", err)
153
}
154
155
return nil
156
}
157
158
// findHosPID translates an in-container PID to the root PID namespace.
159
func (m *Uidmapper) findHostPID(containerPID, inContainerPID uint64) (uint64, error) {
160
paths := []string{fmt.Sprint(containerPID)}
161
seen := make(map[string]struct{})
162
163
for {
164
if len(paths) == 0 {
165
return 0, xerrors.Errorf("cannot find in-container PID %d on the node", inContainerPID)
166
}
167
168
p := paths[0]
169
paths = paths[1:]
170
171
if _, ok := seen[p]; ok {
172
continue
173
}
174
seen[p] = struct{}{}
175
176
p = filepath.Join(m.Config.ProcLocation, p)
177
pid, nspid, err := readStatusFile(filepath.Join(p, "status"))
178
if err != nil {
179
log.WithField("file", filepath.Join(p, "status")).WithError(err).Error("findHostPID: cannot read PID file")
180
continue
181
}
182
for _, nsp := range nspid {
183
if nsp == inContainerPID {
184
return pid, nil
185
}
186
}
187
188
taskfn := filepath.Join(p, "task")
189
tasks, err := os.ReadDir(taskfn)
190
if err != nil {
191
continue
192
}
193
for _, task := range tasks {
194
cldrn, err := os.ReadFile(filepath.Join(taskfn, task.Name(), "children"))
195
if err != nil {
196
continue
197
}
198
paths = append(paths, strings.Fields(string(cldrn))...)
199
}
200
}
201
}
202
203
func (m *Uidmapper) findSupervisorPID(containerPID uint64) (uint64, error) {
204
paths := []string{fmt.Sprint(containerPID)}
205
seen := make(map[string]struct{})
206
207
for {
208
if len(paths) == 0 {
209
return 0, xerrors.Errorf("cannot find supervisor PID for container %v", containerPID)
210
}
211
212
p := paths[0]
213
paths = paths[1:]
214
215
if _, ok := seen[p]; ok {
216
continue
217
}
218
seen[p] = struct{}{}
219
220
procPath := filepath.Join(m.Config.ProcLocation, p)
221
cmdline, err := os.ReadFile(filepath.Join(procPath, "cmdline"))
222
if err != nil {
223
log.WithField("file", filepath.Join(procPath, "cmdline")).WithError(err).Error("cannot read cmdline")
224
continue
225
}
226
227
if strings.HasPrefix(string(cmdline), "supervisor") {
228
pid, err := strconv.ParseUint(p, 10, 64)
229
if err != nil {
230
return 0, err
231
}
232
233
return pid, nil
234
}
235
236
taskfn := filepath.Join(procPath, "task")
237
tasks, err := os.ReadDir(taskfn)
238
if err != nil {
239
continue
240
}
241
for _, task := range tasks {
242
cldrn, err := os.ReadFile(filepath.Join(taskfn, task.Name(), "children"))
243
if err != nil {
244
continue
245
}
246
paths = append(paths, strings.Fields(string(cldrn))...)
247
}
248
}
249
}
250
251
func readStatusFile(fn string) (pid uint64, nspid []uint64, err error) {
252
f, err := os.Open(fn)
253
if err != nil {
254
return
255
}
256
defer f.Close()
257
258
scanner := bufio.NewScanner(f)
259
for scanner.Scan() {
260
line := scanner.Text()
261
if strings.HasPrefix(line, "Pid:") {
262
pid, err = strconv.ParseUint(strings.TrimSpace(strings.TrimPrefix(line, "Pid:")), 10, 64)
263
if err != nil {
264
err = xerrors.Errorf("cannot parse pid in %s: %w", fn, err)
265
return
266
}
267
}
268
if strings.HasPrefix(line, "NSpid:") {
269
fields := strings.Fields(strings.TrimSpace(strings.TrimPrefix(line, "NSpid:")))
270
for _, fld := range fields {
271
var npid uint64
272
npid, err = strconv.ParseUint(fld, 10, 64)
273
if err != nil {
274
err = xerrors.Errorf("cannot parse NSpid %v in %s: %w", fld, fn, err)
275
return
276
}
277
278
nspid = append(nspid, npid)
279
}
280
}
281
}
282
if err = scanner.Err(); err != nil {
283
return
284
}
285
286
return
287
}
288
289