@@ -2576,6 +2576,10 @@ async fn handle_get_sandbox_config_inner(
25762576) -> Result<Response<GetSandboxConfigResponse>, Status> {
25772577 let principal = super::extract_principal(&request)?;
25782578 let req = request.into_inner();
2579+ let legacy_supervisor = matches!(
2580+ &principal,
2581+ Principal::Sandbox(sandbox) if req.workspace_scope.is_none() && req.name == sandbox.sandbox_id
2582+ );
25792583 let workspace = match &principal {
25802584 Principal::Sandbox(_) if req.workspace_scope.is_none() => "",
25812585 _ => crate::auth::workspace_authz::selected_workspace_name(req.workspace_scope.as_ref())?,
@@ -2588,7 +2592,35 @@ async fn handle_get_sandbox_config_inner(
25882592 MinWorkspaceRole::User,
25892593 )
25902594 .await?;
2591- Ok(Response::new(load_sandbox_config(state, &sandbox).await?))
2595+ let mut config = load_sandbox_config(state, &sandbox).await?;
2596+ if legacy_supervisor && let Some(policy) = config.policy.as_mut() {
2597+ populate_legacy_endpoint_modes(policy)?;
2598+ }
2599+ Ok(Response::new(config))
2600+ }
2601+
2602+ #[allow(deprecated)]
2603+ fn populate_legacy_endpoint_modes(policy: &mut ProtoSandboxPolicy) -> Result<(), Status> {
2604+ for rule in policy.network_policies.values_mut() {
2605+ for endpoint in &mut rule.endpoints {
2606+ endpoint.legacy_tls = openshell_policy::network_tls_mode_to_str(endpoint.tls)
2607+ .ok_or_else(|| Status::internal("effective policy contains an unknown TLS mode"))?
2608+ .to_string();
2609+ endpoint.legacy_enforcement =
2610+ openshell_policy::network_enforcement_mode_to_str(endpoint.enforcement)
2611+ .ok_or_else(|| {
2612+ Status::internal("effective policy contains an unknown enforcement mode")
2613+ })?
2614+ .to_string();
2615+ endpoint.legacy_access =
2616+ openshell_policy::network_access_preset_to_str(endpoint.access)
2617+ .ok_or_else(|| {
2618+ Status::internal("effective policy contains an unknown access preset")
2619+ })?
2620+ .to_string();
2621+ }
2622+ }
2623+ Ok(())
25922624}
25932625
25942626/// Resolve the same effective configuration for authenticated RPCs and trusted
@@ -7503,6 +7535,45 @@ mod tests {
75037535 use std::sync::atomic::{AtomicUsize, Ordering};
75047536 use tonic::Code;
75057537
7538+ #[derive(Clone, PartialEq, prost::Message)]
7539+ struct LegacyNetworkEndpoint {
7540+ #[prost(string, tag = "4")]
7541+ tls: String,
7542+ #[prost(string, tag = "5")]
7543+ enforcement: String,
7544+ #[prost(string, tag = "6")]
7545+ access: String,
7546+ }
7547+
7548+ #[test]
7549+ fn legacy_endpoint_modes_remain_decodable_by_pre4_supervisors() {
7550+ use openshell_core::proto::{NetworkAccessPreset, NetworkEnforcementMode, NetworkTlsMode};
7551+
7552+ let mut policy = ProtoSandboxPolicy {
7553+ network_policies: HashMap::from([(
7554+ "api".to_string(),
7555+ NetworkPolicyRule {
7556+ endpoints: vec![NetworkEndpoint {
7557+ tls: NetworkTlsMode::Skip.into(),
7558+ enforcement: NetworkEnforcementMode::Enforce.into(),
7559+ access: NetworkAccessPreset::ReadWrite.into(),
7560+ ..Default::default()
7561+ }],
7562+ ..Default::default()
7563+ },
7564+ )]),
7565+ ..Default::default()
7566+ };
7567+
7568+ populate_legacy_endpoint_modes(&mut policy).unwrap();
7569+ let encoded = policy.network_policies["api"].endpoints[0].encode_to_vec();
7570+ let legacy = LegacyNetworkEndpoint::decode(encoded.as_slice()).unwrap();
7571+
7572+ assert_eq!(legacy.tls, "skip");
7573+ assert_eq!(legacy.enforcement, "enforce");
7574+ assert_eq!(legacy.access, "read-write");
7575+ }
7576+
75067577 /// Wrap a request with a user `Principal` so handler scope guards treat
75077578 /// the test caller as a CLI user. Most handler tests exercise
75087579 /// user-facing behavior and should not trip sandbox equality checks.
0 commit comments