mirror of
https://github.com/NVIDIA/OpenShell.git
synced 2026-10-02 07:34:45 +08:00
fix(podman): support rootless user namespace configurations (#3527)
* test(podman): cover user namespace configurations Signed-off-by: Evan Lezar <elezar@nvidia.com> * fix(podman): support keep-id runtime groups Signed-off-by: Evan Lezar <elezar@nvidia.com> refactor(podman): generalize keep-id group handling Signed-off-by: Evan Lezar <elezar@nvidia.com> * ci(podman): run driver integration tests Signed-off-by: Evan Lezar <elezar@nvidia.com> --------- Signed-off-by: Evan Lezar <elezar@nvidia.com>
This commit is contained in:
@@ -247,21 +247,21 @@ jobs:
|
||||
]
|
||||
|
||||
# Run driver-specific integration tests:
|
||||
# TODO: This should be added as soon as we have driver-specific tests enabled in tmachine.
|
||||
# driver-specific:
|
||||
# needs: prepare-integration
|
||||
# permissions:
|
||||
# actions: read
|
||||
# contents: read
|
||||
# packages: read
|
||||
# uses: ./.github/workflows/integration-runner.yml
|
||||
# with:
|
||||
# category: driver-specific
|
||||
# source-sha: ${{ needs.prepare-integration.outputs.source_sha }}
|
||||
# integration-inputs-artifact-id: ${{ needs.prepare-integration.outputs.integration_inputs_artifact_id }}
|
||||
# test-matrix: >-
|
||||
# [
|
||||
# ]
|
||||
driver-specific-integration:
|
||||
needs: prepare-integration
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
packages: read
|
||||
uses: ./.github/workflows/integration-runner.yml
|
||||
with:
|
||||
category: driver-specific
|
||||
source-sha: ${{ needs.prepare-integration.outputs.source_sha }}
|
||||
integration-inputs-artifact-id: ${{ needs.prepare-integration.outputs.integration_inputs_artifact_id }}
|
||||
test-matrix: >-
|
||||
[
|
||||
{"environment":"fedora-podman-rootless","installer":"binaries","testsuite":"driver-podman"}
|
||||
]
|
||||
|
||||
docker-e2e:
|
||||
needs: [pr_metadata, build-binaries, build-images]
|
||||
@@ -451,6 +451,7 @@ jobs:
|
||||
- pr_metadata
|
||||
- conformance-integration
|
||||
- feature-specific-integration
|
||||
- driver-specific-integration
|
||||
- docker-e2e
|
||||
- vm-e2e
|
||||
- docker-external-driver-e2e
|
||||
|
||||
@@ -1159,6 +1159,9 @@ impl PodmanComputeDriver {
|
||||
&workload_id,
|
||||
&uuid::Uuid::new_v4().to_string(),
|
||||
&identity,
|
||||
crate::isolation::userns_preserves_host_groups(
|
||||
self.config.userns.as_deref(),
|
||||
),
|
||||
child_env,
|
||||
&launch_authentication,
|
||||
)?;
|
||||
@@ -1501,6 +1504,7 @@ impl PodmanComputeDriver {
|
||||
&container_id,
|
||||
generation.as_str(),
|
||||
&restart_metadata.workload_identity,
|
||||
crate::isolation::userns_preserves_host_groups(self.config.userns.as_deref()),
|
||||
restart_metadata.child_env,
|
||||
&launch_authentication,
|
||||
)?;
|
||||
|
||||
@@ -13,6 +13,7 @@ use openshell_core::proto::compute::v1::DriverSandbox;
|
||||
use openshell_isolation_interface::contract::{
|
||||
OuterFenceGuarantee, OuterFenceGuarantees, ResolvedWorkloadIdentity,
|
||||
};
|
||||
use openshell_sandbox_backend::ALLOW_EXTRA_SUPPLEMENTARY_GROUPS_RESOURCE_CLAIM;
|
||||
use openshell_sandbox_backend::boundary_protocol::{
|
||||
BoundaryConfig, BoundaryListener, GatewayVerificationKey, SandboxRuntimeDescriptor,
|
||||
SandboxTlsClientConfig, SandboxTlsServerConfig, SandboxTransport,
|
||||
@@ -70,6 +71,12 @@ pub fn channel_volume_name(id: &str) -> String {
|
||||
format!("openshell-channel-{id}")
|
||||
}
|
||||
|
||||
/// `keep-id` may retain the gateway user's supplementary groups in the
|
||||
/// container. Other user-namespace modes, including `auto`, do not.
|
||||
pub fn userns_preserves_host_groups(userns: Option<&str>) -> bool {
|
||||
userns.is_some_and(|mode| mode.split(':').next() == Some("keep-id"))
|
||||
}
|
||||
|
||||
fn invalid(error: impl std::fmt::Display) -> ComputeDriverError {
|
||||
ComputeDriverError::Precondition(error.to_string())
|
||||
}
|
||||
@@ -195,19 +202,26 @@ pub fn bootstrap_archives(
|
||||
container_id: &str,
|
||||
generation: &str,
|
||||
identity: &ResolvedWorkloadIdentity,
|
||||
allow_extra_supplementary_groups: bool,
|
||||
child_env: HashMap<String, String>,
|
||||
launch_authentication: &openshell_core::jwt::SandboxLaunchAuthentication,
|
||||
) -> Result<BootstrapArchives, ComputeDriverError> {
|
||||
launch_authentication.validate().map_err(invalid)?;
|
||||
let session_id = launch_authentication.supervisor.session_id;
|
||||
let tls = generate_sandbox_tls_material(session_id).map_err(invalid)?;
|
||||
let resource_claims = BTreeMap::from([
|
||||
let mut resource_claims = BTreeMap::from([
|
||||
("podman.container_id".into(), container_id.into()),
|
||||
(
|
||||
"podman.image_identity".into(),
|
||||
identity.resource_digest.clone(),
|
||||
),
|
||||
]);
|
||||
if allow_extra_supplementary_groups {
|
||||
resource_claims.insert(
|
||||
ALLOW_EXTRA_SUPPLEMENTARY_GROUPS_RESOURCE_CLAIM.into(),
|
||||
"true".into(),
|
||||
);
|
||||
}
|
||||
let runtime_generation = launch_authentication
|
||||
.supervisor
|
||||
.runtime_generation
|
||||
@@ -496,6 +510,7 @@ mod tests {
|
||||
"container",
|
||||
"generation-1",
|
||||
&identity,
|
||||
false,
|
||||
child_env.clone(),
|
||||
&authentication,
|
||||
)
|
||||
@@ -541,6 +556,11 @@ mod tests {
|
||||
.outer_fence
|
||||
.validate(&runtime_descriptor.generation)
|
||||
.unwrap();
|
||||
assert!(
|
||||
!config
|
||||
.resource_claims
|
||||
.contains_key(ALLOW_EXTRA_SUPPLEMENTARY_GROUPS_RESOURCE_CLAIM)
|
||||
);
|
||||
let restart_metadata: RestartMetadata = serde_json::from_slice(
|
||||
supervisor
|
||||
.get(&PathBuf::from(
|
||||
@@ -558,4 +578,15 @@ mod tests {
|
||||
.any(|window| window == b"PRIVATE KEY")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keep_id_is_the_only_userns_mode_that_preserves_host_groups() {
|
||||
assert!(userns_preserves_host_groups(Some("keep-id")));
|
||||
assert!(userns_preserves_host_groups(Some(
|
||||
"keep-id:uid=1000,gid=1000"
|
||||
)));
|
||||
assert!(!userns_preserves_host_groups(Some("auto")));
|
||||
assert!(!userns_preserves_host_groups(Some("private")));
|
||||
assert!(!userns_preserves_host_groups(None));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,11 @@ pub const BACKEND_NAME: &str = "openshell-sandbox";
|
||||
/// Resource claim set by compute drivers when the workload requests GPU access.
|
||||
pub const GPU_RESOURCE_CLAIM: &str = "openshell.gpu";
|
||||
|
||||
/// Resource claim set when the runtime may retain supplementary groups in
|
||||
/// addition to the image-derived workload identity.
|
||||
pub const ALLOW_EXTRA_SUPPLEMENTARY_GROUPS_RESOURCE_CLAIM: &str =
|
||||
"openshell.identity.allow_extra_supplementary_groups";
|
||||
|
||||
/// Memory-backed parent used for supervisor CA material.
|
||||
pub const SUPERVISOR_CA_RUNTIME_ROOT: &str = "/run/openshell-supervisor-ca";
|
||||
|
||||
|
||||
@@ -39,7 +39,6 @@ mod linux {
|
||||
BoundaryConfirmation, BoundaryExec, BoundaryLoopbackConnector, BoundaryProcess,
|
||||
BoundaryTerminal, ExecSession, LoopbackTarget, ResolvedWorkloadIdentity,
|
||||
};
|
||||
use openshell_sandbox_backend::GPU_RESOURCE_CLAIM;
|
||||
use openshell_sandbox_backend::mediation::{
|
||||
self, DnsQueryWire, MediationFrame, MediationFrameKind,
|
||||
};
|
||||
@@ -53,6 +52,9 @@ mod linux {
|
||||
SandboxConnectionId, SandboxConnectionRegistry, SandboxProtocolAuthenticator,
|
||||
SandboxProtocolPrincipal,
|
||||
};
|
||||
use openshell_sandbox_backend::{
|
||||
ALLOW_EXTRA_SUPPLEMENTARY_GROUPS_RESOURCE_CLAIM, GPU_RESOURCE_CLAIM,
|
||||
};
|
||||
use tokio::io::{AsyncReadExt as _, AsyncWriteExt as _};
|
||||
use tokio_stream::wrappers::ReceiverStream;
|
||||
|
||||
@@ -389,6 +391,10 @@ mod linux {
|
||||
.resource_claims
|
||||
.get(GPU_RESOURCE_CLAIM)
|
||||
.is_some_and(|value| value == "true")
|
||||
|| config
|
||||
.resource_claims
|
||||
.get(ALLOW_EXTRA_SUPPLEMENTARY_GROUPS_RESOURCE_CLAIM)
|
||||
.is_some_and(|value| value == "true")
|
||||
}
|
||||
|
||||
fn supplementary_groups_match(actual: &[u32], expected: &[u32], allow_extra: bool) -> bool {
|
||||
@@ -3834,6 +3840,33 @@ mod linux {
|
||||
assert!(!supplementary_groups_match(&[44, 992], &[1001], true));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn generic_identity_claim_allows_runtime_supplementary_groups() {
|
||||
let config = BoundaryConfig {
|
||||
boundary_id: "sandbox-1".to_string(),
|
||||
generation: "generation-1".to_string(),
|
||||
session_id: test_session_id(),
|
||||
session_rotation: openshell_core::jwt::SessionRotation::new(1)
|
||||
.expect("session rotation"),
|
||||
auth_epoch: CredentialEpoch::new(1).expect("auth epoch"),
|
||||
gateway_id: "test-gateway".to_string(),
|
||||
verification_keys: vec![],
|
||||
listener: BoundaryListenerConfig::Vsock {
|
||||
control_port: 5500,
|
||||
tls: placeholder_server_tls(),
|
||||
},
|
||||
resource_claims: std::collections::BTreeMap::from([(
|
||||
ALLOW_EXTRA_SUPPLEMENTARY_GROUPS_RESOURCE_CLAIM.to_string(),
|
||||
"true".to_string(),
|
||||
)]),
|
||||
resource_claim_files: std::collections::BTreeMap::new(),
|
||||
workload_identity: test_workload_identity(),
|
||||
outer_fence: test_outer_fence(),
|
||||
child_env: std::collections::HashMap::new(),
|
||||
};
|
||||
assert!(allows_runtime_supplementary_groups(&config));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn control_connection_slots_bound_authenticated_sessions() {
|
||||
let active = Arc::new(AtomicUsize::new(MAX_CONTROL_CONNECTIONS - 1));
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
---
|
||||
- name: Capture the default rootless Podman user-namespace mapping
|
||||
hosts: all
|
||||
gather_facts: false
|
||||
vars:
|
||||
podman_reference_image: "{{ openshell_podman_reference_image }}"
|
||||
podman_reference_uid_map: /var/lib/openshell-test-inputs/podman/reference-uid-map
|
||||
tasks:
|
||||
- name: Wait for SSH
|
||||
ansible.builtin.wait_for_connection:
|
||||
|
||||
- name: Read OpenShell gateway service user
|
||||
become: true
|
||||
ansible.builtin.command:
|
||||
argv:
|
||||
- systemctl
|
||||
- show
|
||||
- openshell-gateway.service
|
||||
- --property
|
||||
- User
|
||||
- --value
|
||||
changed_when: false
|
||||
register: openshell_gateway_service_user
|
||||
|
||||
- name: Require the rootless Podman gateway user
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- openshell_gateway_service_user.stdout == "tmachine"
|
||||
fail_msg: >-
|
||||
The Podman default-userns suite requires a rootless Podman gateway
|
||||
running as tmachine, not {{ openshell_gateway_service_user.stdout | default("unknown") }}
|
||||
|
||||
- name: Read OpenShell gateway configuration
|
||||
become: true
|
||||
ansible.builtin.slurp:
|
||||
src: /etc/openshell/gateway.toml
|
||||
register: openshell_gateway_config
|
||||
|
||||
- name: Require unconfigured Podman user namespaces
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- >-
|
||||
(openshell_gateway_config.content | b64decode)
|
||||
is not regex('(?m)^\\s*(userns|uidmap|gidmap)\\s*=')
|
||||
fail_msg: >-
|
||||
The Podman default-userns suite requires gateway.toml to omit userns,
|
||||
uidmap, and gidmap.
|
||||
|
||||
- name: Resolve tmachine UID
|
||||
ansible.builtin.command:
|
||||
argv:
|
||||
- id
|
||||
- -u
|
||||
- tmachine
|
||||
changed_when: false
|
||||
register: tmachine_uid
|
||||
|
||||
- name: Create Podman test-input directory
|
||||
become: true
|
||||
ansible.builtin.file:
|
||||
path: "{{ podman_reference_uid_map | dirname }}"
|
||||
state: directory
|
||||
owner: tmachine
|
||||
group: tmachine
|
||||
mode: "0700"
|
||||
|
||||
- name: Pull the Podman reference image
|
||||
become: true
|
||||
become_user: tmachine
|
||||
ansible.builtin.command:
|
||||
argv: [podman, pull, "{{ podman_reference_image }}"]
|
||||
environment:
|
||||
HOME: /home/tmachine
|
||||
XDG_RUNTIME_DIR: "/run/user/{{ tmachine_uid.stdout }}"
|
||||
changed_when: false
|
||||
|
||||
- name: Capture direct Podman default UID mapping
|
||||
become: true
|
||||
become_user: tmachine
|
||||
ansible.builtin.command:
|
||||
argv:
|
||||
- podman
|
||||
- run
|
||||
- --rm
|
||||
- --pull
|
||||
- never
|
||||
- --entrypoint
|
||||
- /bin/cat
|
||||
- "{{ podman_reference_image }}"
|
||||
- /proc/self/uid_map
|
||||
environment:
|
||||
HOME: /home/tmachine
|
||||
XDG_RUNTIME_DIR: "/run/user/{{ tmachine_uid.stdout }}"
|
||||
changed_when: false
|
||||
register: podman_default_uid_map
|
||||
|
||||
- name: Require a direct Podman UID mapping
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- podman_default_uid_map.stdout | trim | length > 0
|
||||
fail_msg: Direct rootless Podman reference container returned no UID mapping
|
||||
|
||||
- name: Store direct Podman default UID mapping
|
||||
become: true
|
||||
ansible.builtin.copy:
|
||||
content: "{{ podman_default_uid_map.stdout | trim }}\n"
|
||||
dest: "{{ podman_reference_uid_map }}"
|
||||
owner: tmachine
|
||||
group: tmachine
|
||||
mode: "0600"
|
||||
@@ -0,0 +1,160 @@
|
||||
# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
---
|
||||
- name: Run Podman archive tests
|
||||
hosts: all
|
||||
gather_facts: false
|
||||
vars:
|
||||
podman_test_root: /var/lib/openshell-driver-tests/podman
|
||||
podman_test_input_dir: /var/lib/openshell-test-inputs/podman
|
||||
tasks:
|
||||
- name: Wait for SSH
|
||||
ansible.builtin.wait_for_connection:
|
||||
|
||||
- name: Create Podman test directory
|
||||
become: true
|
||||
ansible.builtin.file:
|
||||
path: "{{ podman_test_root }}"
|
||||
state: directory
|
||||
owner: tmachine
|
||||
group: tmachine
|
||||
mode: "0700"
|
||||
|
||||
- name: Install Podman test bundle
|
||||
become: true
|
||||
ansible.builtin.unarchive:
|
||||
src: "{{ openshell_podman_test_bundle }}"
|
||||
dest: "{{ podman_test_root }}"
|
||||
owner: tmachine
|
||||
group: tmachine
|
||||
|
||||
- name: Remove any previous OpenShell gateway registration
|
||||
ansible.builtin.command:
|
||||
argv: [/usr/local/bin/openshell, gateway, remove, tmachine]
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
|
||||
- name: Register the configured OpenShell gateway
|
||||
ansible.builtin.command:
|
||||
argv: [/usr/local/bin/openshell, gateway, add, http://127.0.0.1:17670, --local, --name, tmachine]
|
||||
|
||||
- name: Run Podman archive tests
|
||||
ansible.builtin.command:
|
||||
argv: [cargo-nextest, nextest, run, --archive-file, "{{ podman_test_root }}/tests.tar.zst", --workspace-remap, "{{ podman_test_root }}", --no-capture]
|
||||
environment:
|
||||
OPENSHELL_BIN: /usr/local/bin/openshell
|
||||
OPENSHELL_TEST_INPUT_DIR: "{{ podman_test_input_dir }}"
|
||||
OPENSHELL_PODMAN_TEST_IMAGE: "{{ openshell_podman_test_image | default('') }}"
|
||||
register: podman_test_result
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
|
||||
- name: Show Podman test diagnostics
|
||||
ansible.builtin.debug:
|
||||
var: podman_test_result.stderr_lines
|
||||
|
||||
- name: Show Podman test result
|
||||
ansible.builtin.debug:
|
||||
var: podman_test_result.stdout_lines
|
||||
|
||||
- name: Capture OpenShell gateway journal after Podman test failure
|
||||
become: true
|
||||
ansible.builtin.command:
|
||||
argv:
|
||||
- journalctl
|
||||
- --unit
|
||||
- openshell-gateway.service
|
||||
- --no-pager
|
||||
- --lines
|
||||
- "200"
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
when: podman_test_result.rc != 0
|
||||
register: podman_gateway_journal
|
||||
|
||||
- name: Show OpenShell gateway journal after Podman test failure
|
||||
ansible.builtin.debug:
|
||||
var: podman_gateway_journal.stdout_lines
|
||||
when: podman_test_result.rc != 0
|
||||
|
||||
- name: Resolve tmachine UID after Podman test failure
|
||||
ansible.builtin.command:
|
||||
argv: [id, --user, tmachine]
|
||||
changed_when: false
|
||||
when: podman_test_result.rc != 0
|
||||
register: podman_tmachine_uid
|
||||
|
||||
- name: Discover Podman containers after test failure
|
||||
become: true
|
||||
become_user: tmachine
|
||||
ansible.builtin.command:
|
||||
argv: [timeout, "15", podman, ps, --all, --quiet, --no-trunc]
|
||||
environment:
|
||||
HOME: /home/tmachine
|
||||
XDG_RUNTIME_DIR: "/run/user/{{ podman_tmachine_uid.stdout }}"
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
when: podman_test_result.rc != 0
|
||||
register: podman_failed_containers
|
||||
|
||||
- name: Capture rootless Podman journal after test failure
|
||||
become: true
|
||||
become_user: tmachine
|
||||
ansible.builtin.command:
|
||||
argv: [journalctl, --user, --unit, podman.service, --no-pager, --lines, "200"]
|
||||
environment:
|
||||
HOME: /home/tmachine
|
||||
XDG_RUNTIME_DIR: "/run/user/{{ podman_tmachine_uid.stdout }}"
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
when: podman_test_result.rc != 0
|
||||
register: podman_service_journal
|
||||
|
||||
- name: Show rootless Podman journal after test failure
|
||||
ansible.builtin.debug:
|
||||
var: podman_service_journal.stdout_lines
|
||||
when: podman_test_result.rc != 0
|
||||
|
||||
- name: Capture Podman container inspection after test failure
|
||||
become: true
|
||||
become_user: tmachine
|
||||
ansible.builtin.command:
|
||||
argv: [timeout, "15", podman, inspect, "{{ item }}"]
|
||||
environment:
|
||||
HOME: /home/tmachine
|
||||
XDG_RUNTIME_DIR: "/run/user/{{ podman_tmachine_uid.stdout }}"
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
when: podman_test_result.rc != 0
|
||||
loop: "{{ podman_failed_containers.stdout_lines | default([]) }}"
|
||||
register: podman_failed_container_inspection
|
||||
|
||||
- name: Show Podman container inspection after test failure
|
||||
ansible.builtin.debug:
|
||||
var: podman_failed_container_inspection.results
|
||||
when: podman_test_result.rc != 0
|
||||
|
||||
- name: Capture Podman container logs after test failure
|
||||
become: true
|
||||
become_user: tmachine
|
||||
ansible.builtin.command:
|
||||
argv: [timeout, "15", podman, logs, "{{ item }}"]
|
||||
environment:
|
||||
HOME: /home/tmachine
|
||||
XDG_RUNTIME_DIR: "/run/user/{{ podman_tmachine_uid.stdout }}"
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
when: podman_test_result.rc != 0
|
||||
loop: "{{ podman_failed_containers.stdout_lines | default([]) }}"
|
||||
register: podman_failed_container_logs
|
||||
|
||||
- name: Show Podman container logs after test failure
|
||||
ansible.builtin.debug:
|
||||
var: podman_failed_container_logs.results
|
||||
when: podman_test_result.rc != 0
|
||||
|
||||
- name: Require Podman archive success
|
||||
ansible.builtin.assert:
|
||||
that: [podman_test_result.rc == 0]
|
||||
fail_msg: Podman archive tests failed
|
||||
@@ -0,0 +1,9 @@
|
||||
# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
---
|
||||
- import_playbook: userns-profile.yaml
|
||||
vars:
|
||||
podman_userns_profile: auto
|
||||
podman_userns_config: "{{ openshell_podman_userns_auto_config }}"
|
||||
podman_userns_reference_args: [--userns, auto]
|
||||
@@ -0,0 +1,9 @@
|
||||
# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
---
|
||||
- import_playbook: userns-profile.yaml
|
||||
vars:
|
||||
podman_userns_profile: keep-id
|
||||
podman_userns_config: "{{ openshell_podman_userns_keep_id_config }}"
|
||||
podman_userns_reference_args: [--userns, keep-id]
|
||||
@@ -0,0 +1,19 @@
|
||||
# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
---
|
||||
- import_playbook: userns-profile.yaml
|
||||
vars:
|
||||
podman_userns_profile: private
|
||||
podman_userns_config: "{{ openshell_podman_userns_private_config }}"
|
||||
podman_userns_reference_args:
|
||||
# Podman infers a private namespace from explicit maps; its CLI rejects
|
||||
# combining --userns private with --uidmap/--gidmap.
|
||||
- --uidmap
|
||||
- 0:0:1
|
||||
- --uidmap
|
||||
- 1:1:65535
|
||||
- --gidmap
|
||||
- 0:0:1
|
||||
- --gidmap
|
||||
- 1:1:65535
|
||||
@@ -0,0 +1,99 @@
|
||||
# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
---
|
||||
- name: Configure and capture a Podman user-namespace reference
|
||||
hosts: all
|
||||
gather_facts: false
|
||||
vars:
|
||||
podman_reference_image: "{{ openshell_podman_reference_image }}"
|
||||
podman_reference_uid_map: /var/lib/openshell-test-inputs/podman/reference-uid-map
|
||||
tasks:
|
||||
- name: Wait for SSH
|
||||
ansible.builtin.wait_for_connection:
|
||||
|
||||
- name: Require the rootless Podman gateway user
|
||||
become: true
|
||||
ansible.builtin.command:
|
||||
argv:
|
||||
- systemctl
|
||||
- show
|
||||
- openshell-gateway.service
|
||||
- --property
|
||||
- User
|
||||
- --value
|
||||
changed_when: false
|
||||
register: openshell_gateway_service_user
|
||||
|
||||
- name: Assert the rootless Podman gateway user
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- openshell_gateway_service_user.stdout == "tmachine"
|
||||
fail_msg: >-
|
||||
The Podman {{ podman_userns_profile }} suite requires a rootless Podman gateway
|
||||
running as tmachine, not {{ openshell_gateway_service_user.stdout | default("unknown") }}
|
||||
|
||||
- name: Apply the Podman user-namespace fixture
|
||||
become: true
|
||||
ansible.builtin.blockinfile:
|
||||
path: /etc/openshell/gateway.toml
|
||||
marker: "# {mark} OpenShell Podman userns test fixture"
|
||||
block: "{{ lookup('ansible.builtin.file', podman_userns_config) | trim }}"
|
||||
|
||||
- name: Restart OpenShell gateway with the Podman user-namespace fixture
|
||||
become: true
|
||||
ansible.builtin.systemd_service:
|
||||
name: openshell-gateway.service
|
||||
state: restarted
|
||||
|
||||
- name: Wait for the configured OpenShell gateway
|
||||
ansible.builtin.wait_for:
|
||||
host: 127.0.0.1
|
||||
port: 17670
|
||||
timeout: 60
|
||||
|
||||
- name: Resolve tmachine UID
|
||||
ansible.builtin.command:
|
||||
argv:
|
||||
- id
|
||||
- -u
|
||||
- tmachine
|
||||
changed_when: false
|
||||
register: tmachine_uid
|
||||
|
||||
- name: Capture direct Podman user-namespace mapping
|
||||
become: true
|
||||
become_user: tmachine
|
||||
ansible.builtin.command:
|
||||
argv: "{{ [\"podman\", \"run\", \"--rm\", \"--pull\", \"never\"] + podman_userns_reference_args + [\"--entrypoint\", \"/bin/cat\", podman_reference_image, \"/proc/self/uid_map\"] }}"
|
||||
environment:
|
||||
HOME: /home/tmachine
|
||||
XDG_RUNTIME_DIR: "/run/user/{{ tmachine_uid.stdout }}"
|
||||
changed_when: false
|
||||
register: podman_userns_uid_map
|
||||
|
||||
- name: Require a direct Podman user-namespace mapping
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- podman_userns_uid_map.stdout | trim | length > 0
|
||||
fail_msg: >-
|
||||
Direct rootless Podman {{ podman_userns_profile }} reference container
|
||||
returned no UID mapping
|
||||
|
||||
- name: Create Podman test-input directory
|
||||
become: true
|
||||
ansible.builtin.file:
|
||||
path: "{{ podman_reference_uid_map | dirname }}"
|
||||
state: directory
|
||||
owner: tmachine
|
||||
group: tmachine
|
||||
mode: "0700"
|
||||
|
||||
- name: Store direct Podman user-namespace mapping
|
||||
become: true
|
||||
ansible.builtin.copy:
|
||||
content: "{{ podman_userns_uid_map.stdout | trim }}\n"
|
||||
dest: "{{ podman_reference_uid_map }}"
|
||||
owner: tmachine
|
||||
group: tmachine
|
||||
mode: "0600"
|
||||
+11
-1
@@ -92,9 +92,17 @@ let
|
||||
target = muslToolchain.target;
|
||||
output = "artifacts/test-archives/${muslToolchain.target}/provider-refresh-keycloak-tests.tar";
|
||||
};
|
||||
podmanDriverArchive = mkTestArchive {
|
||||
name = "podman-driver";
|
||||
workspacePath = "tests/suites/drivers";
|
||||
manifestPath = "tests/suites/drivers/Cargo.toml";
|
||||
package = "openshell-test-suite-podman";
|
||||
target = muslToolchain.target;
|
||||
output = "artifacts/test-archives/${muslToolchain.target}/openshell-podman-tests.tar";
|
||||
};
|
||||
in
|
||||
rec {
|
||||
inherit conformanceCliArchive providerRefreshKeycloakArchive;
|
||||
inherit conformanceCliArchive providerRefreshKeycloakArchive podmanDriverArchive;
|
||||
|
||||
binaries = pkgs.writeShellApplication {
|
||||
name = "build-artifacts-binaries";
|
||||
@@ -139,10 +147,12 @@ rec {
|
||||
runtimeInputs = [
|
||||
conformanceCliArchive
|
||||
providerRefreshKeycloakArchive
|
||||
podmanDriverArchive
|
||||
];
|
||||
text = ''
|
||||
build-openshell-conformance-test-archive
|
||||
build-provider-refresh-keycloak-test-archive
|
||||
build-podman-driver-test-archive
|
||||
'';
|
||||
};
|
||||
|
||||
|
||||
@@ -126,6 +126,28 @@ let
|
||||
provider_refresh_keycloak_test_bundle = "../artifacts/test-archives/${muslTarget}/provider-refresh-keycloak-tests.tar";
|
||||
};
|
||||
}
|
||||
{
|
||||
name = "driver-podman";
|
||||
playbooks = [
|
||||
"ansible/playbooks/drivers/podman/default-userns-baseline.yaml"
|
||||
"ansible/playbooks/drivers/podman/tests.yaml"
|
||||
"ansible/playbooks/drivers/podman/userns-auto.yaml"
|
||||
"ansible/playbooks/drivers/podman/tests.yaml"
|
||||
"ansible/playbooks/drivers/podman/userns-keep-id.yaml"
|
||||
"ansible/playbooks/drivers/podman/tests.yaml"
|
||||
"ansible/playbooks/drivers/podman/userns-private.yaml"
|
||||
"ansible/playbooks/drivers/podman/tests.yaml"
|
||||
];
|
||||
inputs = {
|
||||
openshell_podman_test_bundle = "../artifacts/test-archives/${muslTarget}/openshell-podman-tests.tar";
|
||||
# Match OpenShell's compiled-in default so direct Podman and
|
||||
# OpenShell containers resolve the same workload image metadata.
|
||||
openshell_podman_reference_image = "nvcr.io/nvidia/base/ubuntu:24.04";
|
||||
openshell_podman_userns_auto_config = "suites/drivers/podman/fixtures/userns-auto.toml";
|
||||
openshell_podman_userns_keep_id_config = "suites/drivers/podman/fixtures/userns-keep-id.toml";
|
||||
openshell_podman_userns_private_config = "suites/drivers/podman/fixtures/userns-private.toml";
|
||||
};
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
|
||||
Generated
+393
@@ -0,0 +1,393 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.13.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3ded4057c258ba199e2d26386d3af3780957ecaee6c4ef4041c6b4b8b97c0b06"
|
||||
|
||||
[[package]]
|
||||
name = "bytes"
|
||||
version = "1.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
||||
|
||||
[[package]]
|
||||
name = "errno"
|
||||
version = "0.3.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.3.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"r-efi",
|
||||
"wasip2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.189"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2"
|
||||
|
||||
[[package]]
|
||||
name = "lock_api"
|
||||
version = "0.4.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965"
|
||||
dependencies = [
|
||||
"scopeguard",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
|
||||
|
||||
[[package]]
|
||||
name = "mio"
|
||||
version = "1.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4b18443e9c262bfe8fa82f51666e2642c53393f7e5c27b3e1aeab922cff5b9d8"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"wasi",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "openshell-conformance"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"rand",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "openshell-test-suite-podman"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"openshell-conformance",
|
||||
"serde",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot"
|
||||
version = "0.12.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a"
|
||||
dependencies = [
|
||||
"lock_api",
|
||||
"parking_lot_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot_core"
|
||||
version = "0.9.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"redox_syscall",
|
||||
"smallvec",
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-lite"
|
||||
version = "0.2.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
|
||||
|
||||
[[package]]
|
||||
name = "ppv-lite86"
|
||||
version = "0.2.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
|
||||
dependencies = [
|
||||
"zerocopy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.107"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.47"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "r-efi"
|
||||
version = "5.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.9.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b9ef1d0d795eb7d84685bca4f72f3649f064e6641543d3a8c415898726a57b41"
|
||||
dependencies = [
|
||||
"rand_chacha",
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_chacha"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
|
||||
dependencies = [
|
||||
"ppv-lite86",
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.9.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.5.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "scopeguard"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.229"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba"
|
||||
dependencies = [
|
||||
"serde_core",
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_core"
|
||||
version = "1.0.229"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.229"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 3.0.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.151"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"memchr",
|
||||
"serde",
|
||||
"serde_core",
|
||||
"zmij",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook-registry"
|
||||
version = "1.4.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b"
|
||||
dependencies = [
|
||||
"errno",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.16.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ba467056f1b547ed52077911161fc86985becbc60e8e1857c8a144dab0def891"
|
||||
|
||||
[[package]]
|
||||
name = "socket2"
|
||||
version = "0.6.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.119"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "3.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "12df2e0110f65b775f769bb17ef989067a1d931b2eb822bd4346631eeada89f9"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "1.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "202caea871b69668250d242070849eb495be178ed697a3e98aebce5bc81a0bed"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"libc",
|
||||
"mio",
|
||||
"parking_lot",
|
||||
"pin-project-lite",
|
||||
"signal-hook-registry",
|
||||
"socket2",
|
||||
"tokio-macros",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-macros"
|
||||
version = "2.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "78773a2a397f451582ce068015985c33193cf6dea8b74d2a639fe457b2f07b0e"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 3.0.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.11.1+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
|
||||
|
||||
[[package]]
|
||||
name = "wasip2"
|
||||
version = "1.0.4+wasi-0.2.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487"
|
||||
dependencies = [
|
||||
"wit-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-link"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.61.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen"
|
||||
version = "0.57.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e"
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.8.57"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d35102a9f36d089ccae9e4c6802bc118be4487b80aaffc0ab4e0cf5ce92d2873"
|
||||
dependencies = [
|
||||
"zerocopy-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy-derive"
|
||||
version = "0.8.57"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "146c01f5ab44258da43cf276c74a2763db2ff3969c9c652c3f2de07041d0b2bc"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.119",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zmij"
|
||||
version = "1.0.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b"
|
||||
@@ -0,0 +1,6 @@
|
||||
# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
[workspace]
|
||||
resolver = "2"
|
||||
members = ["podman"]
|
||||
@@ -0,0 +1,15 @@
|
||||
# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
[package]
|
||||
name = "openshell-test-suite-podman"
|
||||
version = "0.0.0"
|
||||
edition = "2024"
|
||||
rust-version = "1.94"
|
||||
license = "Apache-2.0"
|
||||
repository = "https://github.com/NVIDIA/OpenShell"
|
||||
|
||||
[dependencies]
|
||||
openshell-conformance = { path = "../../../../crates/openshell-conformance" }
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
tokio = { version = "1.43", features = ["macros", "rt"] }
|
||||
@@ -0,0 +1,4 @@
|
||||
# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
userns = "auto"
|
||||
@@ -0,0 +1,4 @@
|
||||
# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
userns = "keep-id"
|
||||
@@ -0,0 +1,6 @@
|
||||
# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
userns = "private"
|
||||
uidmap = ["0:0:1", "1:1:65535"]
|
||||
gidmap = ["0:0:1", "1:1:65535"]
|
||||
@@ -0,0 +1,101 @@
|
||||
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
//! Podman-driver user-namespace integration tests.
|
||||
|
||||
mod support;
|
||||
|
||||
use openshell_conformance::OpenShellRunner;
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
use std::time::Duration;
|
||||
|
||||
use support::assert_podman_gateway;
|
||||
|
||||
const SANDBOX_TIMEOUT: Duration = Duration::from_secs(300);
|
||||
const PODMAN_TEST_INPUT_DIR_ENV: &str = "OPENSHELL_TEST_INPUT_DIR";
|
||||
const PODMAN_TEST_IMAGE_ENV: &str = "OPENSHELL_PODMAN_TEST_IMAGE";
|
||||
|
||||
/// Verify that the gateway's user-namespace configuration matches Podman's
|
||||
/// direct behavior for the same profile.
|
||||
///
|
||||
/// The test runs a short-lived sandbox command and compares its user-namespace
|
||||
/// mapping with the direct-Podman reference stored at
|
||||
/// `OPENSHELL_TEST_INPUT_DIR/reference-uid-map`. The tmachine pre-test
|
||||
/// playbook creates that reference in the same gateway-user context. This deliberately
|
||||
/// avoids baking a particular Podman mapping into OpenShell's test contract.
|
||||
///
|
||||
#[tokio::test]
|
||||
async fn configured_userns_matches_podman_reference() {
|
||||
let mut runner = OpenShellRunner::from_env("podman-userns")
|
||||
.expect("candidate openshell CLI is available");
|
||||
let result = async {
|
||||
runner.check_gateway_status().await?;
|
||||
assert_podman_gateway(&runner).await?;
|
||||
|
||||
let test_input_dir = std::env::var_os(PODMAN_TEST_INPUT_DIR_ENV)
|
||||
.map(PathBuf::from)
|
||||
.ok_or_else(|| format!("{PODMAN_TEST_INPUT_DIR_ENV} must name the Podman test-input directory"))?;
|
||||
let expected_path = test_input_dir.join("reference-uid-map");
|
||||
let expected_uid_map = fs::read_to_string(&expected_path).map_err(|error| {
|
||||
format!(
|
||||
"could not read Podman reference UID map {}: {error}",
|
||||
expected_path.display()
|
||||
)
|
||||
})?;
|
||||
let expected_uid_map = normalize_uid_map(&expected_uid_map).ok_or_else(|| {
|
||||
format!(
|
||||
"Podman reference UID map {} contains no mappings",
|
||||
expected_path.display()
|
||||
)
|
||||
})?;
|
||||
|
||||
let workload_image = std::env::var(PODMAN_TEST_IMAGE_ENV)
|
||||
.ok()
|
||||
.filter(|image| !image.trim().is_empty());
|
||||
let sandbox_name = format!("pu-{}", runner.id());
|
||||
runner.track_sandbox(&sandbox_name);
|
||||
let mut create_args = vec!["sandbox", "create", "--name", &sandbox_name];
|
||||
if let Some(image) = workload_image.as_deref() {
|
||||
create_args.extend(["--from", image]);
|
||||
}
|
||||
create_args.extend(["--no-tty", "--", "cat", "/proc/self/uid_map"]);
|
||||
let run = runner
|
||||
.step("userns/uid-map")
|
||||
.description("sandbox exposes its UID map")
|
||||
.with_timeout(SANDBOX_TIMEOUT)
|
||||
.run(&create_args)
|
||||
.await
|
||||
.map_err(|error| error.to_string())?;
|
||||
run.require_success()?;
|
||||
let sandbox_uid_map = normalize_uid_map(run.stdout()).ok_or_else(|| {
|
||||
run.failure_diagnostic("sandbox returns a non-empty UID map")
|
||||
})?;
|
||||
if sandbox_uid_map != expected_uid_map {
|
||||
return Err(format!(
|
||||
"sandbox UID map differs from the direct Podman reference:\nexpected:\n{expected_uid_map}\nactual:\n{sandbox_uid_map}"
|
||||
));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
.await;
|
||||
|
||||
if let Err(error) = runner.finish(result).await {
|
||||
panic!("Podman userns test failed:\n{error}");
|
||||
}
|
||||
}
|
||||
|
||||
fn normalize_uid_map(value: &str) -> Option<String> {
|
||||
let mappings = value
|
||||
.lines()
|
||||
.filter_map(|line| {
|
||||
let fields = line.split_whitespace().collect::<Vec<_>>();
|
||||
(fields.len() == 3
|
||||
&& fields
|
||||
.iter()
|
||||
.all(|field| field.bytes().all(|byte| byte.is_ascii_digit())))
|
||||
.then(|| fields.join(" "))
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
(!mappings.is_empty()).then(|| mappings.join("\n"))
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use openshell_conformance::OpenShellRunner;
|
||||
use serde::Deserialize;
|
||||
use std::time::Duration;
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct GatewayInfo {
|
||||
status: String,
|
||||
compute_drivers: Vec<ComputeDriver>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct ComputeDriver {
|
||||
name: String,
|
||||
capabilities: ComputeDriverCapabilities,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct ComputeDriverCapabilities {
|
||||
driver_name: String,
|
||||
}
|
||||
|
||||
/// Require the target gateway to use Podman as its only compute driver.
|
||||
///
|
||||
/// This interrogates the running gateway rather than accepting a runner
|
||||
/// environment variable. A driver-specific suite must fail, rather than skip,
|
||||
/// when it is pointed at the wrong gateway.
|
||||
pub async fn assert_podman_gateway(runner: &OpenShellRunner) -> Result<(), String> {
|
||||
let result = runner
|
||||
.step("preflight/driver-podman")
|
||||
.description("gateway reports Podman as its only compute driver")
|
||||
.with_timeout(Duration::from_secs(10))
|
||||
.run(&["gateway", "info", "--output", "json"])
|
||||
.await
|
||||
.map_err(|error| format!("could not query gateway compute drivers: {error}"))?;
|
||||
result.require_success()?;
|
||||
|
||||
let info = result
|
||||
.json::<GatewayInfo>()
|
||||
.map_err(|error| format!("gateway returned invalid driver information: {error}"))?;
|
||||
if info.status != "healthy" {
|
||||
return Err(format!(
|
||||
"Podman test suite requires a healthy gateway; gateway status is {:?}",
|
||||
info.status
|
||||
));
|
||||
}
|
||||
|
||||
let driver_names = info
|
||||
.compute_drivers
|
||||
.iter()
|
||||
.map(|driver| driver.name.as_str())
|
||||
.collect::<Vec<_>>();
|
||||
let podman_only = matches!(info.compute_drivers.as_slice(), [driver]
|
||||
if driver.name == "podman" && driver.capabilities.driver_name == "podman");
|
||||
if !podman_only {
|
||||
return Err(format!(
|
||||
"Podman test suite requires exactly one Podman compute driver; gateway reported {driver_names:?}"
|
||||
));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Reference in New Issue
Block a user