Skip to content

Commit ad5af38

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

7 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
@@ -972,7 +972,9 @@ impl PodmanComputeDriver {
972972
&workload_id,
973973
&uuid::Uuid::new_v4().to_string(),
974974
&identity,
975-
self.config.userns.as_deref(),
975+
crate::isolation::userns_preserves_host_groups(
976+
self.config.userns.as_deref(),
977+
),
976978
child_env,
977979
&launch_authentication,
978980
)?;
@@ -1314,7 +1316,7 @@ impl PodmanComputeDriver {
13141316
&container_id,
13151317
generation.as_str(),
13161318
&restart_metadata.workload_identity,
1317-
self.config.userns.as_deref(),
1319+
crate::isolation::userns_preserves_host_groups(self.config.userns.as_deref()),
13181320
restart_metadata.child_env,
13191321
&launch_authentication,
13201322
)?;

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

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ use std::path::PathBuf;
1111
use openshell_core::ComputeDriverError;
1212
use openshell_core::proto::compute::v1::DriverSandbox;
1313
use openshell_isolation_interface::contract::{DriverFenceEvidence, ResolvedWorkloadIdentity};
14+
use openshell_sandbox_backend::ALLOW_EXTRA_SUPPLEMENTARY_GROUPS_RESOURCE_CLAIM;
1415
use openshell_sandbox_backend::boundary_protocol::{
1516
BoundaryConfig, BoundaryListener, GatewayVerificationKey, SandboxRuntimeDescriptor,
1617
SandboxTlsClientConfig, SandboxTlsServerConfig, SandboxTransport,
@@ -25,7 +26,6 @@ pub const BOOTSTRAP_PATH: &str = "/.openshell/channel/sandbox/bootstrap.json";
2526
pub const RUNTIME_DESCRIPTOR_PATH: &str = "/.openshell/supervisor/runtime-descriptor.json";
2627
pub const AUTH_BUNDLE_PATH: &str = "/.openshell/supervisor/auth.json";
2728
pub const RESTART_METADATA_PATH: &str = "/.openshell/supervisor/restart-metadata.json";
28-
pub const USERNS_RESOURCE_CLAIM: &str = "podman.userns";
2929
const SOCKET_PATH: &str = "/.openshell/channel/sandbox/control.sock";
3030

3131
pub fn supervisor_name(id: &str) -> String {
@@ -35,6 +35,12 @@ pub fn channel_volume_name(id: &str) -> String {
3535
format!("openshell-channel-{id}")
3636
}
3737

38+
/// `keep-id` may retain the gateway user's supplementary groups in the
39+
/// container. Other user-namespace modes, including `auto`, do not.
40+
pub fn userns_preserves_host_groups(userns: Option<&str>) -> bool {
41+
userns.is_some_and(|mode| mode.split(':').next() == Some("keep-id"))
42+
}
43+
3844
fn invalid(error: impl std::fmt::Display) -> ComputeDriverError {
3945
ComputeDriverError::Precondition(error.to_string())
4046
}
@@ -160,7 +166,7 @@ pub fn bootstrap_archives(
160166
container_id: &str,
161167
generation: &str,
162168
identity: &ResolvedWorkloadIdentity,
163-
userns: Option<&str>,
169+
allow_extra_supplementary_groups: bool,
164170
child_env: HashMap<String, String>,
165171
launch_authentication: &openshell_core::jwt::SandboxLaunchAuthentication,
166172
) -> Result<BootstrapArchives, ComputeDriverError> {
@@ -174,8 +180,11 @@ pub fn bootstrap_archives(
174180
identity.resource_digest.clone(),
175181
),
176182
]);
177-
if userns.is_some_and(|mode| mode.split(':').next() == Some("keep-id")) {
178-
resource_claims.insert(USERNS_RESOURCE_CLAIM.into(), "keep-id".into());
183+
if allow_extra_supplementary_groups {
184+
resource_claims.insert(
185+
ALLOW_EXTRA_SUPPLEMENTARY_GROUPS_RESOURCE_CLAIM.into(),
186+
"true".into(),
187+
);
179188
}
180189
let driver_fence = DriverFenceEvidence::Podman {
181190
container_id: container_id.into(),
@@ -439,7 +448,7 @@ mod tests {
439448
"container",
440449
"generation-1",
441450
&identity,
442-
None,
451+
false,
443452
child_env.clone(),
444453
&authentication,
445454
)
@@ -481,6 +490,11 @@ mod tests {
481490
assert_eq!(config.session_id, runtime_descriptor.session_id);
482491
assert_eq!(config.driver_fence, runtime_descriptor.driver_fence);
483492
assert_eq!(config.workload_identity, identity);
493+
assert!(
494+
!config
495+
.resource_claims
496+
.contains_key(ALLOW_EXTRA_SUPPLEMENTARY_GROUPS_RESOURCE_CLAIM)
497+
);
484498
runtime_descriptor.driver_fence.validate().unwrap();
485499
let restart_metadata: RestartMetadata = serde_json::from_slice(
486500
supervisor
@@ -499,4 +513,15 @@ mod tests {
499513
.any(|window| window == b"PRIVATE KEY")
500514
);
501515
}
516+
517+
#[test]
518+
fn keep_id_is_the_only_userns_mode_that_preserves_host_groups() {
519+
assert!(userns_preserves_host_groups(Some("keep-id")));
520+
assert!(userns_preserves_host_groups(Some(
521+
"keep-id:uid=1000,gid=1000"
522+
)));
523+
assert!(!userns_preserves_host_groups(Some("auto")));
524+
assert!(!userns_preserves_host_groups(Some("private")));
525+
assert!(!userns_preserves_host_groups(None));
526+
}
502527
}

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: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@ mod linux {
4040
CapabilityEvidence, ExecSession, LoopbackTarget, ResolvedWorkloadIdentity,
4141
SandboxConfirmEvidence,
4242
};
43-
use openshell_sandbox_backend::GPU_RESOURCE_CLAIM;
4443
use openshell_sandbox_backend::mediation::{
4544
self, DnsQueryWire, MediationFrame, MediationFrameKind,
4645
};
@@ -54,6 +53,9 @@ mod linux {
5453
SandboxConnectionId, SandboxConnectionRegistry, SandboxProtocolAuthenticator,
5554
SandboxProtocolPrincipal,
5655
};
56+
use openshell_sandbox_backend::{
57+
ALLOW_EXTRA_SUPPLEMENTARY_GROUPS_RESOURCE_CLAIM, GPU_RESOURCE_CLAIM,
58+
};
5759
use tokio::io::{AsyncReadExt as _, AsyncWriteExt as _};
5860
use tokio_stream::wrappers::ReceiverStream;
5961

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

399401
fn supplementary_groups_match(actual: &[u32], expected: &[u32], allow_extra: bool) -> bool {
@@ -3829,7 +3831,7 @@ mod linux {
38293831
}
38303832

38313833
#[test]
3832-
fn podman_keep_id_allows_runtime_supplementary_groups() {
3834+
fn generic_identity_claim_allows_runtime_supplementary_groups() {
38333835
let config = BoundaryConfig {
38343836
boundary_id: "sandbox-1".to_string(),
38353837
generation: "generation-1".to_string(),
@@ -3844,8 +3846,8 @@ mod linux {
38443846
tls: placeholder_server_tls(),
38453847
},
38463848
resource_claims: std::collections::BTreeMap::from([(
3847-
"podman.userns".to_string(),
3848-
"keep-id".to_string(),
3849+
ALLOW_EXTRA_SUPPLEMENTARY_GROUPS_RESOURCE_CLAIM.to_string(),
3850+
"true".to_string(),
38493851
)]),
38503852
resource_claim_files: std::collections::BTreeMap::new(),
38513853
workload_identity: test_workload_identity(),

tests/ansible/playbooks/conformance/cli.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@
8484
- openshell-gateway.service
8585
- --no-pager
8686
- --lines
87-
- "20"
87+
- "500"
8888
register: openshell_gateway_logs
8989
changed_when: false
9090
failed_when: false

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)