1 package configs
2
3 import (
4 "bytes"
5 "encoding/json"
6 "fmt"
7 "os/exec"
8 "time"
9
10 "github.com/sirupsen/logrus"
11
12 "github.com/opencontainers/runc/libcontainer/devices"
13 "github.com/opencontainers/runtime-spec/specs-go"
14 )
15
16 type Rlimit struct {
17 Type int `json:"type"`
18 Hard uint64 `json:"hard"`
19 Soft uint64 `json:"soft"`
20 }
21
22
23 type IDMap struct {
24 ContainerID int64 `json:"container_id"`
25 HostID int64 `json:"host_id"`
26 Size int64 `json:"size"`
27 }
28
29
30
31
32
33 type Seccomp struct {
34 DefaultAction Action `json:"default_action"`
35 Architectures []string `json:"architectures"`
36 Syscalls []*Syscall `json:"syscalls"`
37 DefaultErrnoRet *uint `json:"default_errno_ret"`
38 ListenerPath string `json:"listener_path,omitempty"`
39 ListenerMetadata string `json:"listener_metadata,omitempty"`
40 }
41
42
43 type Action int
44
45 const (
46 Kill Action = iota + 1
47 Errno
48 Trap
49 Allow
50 Trace
51 Log
52 Notify
53 KillThread
54 KillProcess
55 )
56
57
58 type Operator int
59
60 const (
61 EqualTo Operator = iota + 1
62 NotEqualTo
63 GreaterThan
64 GreaterThanOrEqualTo
65 LessThan
66 LessThanOrEqualTo
67 MaskEqualTo
68 )
69
70
71 type Arg struct {
72 Index uint `json:"index"`
73 Value uint64 `json:"value"`
74 ValueTwo uint64 `json:"value_two"`
75 Op Operator `json:"op"`
76 }
77
78
79 type Syscall struct {
80 Name string `json:"name"`
81 Action Action `json:"action"`
82 ErrnoRet *uint `json:"errnoRet"`
83 Args []*Arg `json:"args"`
84 }
85
86
87
88
89
90 type Config struct {
91
92
93 NoPivotRoot bool `json:"no_pivot_root"`
94
95
96
97 ParentDeathSignal int `json:"parent_death_signal"`
98
99
100 Rootfs string `json:"rootfs"`
101
102
103 Umask *uint32 `json:"umask"`
104
105
106
107 Readonlyfs bool `json:"readonlyfs"`
108
109
110 RootPropagation int `json:"rootPropagation"`
111
112
113
114 Mounts []*Mount `json:"mounts"`
115
116
117 Devices []*devices.Device `json:"devices"`
118
119 MountLabel string `json:"mount_label"`
120
121
122 Hostname string `json:"hostname"`
123
124
125
126 Namespaces Namespaces `json:"namespaces"`
127
128
129
130 Capabilities *Capabilities `json:"capabilities"`
131
132
133 Networks []*Network `json:"networks"`
134
135
136 Routes []*Route `json:"routes"`
137
138
139
140 Cgroups *Cgroup `json:"cgroups"`
141
142
143
144 AppArmorProfile string `json:"apparmor_profile,omitempty"`
145
146
147
148 ProcessLabel string `json:"process_label,omitempty"`
149
150
151
152 Rlimits []Rlimit `json:"rlimits,omitempty"`
153
154
155
156
157
158
159 OomScoreAdj *int `json:"oom_score_adj,omitempty"`
160
161
162 UidMappings []IDMap `json:"uid_mappings"`
163
164
165 GidMappings []IDMap `json:"gid_mappings"`
166
167
168
169 MaskPaths []string `json:"mask_paths"`
170
171
172
173 ReadonlyPaths []string `json:"readonly_paths"`
174
175
176
177 Sysctl map[string]string `json:"sysctl"`
178
179
180
181
182 Seccomp *Seccomp `json:"seccomp"`
183
184
185 NoNewPrivileges bool `json:"no_new_privileges,omitempty"`
186
187
188
189 Hooks Hooks
190
191
192 Version string `json:"version"`
193
194
195 Labels []string `json:"labels"`
196
197
198
199 NoNewKeyring bool `json:"no_new_keyring"`
200
201
202
203 IntelRdt *IntelRdt `json:"intel_rdt,omitempty"`
204
205
206
207
208
209 RootlessEUID bool `json:"rootless_euid,omitempty"`
210
211
212
213 RootlessCgroups bool `json:"rootless_cgroups,omitempty"`
214 }
215
216 type (
217 HookName string
218 HookList []Hook
219 Hooks map[HookName]HookList
220 )
221
222 const (
223
224
225
226
227 Prestart HookName = "prestart"
228
229
230
231
232
233 CreateRuntime HookName = "createRuntime"
234
235
236
237
238 CreateContainer HookName = "createContainer"
239
240
241
242
243 StartContainer HookName = "startContainer"
244
245
246
247 Poststart HookName = "poststart"
248
249
250
251 Poststop HookName = "poststop"
252 )
253
254
255
256 func KnownHookNames() []string {
257 return []string{
258 string(Prestart),
259 string(CreateRuntime),
260 string(CreateContainer),
261 string(StartContainer),
262 string(Poststart),
263 string(Poststop),
264 }
265 }
266
267 type Capabilities struct {
268
269 Bounding []string
270
271 Effective []string
272
273 Inheritable []string
274
275 Permitted []string
276
277 Ambient []string
278 }
279
280 func (hooks HookList) RunHooks(state *specs.State) error {
281 for i, h := range hooks {
282 if err := h.Run(state); err != nil {
283 return fmt.Errorf("error running hook #%d: %w", i, err)
284 }
285 }
286
287 return nil
288 }
289
290 func (hooks *Hooks) UnmarshalJSON(b []byte) error {
291 var state map[HookName][]CommandHook
292
293 if err := json.Unmarshal(b, &state); err != nil {
294 return err
295 }
296
297 *hooks = Hooks{}
298 for n, commandHooks := range state {
299 if len(commandHooks) == 0 {
300 continue
301 }
302
303 (*hooks)[n] = HookList{}
304 for _, h := range commandHooks {
305 (*hooks)[n] = append((*hooks)[n], h)
306 }
307 }
308
309 return nil
310 }
311
312 func (hooks *Hooks) MarshalJSON() ([]byte, error) {
313 serialize := func(hooks []Hook) (serializableHooks []CommandHook) {
314 for _, hook := range hooks {
315 switch chook := hook.(type) {
316 case CommandHook:
317 serializableHooks = append(serializableHooks, chook)
318 default:
319 logrus.Warnf("cannot serialize hook of type %T, skipping", hook)
320 }
321 }
322
323 return serializableHooks
324 }
325
326 return json.Marshal(map[string]interface{}{
327 "prestart": serialize((*hooks)[Prestart]),
328 "createRuntime": serialize((*hooks)[CreateRuntime]),
329 "createContainer": serialize((*hooks)[CreateContainer]),
330 "startContainer": serialize((*hooks)[StartContainer]),
331 "poststart": serialize((*hooks)[Poststart]),
332 "poststop": serialize((*hooks)[Poststop]),
333 })
334 }
335
336 type Hook interface {
337
338 Run(*specs.State) error
339 }
340
341
342 func NewFunctionHook(f func(*specs.State) error) FuncHook {
343 return FuncHook{
344 run: f,
345 }
346 }
347
348 type FuncHook struct {
349 run func(*specs.State) error
350 }
351
352 func (f FuncHook) Run(s *specs.State) error {
353 return f.run(s)
354 }
355
356 type Command struct {
357 Path string `json:"path"`
358 Args []string `json:"args"`
359 Env []string `json:"env"`
360 Dir string `json:"dir"`
361 Timeout *time.Duration `json:"timeout"`
362 }
363
364
365 func NewCommandHook(cmd Command) CommandHook {
366 return CommandHook{
367 Command: cmd,
368 }
369 }
370
371 type CommandHook struct {
372 Command
373 }
374
375 func (c Command) Run(s *specs.State) error {
376 b, err := json.Marshal(s)
377 if err != nil {
378 return err
379 }
380 var stdout, stderr bytes.Buffer
381 cmd := exec.Cmd{
382 Path: c.Path,
383 Args: c.Args,
384 Env: c.Env,
385 Stdin: bytes.NewReader(b),
386 Stdout: &stdout,
387 Stderr: &stderr,
388 }
389 if err := cmd.Start(); err != nil {
390 return err
391 }
392 errC := make(chan error, 1)
393 go func() {
394 err := cmd.Wait()
395 if err != nil {
396 err = fmt.Errorf("error running hook: %w, stdout: %s, stderr: %s", err, stdout.String(), stderr.String())
397 }
398 errC <- err
399 }()
400 var timerCh <-chan time.Time
401 if c.Timeout != nil {
402 timer := time.NewTimer(*c.Timeout)
403 defer timer.Stop()
404 timerCh = timer.C
405 }
406 select {
407 case err := <-errC:
408 return err
409 case <-timerCh:
410 _ = cmd.Process.Kill()
411 <-errC
412 return fmt.Errorf("hook ran past specified timeout of %.1fs", c.Timeout.Seconds())
413 }
414 }
415
View as plain text