From f1e8fcac0e7452241290aa7d5e7914e9d293ace2 Mon Sep 17 00:00:00 2001 From: Anil Madhavapeddy Date: Wed, 13 May 2026 12:13:54 +0100 Subject: [PATCH 1/2] add macos secrets support via a /tmp entry --- lib/sandbox.macos.ml | 48 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 47 insertions(+), 1 deletion(-) diff --git a/lib/sandbox.macos.ml b/lib/sandbox.macos.ml index fc1547c4..8c1d7f4f 100644 --- a/lib/sandbox.macos.ml +++ b/lib/sandbox.macos.ml @@ -46,6 +46,45 @@ let zfs_volume_from path = |> List.tl |> String.concat "/" +let secrets_env_var = "OBUILDER_SECRETS_DIR" + +(* Stage secrets in a private /tmp directory, one file per secret named + by the basename of [secret.target]. The build locates them via the + [OBUILDER_SECRETS_DIR] environment variable (e.g. + "$OBUILDER_SECRETS_DIR/github_token"). Returns the staging directory + (or [None] if there were no secrets) and the list of file paths + created so that [cleanup_secrets] can undo everything. *) +let setup_secrets ~uid ~gid mount_secrets = + match mount_secrets with + | [] -> Lwt.return (None, []) + | _ -> + let tmp = Filename.temp_dir ~temp_dir:"/tmp" "obuilder-macos-secrets-" "" in + Unix.chmod tmp 0o755; + let owner = Printf.sprintf "%d:%d" uid gid in + let names = List.map (fun { Config.Secret.target; _ } -> Filename.basename target) mount_secrets in + let unique = List.sort_uniq String.compare names in + if List.length unique <> List.length names then + Fmt.failwith "Two secrets resolve to the same filename in %s; \ + give each secret a distinct target basename." secrets_env_var; + Lwt_list.fold_left_s + (fun paths { Config.Secret.value; target } -> + let src = Filename.concat tmp (Filename.basename target) in + Os.write_file ~path:src value >>= fun () -> + Os.sudo [ "chown"; owner; src ] >>= fun () -> + Os.sudo [ "chmod"; "0400"; src ] >>= fun () -> + Lwt.return (src :: paths)) + [] mount_secrets + >>= fun paths -> + Lwt.return (Some tmp, List.rev paths) + +let cleanup_secrets ~tmp ~paths = + match tmp with + | None -> Lwt.return_unit + | Some tmp -> + Lwt_list.iter_s (fun src -> Os.sudo [ "rm"; "-f"; src ]) paths + >>= fun () -> + Os.sudo [ "rmdir"; tmp ] + let run ~cancelled ?stdin:stdin ~log (t : t) config result_tmp = Lwt_mutex.with_lock t.lock (fun () -> Log.info (fun f -> f "result_tmp = %s" result_tmp); @@ -65,6 +104,7 @@ let run ~cancelled ?stdin:stdin ~log (t : t) config result_tmp = let uid = string_of_int t.uid in let gid = string_of_int t.gid in Macos.create_new_user ~username:user ~home_dir ~uid ~gid >>= fun _ -> + setup_secrets ~uid:t.uid ~gid:t.gid config.Config.mount_secrets >>= fun (secrets_tmp, secrets_paths) -> let osenv = config.Config.env in let stdout = `FD_move_safely out_w in let stderr = stdout in @@ -75,7 +115,12 @@ let run ~cancelled ?stdin:stdin ~log (t : t) config result_tmp = let pp f = Os.pp_cmd f ("", config.Config.argv) in Os.pread @@ Macos.get_tmpdir ~user >>= fun tmpdir -> let tmpdir = List.hd (String.split_on_char '\n' tmpdir) in - let env = ("TMPDIR", tmpdir) :: osenv in + let env = + let base = ("TMPDIR", tmpdir) :: osenv in + match secrets_tmp with + | None -> base + | Some d -> (secrets_env_var, d) :: base + in let cmd = run_as ~env ~user ~cmd:config.Config.argv in Os.ensure_dir config.Config.cwd; let pid, proc = Os.open_process ?stdin ~stdout ~stderr ~pp ~cwd:config.Config.cwd cmd in @@ -95,6 +140,7 @@ let run ~cancelled ?stdin:stdin ~log (t : t) config result_tmp = ); proc >>= fun r -> copy_log >>= fun () -> + cleanup_secrets ~tmp:secrets_tmp ~paths:secrets_paths >>= fun () -> Lwt_list.iter_s (fun { Config.Mount.src; dst = _; readonly = _; ty = _ } -> Os.sudo [ "zfs"; "inherit"; "mountpoint"; zfs_volume_from src ]) config.Config.mounts >>= fun () -> Macos.sudo_fallback [ "zfs"; "set"; "mountpoint=none"; zfs_home_dir ] [ "zfs"; "unmount"; "-f"; zfs_home_dir ] ~uid:t.uid >>= fun () -> From 87902351507861877a5fa97c02f13aac3e48f396 Mon Sep 17 00:00:00 2001 From: Mark Elvers Date: Mon, 18 May 2026 08:33:29 +0000 Subject: [PATCH 2/2] macos secrets: use Lwt_io.with_temp_dir for staging Filename.temp_dir is OCaml 5.1+, but workers run 4.14. Switch to Lwt_io.with_temp_dir, matching the pattern in sandbox.runc/jail/hcs and docker_sandbox. As a bonus the directory is now removed on failure and cancellation as well as success, so cleanup_secrets goes away. Tighten the staging dir from 0o755 to 0o711 so other local users can't enumerate secret filenames; the build user only needs traverse. --- lib/sandbox.macos.ml | 48 +++++++++++++++++--------------------------- 1 file changed, 18 insertions(+), 30 deletions(-) diff --git a/lib/sandbox.macos.ml b/lib/sandbox.macos.ml index 8c1d7f4f..2490c1d9 100644 --- a/lib/sandbox.macos.ml +++ b/lib/sandbox.macos.ml @@ -48,42 +48,31 @@ let zfs_volume_from path = let secrets_env_var = "OBUILDER_SECRETS_DIR" -(* Stage secrets in a private /tmp directory, one file per secret named - by the basename of [secret.target]. The build locates them via the +(* Stage secrets in a private dir under /tmp, one file per secret named + by [Filename.basename target]. The build locates them via the [OBUILDER_SECRETS_DIR] environment variable (e.g. - "$OBUILDER_SECRETS_DIR/github_token"). Returns the staging directory - (or [None] if there were no secrets) and the list of file paths - created so that [cleanup_secrets] can undo everything. *) -let setup_secrets ~uid ~gid mount_secrets = + "$OBUILDER_SECRETS_DIR/github_token"). [fn] is invoked with [Some + dir] when there are secrets and [None] otherwise; the staging + directory is removed when [fn] resolves or raises. *) +let with_secrets ~uid ~gid mount_secrets fn = match mount_secrets with - | [] -> Lwt.return (None, []) + | [] -> fn None | _ -> - let tmp = Filename.temp_dir ~temp_dir:"/tmp" "obuilder-macos-secrets-" "" in - Unix.chmod tmp 0o755; - let owner = Printf.sprintf "%d:%d" uid gid in let names = List.map (fun { Config.Secret.target; _ } -> Filename.basename target) mount_secrets in - let unique = List.sort_uniq String.compare names in - if List.length unique <> List.length names then - Fmt.failwith "Two secrets resolve to the same filename in %s; \ - give each secret a distinct target basename." secrets_env_var; - Lwt_list.fold_left_s - (fun paths { Config.Secret.value; target } -> + if List.compare_lengths (List.sort_uniq String.compare names) names <> 0 then + Fmt.failwith "Two secrets share a target basename; \ + give each secret a distinct target basename."; + Lwt_io.with_temp_dir ~perm:0o711 ~prefix:"obuilder-macos-secrets-" @@ fun tmp -> + let owner = Printf.sprintf "%d:%d" uid gid in + Lwt_list.iter_s + (fun { Config.Secret.value; target } -> let src = Filename.concat tmp (Filename.basename target) in Os.write_file ~path:src value >>= fun () -> Os.sudo [ "chown"; owner; src ] >>= fun () -> - Os.sudo [ "chmod"; "0400"; src ] >>= fun () -> - Lwt.return (src :: paths)) - [] mount_secrets - >>= fun paths -> - Lwt.return (Some tmp, List.rev paths) - -let cleanup_secrets ~tmp ~paths = - match tmp with - | None -> Lwt.return_unit - | Some tmp -> - Lwt_list.iter_s (fun src -> Os.sudo [ "rm"; "-f"; src ]) paths + Os.sudo [ "chmod"; "0400"; src ]) + mount_secrets >>= fun () -> - Os.sudo [ "rmdir"; tmp ] + fn (Some tmp) let run ~cancelled ?stdin:stdin ~log (t : t) config result_tmp = Lwt_mutex.with_lock t.lock (fun () -> @@ -104,7 +93,7 @@ let run ~cancelled ?stdin:stdin ~log (t : t) config result_tmp = let uid = string_of_int t.uid in let gid = string_of_int t.gid in Macos.create_new_user ~username:user ~home_dir ~uid ~gid >>= fun _ -> - setup_secrets ~uid:t.uid ~gid:t.gid config.Config.mount_secrets >>= fun (secrets_tmp, secrets_paths) -> + with_secrets ~uid:t.uid ~gid:t.gid config.Config.mount_secrets @@ fun secrets_tmp -> let osenv = config.Config.env in let stdout = `FD_move_safely out_w in let stderr = stdout in @@ -140,7 +129,6 @@ let run ~cancelled ?stdin:stdin ~log (t : t) config result_tmp = ); proc >>= fun r -> copy_log >>= fun () -> - cleanup_secrets ~tmp:secrets_tmp ~paths:secrets_paths >>= fun () -> Lwt_list.iter_s (fun { Config.Mount.src; dst = _; readonly = _; ty = _ } -> Os.sudo [ "zfs"; "inherit"; "mountpoint"; zfs_volume_from src ]) config.Config.mounts >>= fun () -> Macos.sudo_fallback [ "zfs"; "set"; "mountpoint=none"; zfs_home_dir ] [ "zfs"; "unmount"; "-f"; zfs_home_dir ] ~uid:t.uid >>= fun () ->