forked from openshift/builder
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathuserns.go
More file actions
165 lines (149 loc) · 5.33 KB
/
userns.go
File metadata and controls
165 lines (149 loc) · 5.33 KB
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
package main
import (
"fmt"
"os"
"strconv"
"strings"
"syscall"
"github.com/containers/storage/pkg/unshare"
specs "github.com/opencontainers/runtime-spec/specs-go"
"github.com/syndtr/gocapability/capability"
"k8s.io/klog/v2"
)
const usernsMarkerVariable = unshare.UsernsEnvName
func parseIDMappings(uidmap, gidmap string) ([]specs.LinuxIDMapping, []specs.LinuxIDMapping) {
// helper for parsing a string of the form "container:host:size[,container:host:size...]"
parseMapping := func(what, mapSpec string) []specs.LinuxIDMapping {
var mapping []specs.LinuxIDMapping
for _, entry := range strings.Split(mapSpec, ",") {
if entry == "" {
continue
}
triple := strings.Split(entry, ":")
if len(triple) != 3 {
klog.Errorf("Invalid format for %s entry %q\n", what, entry)
return nil
}
containerID, err := strconv.ParseUint(triple[0], 10, 32)
if err != nil {
klog.Errorf("Invalid format for %s entry %q container ID %q: %v\n", what, entry, triple[0], err)
return nil
}
hostID, err := strconv.ParseUint(triple[1], 10, 32)
if err != nil {
klog.Errorf("Invalid format for %s entry %q host ID %q: %v\n", what, entry, triple[1], err)
return nil
}
size, err := strconv.ParseUint(triple[2], 10, 32)
if err != nil {
klog.Errorf("Invalid format for %s entry %q size %q: %v\n", what, entry, triple[2], err)
return nil
}
mapping = append(mapping, specs.LinuxIDMapping{
ContainerID: uint32(containerID),
HostID: uint32(hostID),
Size: uint32(size),
})
}
return mapping
}
// Return what's already in place, or whatever was specified.
UIDs, GIDs, err := unshare.GetHostIDMappings("")
if err != nil {
klog.Fatalf("Error reading current ID mappings: %v\n", err)
}
if os.Geteuid() != 0 {
uid := fmt.Sprintf("%d", os.Geteuid())
UIDs, GIDs, err = unshare.GetSubIDMappings(uid, uid)
if err != nil {
klog.Fatalf("Error reading ID mappings for %s: %v\n", uid, err)
}
} else {
for i := range UIDs {
UIDs[i].HostID = UIDs[i].ContainerID
}
for i := range GIDs {
GIDs[i].HostID = GIDs[i].ContainerID
}
}
if uidMappings := parseMapping("uidmap", uidmap); len(uidMappings) != 0 {
UIDs = uidMappings
}
if gidMappings := parseMapping("gidmap", gidmap); len(gidMappings) != 0 {
GIDs = gidMappings
}
return UIDs, GIDs
}
// inOurUserNamespace returns true if we've already set up a user namespace of
// our own
func inOurUserNamespace() bool {
return os.Getenv(usernsMarkerVariable) != ""
}
// isNodeDefaultMapping returns true if the current ID map is the default node
// ID map: one range starting at 0, with length 2^32-1, mapped to itself
func isNodeDefaultMapping(m []specs.LinuxIDMapping) bool {
return len(m) == 1 && m[0].ContainerID == 0 && m[0].HostID == 0 && m[0].Size == 0xffffffff
}
func logUserNamespaceIDMappings() {
// If we've already done all of this, there's no need to do it again.
if inOurUserNamespace() {
return
}
// Log the ID maps we were started with.
UIDs, GIDs, err := unshare.GetHostIDMappings("")
if err != nil {
klog.Fatalf("Error reading current ID mappings: %v\n", err)
}
if !isNodeDefaultMapping(UIDs) || !isNodeDefaultMapping(GIDs) {
uidMap, gidMap := "[", "["
for i, entry := range UIDs {
if i > 0 {
uidMap += ", "
}
uidMap += fmt.Sprintf("(%d:%d:%d)", entry.ContainerID, entry.HostID, entry.Size)
}
for i, entry := range GIDs {
if i > 0 {
gidMap += ", "
}
gidMap += fmt.Sprintf("(%d:%d:%d)", entry.ContainerID, entry.HostID, entry.Size)
}
uidMap += "]"
gidMap += "]"
klog.V(2).Infof("Started in kernel user namespace as %d:%d with UID map %s and GID map %s.", os.Getuid(), os.Getgid(), uidMap, gidMap)
} else {
klog.V(2).Infof("Started in node (default) kernel user namespace as %d:%d.", os.Getuid(), os.Getgid())
}
}
func maybeReexecUsingUserNamespace(uidmap string, useNewuidmap bool, gidmap string, useNewgidmap bool) {
// If we've already done all of this, there's no need to do it again.
if inOurUserNamespace() {
return
}
// If there's actually nothing to do, just return.
if uidmap == "" && gidmap == "" && os.Geteuid() == 0 {
if caps, err := capability.NewPid2(0); err == nil {
if err := caps.Load(); err == nil && caps.Get(capability.EFFECTIVE, capability.CAP_SYS_ADMIN) {
return
}
}
}
// Parse our --uidmap and --gidmap flags into ID mappings and re-exec ourselves.
cmd := unshare.Command(append([]string{fmt.Sprintf("%s-in-a-user-namespace", os.Args[0])}, os.Args[1:]...)...)
// Set up a new user namespace with the ID mappings.
cmd.UnshareFlags = syscall.CLONE_NEWUSER | syscall.CLONE_NEWNS
cmd.UidMappings, cmd.GidMappings = parseIDMappings(uidmap, gidmap)
cmd.UseNewuidmap, cmd.UseNewgidmap = useNewuidmap, useNewgidmap
cmd.GidMappingsEnableSetgroups = true
// Set markers so that we know we've done all of this already, and set
// HOME so that the child doesn't try to read configuration from
// /root/.config, which it can't if it's another user that's being told
// it's root because it's running in a user namespace, which would
// trigger a permissions error. HOME also needs to be writable.
cmd.Env = append(os.Environ(), usernsMarkerVariable+"=done", "HOME=/var/lib/containers")
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
unshare.ExecRunnable(cmd, nil)
klog.Fatalf("Internal error: should not have gotten back from ExecRunnable().\n")
}