mirror of
https://github.com/NVIDIA/OpenShell.git
synced 2026-10-22 06:19:47 +08:00
* feat(policy): accept numeric UIDs in sandbox process identity validation Allow run_as_user and run_as_group to be either the literal 'sandbox' or a numeric UID/GID within [1000, 2_000_000_000]. This removes the hard dependency on a baked-in 'sandbox' user in container images, enabling compute drivers to inject resolved UIDs at sandbox creation. Phase 1 of #1959. Signed-off-by: Seth Jennings <sjenning@redhat.com> * feat(supervisor): accept numeric UIDs for process identity dropping Allow run_as_user and run_as_group to be numeric UIDs/GIDs, removing the hard dependency on a baked-in 'sandbox' user in container images. Changes: - validate_sandbox_user(): accepts numeric UIDs without passwd lookup (logs OCSF event); keeps passwd check for "sandbox" name; rejects non-numeric non-sandbox strings that fail passwd lookup - prepare_filesystem(): passes numeric UIDs/GIDs directly to chown() instead of requiring a passwd entry - drop_privileges(): resolves numeric UIDs/GIDs directly via UID::from_raw / Gid::from_raw; skips initgroups when target uid matches current euid; uses guard conditions before setgid/setuid calls - session_user_and_home(): falls back to ("{uid}", "/sandbox") for numeric UIDs, avoiding a passwd lookup that will fail Re-exports MIN_SANDBOX_UID and MAX_SANDBOX_UID from openshell-policy so callers have consistent range constants. Phase 2 of #1959. Signed-off-by: Seth Jennings <sjenning@redhat.com> * feat(driver-kubernetes): resolve sandbox UID/GID from config or OpenShift SCC annotations Phase 3 of the numeric-UID plan: allow operators to specify explicit sandbox_uid/sandbox_gid in Kubernetes driver config, auto-detect from OpenShift SCC namespace annotations, and propagate resolved values to supervisor container env vars and PVC init container securityContext. Changes: - Add sandbox_uid/sandbox_gid fields to KubernetesComputeConfig - Add SANDBOX_UID/SANDBOX_GID env var constants to openshell-core - Implement resolve_sandbox_identity() to fetch namespace annotations and auto-detect OpenShift SCC UID ranges (sa.scc.uid-range) - Pass resolved UID/GID through SandboxPodParams to pod spec builder - Inject SANDBOX_UID/SANDBOX_GID env vars into supervisor container - Update PVC init container securityContext with resolved UID/GID instead of hard-coded root - Add comprehensive unit tests for resolution logic and annotation parsing (resolve_sandbox_uid, resolve_sandbox_gid, OpenShift SCC annotation parsing) Signed-off-by: Seth Jennings <sjenning@redhat.com> * feat(driver-vm): add configurable sandbox UID/GID and update docs/examples Phase 4 of the numeric-UID plan: replace hardcoded SANDBOX_UID (10001) in VM rootfs preparation with configurable sandbox_uid/sandbox_gid fields. Changes: - Add sandbox_uid/sandbox_gid to VmDriverConfig with serde derives - Pass resolved UID/GID through prepare_sandbox_rootfs_from_image_root to ensure_sandbox_guest_user which writes /etc/passwd/group/gshadow - Update BYOC Dockerfile: remove groupadd/useradd, document runtime UID injection and the ability to skip baked-in sandbox user - Update gateway-config.mdx: document sandbox_uid/sandbox_gid for both Kubernetes (with OpenShift SCC autodetection) and VM drivers - Update sandbox-compute-drivers.mdx: add Sandbox User Identity section explaining numeric UID support across all compute drivers - Update rootfs tests to use non-default UIDs, verify config passthrough Signed-off-by: Seth Jennings <sjenning@redhat.com> * code review changes * fix(supervisor): harden tests for restricted CI container environments Guard tests against CI-specific constraints: root without CAP_SETPCAP, UIDs with no /etc/passwd entry, and restricted /proc access. Signed-off-by: Seth Jennings <sjennings@nvidia.com> Signed-off-by: Seth Jennings <sjenning@redhat.com> --------- Signed-off-by: Seth Jennings <sjenning@redhat.com> Signed-off-by: Seth Jennings <sjennings@nvidia.com>
165 lines
5.8 KiB
Rust
165 lines
5.8 KiB
Rust
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
use clap::Parser;
|
|
use miette::{IntoDiagnostic, Result};
|
|
use std::net::SocketAddr;
|
|
use tracing::info;
|
|
use tracing_subscriber::EnvFilter;
|
|
|
|
use openshell_core::VERSION;
|
|
use openshell_core::proto::compute::v1::compute_driver_server::ComputeDriverServer;
|
|
use openshell_driver_kubernetes::{
|
|
AppArmorProfile, ComputeDriverService, DEFAULT_SANDBOX_SERVICE_ACCOUNT_NAME,
|
|
KubernetesComputeConfig, KubernetesComputeDriver, SupervisorSideloadMethod, SupervisorTopology,
|
|
};
|
|
|
|
#[derive(Parser, Debug)]
|
|
#[command(name = "openshell-driver-kubernetes")]
|
|
#[command(version = VERSION)]
|
|
struct Args {
|
|
#[arg(
|
|
long,
|
|
env = "OPENSHELL_COMPUTE_DRIVER_BIND",
|
|
default_value = "127.0.0.1:50061"
|
|
)]
|
|
bind_address: SocketAddr,
|
|
|
|
#[arg(long, env = "OPENSHELL_LOG_LEVEL", default_value = "info")]
|
|
log_level: String,
|
|
|
|
#[arg(long, env = "OPENSHELL_SANDBOX_NAMESPACE", default_value = "default")]
|
|
sandbox_namespace: String,
|
|
|
|
#[arg(
|
|
long,
|
|
env = "OPENSHELL_K8S_SANDBOX_SERVICE_ACCOUNT",
|
|
default_value = DEFAULT_SANDBOX_SERVICE_ACCOUNT_NAME
|
|
)]
|
|
sandbox_service_account: String,
|
|
|
|
#[arg(long, env = "OPENSHELL_SANDBOX_IMAGE")]
|
|
sandbox_image: Option<String>,
|
|
|
|
#[arg(long, env = "OPENSHELL_SANDBOX_IMAGE_PULL_POLICY")]
|
|
sandbox_image_pull_policy: Option<String>,
|
|
|
|
#[arg(
|
|
long,
|
|
env = "OPENSHELL_SANDBOX_IMAGE_PULL_SECRETS",
|
|
value_delimiter = ','
|
|
)]
|
|
sandbox_image_pull_secrets: Vec<String>,
|
|
|
|
#[arg(long, env = "OPENSHELL_GRPC_ENDPOINT")]
|
|
grpc_endpoint: Option<String>,
|
|
|
|
#[arg(
|
|
long,
|
|
env = "OPENSHELL_SANDBOX_SSH_SOCKET_PATH",
|
|
default_value = "/run/openshell/ssh.sock"
|
|
)]
|
|
sandbox_ssh_socket_path: String,
|
|
|
|
#[arg(long, env = "OPENSHELL_CLIENT_TLS_SECRET_NAME")]
|
|
client_tls_secret_name: Option<String>,
|
|
|
|
#[arg(long, env = "OPENSHELL_HOST_GATEWAY_IP")]
|
|
host_gateway_ip: Option<String>,
|
|
|
|
#[arg(long, env = "OPENSHELL_SUPERVISOR_IMAGE")]
|
|
supervisor_image: Option<String>,
|
|
|
|
#[arg(long, env = "OPENSHELL_SUPERVISOR_IMAGE_PULL_POLICY")]
|
|
supervisor_image_pull_policy: Option<String>,
|
|
|
|
#[arg(
|
|
long,
|
|
env = "OPENSHELL_SUPERVISOR_SIDELOAD_METHOD",
|
|
default_value = "image-volume"
|
|
)]
|
|
supervisor_sideload_method: SupervisorSideloadMethod,
|
|
|
|
#[arg(
|
|
long,
|
|
env = "OPENSHELL_SUPERVISOR_TOPOLOGY",
|
|
default_value = "combined"
|
|
)]
|
|
supervisor_topology: SupervisorTopology,
|
|
|
|
#[arg(long, env = "OPENSHELL_ENABLE_USER_NAMESPACES")]
|
|
enable_user_namespaces: bool,
|
|
|
|
#[arg(long, env = "OPENSHELL_K8S_APP_ARMOR_PROFILE")]
|
|
app_armor_profile: Option<AppArmorProfile>,
|
|
|
|
/// Lifetime (seconds) of the projected `ServiceAccount` token
|
|
/// kubelet writes into each sandbox pod for the `IssueSandboxToken`
|
|
/// bootstrap exchange. Kubelet enforces a minimum of 600s; the
|
|
/// gateway clamps values outside `[600, 86400]`. Default 3600.
|
|
#[arg(long, env = "OPENSHELL_K8S_SA_TOKEN_TTL_SECS", default_value_t = 3600)]
|
|
sa_token_ttl_secs: i64,
|
|
|
|
#[arg(long, env = "OPENSHELL_PROVIDER_SPIFFE_WORKLOAD_API_SOCKET")]
|
|
provider_spiffe_workload_api_socket_path: Option<String>,
|
|
|
|
#[arg(long, env = "OPENSHELL_K8S_SANDBOX_UID")]
|
|
sandbox_uid: Option<u32>,
|
|
|
|
#[arg(long, env = "OPENSHELL_K8S_SANDBOX_GID")]
|
|
sandbox_gid: Option<u32>,
|
|
}
|
|
|
|
#[tokio::main]
|
|
async fn main() -> Result<()> {
|
|
let args = Args::parse();
|
|
tracing_subscriber::fmt()
|
|
.with_env_filter(
|
|
EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new(&args.log_level)),
|
|
)
|
|
.init();
|
|
|
|
let driver = KubernetesComputeDriver::new(KubernetesComputeConfig {
|
|
namespace: args.sandbox_namespace,
|
|
service_account_name: args.sandbox_service_account,
|
|
default_image: args.sandbox_image.unwrap_or_default(),
|
|
image_pull_policy: args.sandbox_image_pull_policy.unwrap_or_default(),
|
|
image_pull_secrets: args.sandbox_image_pull_secrets,
|
|
supervisor_image: args
|
|
.supervisor_image
|
|
.unwrap_or_else(|| openshell_core::config::DEFAULT_SUPERVISOR_IMAGE.to_string()),
|
|
supervisor_image_pull_policy: args.supervisor_image_pull_policy.unwrap_or_default(),
|
|
supervisor_sideload_method: args.supervisor_sideload_method,
|
|
supervisor_topology: args.supervisor_topology,
|
|
grpc_endpoint: args.grpc_endpoint.unwrap_or_default(),
|
|
ssh_socket_path: args.sandbox_ssh_socket_path,
|
|
client_tls_secret_name: args.client_tls_secret_name.unwrap_or_default(),
|
|
host_gateway_ip: args.host_gateway_ip.unwrap_or_default(),
|
|
enable_user_namespaces: args.enable_user_namespaces,
|
|
app_armor_profile: args.app_armor_profile,
|
|
workspace_default_storage_size: std::env::var(
|
|
"OPENSHELL_K8S_WORKSPACE_DEFAULT_STORAGE_SIZE",
|
|
)
|
|
.unwrap_or_else(|_| {
|
|
openshell_driver_kubernetes::DEFAULT_WORKSPACE_STORAGE_SIZE.to_string()
|
|
}),
|
|
default_runtime_class_name: std::env::var("OPENSHELL_K8S_DEFAULT_RUNTIME_CLASS_NAME")
|
|
.unwrap_or_default(),
|
|
sa_token_ttl_secs: args.sa_token_ttl_secs,
|
|
provider_spiffe_workload_api_socket_path: args
|
|
.provider_spiffe_workload_api_socket_path
|
|
.unwrap_or_default(),
|
|
sandbox_uid: args.sandbox_uid,
|
|
sandbox_gid: args.sandbox_gid,
|
|
})
|
|
.await
|
|
.into_diagnostic()?;
|
|
|
|
info!(address = %args.bind_address, "Starting Kubernetes compute driver");
|
|
tonic::transport::Server::builder()
|
|
.add_service(ComputeDriverServer::new(ComputeDriverService::new(driver)))
|
|
.serve(args.bind_address)
|
|
.await
|
|
.into_diagnostic()
|
|
}
|