Skip to content

Commit 815b6db

Browse files
committed
refactor(podman): generalize keep-id group handling
Signed-off-by: Evan Lezar <elezar@nvidia.com>
1 parent 948c6f1 commit 815b6db

6 files changed

Lines changed: 58 additions & 41 deletions

File tree

‎crates/openshell-driver-podman/src/client.rs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ use tracing::debug;
2121
const API_VERSION: &str = "v5.0.0";
2222

2323
/// Timeout for individual Podman API calls.
24-
const API_TIMEOUT: Duration = Duration::from_secs(120);
24+
const API_TIMEOUT: Duration = Duration::from_secs(30);
2525

2626
/// Maximum allowed size for the event stream line buffer (1 MB).
2727
const MAX_EVENT_BUFFER: usize = 1_048_576;

‎crates/openshell-driver-podman/src/driver.rs‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -978,7 +978,9 @@ impl PodmanComputeDriver {
978978
&workload_id,
979979
&uuid::Uuid::new_v4().to_string(),
980980
&identity,
981-
self.config.userns.as_deref(),
981+
crate::isolation::userns_preserves_host_groups(
982+
self.config.userns.as_deref(),
983+
),
982984
child_env,
983985
&launch_authentication,
984986
)?;
@@ -1320,7 +1322,7 @@ impl PodmanComputeDriver {
13201322
&container_id,
13211323
generation.as_str(),
13221324
&restart_metadata.workload_identity,
1323-
self.config.userns.as_deref(),
1325+
crate::isolation::userns_preserves_host_groups(self.config.userns.as_deref()),
13241326
restart_metadata.child_env,
13251327
&launch_authentication,
13261328
)?;

‎crates/openshell-driver-podman/src/isolation.rs‎

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ use openshell_core::proto::compute::v1::DriverSandbox;
1313
use openshell_isolation_interface::contract::{
1414
OuterFenceGuarantee, OuterFenceGuarantees, ResolvedWorkloadIdentity,
1515
};
16+
use openshell_sandbox_backend::ALLOW_EXTRA_SUPPLEMENTARY_GROUPS_RESOURCE_CLAIM;
1617
use openshell_sandbox_backend::boundary_protocol::{
1718
BoundaryConfig, BoundaryListener, GatewayVerificationKey, SandboxRuntimeDescriptor,
1819
SandboxTlsClientConfig, SandboxTlsServerConfig, SandboxTransport,
@@ -27,7 +28,6 @@ pub const BOOTSTRAP_PATH: &str = "/.openshell/channel/sandbox/bootstrap.json";
2728
pub const RUNTIME_DESCRIPTOR_PATH: &str = "/.openshell/supervisor/runtime-descriptor.json";
2829
pub const AUTH_BUNDLE_PATH: &str = "/.openshell/supervisor/auth.json";
2930
pub const RESTART_METADATA_PATH: &str = "/.openshell/supervisor/restart-metadata.json";
30-
pub const USERNS_RESOURCE_CLAIM: &str = "podman.userns";
3131
const SOCKET_PATH: &str = "/.openshell/channel/sandbox/control.sock";
3232

3333
#[derive(Serialize)]
@@ -71,6 +71,12 @@ pub fn channel_volume_name(id: &str) -> String {
7171
format!("openshell-channel-{id}")
7272
}
7373

74+
/// `keep-id` may retain the gateway user's supplementary groups in the
75+
/// container. Other user-namespace modes, including `auto`, do not.
76+
pub fn userns_preserves_host_groups(userns: Option<&str>) -> bool {
77+
userns.is_some_and(|mode| mode.split(':').next() == Some("keep-id"))
78+
}
79+
7480
fn invalid(error: impl std::fmt::Display) -> ComputeDriverError {
7581
ComputeDriverError::Precondition(error.to_string())
7682
}
@@ -196,7 +202,7 @@ pub fn bootstrap_archives(
196202
container_id: &str,
197203
generation: &str,
198204
identity: &ResolvedWorkloadIdentity,
199-
userns: Option<&str>,
205+
allow_extra_supplementary_groups: bool,
200206
child_env: HashMap<String, String>,
201207
launch_authentication: &openshell_core::jwt::SandboxLaunchAuthentication,
202208
) -> Result<BootstrapArchives, ComputeDriverError> {
@@ -210,8 +216,11 @@ pub fn bootstrap_archives(
210216
identity.resource_digest.clone(),
211217
),
212218
]);
213-
if userns.is_some_and(|mode| mode.split(':').next() == Some("keep-id")) {
214-
resource_claims.insert(USERNS_RESOURCE_CLAIM.into(), "keep-id".into());
219+
if allow_extra_supplementary_groups {
220+
resource_claims.insert(
221+
ALLOW_EXTRA_SUPPLEMENTARY_GROUPS_RESOURCE_CLAIM.into(),
222+
"true".into(),
223+
);
215224
}
216225
let runtime_generation = launch_authentication
217226
.supervisor
@@ -501,7 +510,7 @@ mod tests {
501510
"container",
502511
"generation-1",
503512
&identity,
504-
None,
513+
false,
505514
child_env.clone(),
506515
&authentication,
507516
)
@@ -547,6 +556,11 @@ mod tests {
547556
.outer_fence
548557
.validate(&runtime_descriptor.generation)
549558
.unwrap();
559+
assert!(
560+
!config
561+
.resource_claims
562+
.contains_key(ALLOW_EXTRA_SUPPLEMENTARY_GROUPS_RESOURCE_CLAIM)
563+
);
550564
let restart_metadata: RestartMetadata = serde_json::from_slice(
551565
supervisor
552566
.get(&PathBuf::from(
@@ -564,4 +578,15 @@ mod tests {
564578
.any(|window| window == b"PRIVATE KEY")
565579
);
566580
}
581+
582+
#[test]
583+
fn keep_id_is_the_only_userns_mode_that_preserves_host_groups() {
584+
assert!(userns_preserves_host_groups(Some("keep-id")));
585+
assert!(userns_preserves_host_groups(Some(
586+
"keep-id:uid=1000,gid=1000"
587+
)));
588+
assert!(!userns_preserves_host_groups(Some("auto")));
589+
assert!(!userns_preserves_host_groups(Some("private")));
590+
assert!(!userns_preserves_host_groups(None));
591+
}
567592
}

‎crates/openshell-sandbox-backend/src/lib.rs‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,11 @@ pub const BACKEND_NAME: &str = "openshell-sandbox";
2121
/// Resource claim set by compute drivers when the workload requests GPU access.
2222
pub const GPU_RESOURCE_CLAIM: &str = "openshell.gpu";
2323

24+
/// Resource claim set when the runtime may retain supplementary groups in
25+
/// addition to the image-derived workload identity.
26+
pub const ALLOW_EXTRA_SUPPLEMENTARY_GROUPS_RESOURCE_CLAIM: &str =
27+
"openshell.identity.allow_extra_supplementary_groups";
28+
2429
/// Memory-backed parent used for supervisor CA material.
2530
pub const SUPERVISOR_CA_RUNTIME_ROOT: &str = "/run/openshell-supervisor-ca";
2631

‎crates/openshell-sandbox/src/boundary_server.rs‎

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,6 @@ mod linux {
3939
BoundaryConfirmation, BoundaryExec, BoundaryLoopbackConnector, BoundaryProcess,
4040
BoundaryTerminal, ExecSession, LoopbackTarget, ResolvedWorkloadIdentity,
4141
};
42-
use openshell_sandbox_backend::GPU_RESOURCE_CLAIM;
4342
use openshell_sandbox_backend::mediation::{
4443
self, DnsQueryWire, MediationFrame, MediationFrameKind,
4544
};
@@ -53,6 +52,9 @@ mod linux {
5352
SandboxConnectionId, SandboxConnectionRegistry, SandboxProtocolAuthenticator,
5453
SandboxProtocolPrincipal,
5554
};
55+
use openshell_sandbox_backend::{
56+
ALLOW_EXTRA_SUPPLEMENTARY_GROUPS_RESOURCE_CLAIM, GPU_RESOURCE_CLAIM,
57+
};
5658
use tokio::io::{AsyncReadExt as _, AsyncWriteExt as _};
5759
use tokio_stream::wrappers::ReceiverStream;
5860

@@ -391,8 +393,8 @@ mod linux {
391393
.is_some_and(|value| value == "true")
392394
|| config
393395
.resource_claims
394-
.get("podman.userns")
395-
.is_some_and(|value| value == "keep-id")
396+
.get(ALLOW_EXTRA_SUPPLEMENTARY_GROUPS_RESOURCE_CLAIM)
397+
.is_some_and(|value| value == "true")
396398
}
397399

398400
fn supplementary_groups_match(actual: &[u32], expected: &[u32], allow_extra: bool) -> bool {
@@ -3839,7 +3841,7 @@ mod linux {
38393841
}
38403842

38413843
#[test]
3842-
fn podman_keep_id_allows_runtime_supplementary_groups() {
3844+
fn generic_identity_claim_allows_runtime_supplementary_groups() {
38433845
let config = BoundaryConfig {
38443846
boundary_id: "sandbox-1".to_string(),
38453847
generation: "generation-1".to_string(),
@@ -3854,12 +3856,12 @@ mod linux {
38543856
tls: placeholder_server_tls(),
38553857
},
38563858
resource_claims: std::collections::BTreeMap::from([(
3857-
"podman.userns".to_string(),
3858-
"keep-id".to_string(),
3859+
ALLOW_EXTRA_SUPPLEMENTARY_GROUPS_RESOURCE_CLAIM.to_string(),
3860+
"true".to_string(),
38593861
)]),
38603862
resource_claim_files: std::collections::BTreeMap::new(),
38613863
workload_identity: test_workload_identity(),
3862-
driver_fence: test_driver_fence(),
3864+
outer_fence: test_outer_fence(),
38633865
child_env: std::collections::HashMap::new(),
38643866
};
38653867
assert!(allows_runtime_supplementary_groups(&config));

‎tests/suites/drivers/podman/tests/default_userns.rs‎

Lines changed: 9 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@ const PODMAN_TEST_IMAGE_ENV: &str = "OPENSHELL_PODMAN_TEST_IMAGE";
1919
/// Verify that the gateway's user-namespace configuration matches Podman's
2020
/// direct behavior for the same profile.
2121
///
22-
/// The test creates a sandbox and compares its user-namespace mapping with the
23-
/// direct-Podman reference stored at
22+
/// The test runs a short-lived sandbox command and compares its user-namespace
23+
/// mapping with the direct-Podman reference stored at
2424
/// `OPENSHELL_TEST_INPUT_DIR/reference-uid-map`. The tmachine pre-test
2525
/// playbook creates that reference in the same gateway-user context. This deliberately
2626
/// avoids baking a particular Podman mapping into OpenShell's test contract.
@@ -59,38 +59,21 @@ async fn configured_userns_matches_podman_reference() {
5959
if let Some(image) = workload_image.as_deref() {
6060
create_args.extend(["--from", image]);
6161
}
62-
create_args.extend(["--detach", "--", "sleep", "infinity"]);
63-
let create = runner
64-
.step("userns/create")
65-
.description("sandbox from the configured test image is created")
66-
.with_timeout(SANDBOX_TIMEOUT)
67-
.run(&create_args)
68-
.await
69-
.map_err(|error| error.to_string())?;
70-
create.require_success()?;
71-
let exec = runner
62+
create_args.extend(["--no-tty", "--", "cat", "/proc/self/uid_map"]);
63+
let run = runner
7264
.step("userns/uid-map")
7365
.description("sandbox exposes its UID map")
7466
.with_timeout(SANDBOX_TIMEOUT)
75-
.run(&[
76-
"sandbox",
77-
"exec",
78-
"--name",
79-
&sandbox_name,
80-
"--no-tty",
81-
"--",
82-
"cat",
83-
"/proc/self/uid_map",
84-
])
67+
.run(&create_args)
8568
.await
8669
.map_err(|error| error.to_string())?;
87-
exec.require_success()?;
88-
let sandbox_uid_map = normalize_uid_map(exec.stdout()).ok_or_else(|| {
89-
exec.failure_diagnostic("sandbox returns a non-empty UID map")
70+
run.require_success()?;
71+
let sandbox_uid_map = normalize_uid_map(run.stdout()).ok_or_else(|| {
72+
run.failure_diagnostic("sandbox returns a non-empty UID map")
9073
})?;
9174
if sandbox_uid_map != expected_uid_map {
9275
return Err(format!(
93-
"sandbox UID map differs from direct Podman default:\nexpected:\n{expected_uid_map}\nactual:\n{sandbox_uid_map}"
76+
"sandbox UID map differs from the direct Podman reference:\nexpected:\n{expected_uid_map}\nactual:\n{sandbox_uid_map}"
9477
));
9578
}
9679
Ok(())

0 commit comments

Comments
 (0)