aboutsummaryrefslogtreecommitdiffhomepage
path: root/helper/bwrap/config.go
blob: fffddbe0c262b324683e0caba7fe70e56927d05d (plain)
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
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
package bwrap

import (
	"os"
	"strconv"
)

func (c *Config) Args() (args []string) {
	b := c.boolArgs()
	n := c.intArgs()
	g := c.interfaceArgs()
	s := c.stringArgs()
	p := c.pairArgs()

	argc := 0
	for i, arg := range b {
		if arg {
			argc += len(boolArgs[i])
		}
	}
	for _, arg := range n {
		if arg != nil {
			argc += 2
		}
	}
	for _, arg := range g {
		argc += len(arg) * 3
	}
	for _, arg := range s {
		argc += len(arg) * 2
	}
	for _, arg := range p {
		argc += len(arg) * 3
	}

	args = make([]string, 0, argc)
	for i, arg := range b {
		if arg {
			args = append(args, boolArgs[i]...)
		}
	}
	for i, arg := range n {
		if arg != nil {
			args = append(args, intArgs[i], strconv.Itoa(*arg))
		}
	}
	for i, arg := range g {
		for _, v := range arg {
			args = append(args, v.Value(interfaceArgs[i])...)
		}
	}
	for i, arg := range s {
		for _, v := range arg {
			args = append(args, stringArgs[i], v)
		}
	}
	for i, arg := range p {
		for _, v := range arg {
			args = append(args, pairArgs[i], v[0], v[1])
		}
	}

	return
}

type Config struct {
	// unshare every namespace we support by default if nil
	// (--unshare-all)
	Unshare *UnshareConfig `json:"unshare,omitempty"`
	// retain the network namespace (can only combine with nil Unshare)
	// (--share-net)
	Net bool `json:"net"`

	// disable further use of user namespaces inside sandbox and fail unless
	// further use of user namespace inside sandbox is disabled if false
	// (--disable-userns) (--assert-userns-disabled)
	UserNS bool `json:"userns"`

	// custom uid in the sandbox, requires new user namespace
	// (--uid UID)
	UID *int `json:"uid,omitempty"`
	// custom gid in the sandbox, requires new user namespace
	// (--gid GID)
	GID *int `json:"gid,omitempty"`
	// custom hostname in the sandbox, requires new uts namespace
	// (--hostname NAME)
	Hostname string `json:"hostname,omitempty"`

	// change directory
	// (--chdir DIR)
	Chdir string `json:"chdir,omitempty"`
	// unset all environment variables
	// (--clearenv)
	Clearenv bool `json:"clearenv"`
	// set environment variable
	// (--setenv VAR VALUE)
	SetEnv map[string]string `json:"setenv,omitempty"`
	// unset environment variables
	// (--unsetenv VAR)
	UnsetEnv []string `json:"unsetenv,omitempty"`

	// take a lock on file while sandbox is running
	// (--lock-file DEST)
	LockFile []string `json:"lock_file,omitempty"`

	// bind mount host path on sandbox
	// (--bind SRC DEST)
	Bind [][2]string `json:"bind,omitempty"`
	// equal to Bind but ignores non-existent host path
	// (--bind-try SRC DEST)
	BindTry [][2]string `json:"bind_try,omitempty"`

	// bind mount host path on sandbox, allowing device access
	// (--dev-bind SRC DEST)
	DevBind [][2]string `json:"dev_bind,omitempty"`
	// equal to DevBind but ignores non-existent host path
	// (--dev-bind-try SRC DEST)
	DevBindTry [][2]string `json:"dev_bind_try,omitempty"`

	// bind mount host path readonly on sandbox
	// (--ro-bind SRC DEST)
	ROBind [][2]string `json:"ro_bind,omitempty"`
	// equal to ROBind but ignores non-existent host path
	// (--ro-bind-try SRC DEST)
	ROBindTry [][2]string `json:"ro_bind_try,omitempty"`

	// remount path as readonly; does not recursively remount
	// (--remount-ro DEST)
	RemountRO []string `json:"remount_ro,omitempty"`

	// mount new procfs in sandbox
	// (--proc DEST)
	Procfs []string `json:"proc,omitempty"`
	// mount new dev in sandbox
	// (--dev DEST)
	DevTmpfs []string `json:"dev,omitempty"`
	// mount new tmpfs in sandbox
	// (--tmpfs DEST)
	Tmpfs []PermConfig[TmpfsConfig] `json:"tmpfs,omitempty"`
	// mount new mqueue in sandbox
	// (--mqueue DEST)
	Mqueue []string `json:"mqueue,omitempty"`
	// create dir in sandbox
	// (--dir DEST)
	Dir []PermConfig[string] `json:"dir,omitempty"`
	// create symlink within sandbox
	// (--symlink SRC DEST)
	Symlink []PermConfig[[2]string] `json:"symlink,omitempty"`

	// change permissions (must already exist)
	// (--chmod OCTAL PATH)
	Chmod map[string]os.FileMode `json:"chmod,omitempty"`

	// create a new terminal session
	// (--new-session)
	NewSession bool `json:"new_session"`
	// kills with SIGKILL child process (COMMAND) when bwrap or bwrap's parent dies.
	// (--die-with-parent)
	DieWithParent bool `json:"die_with_parent"`
	// do not install a reaper process with PID=1
	// (--as-pid-1)
	AsInit bool `json:"as_init"`

	/* unmapped options include:
	    --unshare-user-try           Create new user namespace if possible else continue by skipping it
	    --unshare-cgroup-try         Create new cgroup namespace if possible else continue by skipping it
	    --userns FD                  Use this user namespace (cannot combine with --unshare-user)
	    --userns2 FD                 After setup switch to this user namespace, only useful with --userns
	    --pidns FD                   Use this pid namespace (as parent namespace if using --unshare-pid)
		--sync-fd FD                 Keep this fd open while sandbox is running
	    --exec-label LABEL           Exec label for the sandbox
	    --file-label LABEL           File label for temporary sandbox content
	    --file FD DEST               Copy from FD to destination DEST
	    --bind-data FD DEST          Copy from FD to file which is bind-mounted on DEST
	    --ro-bind-data FD DEST       Copy from FD to file which is readonly bind-mounted on DEST
	    --seccomp FD                 Load and use seccomp rules from FD (not repeatable)
	    --add-seccomp-fd FD          Load and use seccomp rules from FD (repeatable)
	    --block-fd FD                Block on FD until some data to read is available
	    --userns-block-fd FD         Block on FD until the user namespace is ready
	    --info-fd FD                 Write information about the running container to FD
	    --json-status-fd FD          Write container status to FD as multiple JSON documents
	    --cap-add CAP                Add cap CAP when running as privileged user
	    --cap-drop CAP               Drop cap CAP when running as privileged user

	among which --args is used internally for passing arguments */
}

type UnshareConfig struct {
	// (--unshare-user)
	// create new user namespace
	User bool `json:"user"`
	// (--unshare-ipc)
	// create new ipc namespace
	IPC bool `json:"ipc"`
	// (--unshare-pid)
	// create new pid namespace
	PID bool `json:"pid"`
	// (--unshare-net)
	// create new network namespace
	Net bool `json:"net"`
	// (--unshare-uts)
	// create new uts namespace
	UTS bool `json:"uts"`
	// (--unshare-cgroup)
	// create new cgroup namespace
	CGroup bool `json:"cgroup"`
}

type TmpfsConfig struct {
	// set size of tmpfs
	// (--size BYTES)
	Size int `json:"size,omitempty"`
	// mount point of new tmpfs
	// (--tmpfs DEST)
	Dir string `json:"dir"`
}

type argOf interface {
	Value(arg string) (args []string)
}

func copyToArgOfSlice[T [2]string | string | TmpfsConfig](src []PermConfig[T]) (dst []argOf) {
	dst = make([]argOf, len(src))
	for i, arg := range src {
		dst[i] = arg
	}
	return
}

type PermConfig[T [2]string | string | TmpfsConfig] struct {
	// set permissions of next argument
	// (--perms OCTAL)
	Mode *os.FileMode `json:"mode,omitempty"`
	// path to get the new permission
	// (--bind-data, --file, etc.)
	Path T
}

func (p PermConfig[T]) Value(arg string) (args []string) {
	// max possible size
	if p.Mode != nil {
		args = make([]string, 0, 6)
		args = append(args, "--perms", strconv.Itoa(int(*p.Mode)))
	} else {
		args = make([]string, 0, 4)
	}

	switch v := any(p.Path).(type) {
	case string:
		args = append(args, arg, v)
		return
	case [2]string:
		args = append(args, arg, v[0], v[1])
		return
	case TmpfsConfig:
		if arg != "--tmpfs" {
			panic("unreachable")
		}

		if v.Size > 0 {
			args = append(args, "--size", strconv.Itoa(v.Size))
		}
		args = append(args, arg, v.Dir)
		return
	default:
		panic("unreachable")
	}
}