this repo has no description
6
fork

Configure Feed

Select the types of activity you want to include in your feed.

Make runc the main process engine

+653 -52
+1 -1
src/lib/engine.ml
··· 27 27 28 28 val run : 29 29 config -> 30 - _ Eio.Path.t -> 30 + Eio.Fs.dir_ty Eio.Path.t -> 31 31 _ Eio.Time.clock -> 32 32 Eio_unix.Process.mgr_ty Eio_unix.Process.mgr -> 33 33 entry History.t * ctx ->
+22 -3
src/lib/shelter/config.ml
··· 1 - type t = { no_diffing : bool; no_ebpf : bool } 1 + type t = { 2 + no_diffing : bool; 3 + no_ebpf : bool; 4 + no_runc : bool; 5 + image : string; 6 + shell : string; 7 + } 2 8 3 9 let cmdliner = 4 10 let open Cmdliner in ··· 10 16 let doc = "Disable eBPF." in 11 17 Arg.(value & flag & info [ "no-ebpf" ] ~doc) 12 18 in 19 + let image = 20 + let doc = "Base image to start Shelter with." in 21 + Arg.(value & opt string "alpine" & info [ "image" ] ~doc) 22 + in 23 + let shell = 24 + let doc = "Path to the shell (e.g. /bin/ash)" in 25 + Arg.(value & opt string "/bin/ash" & info [ "shell" ] ~doc) 26 + in 27 + let no_runc = 28 + let doc = "Disable RUNC and use Void processes." in 29 + Arg.(value & flag & info [ "no-runc" ] ~doc) 30 + in 13 31 Term.( 14 - const (fun no_diffing no_ebpf -> { no_diffing; no_ebpf }) 15 - $ no_diffing $ no_ebpf) 32 + const (fun no_diffing no_ebpf no_runc image shell -> 33 + { no_diffing; no_ebpf; no_runc; image; shell }) 34 + $ no_diffing $ no_ebpf $ no_runc $ image $ shell)
+29 -2
src/lib/shelter/fetch.ml
··· 1 1 let ( / ) = Eio.Path.( / ) 2 + let replace_slash s = String.split_on_char '/' s |> String.concat "-" 3 + 4 + let get_user proc image = 5 + Eio.Process.parse_out proc Eio.Buf_read.take_all 6 + [ 7 + "docker"; 8 + "image"; 9 + "inspect"; 10 + "--format"; 11 + {|{{.Config.User}}|}; 12 + "--"; 13 + image; 14 + ] 15 + |> String.trim 16 + 17 + let get_env proc image = 18 + Eio.Process.parse_out proc Eio.Buf_read.take_all 19 + [ 20 + "docker"; 21 + "image"; 22 + "inspect"; 23 + "--format"; 24 + {|{{range .Config.Env}}{{print . "\x00"}}{{end}}|}; 25 + "--"; 26 + image; 27 + ] 28 + |> String.split_on_char '\x00' 2 29 3 30 let get_image ~dir ~proc image = 4 31 let container_id = ··· 6 33 [ "docker"; "run"; "-d"; image ] 7 34 |> String.trim 8 35 in 9 - let tar = image ^ ".tar.gz" in 36 + let tar = replace_slash image ^ ".tar.gz" in 10 37 let dir_s = Eio.Path.native_exn dir in 11 38 let () = 12 39 Eio.Process.run proc ··· 18 45 [ 19 46 "tar"; 20 47 "-xf"; 21 - Filename.concat dir_s "alpine.tar.gz"; 48 + Filename.concat dir_s tar; 22 49 "-C"; 23 50 Filename.concat dir_s "rootfs"; 24 51 ]
+454
src/lib/shelter/runc.ml
··· 1 + (* From Obuilder see License file at end of document *) 2 + let ( / ) = Eio.Path.( / ) 3 + let ( // ) = Filename.concat 4 + 5 + type config = { fast_sync : bool } 6 + 7 + let get_machine () = 8 + let ch = Unix.open_process_in "uname -m" in 9 + let arch = input_line ch in 10 + match Unix.close_process_in ch with 11 + | Unix.WEXITED 0 -> String.trim arch 12 + | _ -> failwith "Failed to get arch with 'uname -m'" 13 + 14 + let get_arches () = 15 + if Sys.unix then 16 + match get_machine () with 17 + | "x86_64" -> [ "SCMP_ARCH_X86_64"; "SCMP_ARCH_X86"; "SCMP_ARCH_X32" ] 18 + | "aarch64" -> [ "SCMP_ARCH_AARCH64"; "SCMP_ARCH_ARM" ] 19 + | _ -> [] 20 + else [] 21 + 22 + let secret_file id = "secret-" ^ string_of_int id 23 + 24 + module Json_config = struct 25 + let mount ?(options = []) ~ty ~src dst = 26 + `Assoc 27 + [ 28 + ("destination", `String dst); 29 + ("type", `String ty); 30 + ("source", `String src); 31 + ("options", `List (List.map (fun x -> `String x) options)); 32 + ] 33 + 34 + let strings xs = `List (List.map (fun x -> `String x) xs) 35 + let namespace x = `Assoc [ ("type", `String x) ] 36 + 37 + (* This is a subset of the capabilities that Docker uses by default. 38 + These control what root can do in the container. 39 + If the init process is non-root, permitted, effective and ambient sets are cleared. 40 + See capabilities(7) for full details. *) 41 + let default_linux_caps = 42 + [ 43 + "CAP_CHOWN"; 44 + (* Make arbitrary changes to file UIDs and GIDs *) 45 + "CAP_DAC_OVERRIDE"; 46 + (* Bypass file read, write, and execute permission checks. *) 47 + "CAP_FSETID"; 48 + (* Set SUID/SGID bits. *) 49 + "CAP_FOWNER"; 50 + (* Bypass permission checks. *) 51 + "CAP_MKNOD"; 52 + (* Create special files using mknod. *) 53 + "CAP_SETGID"; 54 + (* Make arbitrary manipulations of process GIDs. *) 55 + "CAP_SETUID"; 56 + (* Make arbitrary manipulations of process UIDs. *) 57 + "CAP_SETFCAP"; 58 + (* Set arbitrary capabilities on a file. *) 59 + "CAP_SETPCAP"; 60 + (* Add any capability from bounding set to inheritable set. *) 61 + "CAP_SYS_CHROOT"; 62 + (* Use chroot. *) 63 + "CAP_KILL"; 64 + (* Bypass permission checks for sending signals. *) 65 + "CAP_AUDIT_WRITE" 66 + (* Write records to kernel auditing log. *) 67 + (* Allowed by Docker, but disabled here (because we use host networking): 68 + "CAP_NET_RAW"; (* Use RAW and PACKET sockets / bind to any address *) 69 + "CAP_NET_BIND_SERVICE"; (* Bind a socket to Internet domain privileged ports. *) 70 + *); 71 + ] 72 + 73 + let seccomp_syscalls ~fast_sync = 74 + if fast_sync then 75 + [ 76 + `Assoc 77 + [ 78 + (* Sync calls are pointless for the builder, because if the computer crashes then we'll 79 + just throw the build dir away and start again. And btrfs sync is really slow. 80 + Based on https://bblank.thinkmo.de/using-seccomp-to-filter-sync-operations.html 81 + Note: requires runc >= v1.0.0-rc92. *) 82 + ( "names", 83 + strings 84 + [ 85 + "fsync"; 86 + "fdatasync"; 87 + "msync"; 88 + "sync"; 89 + "syncfs"; 90 + "sync_file_range"; 91 + ] ); 92 + ("action", `String "SCMP_ACT_ERRNO"); 93 + ("errnoRet", `Int 0); 94 + (* Return error "success" *) 95 + ]; 96 + ] 97 + else [] 98 + 99 + let seccomp_policy = 100 + let fields = 101 + [ 102 + ("defaultAction", `String "SCMP_ACT_ALLOW"); 103 + ("syscalls", `List (seccomp_syscalls ~fast_sync:true)); 104 + ] 105 + in 106 + `Assoc fields 107 + 108 + type config = { 109 + cwd : string; 110 + argv : string list; 111 + hostname : string; 112 + network : string list; 113 + user : int * int; 114 + env : string list; 115 + entrypoint : string option; 116 + } 117 + 118 + let make { cwd; argv; hostname; network; user; env; entrypoint } ~config_dir 119 + ~results_dir : Yojson.Safe.t = 120 + assert (entrypoint = None); 121 + let user = 122 + let uid, gid = user in 123 + `Assoc [ ("uid", `Int uid); ("gid", `Int gid) ] 124 + in 125 + let network_ns = 126 + match network with 127 + | [ "host" ] -> [] 128 + | [] -> [ "network" ] 129 + | xs -> 130 + Fmt.failwith "Unsupported network configuration %a" 131 + Fmt.Dump.(list string) 132 + xs 133 + in 134 + let namespaces = network_ns @ [ "pid"; "ipc"; "uts"; "mount" ] in 135 + `Assoc 136 + [ 137 + ("ociVersion", `String "1.0.1-dev"); 138 + ( "process", 139 + `Assoc 140 + [ 141 + ("terminal", `Bool false); 142 + ("user", user); 143 + ("args", strings argv); 144 + ("env", strings env); 145 + ("cwd", `String cwd); 146 + ( "capabilities", 147 + `Assoc 148 + [ 149 + ("bounding", strings default_linux_caps); 150 + (* Limits capabilities gained on execve. *) 151 + ("effective", strings default_linux_caps); 152 + (* Checked by kernel to decide access *) 153 + ("inheritable", strings default_linux_caps); 154 + (* Preserved across an execve (if root, or cap in ambient set) *) 155 + ("permitted", strings default_linux_caps); 156 + (* Limiting superset for the effective capabilities *) 157 + ] ); 158 + ( "rlimits", 159 + `List 160 + [ 161 + `Assoc 162 + [ 163 + ("type", `String "RLIMIT_NOFILE"); 164 + ("hard", `Int 1024); 165 + ("soft", `Int 1024); 166 + ]; 167 + ] ); 168 + ("noNewPrivileges", `Bool false); 169 + ] ); 170 + ( "root", 171 + `Assoc 172 + [ 173 + ("path", `String (results_dir // "rootfs")); 174 + ("readonly", `Bool false); 175 + ] ); 176 + ("hostname", `String hostname); 177 + ( "mounts", 178 + `List 179 + (mount "/proc" 180 + ~options: 181 + [ (* TODO: copy to others? *) "nosuid"; "noexec"; "nodev" ] 182 + ~ty:"proc" ~src:"proc" 183 + :: mount "/dev" ~ty:"tmpfs" ~src:"tmpfs" 184 + ~options:[ "nosuid"; "strictatime"; "mode=755"; "size=65536k" ] 185 + :: mount "/dev/pts" ~ty:"devpts" ~src:"devpts" 186 + ~options: 187 + [ 188 + "nosuid"; 189 + "noexec"; 190 + "newinstance"; 191 + "ptmxmode=0666"; 192 + "mode=0620"; 193 + "gid=5"; 194 + (* tty *) 195 + ] 196 + :: mount 197 + "/sys" 198 + (* This is how Docker does it. runc's default is a bit different. *) 199 + ~ty:"sysfs" ~src:"sysfs" 200 + ~options:[ "nosuid"; "noexec"; "nodev"; "ro" ] 201 + :: mount "/sys/fs/cgroup" ~ty:"cgroup" ~src:"cgroup" 202 + ~options:[ "ro"; "nosuid"; "noexec"; "nodev" ] 203 + :: mount "/dev/shm" ~ty:"tmpfs" ~src:"shm" 204 + ~options: 205 + [ "nosuid"; "noexec"; "nodev"; "mode=1777"; "size=65536k" ] 206 + :: mount "/dev/mqueue" ~ty:"mqueue" ~src:"mqueue" 207 + ~options:[ "nosuid"; "noexec"; "nodev" ] 208 + :: mount "/etc/hosts" ~ty:"bind" ~src:(config_dir // "hosts") 209 + ~options:[ "ro"; "rbind"; "rprivate" ] 210 + :: 211 + (if network = [ "host" ] then 212 + [ 213 + mount "/etc/resolv.conf" ~ty:"bind" ~src:"/etc/resolv.conf" 214 + ~options:[ "ro"; "rbind"; "rprivate" ]; 215 + ] 216 + else [])) ); 217 + ( "linux", 218 + `Assoc 219 + [ 220 + ("namespaces", `List (List.map namespace namespaces)); 221 + ( "maskedPaths", 222 + strings 223 + [ 224 + "/proc/acpi"; 225 + "/proc/asound"; 226 + "/proc/kcore"; 227 + "/proc/keys"; 228 + "/proc/latency_stats"; 229 + "/proc/timer_list"; 230 + "/proc/timer_stats"; 231 + "/proc/sched_debug"; 232 + "/sys/firmware"; 233 + "/proc/scsi"; 234 + ] ); 235 + ( "readonlyPaths", 236 + strings 237 + [ 238 + "/proc/bus"; 239 + "/proc/fs"; 240 + "/proc/irq"; 241 + "/proc/sys"; 242 + "/proc/sysrq-trigger"; 243 + ] ); 244 + ("seccomp", seccomp_policy); 245 + ] ); 246 + ] 247 + end 248 + 249 + let next_id = ref 0 250 + 251 + let spawn ~sw fs proc config dir = 252 + let tmp = Filename.temp_dir ~perms:0o700 "shelter-run-" "" in 253 + let eio_tmp = Eio.Path.(fs / tmp) in 254 + let json_config = Json_config.make config ~config_dir:tmp ~results_dir:dir in 255 + Eio.Path.save ~create:(`If_missing 0o644) (eio_tmp / "config.json") 256 + (Yojson.Safe.pretty_to_string json_config ^ "\n"); 257 + Eio.Path.save ~create:(`If_missing 0o644) (eio_tmp / "hosts") 258 + "127.0.0.1 localhost builder"; 259 + let id = string_of_int !next_id in 260 + incr next_id; 261 + let cmd = [ "runc"; "--root"; "runc"; "run"; id ] in 262 + Eio.Process.spawn ~sw proc ~cwd:eio_tmp cmd 263 + 264 + (* 265 + Apache License 266 + Version 2.0, January 2004 267 + https://www.apache.org/licenses/ 268 + 269 + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 270 + 271 + 1. Definitions. 272 + 273 + "License" shall mean the terms and conditions for use, reproduction, 274 + and distribution as defined by Sections 1 through 9 of this document. 275 + 276 + "Licensor" shall mean the copyright owner or entity authorized by 277 + the copyright owner that is granting the License. 278 + 279 + "Legal Entity" shall mean the union of the acting entity and all 280 + other entities that control, are controlled by, or are under common 281 + control with that entity. For the purposes of this definition, 282 + "control" means (i) the power, direct or indirect, to cause the 283 + direction or management of such entity, whether by contract or 284 + otherwise, or (ii) ownership of fifty percent (50%) or more of the 285 + outstanding shares, or (iii) beneficial ownership of such entity. 286 + 287 + "You" (or "Your") shall mean an individual or Legal Entity 288 + exercising permissions granted by this License. 289 + 290 + "Source" form shall mean the preferred form for making modifications, 291 + including but not limited to software source code, documentation 292 + source, and configuration files. 293 + 294 + "Object" form shall mean any form resulting from mechanical 295 + transformation or translation of a Source form, including but 296 + not limited to compiled object code, generated documentation, 297 + and conversions to other media types. 298 + 299 + "Work" shall mean the work of authorship, whether in Source or 300 + Object form, made available under the License, as indicated by a 301 + copyright notice that is included in or attached to the work 302 + (an example is provided in the Appendix below). 303 + 304 + "Derivative Works" shall mean any work, whether in Source or Object 305 + form, that is based on (or derived from) the Work and for which the 306 + editorial revisions, annotations, elaborations, or other modifications 307 + represent, as a whole, an original work of authorship. For the purposes 308 + of this License, Derivative Works shall not include works that remain 309 + separable from, or merely link (or bind by name) to the interfaces of, 310 + the Work and Derivative Works thereof. 311 + 312 + "Contribution" shall mean any work of authorship, including 313 + the original version of the Work and any modifications or additions 314 + to that Work or Derivative Works thereof, that is intentionally 315 + submitted to Licensor for inclusion in the Work by the copyright owner 316 + or by an individual or Legal Entity authorized to submit on behalf of 317 + the copyright owner. For the purposes of this definition, "submitted" 318 + means any form of electronic, verbal, or written communication sent 319 + to the Licensor or its representatives, including but not limited to 320 + communication on electronic mailing lists, source code control systems, 321 + and issue tracking systems that are managed by, or on behalf of, the 322 + Licensor for the purpose of discussing and improving the Work, but 323 + excluding communication that is conspicuously marked or otherwise 324 + designated in writing by the copyright owner as "Not a Contribution." 325 + 326 + "Contributor" shall mean Licensor and any individual or Legal Entity 327 + on behalf of whom a Contribution has been received by Licensor and 328 + subsequently incorporated within the Work. 329 + 330 + 2. Grant of Copyright License. Subject to the terms and conditions of 331 + this License, each Contributor hereby grants to You a perpetual, 332 + worldwide, non-exclusive, no-charge, royalty-free, irrevocable 333 + copyright license to reproduce, prepare Derivative Works of, 334 + publicly display, publicly perform, sublicense, and distribute the 335 + Work and such Derivative Works in Source or Object form. 336 + 337 + 3. Grant of Patent License. Subject to the terms and conditions of 338 + this License, each Contributor hereby grants to You a perpetual, 339 + worldwide, non-exclusive, no-charge, royalty-free, irrevocable 340 + (except as stated in this section) patent license to make, have made, 341 + use, offer to sell, sell, import, and otherwise transfer the Work, 342 + where such license applies only to those patent claims licensable 343 + by such Contributor that are necessarily infringed by their 344 + Contribution(s) alone or by combination of their Contribution(s) 345 + with the Work to which such Contribution(s) was submitted. If You 346 + institute patent litigation against any entity (including a 347 + cross-claim or counterclaim in a lawsuit) alleging that the Work 348 + or a Contribution incorporated within the Work constitutes direct 349 + or contributory patent infringement, then any patent licenses 350 + granted to You under this License for that Work shall terminate 351 + as of the date such litigation is filed. 352 + 353 + 4. Redistribution. You may reproduce and distribute copies of the 354 + Work or Derivative Works thereof in any medium, with or without 355 + modifications, and in Source or Object form, provided that You 356 + meet the following conditions: 357 + 358 + (a) You must give any other recipients of the Work or 359 + Derivative Works a copy of this License; and 360 + 361 + (b) You must cause any modified files to carry prominent notices 362 + stating that You changed the files; and 363 + 364 + (c) You must retain, in the Source form of any Derivative Works 365 + that You distribute, all copyright, patent, trademark, and 366 + attribution notices from the Source form of the Work, 367 + excluding those notices that do not pertain to any part of 368 + the Derivative Works; and 369 + 370 + (d) If the Work includes a "NOTICE" text file as part of its 371 + distribution, then any Derivative Works that You distribute must 372 + include a readable copy of the attribution notices contained 373 + within such NOTICE file, excluding those notices that do not 374 + pertain to any part of the Derivative Works, in at least one 375 + of the following places: within a NOTICE text file distributed 376 + as part of the Derivative Works; within the Source form or 377 + documentation, if provided along with the Derivative Works; or, 378 + within a display generated by the Derivative Works, if and 379 + wherever such third-party notices normally appear. The contents 380 + of the NOTICE file are for informational purposes only and 381 + do not modify the License. You may add Your own attribution 382 + notices within Derivative Works that You distribute, alongside 383 + or as an addendum to the NOTICE text from the Work, provided 384 + that such additional attribution notices cannot be construed 385 + as modifying the License. 386 + 387 + You may add Your own copyright statement to Your modifications and 388 + may provide additional or different license terms and conditions 389 + for use, reproduction, or distribution of Your modifications, or 390 + for any such Derivative Works as a whole, provided Your use, 391 + reproduction, and distribution of the Work otherwise complies with 392 + the conditions stated in this License. 393 + 394 + 5. Submission of Contributions. Unless You explicitly state otherwise, 395 + any Contribution intentionally submitted for inclusion in the Work 396 + by You to the Licensor shall be under the terms and conditions of 397 + this License, without any additional terms or conditions. 398 + Notwithstanding the above, nothing herein shall supersede or modify 399 + the terms of any separate license agreement you may have executed 400 + with Licensor regarding such Contributions. 401 + 402 + 6. Trademarks. This License does not grant permission to use the trade 403 + names, trademarks, service marks, or product names of the Licensor, 404 + except as required for reasonable and customary use in describing the 405 + origin of the Work and reproducing the content of the NOTICE file. 406 + 407 + 7. Disclaimer of Warranty. Unless required by applicable law or 408 + agreed to in writing, Licensor provides the Work (and each 409 + Contributor provides its Contributions) on an "AS IS" BASIS, 410 + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 411 + implied, including, without limitation, any warranties or conditions 412 + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 413 + PARTICULAR PURPOSE. You are solely responsible for determining the 414 + appropriateness of using or redistributing the Work and assume any 415 + risks associated with Your exercise of permissions under this License. 416 + 417 + 8. Limitation of Liability. In no event and under no legal theory, 418 + whether in tort (including negligence), contract, or otherwise, 419 + unless required by applicable law (such as deliberate and grossly 420 + negligent acts) or agreed to in writing, shall any Contributor be 421 + liable to You for damages, including any direct, indirect, special, 422 + incidental, or consequential damages of any character arising as a 423 + result of this License or out of the use or inability to use the 424 + Work (including but not limited to damages for loss of goodwill, 425 + work stoppage, computer failure or malfunction, or any and all 426 + other commercial damages or losses), even if such Contributor 427 + has been advised of the possibility of such damages. 428 + 429 + 9. Accepting Warranty or Additional Liability. While redistributing 430 + the Work or Derivative Works thereof, You may choose to offer, 431 + and charge a fee for, acceptance of support, warranty, indemnity, 432 + or other liability obligations and/or rights consistent with this 433 + License. However, in accepting such obligations, You may act only 434 + on Your own behalf and on Your sole responsibility, not on behalf 435 + of any other Contributor, and only if You agree to indemnify, 436 + defend, and hold each Contributor harmless for any liability 437 + incurred by, or claims asserted against, such Contributor by reason 438 + of your accepting any such warranty or additional liability. 439 + 440 + END OF TERMS AND CONDITIONS 441 + 442 + Copyright 2020 Thomas Leonard 443 + 444 + Licensed under the Apache License, Version 2.0 (the "License"); 445 + you may not use this file except in compliance with the License. 446 + You may obtain a copy of the License at 447 + 448 + https://www.apache.org/licenses/LICENSE-2.0 449 + 450 + Unless required by applicable law or agreed to in writing, software 451 + distributed under the License is distributed on an "AS IS" BASIS, 452 + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 453 + See the License for the specific language governing permissions and 454 + limitations under the License. *)
+78 -33
src/lib/shelter/shelter_main.ml
··· 18 18 time : int64; 19 19 env : string list; 20 20 cwd : string; 21 + user : int * int; 21 22 diff : Diff.t; 22 23 } 23 24 [@@deriving repr] ··· 174 175 (list s); 175 176 store 176 177 177 - let run (_config : config) fs clock _proc 178 + let run (config : config) fs clock proc 178 179 (((H.Store ((module S), store) : entry H.t) as s), ctx) = function 179 180 | Set_mode mode -> 180 181 with_latest ~default:(fun _ -> Ok (s, ctx)) s @@ fun (_, entry) -> ··· 238 239 History. 239 240 { 240 241 mode = Void.RW; 241 - build = Store.Build.Image "alpine"; 242 + build = Store.Build.Image config.image; 242 243 args = command; 243 244 time = 0L; 244 245 diff = []; 245 246 (* TODO: extract with fetch *) 246 247 env = []; 247 248 cwd = "/"; 249 + user = (0, 0); 248 250 }) 249 251 s 250 252 @@ fun (_, e) -> e 251 253 in 252 - let build = 254 + let build, env, (uid, gid) = 253 255 match entry.build with 254 - | Store.Build.Image img -> Store.fetch ctx img 255 - | Store.Build.Build cid -> cid 256 + | Store.Build.Image img -> 257 + let build, env, user = Store.fetch ctx img in 258 + (build, env, Option.value ~default:(0, 0) user) 259 + | Store.Build.Build cid -> (cid, entry.env, entry.user) 256 260 in 257 261 let hash_entry = { entry with build = Build build; args = command } in 258 262 let new_cid = Store.cid (Repr.to_string History.t hash_entry) in ··· 263 267 try 264 268 let new_entry, diff = 265 269 with_rootfs @@ fun rootfs -> 266 - let void = 267 - Void.empty 268 - |> Void.rootfs ~mode:entry.mode rootfs 269 - |> Void.cwd entry.cwd 270 - |> Void.exec ~env:entry.env 271 - [ 272 - "/bin/ash"; 273 - "-c"; 274 - String.concat " " command ^ " && env > /shelter-env"; 275 - ] 270 + let spawn sw = 271 + if config.no_runc then 272 + let rootfs = Filename.concat rootfs "rootfs" in 273 + let void = 274 + Void.empty 275 + |> Void.rootfs ~mode:entry.mode rootfs 276 + |> Void.cwd entry.cwd 277 + (* TODO: Support UIDs |> Void.uid 1000 *) 278 + |> Void.exec ~env 279 + [ 280 + config.shell; 281 + "-c"; 282 + String.concat " " command ^ " && env > /tmp/shelter-env"; 283 + ] 284 + in 285 + `Void (Void.spawn ~sw void |> Void.exit_status) 286 + else 287 + let config = 288 + Runc.Json_config. 289 + { 290 + cwd = entry.cwd; 291 + argv = 292 + [ 293 + config.shell; 294 + "-c"; 295 + String.concat " " command ^ " && env > /tmp/shelter-env"; 296 + ]; 297 + hostname = ""; 298 + network = []; 299 + user = (uid, gid); 300 + env = entry.env; 301 + entrypoint = None; 302 + } 303 + in 304 + `Runc (Runc.spawn ~sw fs proc config rootfs) 276 305 in 277 306 Switch.run @@ fun sw -> 307 + let res = spawn sw in 278 308 let start = Mtime_clock.now () in 279 - let proc = Void.spawn ~sw void in 280 309 let res = 281 - Void.exit_status proc |> Eio.Promise.await |> Void.to_eio_status 310 + match res with 311 + | `Runc r -> Eio.Process.await r 312 + | `Void v -> Void.to_eio_status (Eio.Promise.await v) 282 313 in 283 314 let stop = Mtime_clock.now () in 284 - (* Extract env *) 285 - let env_path = Eio.Path.(fs / rootfs / "shelter-env") in 286 - let env = Eio.Path.(load env_path) |> String.split_on_char '\n' in 287 - Eio.Path.unlink env_path; 288 - let cwd = 289 - List.find_map 290 - (fun v -> 291 - match Astring.String.cut ~sep:"=" v with 292 - | Some ("PWD", dir) -> Some dir 293 - | _ -> None) 294 - env 295 - |> Option.value ~default:hash_entry.cwd 296 - in 297 315 let span = Mtime.span start stop in 298 316 let time = Mtime.Span.to_uint64_ns span in 299 317 (* Add command to history regardless of exit status *) 300 318 let _ : (unit, string) result = 301 319 LNoise.history_add (String.concat " " command) 302 320 in 303 - if res = `Exited 0 then 321 + if res = `Exited 0 then ( 322 + (* Extract env *) 323 + let env_path = 324 + Eio.Path.(fs / rootfs / "rootfs" / "tmp" / "shelter-env") 325 + in 326 + let env = 327 + Eio.Path.(load env_path) 328 + |> String.split_on_char '\n' 329 + |> List.filter (fun s -> not (String.equal "" s)) 330 + in 331 + Eio.Path.unlink env_path; 332 + let cwd = 333 + List.find_map 334 + (fun v -> 335 + match Astring.String.cut ~sep:"=" v with 336 + | Some ("PWD", dir) -> Some dir 337 + | _ -> None) 338 + env 339 + |> Option.value ~default:hash_entry.cwd 340 + in 304 341 if entry.mode = RW then 305 - Ok { hash_entry with build = Build new_cid; time; env; cwd } 306 - else Ok { hash_entry with cwd; env } 342 + Ok 343 + { 344 + hash_entry with 345 + build = Build new_cid; 346 + time; 347 + env; 348 + cwd; 349 + user = (uid, gid); 350 + } 351 + else Ok { hash_entry with time; cwd; env; user = (uid, gid) }) 307 352 else Error (Eio.Process.Child_error res) 308 353 in 309 354 match new_entry with
+1
src/lib/shelter/shelter_main.mli
··· 8 8 time : int64; 9 9 env : string list; 10 10 cwd : string; 11 + user : int * int; 11 12 diff : Diff.t; 12 13 } 13 14 [@@deriving repr]
+24 -7
src/lib/shelter/store.ml
··· 126 126 in 127 127 Cid.v ~version:`Cidv1 ~base:`Base32 ~codec:`Raw ~hash 128 128 129 + let not_empty s = not String.(equal empty s) 130 + 131 + let get_uid_gid ~username rootfs = 132 + let passwd = 133 + Eio.Path.load Eio.Path.(rootfs / "etc" / "passwd") 134 + |> String.split_on_char '\n' 135 + |> List.map (String.split_on_char ':') 136 + |> List.map (List.filter not_empty) 137 + in 138 + List.find_map 139 + (function 140 + | user :: _ :: uid :: gid :: _ when String.equal user username -> 141 + Some (int_of_string uid, int_of_string gid) 142 + | _ -> None) 143 + passwd 144 + 129 145 let fetch t image = 130 146 let cid = cid image in 131 147 let cids = cid |> Cid.to_string in 132 148 let dataset = Datasets.build t.pool cids in 133 149 let dir = Eio.Path.(t.fs / ("/" ^ (Datasets.build t.pool cids :> string))) in 134 - if Zfs.exists t.zfs (dataset :> string) Zfs.Types.filesystem then cid 135 - else ( 136 - create_and_mount t dataset; 137 - let _dir : string = Fetch.get_image ~dir ~proc:t.proc image in 138 - snapshot t (Datasets.snapshot dataset); 139 - cid) 150 + create_and_mount t dataset; 151 + let _dir : string = Fetch.get_image ~dir ~proc:t.proc image in 152 + snapshot t (Datasets.snapshot dataset); 153 + let username = Fetch.get_user t.proc image in 154 + ( cid, 155 + Fetch.get_env t.proc image, 156 + get_uid_gid ~username Eio.Path.(dir / "rootfs") ) 140 157 141 158 module Run = struct 142 159 let with_build t cid fn = 143 160 let ds = Datasets.build t.pool (Cid.to_string cid) in 144 161 Fun.protect ~finally:(fun () -> unmount_dataset t ds) @@ fun () -> 145 162 mount_dataset t ds; 146 - fn ("/" ^ (ds :> string) ^ "/rootfs") 163 + fn ("/" ^ (ds :> string)) 147 164 148 165 let with_clone t ~src new_cid fn = 149 166 let ds = Datasets.build t.pool (Cid.to_string src) in
+19 -2
vendor/void/src/void.ml
··· 11 11 args : string list; 12 12 env : string list; 13 13 cwd : string; 14 + uid : int; 15 + (* TODO: gid *) 14 16 rootfs : (string * mode) option; 15 17 mounts : mount list; 16 18 } ··· 60 62 (action_pivot_root, new_root, new_root_flags, tmpfs, mounts))); 61 63 } 62 64 65 + external action_setuid : unit -> Fork_action.fork_fn 66 + = "void_fork_setuid" 67 + 68 + let action_setuid = action_setuid () 69 + 70 + let setuid (uid : int) = Fork_action. 71 + { 72 + run = 73 + (fun k -> 74 + k 75 + (Obj.repr 76 + (action_setuid, uid))); 77 + } 78 + 63 79 external action_map_uid_gid : unit -> Fork_action.fork_fn 64 80 = "void_fork_map_uid_gid" 65 81 ··· 100 116 101 117 type path = string 102 118 103 - let empty = { args = []; env = []; rootfs = None; mounts = []; cwd = "/" } 119 + let empty = { args = []; env = []; rootfs = None; mounts = []; cwd = "/"; uid = 0 } 104 120 105 121 let actions v : Fork_action.t list = 106 122 let root, tmpfs, root_mode = ··· 128 144 let mounts = pivot_root root root_flags tmpfs mounts in 129 145 let uid, gid = Unix.(getuid (), getgid ()) in 130 146 let user_namespace = map_uid_gid ~uid ~gid in 131 - [ user_namespace; mounts; Process.Fork_action.chdir v.cwd; e ] 147 + [ user_namespace; mounts; setuid v.uid; Process.Fork_action.chdir v.cwd; e ] 132 148 133 149 let rootfs ~mode path v = { v with rootfs = Some (path, mode) } 134 150 let cwd cwd v = { v with cwd } 151 + let uid uid v = { v with uid } 135 152 let exec ?(env=[]) args v = { v with args; env } 136 153 137 154 let mount ~mode ~src ~tgt v =
+3
vendor/void/src/void.mli
··· 51 51 val cwd : string -> void -> void 52 52 (** Set the current working directory *) 53 53 54 + val uid : int -> void -> void 55 + (** Set the UID *) 56 + 54 57 val exec : ?env:string list -> string list -> void -> void 55 58 (** Make a void configuration ready to be spawned *) 56 59
+22 -4
vendor/void/src/void_action.c
··· 228 228 return Val_fork_fn (action_map_uid_gid); 229 229 } 230 230 231 + static void 232 + action_setuid (int errors, value v_config) 233 + { 234 + value v_uid = Field(v_config, 1); 235 + 236 + // if (setuid(1000)) { 237 + // eio_unix_fork_error (errors, "setuid", strerror (errno)); 238 + // _exit (1); 239 + // } 240 + } 241 + 242 + // SETUID 243 + CAMLprim value 244 + void_fork_setuid (value v_unit) 245 + { 246 + return Val_fork_fn (action_setuid); 247 + } 248 + 231 249 // PIVOT ROOT 232 250 // 233 251 static int ··· 267 285 //} 268 286 269 287 if (mount ("tmpfs", new_root, "tmpfs", 0, NULL) <= -1) 270 - { 271 - eio_unix_fork_error (errors, "pivot_root-tmpfs", strerror (errno)); 272 - _exit (1); 273 - } 288 + { 289 + eio_unix_fork_error (errors, "pivot_root-tmpfs", strerror (errno)); 290 + _exit (1); 291 + } 274 292 } 275 293 else 276 294 {