mirror of
https://github.com/openai/codex.git
synced 2026-05-29 23:40:29 +00:00
Add Windows sandbox provisioning setup command
This commit is contained in:
@@ -50,6 +50,7 @@ mod marketplace_cmd;
|
||||
mod mcp_cmd;
|
||||
mod plugin_cmd;
|
||||
mod remote_control_cmd;
|
||||
mod sandbox_setup;
|
||||
mod state_db_recovery;
|
||||
#[cfg(not(windows))]
|
||||
mod wsl_paths;
|
||||
@@ -1250,6 +1251,16 @@ async fn cli_main(arg0_paths: Arg0DispatchPaths) -> anyhow::Result<()> {
|
||||
.await?;
|
||||
}
|
||||
Some(Subcommand::Sandbox(mut sandbox_cli)) => {
|
||||
#[cfg(target_os = "windows")]
|
||||
if let Some(setup_cli) = sandbox_setup::parse_setup_command(&sandbox_cli.command)? {
|
||||
reject_remote_mode_for_subcommand(
|
||||
root_remote.as_deref(),
|
||||
root_remote_auth_token_env.as_deref(),
|
||||
"sandbox setup",
|
||||
)?;
|
||||
sandbox_setup::run(setup_cli).await?;
|
||||
return Ok(());
|
||||
}
|
||||
reject_remote_mode_for_subcommand(
|
||||
root_remote.as_deref(),
|
||||
root_remote_auth_token_env.as_deref(),
|
||||
|
||||
206
codex-rs/cli/src/sandbox_setup.rs
Normal file
206
codex-rs/cli/src/sandbox_setup.rs
Normal file
@@ -0,0 +1,206 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use clap::ArgAction;
|
||||
use clap::ArgGroup;
|
||||
use clap::Parser;
|
||||
use codex_core::config::edit::ConfigEditsBuilder;
|
||||
use codex_core::config::find_codex_home;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
#[command(group(
|
||||
ArgGroup::new("sandbox_user")
|
||||
.required(true)
|
||||
.args(["user", "current_user"])
|
||||
))]
|
||||
pub(crate) struct SandboxSetupCommand {
|
||||
/// Set up the elevated Windows sandbox.
|
||||
#[arg(long = "elevated", action = ArgAction::SetTrue)]
|
||||
elevated_sandbox_level: bool,
|
||||
|
||||
/// Windows user that will run Codex after managed deployment.
|
||||
#[arg(
|
||||
long = "user",
|
||||
value_name = "USER",
|
||||
conflicts_with = "current_user",
|
||||
requires = "codex_home"
|
||||
)]
|
||||
user: Option<String>,
|
||||
|
||||
/// Use the current Windows user as the Codex user.
|
||||
#[arg(
|
||||
long = "current-user",
|
||||
default_value_t = false,
|
||||
conflicts_with = "user"
|
||||
)]
|
||||
current_user: bool,
|
||||
|
||||
/// CODEX_HOME for the Codex user. Required with --user.
|
||||
#[arg(long = "codex-home", value_name = "DIR")]
|
||||
codex_home: Option<PathBuf>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
enum SandboxSetupLevel {
|
||||
Elevated,
|
||||
}
|
||||
|
||||
impl SandboxSetupCommand {
|
||||
fn setup_level(&self) -> anyhow::Result<SandboxSetupLevel> {
|
||||
if self.elevated_sandbox_level {
|
||||
Ok(SandboxSetupLevel::Elevated)
|
||||
} else {
|
||||
anyhow::bail!("`codex sandbox setup` currently requires --elevated");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn run(cmd: SandboxSetupCommand) -> anyhow::Result<()> {
|
||||
match cmd.setup_level()? {
|
||||
SandboxSetupLevel::Elevated => run_elevated(cmd).await,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn parse_setup_command(
|
||||
sandbox_command: &[String],
|
||||
) -> anyhow::Result<Option<SandboxSetupCommand>> {
|
||||
if !sandbox_command
|
||||
.first()
|
||||
.is_some_and(|command| command == "setup")
|
||||
{
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
SandboxSetupCommand::try_parse_from(sandbox_command.iter().map(String::as_str))
|
||||
.map(Some)
|
||||
.map_err(anyhow::Error::from)
|
||||
}
|
||||
|
||||
async fn run_elevated(cmd: SandboxSetupCommand) -> anyhow::Result<()> {
|
||||
let identity = resolve_sandbox_setup_identity(&cmd)?;
|
||||
|
||||
codex_core::windows_sandbox::run_elevated_provisioning_setup(
|
||||
identity.codex_home.as_path(),
|
||||
identity.real_user.as_str(),
|
||||
)?;
|
||||
ConfigEditsBuilder::new(identity.codex_home.as_path())
|
||||
.set_windows_sandbox_mode("elevated")
|
||||
.apply()
|
||||
.await
|
||||
.map_err(|err| {
|
||||
anyhow::anyhow!(
|
||||
"sandbox provisioning succeeded, but failed to persist elevated sandbox config: {err}"
|
||||
)
|
||||
})?;
|
||||
|
||||
println!(
|
||||
"Windows elevated sandbox setup completed for {} at {}.",
|
||||
identity.real_user,
|
||||
identity.codex_home.display()
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
struct SandboxSetupIdentity {
|
||||
real_user: String,
|
||||
codex_home: PathBuf,
|
||||
}
|
||||
|
||||
fn resolve_sandbox_setup_identity(
|
||||
cmd: &SandboxSetupCommand,
|
||||
) -> anyhow::Result<SandboxSetupIdentity> {
|
||||
if cmd.current_user {
|
||||
let real_user = std::env::var("USERNAME")
|
||||
.or_else(|_| std::env::var("USER"))
|
||||
.map_err(|err| {
|
||||
anyhow::anyhow!("failed to determine current user from environment: {err}")
|
||||
})?;
|
||||
let codex_home = match cmd.codex_home.clone() {
|
||||
Some(codex_home) => codex_home,
|
||||
None => find_codex_home()?.to_path_buf(),
|
||||
};
|
||||
return Ok(SandboxSetupIdentity {
|
||||
real_user,
|
||||
codex_home,
|
||||
});
|
||||
}
|
||||
|
||||
let real_user = cmd
|
||||
.user
|
||||
.clone()
|
||||
.ok_or_else(|| anyhow::anyhow!("--user or --current-user is required"))?;
|
||||
let codex_home = cmd
|
||||
.codex_home
|
||||
.clone()
|
||||
.ok_or_else(|| anyhow::anyhow!("--codex-home is required with --user"))?;
|
||||
Ok(SandboxSetupIdentity {
|
||||
real_user,
|
||||
codex_home,
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn parses_managed_user_identity() {
|
||||
let command = SandboxSetupCommand::try_parse_from([
|
||||
"setup",
|
||||
"--elevated",
|
||||
"--user",
|
||||
"DOMAIN\\alice",
|
||||
"--codex-home",
|
||||
r"C:\Users\alice\.codex",
|
||||
])
|
||||
.expect("parse");
|
||||
|
||||
assert!(command.elevated_sandbox_level);
|
||||
assert_eq!(command.user.as_deref(), Some(r"DOMAIN\alice"));
|
||||
assert!(!command.current_user);
|
||||
assert_eq!(
|
||||
command.codex_home.as_deref(),
|
||||
Some(std::path::Path::new(r"C:\Users\alice\.codex"))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn requires_explicit_user_identity() {
|
||||
let err = SandboxSetupCommand::try_parse_from(["setup", "--elevated"])
|
||||
.expect_err("parse should fail");
|
||||
|
||||
assert_eq!(err.kind(), clap::error::ErrorKind::MissingRequiredArgument);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn requires_codex_home_for_managed_user() {
|
||||
let err =
|
||||
SandboxSetupCommand::try_parse_from(["setup", "--elevated", "--user", "DOMAIN\\alice"])
|
||||
.expect_err("parse should fail");
|
||||
|
||||
assert_eq!(err.kind(), clap::error::ErrorKind::MissingRequiredArgument);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parses_setup_from_sandbox_command_args() {
|
||||
let command = parse_setup_command(&[
|
||||
"setup".to_string(),
|
||||
"--elevated".to_string(),
|
||||
"--user".to_string(),
|
||||
r"DOMAIN\alice".to_string(),
|
||||
"--codex-home".to_string(),
|
||||
r"C:\Users\alice\.codex".to_string(),
|
||||
])
|
||||
.expect("parse")
|
||||
.expect("setup command");
|
||||
|
||||
assert_eq!(command.user.as_deref(), Some(r"DOMAIN\alice"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ignores_non_setup_sandbox_command_args() {
|
||||
let command =
|
||||
parse_setup_command(&["echo".to_string(), "hello".to_string()]).expect("parse");
|
||||
|
||||
assert!(command.is_none());
|
||||
}
|
||||
}
|
||||
@@ -168,6 +168,11 @@ pub fn run_elevated_setup(
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
pub fn run_elevated_provisioning_setup(codex_home: &Path, real_user: &str) -> anyhow::Result<()> {
|
||||
codex_windows_sandbox::run_elevated_provisioning_setup(codex_home, real_user)
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
pub fn run_elevated_setup(
|
||||
_permission_profile: &PermissionProfile,
|
||||
@@ -179,6 +184,11 @@ pub fn run_elevated_setup(
|
||||
anyhow::bail!("elevated Windows sandbox setup is only supported on Windows")
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
pub fn run_elevated_provisioning_setup(_codex_home: &Path, _real_user: &str) -> anyhow::Result<()> {
|
||||
anyhow::bail!("elevated Windows sandbox setup is only supported on Windows")
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
pub fn run_legacy_setup_preflight(
|
||||
permission_profile: &PermissionProfile,
|
||||
|
||||
@@ -105,6 +105,7 @@ struct Payload {
|
||||
enum SetupMode {
|
||||
#[default]
|
||||
Full,
|
||||
ProvisionOnly,
|
||||
ReadAclsOnly,
|
||||
}
|
||||
|
||||
@@ -476,6 +477,7 @@ fn real_main() -> Result<()> {
|
||||
fn run_setup(payload: &Payload, log: &mut dyn Write, sbx_dir: &Path) -> Result<()> {
|
||||
match payload.mode {
|
||||
SetupMode::ReadAclsOnly => run_read_acl_only(payload, log),
|
||||
SetupMode::ProvisionOnly => run_provision_only(payload, log, sbx_dir),
|
||||
SetupMode::Full => run_setup_full(payload, log, sbx_dir),
|
||||
}
|
||||
}
|
||||
@@ -543,31 +545,182 @@ fn run_read_acl_only(payload: &Payload, log: &mut dyn Write) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn provision_and_hide_sandbox_users(
|
||||
payload: &Payload,
|
||||
log: &mut dyn Write,
|
||||
sbx_dir: &Path,
|
||||
) -> Result<()> {
|
||||
let provision_result = provision_sandbox_users(
|
||||
&payload.codex_home,
|
||||
&payload.offline_username,
|
||||
&payload.online_username,
|
||||
&payload.proxy_ports,
|
||||
payload.allow_local_binding,
|
||||
log,
|
||||
);
|
||||
if let Err(err) = provision_result {
|
||||
if extract_setup_failure(&err).is_some() {
|
||||
return Err(err);
|
||||
}
|
||||
return Err(anyhow::Error::new(SetupFailure::new(
|
||||
SetupErrorCode::HelperUserProvisionFailed,
|
||||
format!("provision sandbox users failed: {err}"),
|
||||
)));
|
||||
}
|
||||
let users = vec![
|
||||
payload.offline_username.clone(),
|
||||
payload.online_username.clone(),
|
||||
];
|
||||
hide_newly_created_users(&users, sbx_dir);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn configure_offline_sandbox_network(
|
||||
payload: &Payload,
|
||||
offline_sid_str: &str,
|
||||
log: &mut dyn Write,
|
||||
) -> Result<()> {
|
||||
let proxy_allowlist_result = firewall::ensure_offline_proxy_allowlist(
|
||||
offline_sid_str,
|
||||
&payload.proxy_ports,
|
||||
payload.allow_local_binding,
|
||||
log,
|
||||
);
|
||||
if let Err(err) = proxy_allowlist_result {
|
||||
if extract_setup_failure(&err).is_some() {
|
||||
return Err(err);
|
||||
}
|
||||
return Err(anyhow::Error::new(SetupFailure::new(
|
||||
SetupErrorCode::HelperFirewallRuleCreateOrAddFailed,
|
||||
format!("ensure offline proxy allowlist failed: {err}"),
|
||||
)));
|
||||
}
|
||||
let firewall_result = firewall::ensure_offline_outbound_block(offline_sid_str, log);
|
||||
if let Err(err) = firewall_result {
|
||||
if extract_setup_failure(&err).is_some() {
|
||||
return Err(err);
|
||||
}
|
||||
return Err(anyhow::Error::new(SetupFailure::new(
|
||||
SetupErrorCode::HelperFirewallRuleCreateOrAddFailed,
|
||||
format!("ensure offline outbound block failed: {err}"),
|
||||
)));
|
||||
}
|
||||
install_wfp_filters(
|
||||
&payload.codex_home,
|
||||
&payload.offline_username,
|
||||
payload.otel.as_ref(),
|
||||
|message| {
|
||||
let _ = log_line(log, message);
|
||||
},
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn lock_persistent_sandbox_dirs(
|
||||
payload: &Payload,
|
||||
sandbox_group_sid: &[u8],
|
||||
log: &mut dyn Write,
|
||||
) -> Result<()> {
|
||||
lock_sandbox_dir(
|
||||
&sandbox_dir(&payload.codex_home),
|
||||
&payload.real_user,
|
||||
sandbox_group_sid,
|
||||
GRANT_ACCESS,
|
||||
FILE_GENERIC_READ | FILE_GENERIC_WRITE | FILE_GENERIC_EXECUTE | DELETE,
|
||||
FILE_GENERIC_READ | FILE_GENERIC_WRITE | FILE_GENERIC_EXECUTE,
|
||||
log,
|
||||
)
|
||||
.map_err(|err| {
|
||||
anyhow::Error::new(SetupFailure::new(
|
||||
SetupErrorCode::HelperSandboxLockFailed,
|
||||
format!(
|
||||
"lock sandbox dir {} failed: {err}",
|
||||
sandbox_dir(&payload.codex_home).display()
|
||||
),
|
||||
))
|
||||
})?;
|
||||
lock_sandbox_dir(
|
||||
&sandbox_secrets_dir(&payload.codex_home),
|
||||
&payload.real_user,
|
||||
sandbox_group_sid,
|
||||
DENY_ACCESS,
|
||||
FILE_GENERIC_READ | FILE_GENERIC_WRITE | FILE_GENERIC_EXECUTE | DELETE,
|
||||
FILE_GENERIC_READ | FILE_GENERIC_WRITE | FILE_GENERIC_EXECUTE,
|
||||
log,
|
||||
)
|
||||
.map_err(|err| {
|
||||
anyhow::Error::new(SetupFailure::new(
|
||||
SetupErrorCode::HelperSandboxLockFailed,
|
||||
format!(
|
||||
"lock sandbox secrets dir {} failed: {err}",
|
||||
sandbox_secrets_dir(&payload.codex_home).display()
|
||||
),
|
||||
))
|
||||
})?;
|
||||
let legacy_users = sandbox_dir(&payload.codex_home).join("sandbox_users.json");
|
||||
if legacy_users.exists() {
|
||||
let _ = std::fs::remove_file(&legacy_users);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn lock_sandbox_bin_dir(
|
||||
payload: &Payload,
|
||||
sandbox_group_sid: &[u8],
|
||||
log: &mut dyn Write,
|
||||
) -> Result<()> {
|
||||
lock_sandbox_dir(
|
||||
&sandbox_bin_dir(&payload.codex_home),
|
||||
&payload.real_user,
|
||||
sandbox_group_sid,
|
||||
GRANT_ACCESS,
|
||||
FILE_GENERIC_READ | FILE_GENERIC_EXECUTE,
|
||||
FILE_GENERIC_READ | FILE_GENERIC_WRITE | FILE_GENERIC_EXECUTE | DELETE,
|
||||
log,
|
||||
)
|
||||
.map_err(|err| {
|
||||
anyhow::Error::new(SetupFailure::new(
|
||||
SetupErrorCode::HelperSandboxLockFailed,
|
||||
format!(
|
||||
"lock sandbox bin dir {} failed: {err}",
|
||||
sandbox_bin_dir(&payload.codex_home).display()
|
||||
),
|
||||
))
|
||||
})
|
||||
}
|
||||
|
||||
fn run_provision_only(payload: &Payload, log: &mut dyn Write, sbx_dir: &Path) -> Result<()> {
|
||||
provision_and_hide_sandbox_users(payload, log, sbx_dir)?;
|
||||
let offline_sid = resolve_sid(&payload.offline_username).map_err(|err| {
|
||||
anyhow::Error::new(SetupFailure::new(
|
||||
SetupErrorCode::HelperSidResolveFailed,
|
||||
format!(
|
||||
"resolve SID for offline user {} failed: {err}",
|
||||
payload.offline_username
|
||||
),
|
||||
))
|
||||
})?;
|
||||
let offline_sid_str = string_from_sid_bytes(&offline_sid).map_err(anyhow::Error::msg)?;
|
||||
|
||||
let sandbox_group_sid = resolve_sandbox_users_group_sid().map_err(|err| {
|
||||
anyhow::Error::new(SetupFailure::new(
|
||||
SetupErrorCode::HelperSidResolveFailed,
|
||||
format!("resolve sandbox users group SID failed: {err}"),
|
||||
))
|
||||
})?;
|
||||
|
||||
configure_offline_sandbox_network(payload, &offline_sid_str, log)?;
|
||||
|
||||
lock_sandbox_bin_dir(payload, &sandbox_group_sid, log)?;
|
||||
lock_persistent_sandbox_dirs(payload, &sandbox_group_sid, log)?;
|
||||
log_note("setup provisioning binary completed", Some(sbx_dir));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn run_setup_full(payload: &Payload, log: &mut dyn Write, sbx_dir: &Path) -> Result<()> {
|
||||
let refresh_only = payload.refresh_only;
|
||||
if !refresh_only {
|
||||
let provision_result = provision_sandbox_users(
|
||||
&payload.codex_home,
|
||||
&payload.offline_username,
|
||||
&payload.online_username,
|
||||
&payload.proxy_ports,
|
||||
payload.allow_local_binding,
|
||||
log,
|
||||
);
|
||||
if let Err(err) = provision_result {
|
||||
if extract_setup_failure(&err).is_some() {
|
||||
return Err(err);
|
||||
}
|
||||
return Err(anyhow::Error::new(SetupFailure::new(
|
||||
SetupErrorCode::HelperUserProvisionFailed,
|
||||
format!("provision sandbox users failed: {err}"),
|
||||
)));
|
||||
}
|
||||
let users = vec![
|
||||
payload.offline_username.clone(),
|
||||
payload.online_username.clone(),
|
||||
];
|
||||
hide_newly_created_users(&users, sbx_dir);
|
||||
provision_and_hide_sandbox_users(payload, log, sbx_dir)?;
|
||||
}
|
||||
let offline_sid = resolve_sid(&payload.offline_username).map_err(|err| {
|
||||
anyhow::Error::new(SetupFailure::new(
|
||||
@@ -597,39 +750,7 @@ fn run_setup_full(payload: &Payload, log: &mut dyn Write, sbx_dir: &Path) -> Res
|
||||
|
||||
let mut refresh_errors: Vec<String> = Vec::new();
|
||||
if !refresh_only {
|
||||
let proxy_allowlist_result = firewall::ensure_offline_proxy_allowlist(
|
||||
&offline_sid_str,
|
||||
&payload.proxy_ports,
|
||||
payload.allow_local_binding,
|
||||
log,
|
||||
);
|
||||
if let Err(err) = proxy_allowlist_result {
|
||||
if extract_setup_failure(&err).is_some() {
|
||||
return Err(err);
|
||||
}
|
||||
return Err(anyhow::Error::new(SetupFailure::new(
|
||||
SetupErrorCode::HelperFirewallRuleCreateOrAddFailed,
|
||||
format!("ensure offline proxy allowlist failed: {err}"),
|
||||
)));
|
||||
}
|
||||
let firewall_result = firewall::ensure_offline_outbound_block(&offline_sid_str, log);
|
||||
if let Err(err) = firewall_result {
|
||||
if extract_setup_failure(&err).is_some() {
|
||||
return Err(err);
|
||||
}
|
||||
return Err(anyhow::Error::new(SetupFailure::new(
|
||||
SetupErrorCode::HelperFirewallRuleCreateOrAddFailed,
|
||||
format!("ensure offline outbound block failed: {err}"),
|
||||
)));
|
||||
}
|
||||
install_wfp_filters(
|
||||
&payload.codex_home,
|
||||
&payload.offline_username,
|
||||
payload.otel.as_ref(),
|
||||
|message| {
|
||||
let _ = log_line(log, message);
|
||||
},
|
||||
);
|
||||
configure_offline_sandbox_network(payload, &offline_sid_str, log)?;
|
||||
}
|
||||
|
||||
// Deny-read ACEs must be present before the sandboxed command starts. Apply
|
||||
@@ -865,24 +986,7 @@ fn run_setup_full(payload: &Payload, log: &mut dyn Write, sbx_dir: &Path) -> Res
|
||||
}
|
||||
}
|
||||
|
||||
lock_sandbox_dir(
|
||||
&sandbox_bin_dir(&payload.codex_home),
|
||||
&payload.real_user,
|
||||
&sandbox_group_sid,
|
||||
GRANT_ACCESS,
|
||||
FILE_GENERIC_READ | FILE_GENERIC_EXECUTE,
|
||||
FILE_GENERIC_READ | FILE_GENERIC_WRITE | FILE_GENERIC_EXECUTE | DELETE,
|
||||
log,
|
||||
)
|
||||
.map_err(|err| {
|
||||
anyhow::Error::new(SetupFailure::new(
|
||||
SetupErrorCode::HelperSandboxLockFailed,
|
||||
format!(
|
||||
"lock sandbox bin dir {} failed: {err}",
|
||||
sandbox_bin_dir(&payload.codex_home).display()
|
||||
),
|
||||
))
|
||||
})?;
|
||||
lock_sandbox_bin_dir(payload, &sandbox_group_sid, log)?;
|
||||
|
||||
if refresh_only {
|
||||
log_line(
|
||||
@@ -895,46 +999,7 @@ fn run_setup_full(payload: &Payload, log: &mut dyn Write, sbx_dir: &Path) -> Res
|
||||
)?;
|
||||
}
|
||||
if !refresh_only {
|
||||
lock_sandbox_dir(
|
||||
&sandbox_dir(&payload.codex_home),
|
||||
&payload.real_user,
|
||||
&sandbox_group_sid,
|
||||
GRANT_ACCESS,
|
||||
FILE_GENERIC_READ | FILE_GENERIC_WRITE | FILE_GENERIC_EXECUTE | DELETE,
|
||||
FILE_GENERIC_READ | FILE_GENERIC_WRITE | FILE_GENERIC_EXECUTE,
|
||||
log,
|
||||
)
|
||||
.map_err(|err| {
|
||||
anyhow::Error::new(SetupFailure::new(
|
||||
SetupErrorCode::HelperSandboxLockFailed,
|
||||
format!(
|
||||
"lock sandbox dir {} failed: {err}",
|
||||
sandbox_dir(&payload.codex_home).display()
|
||||
),
|
||||
))
|
||||
})?;
|
||||
lock_sandbox_dir(
|
||||
&sandbox_secrets_dir(&payload.codex_home),
|
||||
&payload.real_user,
|
||||
&sandbox_group_sid,
|
||||
DENY_ACCESS,
|
||||
FILE_GENERIC_READ | FILE_GENERIC_WRITE | FILE_GENERIC_EXECUTE | DELETE,
|
||||
FILE_GENERIC_READ | FILE_GENERIC_WRITE | FILE_GENERIC_EXECUTE,
|
||||
log,
|
||||
)
|
||||
.map_err(|err| {
|
||||
anyhow::Error::new(SetupFailure::new(
|
||||
SetupErrorCode::HelperSandboxLockFailed,
|
||||
format!(
|
||||
"lock sandbox secrets dir {} failed: {err}",
|
||||
sandbox_secrets_dir(&payload.codex_home).display()
|
||||
),
|
||||
))
|
||||
})?;
|
||||
let legacy_users = sandbox_dir(&payload.codex_home).join("sandbox_users.json");
|
||||
if legacy_users.exists() {
|
||||
let _ = std::fs::remove_file(&legacy_users);
|
||||
}
|
||||
lock_persistent_sandbox_dirs(payload, &sandbox_group_sid, log)?;
|
||||
}
|
||||
|
||||
unsafe {
|
||||
@@ -986,6 +1051,15 @@ mod tests {
|
||||
assert_eq!(payload.otel, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn payload_accepts_provision_only_mode() {
|
||||
let mut payload = payload_json();
|
||||
payload["mode"] = json!("provision-only");
|
||||
let payload: Payload = serde_json::from_value(payload).expect("payload");
|
||||
|
||||
assert_eq!(payload.mode, super::SetupMode::ProvisionOnly);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn payload_accepts_otel_settings() {
|
||||
let mut payload = payload_json();
|
||||
|
||||
@@ -211,6 +211,8 @@ pub use setup::SandboxSetupRequest;
|
||||
#[cfg(target_os = "windows")]
|
||||
pub use setup::SetupRootOverrides;
|
||||
#[cfg(target_os = "windows")]
|
||||
pub use setup::run_elevated_provisioning_setup;
|
||||
#[cfg(target_os = "windows")]
|
||||
pub use setup::run_elevated_setup;
|
||||
#[cfg(target_os = "windows")]
|
||||
pub use setup::run_setup_refresh;
|
||||
|
||||
@@ -198,6 +198,7 @@ fn run_setup_refresh_inner(
|
||||
allow_local_binding: offline_proxy_settings.allow_local_binding,
|
||||
otel: None,
|
||||
real_user: std::env::var("USERNAME").unwrap_or_else(|_| "Administrators".to_string()),
|
||||
mode: SetupMode::Full,
|
||||
refresh_only: true,
|
||||
};
|
||||
let json = serde_json::to_vec(&payload)?;
|
||||
@@ -491,10 +492,18 @@ struct ElevationPayload {
|
||||
allow_local_binding: bool,
|
||||
otel: Option<codex_otel::StatsigMetricsSettings>,
|
||||
real_user: String,
|
||||
mode: SetupMode,
|
||||
#[serde(default)]
|
||||
refresh_only: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Serialize)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
enum SetupMode {
|
||||
Full,
|
||||
ProvisionOnly,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub(crate) struct OfflineProxySettings {
|
||||
pub proxy_ports: Vec<u16>,
|
||||
@@ -803,6 +812,7 @@ pub fn run_elevated_setup(
|
||||
allow_local_binding: offline_proxy_settings.allow_local_binding,
|
||||
real_user: std::env::var("USERNAME").unwrap_or_else(|_| "Administrators".to_string()),
|
||||
otel: codex_otel::global_statsig_metrics_settings(),
|
||||
mode: SetupMode::Full,
|
||||
refresh_only: false,
|
||||
};
|
||||
let needs_elevation = !is_elevated().map_err(|err| {
|
||||
@@ -814,6 +824,45 @@ pub fn run_elevated_setup(
|
||||
run_setup_exe(&payload, needs_elevation, request.codex_home)
|
||||
}
|
||||
|
||||
pub fn run_elevated_provisioning_setup(codex_home: &Path, real_user: &str) -> Result<()> {
|
||||
let sbx_dir = sandbox_dir(codex_home);
|
||||
std::fs::create_dir_all(&sbx_dir).map_err(|err| {
|
||||
failure(
|
||||
SetupErrorCode::OrchestratorSandboxDirCreateFailed,
|
||||
format!("failed to create sandbox dir {}: {err}", sbx_dir.display()),
|
||||
)
|
||||
})?;
|
||||
if !is_elevated().map_err(|err| {
|
||||
failure(
|
||||
SetupErrorCode::OrchestratorElevationCheckFailed,
|
||||
format!("failed to determine elevation state: {err}"),
|
||||
)
|
||||
})? {
|
||||
return Err(failure(
|
||||
SetupErrorCode::OrchestratorElevationRequired,
|
||||
"sandbox provisioning setup must be run from an elevated process",
|
||||
));
|
||||
}
|
||||
let payload = ElevationPayload {
|
||||
version: SETUP_VERSION,
|
||||
offline_username: OFFLINE_USERNAME.to_string(),
|
||||
online_username: ONLINE_USERNAME.to_string(),
|
||||
codex_home: codex_home.to_path_buf(),
|
||||
command_cwd: codex_home.to_path_buf(),
|
||||
read_roots: Vec::new(),
|
||||
write_roots: Vec::new(),
|
||||
deny_read_paths: Vec::new(),
|
||||
deny_write_paths: Vec::new(),
|
||||
proxy_ports: Vec::new(),
|
||||
allow_local_binding: false,
|
||||
otel: codex_otel::global_statsig_metrics_settings(),
|
||||
real_user: real_user.to_string(),
|
||||
mode: SetupMode::ProvisionOnly,
|
||||
refresh_only: false,
|
||||
};
|
||||
run_setup_exe(&payload, /*needs_elevation*/ false, codex_home)
|
||||
}
|
||||
|
||||
fn build_payload_roots(
|
||||
request: &SandboxSetupRequest<'_>,
|
||||
overrides: &SetupRootOverrides,
|
||||
|
||||
@@ -19,6 +19,8 @@ pub enum SetupErrorCode {
|
||||
OrchestratorSandboxDirCreateFailed,
|
||||
/// Failed to determine whether the current process is elevated.
|
||||
OrchestratorElevationCheckFailed,
|
||||
/// The setup command requires an already elevated process.
|
||||
OrchestratorElevationRequired,
|
||||
/// Failed to serialize the elevation payload before launching the helper.
|
||||
OrchestratorPayloadSerializeFailed,
|
||||
/// Failed to launch the setup helper process (spawn or ShellExecuteExW).
|
||||
@@ -75,6 +77,7 @@ impl SetupErrorCode {
|
||||
match self {
|
||||
Self::OrchestratorSandboxDirCreateFailed => "orchestrator_sandbox_dir_create_failed",
|
||||
Self::OrchestratorElevationCheckFailed => "orchestrator_elevation_check_failed",
|
||||
Self::OrchestratorElevationRequired => "orchestrator_elevation_required",
|
||||
Self::OrchestratorPayloadSerializeFailed => "orchestrator_payload_serialize_failed",
|
||||
Self::OrchestratorHelperLaunchFailed => "orchestrator_helper_launch_failed",
|
||||
Self::OrchestratorHelperLaunchCanceled => "orchestrator_helper_launch_canceled",
|
||||
|
||||
Reference in New Issue
Block a user