diff --git a/.agents/skills/helm-dev-environment/SKILL.md b/.agents/skills/helm-dev-environment/SKILL.md index 1b0b6bb95..6225dce31 100644 --- a/.agents/skills/helm-dev-environment/SKILL.md +++ b/.agents/skills/helm-dev-environment/SKILL.md @@ -70,7 +70,8 @@ mise run helm:skaffold:run ``` Both commands build the gateway and trusted runtime image and deploy the -OpenShell Helm chart. Kubernetes sandboxes always use the proxy-pod topology: +OpenShell Helm chart. The Kubernetes driver creates a sandbox workload Pod and a +separate supervisor Deployment: a capability-free sandbox Pod behind an empty-egress NetworkPolicy and a separate capability-free supervisor Deployment. The default Skaffold values export gateway and Kubernetes-driver traces to the collector service installed diff --git a/.github/workflows/branch-e2e.yml b/.github/workflows/branch-e2e.yml index cb292a1a7..aa5a74e5e 100644 --- a/.github/workflows/branch-e2e.yml +++ b/.github/workflows/branch-e2e.yml @@ -406,12 +406,10 @@ jobs: include: - agent_sandbox_api: v1beta1 agent_sandbox_version: v0.5.0 - topology: proxy-pod - extra_helm_values: deploy/helm/openshell/ci/values-proxy-pod.yaml + extra_helm_values: deploy/helm/openshell/ci/values-sandbox-runtime.yaml - agent_sandbox_api: v1alpha1 agent_sandbox_version: v0.4.6 - topology: proxy-pod - extra_helm_values: deploy/helm/openshell/ci/values-proxy-pod.yaml + extra_helm_values: deploy/helm/openshell/ci/values-sandbox-runtime.yaml permissions: actions: read contents: read @@ -419,7 +417,7 @@ jobs: uses: ./.github/workflows/e2e-kubernetes-test.yml with: image-tag: ${{ github.sha }} - job-name: Kubernetes E2E (Rust smoke, ${{ matrix.topology }}, Agent Sandbox ${{ matrix.agent_sandbox_api }}) + job-name: Kubernetes E2E (Rust smoke, Agent Sandbox ${{ matrix.agent_sandbox_api }}) agent-sandbox-version: ${{ matrix.agent_sandbox_version }} extra-helm-values: ${{ matrix.extra_helm_values }} conformance-artifact-prefix: openshell-conformance @@ -436,7 +434,7 @@ jobs: image-tag: ${{ github.sha }} job-name: Kubernetes E2E (workspace managed mode) e2e-task: e2e:kubernetes:workspace-managed - extra-helm-values: deploy/helm/openshell/ci/values-proxy-pod.yaml + extra-helm-values: deploy/helm/openshell/ci/values-sandbox-runtime.yaml conformance-artifact-prefix: openshell-conformance kubernetes-external-driver-e2e: @@ -454,7 +452,7 @@ jobs: gateway-artifact: openshell-gateway-plain-x86_64-unknown-linux-gnu external-driver-binary: openshell-driver-kubernetes cluster-images: sandbox supervisor - extra-helm-values: deploy/helm/openshell/ci/values-proxy-pod.yaml + extra-helm-values: deploy/helm/openshell/ci/values-sandbox-runtime.yaml conformance-artifact-prefix: openshell-conformance kubernetes-workspace-operator-e2e: @@ -469,7 +467,7 @@ jobs: image-tag: ${{ github.sha }} job-name: Kubernetes E2E (workspace operator mode) e2e-task: e2e:kubernetes:workspace-operator - extra-helm-values: deploy/helm/openshell/ci/values-proxy-pod.yaml + extra-helm-values: deploy/helm/openshell/ci/values-sandbox-runtime.yaml conformance-artifact-prefix: openshell-conformance kubernetes-ha-e2e: diff --git a/crates/openshell-core/src/provider_credentials.rs b/crates/openshell-core/src/provider_credentials.rs index c42c0c98b..4d893fa36 100644 --- a/crates/openshell-core/src/provider_credentials.rs +++ b/crates/openshell-core/src/provider_credentials.rs @@ -171,7 +171,7 @@ impl ProviderCredentialState { /// Build a static provider state from an already-prepared child /// environment snapshot. /// - /// The Kubernetes proxy-pod topology uses this in the sandbox process: + /// The Kubernetes sandbox runtime uses this in the sandbox process: /// the supervisor owns provider credential resolvers and sends the /// workload-facing env map over a local control channel. The process leaf /// must inject that map into child processes without re-placeholderizing it diff --git a/crates/openshell-core/src/sandbox_env.rs b/crates/openshell-core/src/sandbox_env.rs index c6a6824fa..dfbc920cc 100644 --- a/crates/openshell-core/src/sandbox_env.rs +++ b/crates/openshell-core/src/sandbox_env.rs @@ -133,9 +133,9 @@ pub const TELEMETRY_ENABLED: &str = "OPENSHELL_TELEMETRY_ENABLED"; /// The isolation backend admitted by the deployment configuration (RFC 0012). /// -/// Delivered on a channel separate from the topology descriptor so descriptor +/// Delivered on a channel separate from the backend descriptor so descriptor /// verification against the admitted backend is not self-referential. Required -/// whenever a topology descriptor is supplied. +/// whenever a backend descriptor is supplied. pub const ADMITTED_ISOLATION_BACKEND: &str = "OPENSHELL_ADMITTED_ISOLATION_BACKEND"; /// Comma-separated runtime networking capabilities supplied by the compute diff --git a/crates/openshell-driver-kubernetes/README.md b/crates/openshell-driver-kubernetes/README.md index ce16bde25..4c90f8699 100644 --- a/crates/openshell-driver-kubernetes/README.md +++ b/crates/openshell-driver-kubernetes/README.md @@ -67,7 +67,7 @@ The driver creates an empty-egress `NetworkPolicy` before it releases the workload Pod. A second rule permits only the paired supervisor Pod to reach the sandbox TLS port. The supervisor has normal egress for gateway, DNS, and policy-approved upstream connections. Set -`proxy_pod.network_policy_enforced = true` only after verifying that the cluster +`sandbox_runtime.network_policy_enforced = true` only after verifying that the cluster CNI enforces ingress and egress `NetworkPolicy` for sandbox namespaces. Each sandbox generation uses two immutable bootstrap Secrets. A trusted init diff --git a/crates/openshell-driver-kubernetes/src/config.rs b/crates/openshell-driver-kubernetes/src/config.rs index 7bd1e0efa..b69fda020 100644 --- a/crates/openshell-driver-kubernetes/src/config.rs +++ b/crates/openshell-driver-kubernetes/src/config.rs @@ -22,10 +22,10 @@ pub const DEFAULT_SANDBOX_SERVICE_ACCOUNT_NAME: &str = "default"; /// Default storage size for the workspace PVC. pub const DEFAULT_WORKSPACE_STORAGE_SIZE: &str = "2Gi"; -/// Driver-owned requirements for the cross-pod RFC 0012 topology. +/// Driver-owned requirements for the Kubernetes sandbox runtime. #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(default, deny_unknown_fields)] -pub struct KubernetesProxyPodConfig { +pub struct KubernetesSandboxRuntimeConfig { /// Explicit operator assertion that the cluster CNI enforces /// `networking.k8s.io/v1` `NetworkPolicy` for the sandbox namespaces. pub network_policy_enforced: bool, @@ -33,7 +33,7 @@ pub struct KubernetesProxyPodConfig { pub boundary_port: u16, } -impl Default for KubernetesProxyPodConfig { +impl Default for KubernetesSandboxRuntimeConfig { fn default() -> Self { Self { network_policy_enforced: false, @@ -42,16 +42,16 @@ impl Default for KubernetesProxyPodConfig { } } -impl KubernetesProxyPodConfig { +impl KubernetesSandboxRuntimeConfig { pub fn validate(&self) -> Result<(), String> { if !self.network_policy_enforced { return Err( - "proxy-pod topology requires proxy_pod.network_policy_enforced = true after the operator has verified CNI NetworkPolicy enforcement" + "sandbox_runtime.network_policy_enforced must be true after the operator has verified CNI NetworkPolicy enforcement" .to_string(), ); } if self.boundary_port < 1024 { - return Err("proxy_pod.boundary_port must be at least 1024".to_string()); + return Err("sandbox_runtime.boundary_port must be at least 1024".to_string()); } Ok(()) } @@ -132,7 +132,7 @@ pub struct KubernetesComputeConfig { /// operator mode. Hot-reloaded on change. Delivered via `ConfigMap` volume mount. #[serde(default, skip_serializing_if = "Option::is_none")] pub operator_namespace_file: Option, - /// Kubernetes `ServiceAccount` assigned to both topology Pods. Automatic + /// Kubernetes `ServiceAccount` assigned to the workload and supervisor Pods. Automatic /// token mounting is disabled; only the supervisor receives an explicit /// audience-bound projected token accepted by the bootstrap authenticator. pub service_account_name: String, @@ -155,7 +155,7 @@ pub struct KubernetesComputeConfig { /// Empty string delegates to the Kubernetes default. pub supervisor_image_pull_policy: String, /// Cross-pod sandbox/supervisor settings. - pub proxy_pod: KubernetesProxyPodConfig, + pub sandbox_runtime: KubernetesSandboxRuntimeConfig, /// Corporate HTTP forward proxy used by the network supervisor for /// policy-approved TLS CONNECT egress. pub https_proxy: Option, @@ -269,7 +269,7 @@ impl Default for KubernetesComputeConfig { sandbox_runtime_image_pull_policy: String::new(), supervisor_image: config::default_supervisor_image(), supervisor_image_pull_policy: String::new(), - proxy_pod: KubernetesProxyPodConfig::default(), + sandbox_runtime: KubernetesSandboxRuntimeConfig::default(), https_proxy: None, no_proxy: None, proxy_auth_secret_name: None, @@ -330,7 +330,7 @@ impl KubernetesComputeConfig { } pub fn validate_proxy_uid(&self) -> Result<(), String> { - self.proxy_pod.validate() + self.sandbox_runtime.validate() } /// Validate the operator-owned corporate upstream proxy configuration. @@ -731,7 +731,7 @@ mod tests { } #[test] - fn proxy_pod_requires_network_policy_enforcement_acknowledgement() { + fn sandbox_runtime_requires_network_policy_enforcement_acknowledgement() { let mut cfg = KubernetesComputeConfig::default(); assert!( cfg.validate_proxy_uid() @@ -739,7 +739,7 @@ mod tests { .contains("network_policy_enforced") ); - cfg.proxy_pod.network_policy_enforced = true; + cfg.sandbox_runtime.network_policy_enforced = true; cfg.validate_proxy_uid().unwrap(); } @@ -754,19 +754,6 @@ mod tests { assert!(err.to_string().contains("unknown field")); } - #[test] - fn serde_rejects_removed_topology_alias_field() { - let mut json = serde_json::Map::new(); - json.insert( - ["supervisor", "topology"].join("_"), - serde_json::json!("sidecar"), - ); - let err = - serde_json::from_value::(serde_json::Value::Object(json)) - .unwrap_err(); - assert!(err.to_string().contains("unknown field")); - } - #[test] fn serde_rejects_removed_flat_sidecar_fields() { for json in [ diff --git a/crates/openshell-driver-kubernetes/src/driver.rs b/crates/openshell-driver-kubernetes/src/driver.rs index 499ece412..1c65785f5 100644 --- a/crates/openshell-driver-kubernetes/src/driver.rs +++ b/crates/openshell-driver-kubernetes/src/driver.rs @@ -8,11 +8,14 @@ use crate::config::{ KubernetesComputeConfig, OperatorNamespaceAllowlist, WorkspaceMode, is_dns_1123_label, managed_namespace, managed_namespace_prefix, validate_managed_namespace_name, }; -use crate::isolation::{BOUNDARY_PAIR_LABEL, BOUNDARY_ROLE_LABEL, KubernetesProxyPodBoundarySpec}; -use crate::proxy_pod::{ - BOUNDARY_CERTIFICATE_PATH, BOUNDARY_CONFIG_PATH, BOUNDARY_PRIVATE_KEY_PATH, ProxyPodNames, - boundary_service, control_deployment, control_egress_policy, generate_proxy_ca_material, - sandbox_bootstrap_secret, sandbox_owner_reference as proxy_pod_sandbox_owner_reference, +use crate::isolation::{ + BOUNDARY_PAIR_LABEL, BOUNDARY_ROLE_LABEL, KubernetesSandboxRuntimeBoundarySpec, +}; +use crate::sandbox_runtime::{ + BOUNDARY_CERTIFICATE_PATH, BOUNDARY_CONFIG_PATH, BOUNDARY_PRIVATE_KEY_PATH, + SandboxRuntimeNames, boundary_service, control_deployment, control_egress_policy, + generate_proxy_ca_material, sandbox_bootstrap_secret, + sandbox_owner_reference as sandbox_runtime_sandbox_owner_reference, supervisor_bootstrap_secret, workload_fence, }; use futures::{Stream, StreamExt, TryStreamExt}; @@ -80,17 +83,18 @@ pub type WatchStream = const MANAGED_SSH_NETWORK_POLICY_NAME: &str = "openshell-sandbox-ssh"; const AGENT_SANDBOX_TRACE_CONTEXT_ANNOTATION: &str = "opentelemetry.io/trace-context"; -const ANNOTATION_PROXY_POD_BOOTSTRAPPING: &str = "openshell.ai/proxy-pod-bootstrapping"; -const ANNOTATION_PROXY_POD_BOOTSTRAP_STARTED_AT: &str = - "openshell.ai/proxy-pod-bootstrap-started-at-ms"; -const ANNOTATION_PROXY_POD_BOOTSTRAP_OPERATION: &str = "openshell.ai/proxy-pod-bootstrap-operation"; -const ANNOTATION_PROXY_POD_GENERATION: &str = "openshell.ai/proxy-pod-generation"; -const ANNOTATION_PROXY_POD_READINESS: &str = "openshell.ai/proxy-pod-readiness"; -const ANNOTATION_PROXY_POD_WORKLOAD_UID: &str = "openshell.ai/proxy-pod-workload-uid"; +const ANNOTATION_SANDBOX_RUNTIME_BOOTSTRAPPING: &str = "openshell.ai/sandbox-runtime-bootstrapping"; +const ANNOTATION_SANDBOX_RUNTIME_BOOTSTRAP_STARTED_AT: &str = + "openshell.ai/sandbox-runtime-bootstrap-started-at-ms"; +const ANNOTATION_SANDBOX_RUNTIME_BOOTSTRAP_OPERATION: &str = + "openshell.ai/sandbox-runtime-bootstrap-operation"; +const ANNOTATION_SANDBOX_RUNTIME_GENERATION: &str = "openshell.ai/sandbox-runtime-generation"; +const ANNOTATION_SANDBOX_RUNTIME_READINESS: &str = "openshell.ai/sandbox-runtime-readiness"; +const ANNOTATION_SANDBOX_RUNTIME_WORKLOAD_UID: &str = "openshell.ai/sandbox-runtime-workload-uid"; fn boundary_service_authority( namespace: &str, - names: &ProxyPodNames, + names: &SandboxRuntimeNames, boundary_port: u16, ) -> String { format!( @@ -182,15 +186,15 @@ impl From for openshell_core::ComputeDriverError { /// This prevents gRPC handlers from blocking indefinitely when the k8s /// API server is unreachable or slow. const KUBE_API_TIMEOUT: Duration = Duration::from_secs(30); -const PROXY_POD_RECONCILE_INTERVAL: Duration = Duration::from_secs(30); -const PROXY_POD_FENCE_QUIESCE_WINDOW: Duration = Duration::from_secs(6); -const PROXY_POD_FENCE_QUIESCE_INTERVAL: Duration = Duration::from_secs(2); +const SANDBOX_RUNTIME_RECONCILE_INTERVAL: Duration = Duration::from_secs(30); +const SANDBOX_RUNTIME_FENCE_QUIESCE_WINDOW: Duration = Duration::from_secs(6); +const SANDBOX_RUNTIME_FENCE_QUIESCE_INTERVAL: Duration = Duration::from_secs(2); /// A pre-created fence may legitimately have no Sandbox CR while create is in flight. -const PROXY_POD_ORPHAN_FENCE_MIN_AGE: Duration = Duration::from_mins(5); +const SANDBOX_RUNTIME_ORPHAN_FENCE_MIN_AGE: Duration = Duration::from_mins(5); /// Bound how long a crash-interrupted, fail-closed bootstrap may remain stranded. -const PROXY_POD_BOOTSTRAP_GRACE: Duration = Duration::from_mins(5); +const SANDBOX_RUNTIME_BOOTSTRAP_GRACE: Duration = Duration::from_mins(5); -fn random_proxy_pod_token() -> String { +fn random_sandbox_runtime_token() -> String { use std::fmt::Write as _; let mut bytes = [0_u8; 32]; @@ -799,7 +803,7 @@ impl KubernetesComputeDriver { return Ok((owner.clone(), false)); } validate_proxy_control_labels(pod, sandbox_id)?; - let names = ProxyPodNames::new(sandbox_id); + let names = SandboxRuntimeNames::new(sandbox_id); let replica_set_owner = controller_owner_reference( pod.metadata.owner_references.as_deref().unwrap_or_default(), ) @@ -825,7 +829,7 @@ impl KubernetesComputeDriver { })?; if deployment_owner.name != names.control_deployment { return Err(tonic::Status::permission_denied( - "control pod is not owned by the expected proxy-pod Deployment", + "control pod is not owned by the expected sandbox-runtime Deployment", )); } let deployment = Api::::namespaced(self.client.clone(), namespace) @@ -1549,7 +1553,7 @@ impl KubernetesComputeDriver { .clone() .unwrap_or_else(|| self.config.namespace.clone()); Ok( - sandbox_from_object_with_proxy_pod_readiness(&self.client, &ns, obj) + sandbox_from_object_with_sandbox_runtime_readiness(&self.client, &ns, obj) .await .ok() .map(|(_, sandbox)| sandbox), @@ -1604,7 +1608,8 @@ impl KubernetesComputeDriver { .namespace .clone() .unwrap_or_else(|| self.config.namespace.clone()); - match sandbox_from_object_with_proxy_pod_readiness(&self.client, &ns, obj).await + match sandbox_from_object_with_sandbox_runtime_readiness(&self.client, &ns, obj) + .await { Ok((_, sandbox)) => sandboxes.push(sandbox), Err(err) => { @@ -1716,8 +1721,8 @@ impl KubernetesComputeDriver { .resolve_sandbox_identity_in_namespace(&target_namespace) .await; - let generation = random_proxy_pod_token(); - let proxy_names = ProxyPodNames::for_generation(&sandbox.id, &generation); + let generation = random_sandbox_runtime_token(); + let proxy_names = SandboxRuntimeNames::for_generation(&sandbox.id, &generation); let params = SandboxPodParams { default_image: &self.config.default_image, image_pull_policy: &self.config.image_pull_policy, @@ -1732,13 +1737,13 @@ impl KubernetesComputeDriver { default_runtime_class_name: &self.config.default_runtime_class_name, sandbox_uid: resolved_user_id, sandbox_gid: resolved_group_id, - boundary_port: self.config.proxy_pod.boundary_port, + boundary_port: self.config.sandbox_runtime.boundary_port, sandbox_secret_name: &proxy_names.sandbox_secret, }; let kube_name = self.config.kube_resource_name(workspace, name); let mut data = sandbox_to_k8s_spec(sandbox.spec.as_ref(), ¶ms) .map_err(KubernetesDriverError::InvalidArgument)?; - self.create_proxy_pod_fence(&target_namespace, &proxy_names, sandbox, &kube_name) + self.create_sandbox_runtime_fence(&target_namespace, &proxy_names, sandbox, &kube_name) .await?; // A missing bootstrap Secret keeps both pods inert as defense in // depth, but the CR is also created suspended so the controller @@ -1752,19 +1757,19 @@ impl KubernetesComputeDriver { let mut annotations = sandbox_annotations(sandbox); add_trace_context_annotation(&mut annotations); annotations.insert( - ANNOTATION_PROXY_POD_BOOTSTRAPPING.to_string(), + ANNOTATION_SANDBOX_RUNTIME_BOOTSTRAPPING.to_string(), "true".to_string(), ); annotations.insert( - ANNOTATION_PROXY_POD_BOOTSTRAP_STARTED_AT.to_string(), + ANNOTATION_SANDBOX_RUNTIME_BOOTSTRAP_STARTED_AT.to_string(), openshell_core::time::now_ms().to_string(), ); annotations.insert( - ANNOTATION_PROXY_POD_BOOTSTRAP_OPERATION.to_string(), + ANNOTATION_SANDBOX_RUNTIME_BOOTSTRAP_OPERATION.to_string(), "create".to_string(), ); annotations.insert( - ANNOTATION_PROXY_POD_GENERATION.to_string(), + ANNOTATION_SANDBOX_RUNTIME_GENERATION.to_string(), generation.clone(), ); for key in [ @@ -1821,7 +1826,7 @@ impl KubernetesComputeDriver { } }; if let Err(error) = self - .create_proxy_pod_companions( + .create_sandbox_runtime_companions( sandbox, &target_namespace, &kube_name, @@ -1834,7 +1839,7 @@ impl KubernetesComputeDriver { ) .await { - warn!(sandbox_id = %sandbox.id, %error, "proxy-pod provisioning failed; rolling back Sandbox CR"); + warn!(sandbox_id = %sandbox.id, %error, "sandbox-runtime provisioning failed; rolling back Sandbox CR"); let _ = agent_sandbox_api .api .delete(&kube_name, &DeleteParams::default()) @@ -1844,10 +1849,10 @@ impl KubernetesComputeDriver { Ok(()) } - async fn create_proxy_pod_fence( + async fn create_sandbox_runtime_fence( &self, namespace: &str, - names: &ProxyPodNames, + names: &SandboxRuntimeNames, sandbox: &Sandbox, workload_pod_name: &str, ) -> Result<(), KubernetesDriverError> { @@ -1855,7 +1860,7 @@ impl KubernetesComputeDriver { namespace, names, &sandbox.id, - self.config.proxy_pod.boundary_port, + self.config.sandbox_runtime.boundary_port, ) .workload_policy; let labels = policy.metadata.labels.get_or_insert_default(); @@ -1874,7 +1879,7 @@ impl KubernetesComputeDriver { workload_pod_name.to_string(), ); let policies: Api = Api::namespaced(self.client.clone(), namespace); - create_or_validate_proxy_pod_fence(&policies, &policy).await + create_or_validate_sandbox_runtime_fence(&policies, &policy).await } async fn wait_for_bootstrap_workload_pod( @@ -1954,7 +1959,7 @@ impl KubernetesComputeDriver { .labels .as_ref() .ok_or_else(|| fail("missing labels"))?; - let expected_pair = crate::proxy_pod::pair_label_value(sandbox_id); + let expected_pair = crate::sandbox_runtime::pair_label_value(sandbox_id); if labels.get(BOUNDARY_ROLE_LABEL).map(String::as_str) != Some("workload") || labels.get(BOUNDARY_PAIR_LABEL).map(String::as_str) != Some(expected_pair.as_str()) { @@ -2126,14 +2131,14 @@ impl KubernetesComputeDriver { } #[allow(clippy::too_many_arguments, clippy::similar_names)] - async fn create_proxy_pod_companions( + async fn create_sandbox_runtime_companions( &self, sandbox: &Sandbox, namespace: &str, cr_name: &str, sandbox_api: &AgentSandboxApi, sandbox_cr: &DynamicObject, - names: &ProxyPodNames, + names: &SandboxRuntimeNames, generation: &str, agent_uid: u32, agent_gid: u32, @@ -2150,13 +2155,13 @@ impl KubernetesComputeDriver { .ok_or_else(|| { KubernetesDriverError::Message("sandbox namespace has no UID".to_string()) })?; - let dependent_owner = proxy_pod_sandbox_owner_reference( + let dependent_owner = sandbox_runtime_sandbox_owner_reference( cr_name, cr_uid, &sandbox_api.resource.api_version, false, ); - let controller_owner = proxy_pod_sandbox_owner_reference( + let controller_owner = sandbox_runtime_sandbox_owner_reference( cr_name, cr_uid, &sandbox_api.resource.api_version, @@ -2171,7 +2176,7 @@ impl KubernetesComputeDriver { namespace, names, &sandbox.id, - self.config.proxy_pod.boundary_port, + self.config.sandbox_runtime.boundary_port, dependent_owner.clone(), ), ) @@ -2295,7 +2300,7 @@ impl KubernetesComputeDriver { "metadata": { "resourceVersion": version, "annotations": { - ANNOTATION_PROXY_POD_WORKLOAD_UID: workload_pod_uid.clone(), + ANNOTATION_SANDBOX_RUNTIME_WORKLOAD_UID: workload_pod_uid.clone(), } } }) @@ -2338,7 +2343,7 @@ impl KubernetesComputeDriver { format!("sandbox:{cr_uid}"), ) .map_err(|error| KubernetesDriverError::Message(error.to_string()))?; - let provisioned = KubernetesProxyPodBoundarySpec { + let provisioned = KubernetesSandboxRuntimeBoundarySpec { boundary_id: sandbox.id.clone(), generation: generation.to_string(), session_id, @@ -2357,16 +2362,16 @@ impl KubernetesComputeDriver { } else { std::net::IpAddr::V4(std::net::Ipv4Addr::UNSPECIFIED) }, - self.config.proxy_pod.boundary_port, + self.config.sandbox_runtime.boundary_port, ), control_authority: boundary_service_authority( namespace, names, - self.config.proxy_pod.boundary_port, + self.config.sandbox_runtime.boundary_port, ), control_address: std::net::SocketAddr::new( service_ip, - self.config.proxy_pod.boundary_port, + self.config.sandbox_runtime.boundary_port, ), sandbox_tls: SandboxTlsServerConfig { certificate_chain_path: PathBuf::from(BOUNDARY_CERTIFICATE_PATH), @@ -2382,8 +2387,8 @@ impl KubernetesComputeDriver { } .provision(); let descriptor = provisioned - .topology - .descriptor(crate::isolation::BACKEND_NAME) + .runtime_descriptor + .backend_descriptor() .map_err(|error| KubernetesDriverError::Message(error.to_string()))?; let sandbox_secret = sandbox_bootstrap_secret( namespace, @@ -2448,7 +2453,7 @@ impl KubernetesComputeDriver { ) .await .map_err(KubernetesDriverError::from_kube)?; - spawn_proxy_pod_bootstrap_completion( + spawn_sandbox_runtime_bootstrap_completion( deployments.clone(), sandbox_api.api.clone(), names.control_deployment.clone(), @@ -2465,14 +2470,14 @@ impl KubernetesComputeDriver { } #[allow(clippy::too_many_arguments, clippy::similar_names)] - async fn install_proxy_pod_generation( + async fn install_sandbox_runtime_generation( &self, namespace: &str, cr_name: &str, sandbox_api: &AgentSandboxApi, sandbox_id: &str, cr_uid: &str, - names: &ProxyPodNames, + names: &SandboxRuntimeNames, generation: &str, deployment_uid: &str, agent_uid: u32, @@ -2544,7 +2549,7 @@ impl KubernetesComputeDriver { "metadata": { "resourceVersion": version, "annotations": { - ANNOTATION_PROXY_POD_WORKLOAD_UID: workload_pod_uid.clone(), + ANNOTATION_SANDBOX_RUNTIME_WORKLOAD_UID: workload_pod_uid.clone(), } } }) @@ -2565,7 +2570,7 @@ impl KubernetesComputeDriver { format!("sandbox:{cr_uid}"), ) .map_err(|error| KubernetesDriverError::Message(error.to_string()))?; - let provisioned = KubernetesProxyPodBoundarySpec { + let provisioned = KubernetesSandboxRuntimeBoundarySpec { boundary_id: sandbox_id.to_string(), generation: generation.to_string(), session_id, @@ -2584,16 +2589,16 @@ impl KubernetesComputeDriver { } else { std::net::IpAddr::V4(std::net::Ipv4Addr::UNSPECIFIED) }, - self.config.proxy_pod.boundary_port, + self.config.sandbox_runtime.boundary_port, ), control_authority: boundary_service_authority( namespace, names, - self.config.proxy_pod.boundary_port, + self.config.sandbox_runtime.boundary_port, ), control_address: std::net::SocketAddr::new( service_ip, - self.config.proxy_pod.boundary_port, + self.config.sandbox_runtime.boundary_port, ), sandbox_tls: SandboxTlsServerConfig { certificate_chain_path: PathBuf::from(BOUNDARY_CERTIFICATE_PATH), @@ -2609,8 +2614,8 @@ impl KubernetesComputeDriver { } .provision(); let descriptor = provisioned - .topology - .descriptor(crate::isolation::BACKEND_NAME) + .runtime_descriptor + .backend_descriptor() .map_err(|error| KubernetesDriverError::Message(error.to_string()))?; let sandbox_secret = sandbox_bootstrap_secret( namespace, @@ -2676,7 +2681,7 @@ impl KubernetesComputeDriver { ) .await .map_err(KubernetesDriverError::from_kube)?; - spawn_proxy_pod_bootstrap_completion( + spawn_sandbox_runtime_bootstrap_completion( deployments, sandbox_api.api.clone(), names.control_deployment.clone(), @@ -2743,13 +2748,13 @@ impl KubernetesComputeDriver { ); if stop_is_complete { if let Err(error) = self - .scale_proxy_pod_control(sandbox_id, &namespace, 0) + .scale_sandbox_runtime_control(sandbox_id, &namespace, 0) .await { // The workload is already stopped. Treat a stranded // control replica as resource drift and let periodic // reconciliation retry rather than failing the stop. - warn!(sandbox_id, %error, "failed to scale stopped proxy-pod control Deployment to zero"); + warn!(sandbox_id, %error, "failed to scale stopped sandbox-runtime control Deployment to zero"); } return Ok(()); } @@ -2781,12 +2786,13 @@ impl KubernetesComputeDriver { ) -> Result<(), KubernetesDriverError> { let span_status = openshell_otel::ErrorStatusGuard::current(); let result = - Box::pin(self.start_proxy_pod_generation(sandbox_id, launch_authentication)).await; + Box::pin(self.start_sandbox_runtime_generation(sandbox_id, launch_authentication)) + .await; span_status.finish(result) } #[allow(clippy::similar_names)] - async fn start_proxy_pod_generation( + async fn start_sandbox_runtime_generation( &self, sandbox_id: &str, encoded_authentication: &[u8], @@ -2804,7 +2810,7 @@ impl KubernetesComputeDriver { .map_err(KubernetesDriverError::from_kube)? .items; let object = objects.pop().ok_or(KubernetesDriverError::NotFound)?; - if proxy_pod_bootstrap_in_progress(&object) { + if sandbox_runtime_bootstrap_in_progress(&object) { return Err(KubernetesDriverError::Precondition( "sandbox bootstrap has not completed; wait for reconciliation or recreate the sandbox" .to_string(), @@ -2838,9 +2844,9 @@ impl KubernetesComputeDriver { )); } - let generation = random_proxy_pod_token(); - let names = ProxyPodNames::for_generation(sandbox_id, &generation); - self.create_proxy_pod_fence( + let generation = random_sandbox_runtime_token(); + let names = SandboxRuntimeNames::for_generation(sandbox_id, &generation); + self.create_sandbox_runtime_fence( &namespace, &names, &Sandbox { @@ -2958,10 +2964,10 @@ impl KubernetesComputeDriver { let mut running_patch = sandbox_operating_state_patch(&sandbox_api.resource.version, version, true); running_patch["metadata"]["annotations"] = serde_json::json!({ - ANNOTATION_PROXY_POD_BOOTSTRAPPING: "true", - ANNOTATION_PROXY_POD_BOOTSTRAP_STARTED_AT: openshell_core::time::now_ms().to_string(), - ANNOTATION_PROXY_POD_BOOTSTRAP_OPERATION: "restart", - ANNOTATION_PROXY_POD_GENERATION: generation, + ANNOTATION_SANDBOX_RUNTIME_BOOTSTRAPPING: "true", + ANNOTATION_SANDBOX_RUNTIME_BOOTSTRAP_STARTED_AT: openshell_core::time::now_ms().to_string(), + ANNOTATION_SANDBOX_RUNTIME_BOOTSTRAP_OPERATION: "restart", + ANNOTATION_SANDBOX_RUNTIME_GENERATION: generation, }); running_patch["spec"]["podTemplate"]["spec"]["volumes"] = serde_json::Value::Array(volumes.clone()); @@ -2972,7 +2978,7 @@ impl KubernetesComputeDriver { let (agent_uid, agent_gid, _) = self.resolve_sandbox_identity_in_namespace(&namespace).await; let child_env = child_environment_from_sandbox_object(&object); - self.install_proxy_pod_generation( + self.install_sandbox_runtime_generation( &namespace, cr_name, &sandbox_api, @@ -3040,13 +3046,13 @@ impl KubernetesComputeDriver { .cloned() .unwrap_or_else(|| kube_name.clone()); if running { - if proxy_pod_bootstrap_in_progress(&object) { + if sandbox_runtime_bootstrap_in_progress(&object) { return Err(KubernetesDriverError::Precondition( - "proxy-pod bootstrap has not completed; wait for reconciliation or recreate the sandbox" + "sandbox-runtime bootstrap has not completed; wait for reconciliation or recreate the sandbox" .to_string(), )); } - self.scale_proxy_pod_control(sandbox_id, &namespace, 1) + self.scale_sandbox_runtime_control(sandbox_id, &namespace, 1) .await?; } patch_dynamic_object_with_resource_version_retry( @@ -3073,13 +3079,13 @@ impl KubernetesComputeDriver { )) } - async fn scale_proxy_pod_control( + async fn scale_sandbox_runtime_control( &self, sandbox_id: &str, namespace: &str, replicas: i32, ) -> Result<(), KubernetesDriverError> { - let names = ProxyPodNames::new(sandbox_id); + let names = SandboxRuntimeNames::new(sandbox_id); Api::::namespaced(self.client.clone(), namespace) .patch( &names.control_deployment, @@ -3199,7 +3205,7 @@ impl KubernetesComputeDriver { Ok(false) | Err(_) => { warn!( sandbox_id, - "retaining proxy-pod workload fence because workload Pod deletion was not confirmed" + "retaining sandbox-runtime workload fence because workload Pod deletion was not confirmed" ); return Ok(true); } @@ -3218,14 +3224,14 @@ impl KubernetesComputeDriver { ) { warn!( sandbox_id, - "retaining proxy-pod workload fence because Sandbox CR deletion was not confirmed" + "retaining sandbox-runtime workload fence because Sandbox CR deletion was not confirmed" ); return Ok(true); } let quiesce_deadline = - tokio::time::Instant::now() + PROXY_POD_FENCE_QUIESCE_WINDOW; + tokio::time::Instant::now() + SANDBOX_RUNTIME_FENCE_QUIESCE_WINDOW; while tokio::time::Instant::now() < quiesce_deadline { - tokio::time::sleep(PROXY_POD_FENCE_QUIESCE_INTERVAL).await; + tokio::time::sleep(SANDBOX_RUNTIME_FENCE_QUIESCE_INTERVAL).await; let cr_is_gone = matches!( tokio::time::timeout(KUBE_API_TIMEOUT, delete_api.api.get(&kube_name)) .await, @@ -3244,12 +3250,12 @@ impl KubernetesComputeDriver { if !cr_is_gone || !pod_is_gone { warn!( sandbox_id, - "retaining proxy-pod workload fence because deletion did not remain quiescent" + "retaining sandbox-runtime workload fence because deletion did not remain quiescent" ); return Ok(true); } } - let names = ProxyPodNames::new(sandbox_id); + let names = SandboxRuntimeNames::new(sandbox_id); let policies: Api = Api::namespaced(self.client.clone(), &obj_namespace); match policies @@ -3259,7 +3265,7 @@ impl KubernetesComputeDriver { Ok(_) | Err(KubeError::Api(kube::core::ErrorResponse { code: 404, .. })) => {} Err(error) => { - warn!(sandbox_id, %error, "failed to delete proxy-pod workload fence; reconciliation will retry"); + warn!(sandbox_id, %error, "failed to delete sandbox-runtime workload fence; reconciliation will retry"); } } } @@ -3311,14 +3317,14 @@ impl KubernetesComputeDriver { /// gateway's sandbox watch is alive. Bootstrap material is immutable and /// intentionally not read by the gateway, so this pass only repairs state /// that can be proven from the Sandbox CR and named companion objects. - async fn reconcile_proxy_pod_resources(&self) { + async fn reconcile_sandbox_runtime_resources(&self) { let lookup_api = match self .supported_sandbox_api_for_lookup(self.client.clone()) .await { Ok(api) => api, Err(error) => { - warn!(%error, "skipping proxy-pod reconciliation: Sandbox API unavailable"); + warn!(%error, "skipping sandbox-runtime reconciliation: Sandbox API unavailable"); return; } }; @@ -3332,11 +3338,11 @@ impl KubernetesComputeDriver { { Ok(Ok(list)) => list, Ok(Err(error)) => { - warn!(%error, "skipping proxy-pod reconciliation: Sandbox list failed"); + warn!(%error, "skipping sandbox-runtime reconciliation: Sandbox list failed"); return; } Err(_) => { - warn!("skipping proxy-pod reconciliation: Sandbox list timed out"); + warn!("skipping sandbox-runtime reconciliation: Sandbox list timed out"); return; } }; @@ -3353,7 +3359,7 @@ impl KubernetesComputeDriver { .as_deref() .unwrap_or(&self.config.namespace); let cr_name = object.metadata.name.as_deref().unwrap_or_default(); - let names = ProxyPodNames::new(&sandbox_id); + let names = SandboxRuntimeNames::new(&sandbox_id); let policies = Api::::namespaced(self.client.clone(), namespace); let fence_was_missing = matches!( tokio::time::timeout(KUBE_API_TIMEOUT, policies.get_opt(&names.workload_policy)) @@ -3365,18 +3371,18 @@ impl KubernetesComputeDriver { ..Default::default() }; match self - .create_proxy_pod_fence(namespace, &names, &sandbox, cr_name) + .create_sandbox_runtime_fence(namespace, &names, &sandbox, cr_name) .await { Ok(()) => {} Err(KubernetesDriverError::Precondition(error)) => { - warn!(sandbox_id, %error, "proxy-pod workload fence is altered; suspending workload"); - self.suspend_proxy_pod_after_dependency_failure(&lookup_api, &object) + warn!(sandbox_id, %error, "sandbox-runtime workload fence is altered; suspending workload"); + self.suspend_sandbox_runtime_after_dependency_failure(&lookup_api, &object) .await; continue; } Err(error) => { - warn!(sandbox_id, %error, "could not verify proxy-pod workload fence; reconciliation will retry"); + warn!(sandbox_id, %error, "could not verify sandbox-runtime workload fence; reconciliation will retry"); continue; } } @@ -3387,28 +3393,28 @@ impl KubernetesComputeDriver { // re-confirmed after a control restart. warn!( sandbox_id, - "recreated missing proxy-pod workload fence; suspending stale boundary" + "recreated missing sandbox-runtime workload fence; suspending stale boundary" ); - self.suspend_proxy_pod_after_dependency_failure(&lookup_api, &object) + self.suspend_sandbox_runtime_after_dependency_failure(&lookup_api, &object) .await; continue; } - if proxy_pod_bootstrap_in_progress(&object) { - if proxy_pod_control_availability(&self.client, namespace, &sandbox_id).await - == ProxyPodControlAvailability::Available - && proxy_pod_runtime_is_ready(&object) + if sandbox_runtime_bootstrap_in_progress(&object) { + if sandbox_runtime_control_availability(&self.client, namespace, &sandbox_id).await + == SandboxRuntimeControlAvailability::Available + && sandbox_runtime_runtime_is_ready(&object) { - self.complete_proxy_pod_bootstrap(&lookup_api, &object) + self.complete_sandbox_runtime_bootstrap(&lookup_api, &object) .await; } else { - self.reap_stale_proxy_pod_bootstrap(&lookup_api, &object) + self.reap_stale_sandbox_runtime_bootstrap(&lookup_api, &object) .await; } continue; } - let desired = desired_proxy_pod_control_replicas(&object); + let desired = desired_sandbox_runtime_control_replicas(&object); if desired > 0 { - match proxy_pod_workload_generation_matches( + match sandbox_runtime_workload_generation_matches( &self.client, namespace, cr_name, @@ -3416,64 +3422,64 @@ impl KubernetesComputeDriver { ) .await { - ProxyPodControlAvailability::Available => {} - ProxyPodControlAvailability::Unavailable => { + SandboxRuntimeControlAvailability::Available => {} + SandboxRuntimeControlAvailability::Unavailable => { warn!( sandbox_id, - "proxy-pod workload generation changed; suspending stale boundary" + "sandbox-runtime workload generation changed; suspending stale boundary" ); - self.suspend_proxy_pod_after_dependency_failure(&lookup_api, &object) + self.suspend_sandbox_runtime_after_dependency_failure(&lookup_api, &object) .await; continue; } - ProxyPodControlAvailability::Unknown => { + SandboxRuntimeControlAvailability::Unknown => { warn!( sandbox_id, - "could not verify proxy-pod workload generation; reconciliation will retry" + "could not verify sandbox-runtime workload generation; reconciliation will retry" ); continue; } } } match self - .reconcile_proxy_pod_control_replicas(&sandbox_id, namespace, desired) + .reconcile_sandbox_runtime_control_replicas(&sandbox_id, namespace, desired) .await { Ok(()) => {} Err(KubernetesDriverError::Precondition(error)) => { - warn!(sandbox_id, %error, "proxy-pod companion is missing; suspending workload"); - self.suspend_proxy_pod_after_dependency_failure(&lookup_api, &object) + warn!(sandbox_id, %error, "sandbox-runtime companion is missing; suspending workload"); + self.suspend_sandbox_runtime_after_dependency_failure(&lookup_api, &object) .await; } Err(error) => { - warn!(sandbox_id, %error, "failed to reconcile proxy-pod control Deployment"); + warn!(sandbox_id, %error, "failed to reconcile sandbox-runtime control Deployment"); } } let availability = - proxy_pod_control_availability(&self.client, namespace, &sandbox_id).await; - self.publish_proxy_pod_readiness_transition(&lookup_api, &object, availability) + sandbox_runtime_control_availability(&self.client, namespace, &sandbox_id).await; + self.publish_sandbox_runtime_readiness_transition(&lookup_api, &object, availability) .await; } - self.reap_orphaned_proxy_pod_fences(&live_ids).await; + self.reap_orphaned_sandbox_runtime_fences(&live_ids).await; } - async fn publish_proxy_pod_readiness_transition( + async fn publish_sandbox_runtime_readiness_transition( &self, lookup_api: &AgentSandboxApi, object: &DynamicObject, - availability: ProxyPodControlAvailability, + availability: SandboxRuntimeControlAvailability, ) { let state = match availability { - ProxyPodControlAvailability::Available => "ready", - ProxyPodControlAvailability::Unavailable => "unavailable", - ProxyPodControlAvailability::Unknown => return, + SandboxRuntimeControlAvailability::Available => "ready", + SandboxRuntimeControlAvailability::Unavailable => "unavailable", + SandboxRuntimeControlAvailability::Unknown => return, }; if object .metadata .annotations .as_ref() - .and_then(|annotations| annotations.get(ANNOTATION_PROXY_POD_READINESS)) + .and_then(|annotations| annotations.get(ANNOTATION_SANDBOX_RUNTIME_READINESS)) .is_some_and(|current| current == state) { return; @@ -3491,7 +3497,7 @@ impl KubernetesComputeDriver { .unwrap_or(&self.config.namespace); let api = Self::agent_sandbox_api(self.client.clone(), &lookup_api.resource.version, namespace); - let patch = proxy_pod_readiness_transition_patch(resource_version, state); + let patch = sandbox_runtime_readiness_transition_patch(resource_version, state); match tokio::time::timeout( KUBE_API_TIMEOUT, api.api @@ -3501,18 +3507,18 @@ impl KubernetesComputeDriver { { Ok(Ok(_)) => {} Ok(Err(error)) => { - debug!(sandbox = name, %error, "proxy-pod readiness transition publication raced; reconciliation will retry"); + debug!(sandbox = name, %error, "sandbox-runtime readiness transition publication raced; reconciliation will retry"); } Err(_) => { warn!( sandbox = name, - "timed out publishing proxy-pod readiness transition" + "timed out publishing sandbox-runtime readiness transition" ); } } } - async fn suspend_proxy_pod_after_dependency_failure( + async fn suspend_sandbox_runtime_after_dependency_failure( &self, lookup_api: &AgentSandboxApi, object: &DynamicObject, @@ -3545,28 +3551,32 @@ impl KubernetesComputeDriver { { Ok(Ok(_)) => {} Ok(Err(error)) => { - warn!(sandbox = name, %error, "failed to suspend proxy-pod after fence failure"); + warn!(sandbox = name, %error, "failed to suspend sandbox-runtime after fence failure"); } Err(error) => { - warn!(sandbox = name, %error, "timed out suspending proxy-pod after fence failure"); + warn!(sandbox = name, %error, "timed out suspending sandbox-runtime after fence failure"); } } } - async fn reap_stale_proxy_pod_bootstrap( + async fn reap_stale_sandbox_runtime_bootstrap( &self, lookup_api: &AgentSandboxApi, object: &DynamicObject, ) { - if !proxy_pod_bootstrap_is_stale(object, SystemTime::now(), PROXY_POD_BOOTSTRAP_GRACE) { + if !sandbox_runtime_bootstrap_is_stale( + object, + SystemTime::now(), + SANDBOX_RUNTIME_BOOTSTRAP_GRACE, + ) { return; } - if proxy_pod_bootstrap_operation(object) != Some("create") { + if sandbox_runtime_bootstrap_operation(object) != Some("create") { warn!( sandbox = object.metadata.name.as_deref().unwrap_or(""), - "suspending stale proxy-pod restart bootstrap" + "suspending stale sandbox-runtime restart bootstrap" ); - self.suspend_proxy_pod_after_dependency_failure(lookup_api, object) + self.suspend_sandbox_runtime_after_dependency_failure(lookup_api, object) .await; return; } @@ -3591,22 +3601,22 @@ impl KubernetesComputeDriver { match tokio::time::timeout(KUBE_API_TIMEOUT, api.api.delete(name, ¶ms)).await { Ok(Ok(_)) => warn!( sandbox = name, - "rolled back stale fail-closed proxy-pod bootstrap" + "rolled back stale fail-closed sandbox-runtime bootstrap" ), Ok(Err(KubeError::Api(error))) if error.code == 404 || error.code == 409 => {} Ok(Err(error)) => { - warn!(sandbox = name, %error, "failed to roll back stale proxy-pod bootstrap"); + warn!(sandbox = name, %error, "failed to roll back stale sandbox-runtime bootstrap"); } Err(_) => { warn!( sandbox = name, - "timed out rolling back stale proxy-pod bootstrap" + "timed out rolling back stale sandbox-runtime bootstrap" ); } } } - async fn complete_proxy_pod_bootstrap( + async fn complete_sandbox_runtime_bootstrap( &self, lookup_api: &AgentSandboxApi, object: &DynamicObject, @@ -3624,7 +3634,7 @@ impl KubernetesComputeDriver { .unwrap_or(&self.config.namespace); let api = Self::agent_sandbox_api(self.client.clone(), &lookup_api.resource.version, namespace); - let patch = proxy_pod_bootstrap_completion_patch(resource_version); + let patch = sandbox_runtime_bootstrap_completion_patch(resource_version); match tokio::time::timeout( KUBE_API_TIMEOUT, api.api @@ -3634,33 +3644,36 @@ impl KubernetesComputeDriver { { Ok(Ok(_)) => {} Ok(Err(error)) => { - debug!(sandbox = name, %error, "proxy-pod bootstrap completion raced; reconciliation will retry"); + debug!(sandbox = name, %error, "sandbox-runtime bootstrap completion raced; reconciliation will retry"); } - Err(_) => warn!(sandbox = name, "timed out completing proxy-pod bootstrap"), + Err(_) => warn!( + sandbox = name, + "timed out completing sandbox-runtime bootstrap" + ), } } - async fn reconcile_proxy_pod_control_replicas( + async fn reconcile_sandbox_runtime_control_replicas( &self, sandbox_id: &str, namespace: &str, desired: i32, ) -> Result<(), KubernetesDriverError> { - let names = ProxyPodNames::new(sandbox_id); + let names = SandboxRuntimeNames::new(sandbox_id); let services = Api::::namespaced(self.client.clone(), namespace); let service_exists = tokio::time::timeout(KUBE_API_TIMEOUT, services.get_opt(&names.boundary_service)) .await .map_err(|_| { KubernetesDriverError::Message( - "timed out reading proxy-pod boundary Service".to_string(), + "timed out reading sandbox-runtime boundary Service".to_string(), ) })? .map_err(KubernetesDriverError::from_kube)? .is_some(); if !service_exists { return Err(KubernetesDriverError::Precondition(format!( - "proxy-pod boundary Service {} is missing and its allocated address cannot be safely reconstructed", + "sandbox-runtime boundary Service {} is missing and its allocated address cannot be safely reconstructed", names.boundary_service ))); } @@ -3672,24 +3685,24 @@ impl KubernetesComputeDriver { .await .map_err(|_| { KubernetesDriverError::Message( - "timed out reading proxy-pod control Deployment".to_string(), + "timed out reading sandbox-runtime control Deployment".to_string(), ) })? .map_err(KubernetesDriverError::from_kube)? .ok_or_else(|| { KubernetesDriverError::Precondition(format!( - "proxy-pod control Deployment {} is missing and cannot be safely reconstructed from the Sandbox CR", + "sandbox-runtime control Deployment {} is missing and cannot be safely reconstructed from the Sandbox CR", names.control_deployment )) })?; if deployment.spec.as_ref().and_then(|spec| spec.replicas) == Some(desired) { return Ok(()); } - self.scale_proxy_pod_control(sandbox_id, namespace, desired) + self.scale_sandbox_runtime_control(sandbox_id, namespace, desired) .await } - async fn reap_orphaned_proxy_pod_fences(&self, live_ids: &HashSet) { + async fn reap_orphaned_sandbox_runtime_fences(&self, live_ids: &HashSet) { let policies: Api = if self.config.is_multi_namespace() { Api::all(self.client.clone()) } else { @@ -3707,11 +3720,11 @@ impl KubernetesComputeDriver { { Ok(Ok(list)) => list, Ok(Err(error)) => { - warn!(%error, "failed to list proxy-pod workload fences for orphan cleanup"); + warn!(%error, "failed to list sandbox-runtime workload fences for orphan cleanup"); return; } Err(_) => { - warn!("timed out listing proxy-pod workload fences for orphan cleanup"); + warn!("timed out listing sandbox-runtime workload fences for orphan cleanup"); return; } }; @@ -3726,10 +3739,10 @@ impl KubernetesComputeDriver { if sandbox_id.is_empty() || live_ids.contains(&sandbox_id) { continue; } - if !proxy_pod_fence_is_old_enough(&policy, SystemTime::now()) { + if !sandbox_runtime_fence_is_old_enough(&policy, SystemTime::now()) { debug!( sandbox_id, - "retaining young proxy-pod fence while Sandbox creation may be in flight" + "retaining young sandbox-runtime fence while Sandbox creation may be in flight" ); continue; } @@ -3749,7 +3762,7 @@ impl KubernetesComputeDriver { else { warn!( sandbox_id, - "retaining orphaned proxy-pod fence without workload Pod annotation" + "retaining orphaned sandbox-runtime fence without workload Pod annotation" ); continue; }; @@ -3760,14 +3773,16 @@ impl KubernetesComputeDriver { ) { debug!( sandbox_id, - pod_name, "retaining proxy-pod fence until workload Pod absence is confirmed" + pod_name, + "retaining sandbox-runtime fence until workload Pod absence is confirmed" ); continue; } - let quiesce_deadline = tokio::time::Instant::now() + PROXY_POD_FENCE_QUIESCE_WINDOW; + let quiesce_deadline = + tokio::time::Instant::now() + SANDBOX_RUNTIME_FENCE_QUIESCE_WINDOW; let mut quiescent = true; while tokio::time::Instant::now() < quiesce_deadline { - tokio::time::sleep(PROXY_POD_FENCE_QUIESCE_INTERVAL).await; + tokio::time::sleep(SANDBOX_RUNTIME_FENCE_QUIESCE_INTERVAL).await; if self.sandbox_exists(&sandbox_id).await != Ok(false) || !matches!( tokio::time::timeout(KUBE_API_TIMEOUT, pods.get_opt(pod_name)).await, @@ -3781,7 +3796,7 @@ impl KubernetesComputeDriver { if !quiescent { debug!( sandbox_id, - pod_name, "retaining orphaned proxy-pod fence after quiescence recheck" + pod_name, "retaining orphaned sandbox-runtime fence after quiescence recheck" ); continue; } @@ -3799,34 +3814,34 @@ impl KubernetesComputeDriver { info!( sandbox_id, policy = name, - "reaped orphaned proxy-pod workload fence" + "reaped orphaned sandbox-runtime workload fence" ); } Ok(Err(error)) => { - warn!(sandbox_id, policy = name, %error, "failed to reap orphaned proxy-pod workload fence"); + warn!(sandbox_id, policy = name, %error, "failed to reap orphaned sandbox-runtime workload fence"); } Err(_) => warn!( sandbox_id, policy = name, - "timed out reaping orphaned proxy-pod workload fence" + "timed out reaping orphaned sandbox-runtime workload fence" ), } } } - fn spawn_proxy_pod_periodic_reconcile( + fn spawn_sandbox_runtime_periodic_reconcile( &self, tx: mpsc::Sender>, ) { let driver = self.clone(); tokio::spawn(async move { - let mut interval = tokio::time::interval(PROXY_POD_RECONCILE_INTERVAL); + let mut interval = tokio::time::interval(SANDBOX_RUNTIME_RECONCILE_INTERVAL); interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip); interval.tick().await; loop { tokio::select! { _ = interval.tick() => { - driver.reconcile_proxy_pod_resources().await; + driver.reconcile_sandbox_runtime_resources().await; if let Ok(sandboxes) = driver.list_sandboxes().await { for sandbox in sandboxes { if tx.send(Ok(WatchSandboxesEvent { @@ -3848,7 +3863,7 @@ impl KubernetesComputeDriver { // Kept `async` to match the gRPC handler signature in `grpc.rs`, which awaits this method. #[allow(clippy::unused_async)] pub async fn watch_sandboxes(&self) -> Result { - self.reconcile_proxy_pod_resources().await; + self.reconcile_sandbox_runtime_resources().await; if self.config.is_multi_namespace() { self.watch_sandboxes_cluster_wide().await } else { @@ -3874,7 +3889,7 @@ impl KubernetesComputeDriver { ) .boxed(); let (tx, rx) = mpsc::channel(256); - self.spawn_proxy_pod_periodic_reconcile(tx.clone()); + self.spawn_sandbox_runtime_periodic_reconcile(tx.clone()); let readiness_client = self.watch_client.clone(); tokio::spawn(async move { @@ -3885,7 +3900,7 @@ impl KubernetesComputeDriver { tokio::select! { event = sandbox_stream.next() => match event { Some(Event::Apply(obj) | Event::InitApply(obj)) => { - if let Ok((kube_name, sandbox)) = sandbox_from_object_with_proxy_pod_readiness(&readiness_client, &namespace, obj).await { + if let Ok((kube_name, sandbox)) = sandbox_from_object_with_sandbox_runtime_readiness(&readiness_client, &namespace, obj).await { update_indexes(&mut sandbox_name_to_id, &mut agent_pod_to_id, &kube_name, &sandbox); let event = WatchSandboxesEvent { payload: Some(watch_sandboxes_event::Payload::Sandbox( @@ -3979,21 +3994,21 @@ impl KubernetesComputeDriver { } } -fn proxy_pod_bootstrap_completion_patch(resource_version: &str) -> serde_json::Value { +fn sandbox_runtime_bootstrap_completion_patch(resource_version: &str) -> serde_json::Value { serde_json::json!({ "metadata": { "resourceVersion": resource_version, "annotations": { - ANNOTATION_PROXY_POD_BOOTSTRAPPING: serde_json::Value::Null, - ANNOTATION_PROXY_POD_BOOTSTRAP_STARTED_AT: serde_json::Value::Null, - ANNOTATION_PROXY_POD_BOOTSTRAP_OPERATION: serde_json::Value::Null, - ANNOTATION_PROXY_POD_READINESS: "ready", + ANNOTATION_SANDBOX_RUNTIME_BOOTSTRAPPING: serde_json::Value::Null, + ANNOTATION_SANDBOX_RUNTIME_BOOTSTRAP_STARTED_AT: serde_json::Value::Null, + ANNOTATION_SANDBOX_RUNTIME_BOOTSTRAP_OPERATION: serde_json::Value::Null, + ANNOTATION_SANDBOX_RUNTIME_READINESS: "ready", } } }) } -fn spawn_proxy_pod_bootstrap_completion( +fn spawn_sandbox_runtime_bootstrap_completion( deployments: Api, sandboxes: Api, deployment_name: String, @@ -4001,11 +4016,11 @@ fn spawn_proxy_pod_bootstrap_completion( expected_sandbox_uid: Option, ) { tokio::spawn(async move { - let deadline = tokio::time::Instant::now() + PROXY_POD_BOOTSTRAP_GRACE; + let deadline = tokio::time::Instant::now() + SANDBOX_RUNTIME_BOOTSTRAP_GRACE; let available = |deployment: Option<&Deployment>| { deployment.is_some_and(|deployment| { - proxy_pod_control_availability_from_deployment(deployment) - == ProxyPodControlAvailability::Available + sandbox_runtime_control_availability_from_deployment(deployment) + == SandboxRuntimeControlAvailability::Available }) }; match tokio::time::timeout_at( @@ -4017,7 +4032,7 @@ fn spawn_proxy_pod_bootstrap_completion( Ok(Ok(Some(_))) => {} Ok(Ok(None)) | Err(_) => return, Ok(Err(error)) => { - debug!(%error, sandbox = sandbox_name, "proxy-pod bootstrap availability watch failed; reconciliation will retry"); + debug!(%error, sandbox = sandbox_name, "sandbox-runtime bootstrap availability watch failed; reconciliation will retry"); return; } } @@ -4031,8 +4046,8 @@ fn spawn_proxy_pod_bootstrap_completion( let runtime_ready = |object: Option<&DynamicObject>| { object.is_some_and(|object| { object.metadata.uid == expected_sandbox_uid - && proxy_pod_bootstrap_in_progress(object) - && proxy_pod_runtime_is_ready(object) + && sandbox_runtime_bootstrap_in_progress(object) + && sandbox_runtime_runtime_is_ready(object) }) }; let object = match tokio::time::timeout_at( @@ -4044,14 +4059,14 @@ fn spawn_proxy_pod_bootstrap_completion( Ok(Ok(Some(object))) => object, Ok(Ok(None)) | Err(_) => return, Ok(Err(error)) => { - debug!(%error, sandbox = sandbox_name, "proxy-pod runtime readiness watch failed; reconciliation will retry"); + debug!(%error, sandbox = sandbox_name, "sandbox-runtime runtime readiness watch failed; reconciliation will retry"); return; } }; let Some(resource_version) = object.metadata.resource_version.as_deref() else { return; }; - let patch = proxy_pod_bootstrap_completion_patch(resource_version); + let patch = sandbox_runtime_bootstrap_completion_patch(resource_version); if let Err(error) = sandboxes .patch( &sandbox_name, @@ -4060,12 +4075,12 @@ fn spawn_proxy_pod_bootstrap_completion( ) .await { - debug!(%error, sandbox = sandbox_name, "proxy-pod bootstrap completion raced; reconciliation will retry"); + debug!(%error, sandbox = sandbox_name, "sandbox-runtime bootstrap completion raced; reconciliation will retry"); } }); } -fn proxy_pod_runtime_is_ready(object: &DynamicObject) -> bool { +fn sandbox_runtime_runtime_is_ready(object: &DynamicObject) -> bool { let Some(generation) = object.metadata.generation else { return false; }; @@ -4096,29 +4111,29 @@ fn proxy_pod_runtime_is_ready(object: &DynamicObject) -> bool { ready && !suspended } -fn proxy_pod_fence_is_old_enough(policy: &NetworkPolicy, now: SystemTime) -> bool { - metadata_is_older_than(&policy.metadata, now, PROXY_POD_ORPHAN_FENCE_MIN_AGE) +fn sandbox_runtime_fence_is_old_enough(policy: &NetworkPolicy, now: SystemTime) -> bool { + metadata_is_older_than(&policy.metadata, now, SANDBOX_RUNTIME_ORPHAN_FENCE_MIN_AGE) } -fn proxy_pod_bootstrap_in_progress(object: &DynamicObject) -> bool { +fn sandbox_runtime_bootstrap_in_progress(object: &DynamicObject) -> bool { object .metadata .annotations .as_ref() - .and_then(|annotations| annotations.get(ANNOTATION_PROXY_POD_BOOTSTRAPPING)) + .and_then(|annotations| annotations.get(ANNOTATION_SANDBOX_RUNTIME_BOOTSTRAPPING)) .is_some_and(|value| value == "true") } -fn proxy_pod_bootstrap_operation(object: &DynamicObject) -> Option<&str> { +fn sandbox_runtime_bootstrap_operation(object: &DynamicObject) -> Option<&str> { object .metadata .annotations .as_ref() - .and_then(|annotations| annotations.get(ANNOTATION_PROXY_POD_BOOTSTRAP_OPERATION)) + .and_then(|annotations| annotations.get(ANNOTATION_SANDBOX_RUNTIME_BOOTSTRAP_OPERATION)) .map(String::as_str) } -fn proxy_pod_bootstrap_is_stale( +fn sandbox_runtime_bootstrap_is_stale( object: &DynamicObject, now: SystemTime, minimum_age: Duration, @@ -4127,7 +4142,7 @@ fn proxy_pod_bootstrap_is_stale( .metadata .annotations .as_ref() - .and_then(|annotations| annotations.get(ANNOTATION_PROXY_POD_BOOTSTRAP_STARTED_AT)) + .and_then(|annotations| annotations.get(ANNOTATION_SANDBOX_RUNTIME_BOOTSTRAP_STARTED_AT)) .and_then(|value| value.parse::().ok()) else { return true; @@ -4136,11 +4151,14 @@ fn proxy_pod_bootstrap_is_stale( .is_ok_and(|age| age >= minimum_age) } -fn proxy_pod_readiness_transition_patch(resource_version: &str, state: &str) -> serde_json::Value { +fn sandbox_runtime_readiness_transition_patch( + resource_version: &str, + state: &str, +) -> serde_json::Value { serde_json::json!({ "metadata": { "resourceVersion": resource_version, - "annotations": { ANNOTATION_PROXY_POD_READINESS: state }, + "annotations": { ANNOTATION_SANDBOX_RUNTIME_READINESS: state }, } }) } @@ -4166,7 +4184,7 @@ where S: Stream> + Send + Unpin + 'static, { let (tx, rx) = mpsc::channel(256); - driver.spawn_proxy_pod_periodic_reconcile(tx.clone()); + driver.spawn_sandbox_runtime_periodic_reconcile(tx.clone()); tokio::spawn(async move { loop { @@ -4175,7 +4193,7 @@ where Some(Event::Apply(obj) | Event::InitApply(obj)) => { let ns = obj.metadata.namespace.clone() .unwrap_or_else(|| default_namespace.clone()); - if let Ok((_kube_name, sandbox)) = sandbox_from_object_with_proxy_pod_readiness(&readiness_client, &ns, obj).await { + if let Ok((_kube_name, sandbox)) = sandbox_from_object_with_sandbox_runtime_readiness(&readiness_client, &ns, obj).await { let event = WatchSandboxesEvent { payload: Some(watch_sandboxes_event::Payload::Sandbox( WatchSandboxesSandboxEvent { sandbox: Some(sandbox) } @@ -4547,9 +4565,9 @@ fn validate_proxy_control_labels_from_metadata( sandbox_id: &str, ) -> Result<(), tonic::Status> { let labels = metadata.labels.as_ref().ok_or_else(|| { - tonic::Status::permission_denied("control workload has no proxy-pod labels") + tonic::Status::permission_denied("control workload has no sandbox-runtime labels") })?; - let expected_pair = crate::proxy_pod::pair_label_value(sandbox_id); + let expected_pair = crate::sandbox_runtime::pair_label_value(sandbox_id); let matches = labels .get(BOUNDARY_ROLE_LABEL) .is_some_and(|role| role == "supervisor") @@ -4563,7 +4581,7 @@ fn validate_proxy_control_labels_from_metadata( Ok(()) } else { Err(tonic::Status::permission_denied( - "control workload proxy-pod labels do not match the sandbox identity", + "control workload sandbox-runtime labels do not match the sandbox identity", )) } } @@ -4713,15 +4731,15 @@ fn sandbox_from_object(namespace: &str, obj: DynamicObject) -> Result<(String, S } #[derive(Clone, Copy, Debug, PartialEq, Eq)] -enum ProxyPodControlAvailability { +enum SandboxRuntimeControlAvailability { Available, Unavailable, Unknown, } -fn proxy_pod_control_availability_from_deployment( +fn sandbox_runtime_control_availability_from_deployment( deployment: &Deployment, -) -> ProxyPodControlAvailability { +) -> SandboxRuntimeControlAvailability { if deployment .status .as_ref() @@ -4729,18 +4747,18 @@ fn proxy_pod_control_availability_from_deployment( .unwrap_or_default() > 0 { - ProxyPodControlAvailability::Available + SandboxRuntimeControlAvailability::Available } else { - ProxyPodControlAvailability::Unavailable + SandboxRuntimeControlAvailability::Unavailable } } -async fn proxy_pod_control_availability( +async fn sandbox_runtime_control_availability( client: &Client, namespace: &str, sandbox_id: &str, -) -> ProxyPodControlAvailability { - let names = ProxyPodNames::new(sandbox_id); +) -> SandboxRuntimeControlAvailability { + let names = SandboxRuntimeNames::new(sandbox_id); let deployments = Api::::namespaced(client.clone(), namespace); let services = Api::::namespaced(client.clone(), namespace); let policies = Api::::namespaced(client.clone(), namespace); @@ -4758,33 +4776,35 @@ async fn proxy_pod_control_availability( )); let (deployment, service, fence) = tokio::join!(deployment, service, fence); let control = match deployment { - Ok(Ok(Some(deployment))) => proxy_pod_control_availability_from_deployment(&deployment), - Ok(Ok(None)) => ProxyPodControlAvailability::Unavailable, + Ok(Ok(Some(deployment))) => { + sandbox_runtime_control_availability_from_deployment(&deployment) + } + Ok(Ok(None)) => SandboxRuntimeControlAvailability::Unavailable, Ok(Err(error)) => { - warn!(sandbox_id, %error, "could not determine proxy-pod control availability"); - ProxyPodControlAvailability::Unknown + warn!(sandbox_id, %error, "could not determine sandbox-runtime control availability"); + SandboxRuntimeControlAvailability::Unknown } Err(_) => { warn!( sandbox_id, - "timed out checking proxy-pod control availability" + "timed out checking sandbox-runtime control availability" ); - ProxyPodControlAvailability::Unknown + SandboxRuntimeControlAvailability::Unknown } }; let service = match service { - Ok(Ok(Some(_))) => ProxyPodControlAvailability::Available, - Ok(Ok(None)) => ProxyPodControlAvailability::Unavailable, + Ok(Ok(Some(_))) => SandboxRuntimeControlAvailability::Available, + Ok(Ok(None)) => SandboxRuntimeControlAvailability::Unavailable, Ok(Err(error)) => { - warn!(sandbox_id, %error, "could not determine proxy-pod boundary Service availability"); - ProxyPodControlAvailability::Unknown + warn!(sandbox_id, %error, "could not determine sandbox-runtime boundary Service availability"); + SandboxRuntimeControlAvailability::Unknown } Err(_) => { warn!( sandbox_id, - "timed out checking proxy-pod boundary Service availability" + "timed out checking sandbox-runtime boundary Service availability" ); - ProxyPodControlAvailability::Unknown + SandboxRuntimeControlAvailability::Unknown } }; @@ -4792,69 +4812,69 @@ async fn proxy_pod_control_availability( // unowned fence would otherwise leave a live boundary with direct pod // egress while the driver continued to publish Ready. let fence = match fence { - Ok(Ok(Some(_))) => ProxyPodControlAvailability::Available, - Ok(Ok(None)) => ProxyPodControlAvailability::Unavailable, + Ok(Ok(Some(_))) => SandboxRuntimeControlAvailability::Available, + Ok(Ok(None)) => SandboxRuntimeControlAvailability::Unavailable, Ok(Err(error)) => { - warn!(sandbox_id, %error, "could not determine proxy-pod workload fence availability"); - ProxyPodControlAvailability::Unknown + warn!(sandbox_id, %error, "could not determine sandbox-runtime workload fence availability"); + SandboxRuntimeControlAvailability::Unknown } Err(_) => { warn!( sandbox_id, - "timed out checking proxy-pod workload fence availability" + "timed out checking sandbox-runtime workload fence availability" ); - ProxyPodControlAvailability::Unknown + SandboxRuntimeControlAvailability::Unknown } }; - if [control, service, fence].contains(&ProxyPodControlAvailability::Unavailable) { - ProxyPodControlAvailability::Unavailable - } else if [control, service, fence].contains(&ProxyPodControlAvailability::Unknown) { - ProxyPodControlAvailability::Unknown + if [control, service, fence].contains(&SandboxRuntimeControlAvailability::Unavailable) { + SandboxRuntimeControlAvailability::Unavailable + } else if [control, service, fence].contains(&SandboxRuntimeControlAvailability::Unknown) { + SandboxRuntimeControlAvailability::Unknown } else { - ProxyPodControlAvailability::Available + SandboxRuntimeControlAvailability::Available } } -async fn proxy_pod_workload_generation_matches( +async fn sandbox_runtime_workload_generation_matches( client: &Client, namespace: &str, pod_name: &str, sandbox: &DynamicObject, -) -> ProxyPodControlAvailability { +) -> SandboxRuntimeControlAvailability { let Some(expected_uid) = sandbox .metadata .annotations .as_ref() - .and_then(|annotations| annotations.get(ANNOTATION_PROXY_POD_WORKLOAD_UID)) + .and_then(|annotations| annotations.get(ANNOTATION_SANDBOX_RUNTIME_WORKLOAD_UID)) else { - return ProxyPodControlAvailability::Unavailable; + return SandboxRuntimeControlAvailability::Unavailable; }; let pods = Api::::namespaced(client.clone(), namespace); match tokio::time::timeout(KUBE_API_TIMEOUT, pods.get_opt(pod_name)).await { Ok(Ok(Some(pod))) if pod.metadata.uid.as_deref() == Some(expected_uid.as_str()) => { - ProxyPodControlAvailability::Available + SandboxRuntimeControlAvailability::Available } - Ok(Ok(_)) => ProxyPodControlAvailability::Unavailable, + Ok(Ok(_)) => SandboxRuntimeControlAvailability::Unavailable, Ok(Err(error)) => { - warn!(pod = pod_name, %error, "could not verify proxy-pod workload generation"); - ProxyPodControlAvailability::Unknown + warn!(pod = pod_name, %error, "could not verify sandbox-runtime workload generation"); + SandboxRuntimeControlAvailability::Unknown } Err(_) => { warn!( pod = pod_name, - "timed out checking proxy-pod workload generation" + "timed out checking sandbox-runtime workload generation" ); - ProxyPodControlAvailability::Unknown + SandboxRuntimeControlAvailability::Unknown } } } -async fn sandbox_from_object_with_proxy_pod_readiness( +async fn sandbox_from_object_with_sandbox_runtime_readiness( client: &Client, namespace: &str, obj: DynamicObject, ) -> Result<(String, Sandbox), String> { - let bootstrapping = proxy_pod_bootstrap_in_progress(&obj); + let bootstrapping = sandbox_runtime_bootstrap_in_progress(&obj); let sandbox_id = sandbox_id_from_object(&obj).unwrap_or_default(); let object_namespace = obj .metadata @@ -4863,31 +4883,31 @@ async fn sandbox_from_object_with_proxy_pod_readiness( .unwrap_or_else(|| namespace.to_string()); let (name, mut sandbox) = sandbox_from_object(namespace, obj.clone())?; if bootstrapping { - mark_proxy_pod_bootstrapping(&mut sandbox); + mark_sandbox_runtime_bootstrapping(&mut sandbox); } if !sandbox_id.is_empty() { - let dependencies = Box::pin(proxy_pod_control_availability( + let dependencies = Box::pin(sandbox_runtime_control_availability( client, &object_namespace, &sandbox_id, )); - let workload_generation = Box::pin(proxy_pod_workload_generation_matches( + let workload_generation = Box::pin(sandbox_runtime_workload_generation_matches( client, &object_namespace, &name, &obj, )); let (dependencies, workload_generation) = tokio::join!(dependencies, workload_generation); - if dependencies != ProxyPodControlAvailability::Available - || workload_generation != ProxyPodControlAvailability::Available + if dependencies != SandboxRuntimeControlAvailability::Available + || workload_generation != SandboxRuntimeControlAvailability::Available { - mark_proxy_pod_control_unavailable(&mut sandbox); + mark_sandbox_runtime_control_unavailable(&mut sandbox); } } Ok((name, sandbox)) } -fn mark_proxy_pod_bootstrapping(sandbox: &mut Sandbox) { +fn mark_sandbox_runtime_bootstrapping(sandbox: &mut Sandbox) { if let Some(status) = sandbox.status.as_mut() { status.conditions.retain(|condition| { condition.r#type != SANDBOX_SUSPENDED_CONDITION && condition.r#type != "Bootstrapping" @@ -4895,17 +4915,17 @@ fn mark_proxy_pod_bootstrapping(sandbox: &mut Sandbox) { status.conditions.push(SandboxCondition { r#type: "Bootstrapping".to_string(), status: "True".to_string(), - reason: "ProxyPodGenerationStarting".to_string(), - message: "replacement proxy-pod generation is starting".to_string(), + reason: "SandboxRuntimeGenerationStarting".to_string(), + message: "replacement sandbox-runtime generation is starting".to_string(), last_transition_time: String::new(), }); } - mark_proxy_pod_control_unavailable(sandbox); + mark_sandbox_runtime_control_unavailable(sandbox); } -fn mark_proxy_pod_control_unavailable(sandbox: &mut Sandbox) { +fn mark_sandbox_runtime_control_unavailable(sandbox: &mut Sandbox) { const REASON: &str = "DependenciesNotReady"; - const MESSAGE: &str = "proxy-pod enforcement dependencies are not ready"; + const MESSAGE: &str = "sandbox-runtime enforcement dependencies are not ready"; let Some(status) = sandbox.status.as_mut() else { return; }; @@ -4928,7 +4948,7 @@ fn mark_proxy_pod_control_unavailable(sandbox: &mut Sandbox) { } } -fn desired_proxy_pod_control_replicas(obj: &DynamicObject) -> i32 { +fn desired_sandbox_runtime_control_replicas(obj: &DynamicObject) -> i32 { if let Some(mode) = obj .data .get("spec") @@ -5085,12 +5105,12 @@ const SANDBOX_PROXY_CA_VOLUME_NAME: &str = "openshell-run"; const SANDBOX_PROXY_CA_MOUNT_PATH: &str = "/run"; const SANDBOX_BOOTSTRAP_SCHEDULING_GATE: &str = "openshell.ai/bootstrap"; -/// Render the workload pod half of the RFC 0012 proxy-pod topology. +/// Render the workload Pod that runs the `OpenShell` sandbox runtime. /// /// The pod receives no gateway credential or endpoint. Its non-root sandbox /// owns the workload process and seccomp listener; only the paired supervisor /// Deployment can reach its TLS listener through `NetworkPolicy`. -fn apply_supervisor_proxy_pod_boundary( +fn apply_supervisor_sandbox_runtime_boundary( pod_template: &mut serde_json::Value, params: &SandboxPodParams<'_>, ) { @@ -5109,7 +5129,7 @@ fn apply_supervisor_proxy_pod_boundary( .expect("pod labels must be an object"); labels.insert( BOUNDARY_PAIR_LABEL.to_string(), - serde_json::json!(crate::proxy_pod::pair_label_value(params.sandbox_id)), + serde_json::json!(crate::sandbox_runtime::pair_label_value(params.sandbox_id)), ); labels.insert( BOUNDARY_ROLE_LABEL.to_string(), @@ -5216,7 +5236,7 @@ fn apply_supervisor_proxy_pod_boundary( "capabilities": {"drop": ["ALL"]} }, "volumeMounts": [ - {"name": SANDBOX_BOOTSTRAP_VOLUME_NAME, "mountPath": crate::proxy_pod::SANDBOX_BOOTSTRAP_INPUT_PATH, "readOnly": true}, + {"name": SANDBOX_BOOTSTRAP_VOLUME_NAME, "mountPath": crate::sandbox_runtime::SANDBOX_BOOTSTRAP_INPUT_PATH, "readOnly": true}, {"name": SANDBOX_RUNTIME_VOLUME_NAME, "mountPath": SANDBOX_RUNTIME_MOUNT_PATH}, {"name": SANDBOX_STATE_VOLUME_NAME, "mountPath": SANDBOX_STATE_MOUNT_PATH} ] @@ -5833,7 +5853,7 @@ fn sandbox_template_to_k8s_with_validated_config( let mut result = serde_json::Value::Object(template_value); - apply_supervisor_proxy_pod_boundary(&mut result, params); + apply_supervisor_sandbox_runtime_boundary(&mut result, params); // Inject workspace persistence (init container + PVC volume mount) so // that /sandbox data survives pod rescheduling. Skipped when the user @@ -6145,20 +6165,20 @@ fn status_from_object(obj: &DynamicObject) -> Option { }) } -async fn create_or_validate_proxy_pod_fence( +async fn create_or_validate_sandbox_runtime_fence( policies: &Api, expected: &NetworkPolicy, ) -> Result<(), KubernetesDriverError> { let name = expected.metadata.name.as_deref().unwrap_or_default(); match tokio::time::timeout(KUBE_API_TIMEOUT, policies.get_opt(name)).await { Ok(Ok(Some(existing))) => { - return validate_proxy_pod_fence(&existing, expected); + return validate_sandbox_runtime_fence(&existing, expected); } Ok(Ok(None)) => {} Ok(Err(error)) => return Err(KubernetesDriverError::from_kube(error)), Err(_) => { return Err(KubernetesDriverError::Message( - "timed out reading proxy-pod workload fence".to_string(), + "timed out reading sandbox-runtime workload fence".to_string(), )); } } @@ -6175,34 +6195,34 @@ async fn create_or_validate_proxy_pod_fence( .await .map_err(|_| { KubernetesDriverError::Message( - "timed out validating existing proxy-pod workload fence".to_string(), + "timed out validating existing sandbox-runtime workload fence".to_string(), ) })? .map_err(KubernetesDriverError::from_kube)?; - validate_proxy_pod_fence(&existing, expected) + validate_sandbox_runtime_fence(&existing, expected) } Ok(Err(error)) => Err(KubernetesDriverError::from_kube(error)), Err(_) => Err(KubernetesDriverError::Message( - "timed out creating proxy-pod workload fence".to_string(), + "timed out creating sandbox-runtime workload fence".to_string(), )), } } -fn validate_proxy_pod_fence( +fn validate_sandbox_runtime_fence( existing: &NetworkPolicy, expected: &NetworkPolicy, ) -> Result<(), KubernetesDriverError> { - if proxy_pod_fence_matches(existing, expected) { + if sandbox_runtime_fence_matches(existing, expected) { Ok(()) } else { let name = expected.metadata.name.as_deref().unwrap_or_default(); Err(KubernetesDriverError::Precondition(format!( - "proxy-pod workload fence {name} exists but does not match the intended enforcement" + "sandbox-runtime workload fence {name} exists but does not match the intended enforcement" ))) } } -fn proxy_pod_fence_matches(existing: &NetworkPolicy, expected: &NetworkPolicy) -> bool { +fn sandbox_runtime_fence_matches(existing: &NetworkPolicy, expected: &NetworkPolicy) -> bool { fn normalized_spec(mut spec: Option) -> Option { if let Some(spec) = spec.as_mut() { // The Kubernetes API server omits explicitly empty rule arrays when it @@ -6355,10 +6375,10 @@ fn sandbox_operating_state_patch( "metadata": { "resourceVersion": resource_version, "annotations": { - ANNOTATION_PROXY_POD_BOOTSTRAPPING: serde_json::Value::Null, - ANNOTATION_PROXY_POD_BOOTSTRAP_STARTED_AT: serde_json::Value::Null, - ANNOTATION_PROXY_POD_BOOTSTRAP_OPERATION: serde_json::Value::Null, - ANNOTATION_PROXY_POD_WORKLOAD_UID: serde_json::Value::Null, + ANNOTATION_SANDBOX_RUNTIME_BOOTSTRAPPING: serde_json::Value::Null, + ANNOTATION_SANDBOX_RUNTIME_BOOTSTRAP_STARTED_AT: serde_json::Value::Null, + ANNOTATION_SANDBOX_RUNTIME_BOOTSTRAP_OPERATION: serde_json::Value::Null, + ANNOTATION_SANDBOX_RUNTIME_WORKLOAD_UID: serde_json::Value::Null, }, }, "spec": {"operatingMode": "Suspended"} @@ -6375,10 +6395,10 @@ fn sandbox_operating_state_patch( "metadata": { "resourceVersion": resource_version, "annotations": { - ANNOTATION_PROXY_POD_BOOTSTRAPPING: serde_json::Value::Null, - ANNOTATION_PROXY_POD_BOOTSTRAP_STARTED_AT: serde_json::Value::Null, - ANNOTATION_PROXY_POD_BOOTSTRAP_OPERATION: serde_json::Value::Null, - ANNOTATION_PROXY_POD_WORKLOAD_UID: serde_json::Value::Null, + ANNOTATION_SANDBOX_RUNTIME_BOOTSTRAPPING: serde_json::Value::Null, + ANNOTATION_SANDBOX_RUNTIME_BOOTSTRAP_STARTED_AT: serde_json::Value::Null, + ANNOTATION_SANDBOX_RUNTIME_BOOTSTRAP_OPERATION: serde_json::Value::Null, + ANNOTATION_SANDBOX_RUNTIME_WORKLOAD_UID: serde_json::Value::Null, }, }, "spec": {"replicas": 0} @@ -6668,7 +6688,7 @@ mod tests { #[test] fn boundary_authority_uses_stable_service_dns_name() { - let names = ProxyPodNames::new("sandbox-1"); + let names = SandboxRuntimeNames::new("sandbox-1"); assert_eq!( boundary_service_authority("workspace-a", &names, 5500), @@ -7075,7 +7095,7 @@ mod tests { (LABEL_SANDBOX_ID.to_string(), sandbox_id.to_string()), ( BOUNDARY_PAIR_LABEL.to_string(), - crate::proxy_pod::pair_label_value(sandbox_id), + crate::sandbox_runtime::pair_label_value(sandbox_id), ), (BOUNDARY_ROLE_LABEL.to_string(), "supervisor".to_string()), ])), @@ -7119,23 +7139,23 @@ mod tests { })) .unwrap(); let created = SystemTime::UNIX_EPOCH + Duration::from_hours(490_896); - assert!(!proxy_pod_fence_is_old_enough( + assert!(!sandbox_runtime_fence_is_old_enough( &policy, - created + PROXY_POD_ORPHAN_FENCE_MIN_AGE - Duration::from_secs(1) + created + SANDBOX_RUNTIME_ORPHAN_FENCE_MIN_AGE - Duration::from_secs(1) )); - assert!(proxy_pod_fence_is_old_enough( + assert!(sandbox_runtime_fence_is_old_enough( &policy, - created + PROXY_POD_ORPHAN_FENCE_MIN_AGE + created + SANDBOX_RUNTIME_ORPHAN_FENCE_MIN_AGE )); - assert!(!proxy_pod_fence_is_old_enough( + assert!(!sandbox_runtime_fence_is_old_enough( &NetworkPolicy::default(), - created + PROXY_POD_ORPHAN_FENCE_MIN_AGE + created + SANDBOX_RUNTIME_ORPHAN_FENCE_MIN_AGE )); } #[test] - fn proxy_pod_fence_validation_accepts_api_normalization_and_injected_metadata() { - let names = ProxyPodNames::new("sandbox-id-a"); + fn sandbox_runtime_fence_validation_accepts_api_normalization_and_injected_metadata() { + let names = SandboxRuntimeNames::new("sandbox-id-a"); let mut expected = workload_fence("namespace-a", &names, "sandbox-id-a", 5000).workload_policy; expected.metadata.labels = Some(BTreeMap::from([( @@ -7155,19 +7175,19 @@ mod tests { .as_mut() .unwrap() .insert("admission.example/injected".to_string(), "true".to_string()); - assert!(proxy_pod_fence_matches(&persisted, &expected)); - assert!(validate_proxy_pod_fence(&persisted, &expected).is_ok()); + assert!(sandbox_runtime_fence_matches(&persisted, &expected)); + assert!(validate_sandbox_runtime_fence(&persisted, &expected).is_ok()); persisted.spec.as_mut().unwrap().policy_types = Some(vec!["Ingress".to_string()]); - assert!(!proxy_pod_fence_matches(&persisted, &expected)); + assert!(!sandbox_runtime_fence_matches(&persisted, &expected)); assert!(matches!( - validate_proxy_pod_fence(&persisted, &expected), + validate_sandbox_runtime_fence(&persisted, &expected), Err(KubernetesDriverError::Precondition(_)) )); } #[test] - fn proxy_pod_bootstrap_marker_and_age_gate_rollback() { + fn sandbox_runtime_bootstrap_marker_and_age_gate_rollback() { let started = Duration::from_hours(490_896); let mut object: DynamicObject = serde_json::from_value(serde_json::json!({ "apiVersion": "agents.x-k8s.io/v1beta1", @@ -7176,44 +7196,47 @@ mod tests { "name": "sandbox-a", "creationTimestamp": "2020-01-01T00:00:00Z", "annotations": { - ANNOTATION_PROXY_POD_BOOTSTRAPPING: "true", - ANNOTATION_PROXY_POD_BOOTSTRAP_STARTED_AT: started.as_millis().to_string(), - ANNOTATION_PROXY_POD_BOOTSTRAP_OPERATION: "restart", + ANNOTATION_SANDBOX_RUNTIME_BOOTSTRAPPING: "true", + ANNOTATION_SANDBOX_RUNTIME_BOOTSTRAP_STARTED_AT: started.as_millis().to_string(), + ANNOTATION_SANDBOX_RUNTIME_BOOTSTRAP_OPERATION: "restart", } } })) .unwrap(); - assert!(proxy_pod_bootstrap_in_progress(&object)); - assert_eq!(proxy_pod_bootstrap_operation(&object), Some("restart")); + assert!(sandbox_runtime_bootstrap_in_progress(&object)); + assert_eq!( + sandbox_runtime_bootstrap_operation(&object), + Some("restart") + ); let started_at = SystemTime::UNIX_EPOCH + started; - assert!(!proxy_pod_bootstrap_is_stale( + assert!(!sandbox_runtime_bootstrap_is_stale( &object, - started_at + PROXY_POD_BOOTSTRAP_GRACE - Duration::from_secs(1), - PROXY_POD_BOOTSTRAP_GRACE + started_at + SANDBOX_RUNTIME_BOOTSTRAP_GRACE - Duration::from_secs(1), + SANDBOX_RUNTIME_BOOTSTRAP_GRACE )); - assert!(proxy_pod_bootstrap_is_stale( + assert!(sandbox_runtime_bootstrap_is_stale( &object, - started_at + PROXY_POD_BOOTSTRAP_GRACE, - PROXY_POD_BOOTSTRAP_GRACE + started_at + SANDBOX_RUNTIME_BOOTSTRAP_GRACE, + SANDBOX_RUNTIME_BOOTSTRAP_GRACE )); object .metadata .annotations .as_mut() .unwrap() - .remove(ANNOTATION_PROXY_POD_BOOTSTRAP_STARTED_AT); - assert!(proxy_pod_bootstrap_is_stale( + .remove(ANNOTATION_SANDBOX_RUNTIME_BOOTSTRAP_STARTED_AT); + assert!(sandbox_runtime_bootstrap_is_stale( &object, started_at, - PROXY_POD_BOOTSTRAP_GRACE + SANDBOX_RUNTIME_BOOTSTRAP_GRACE )); object .metadata .annotations .as_mut() .unwrap() - .remove(ANNOTATION_PROXY_POD_BOOTSTRAPPING); - assert!(!proxy_pod_bootstrap_in_progress(&object)); + .remove(ANNOTATION_SANDBOX_RUNTIME_BOOTSTRAPPING); + assert!(!sandbox_runtime_bootstrap_in_progress(&object)); } #[test] @@ -7248,7 +7271,7 @@ mod tests { let beta_stop = sandbox_operating_state_patch(SANDBOX_VERSION_V1BETA1, "42", false); assert_eq!(beta_stop["metadata"]["resourceVersion"], "42"); assert_eq!( - beta_stop["metadata"]["annotations"][ANNOTATION_PROXY_POD_BOOTSTRAPPING], + beta_stop["metadata"]["annotations"][ANNOTATION_SANDBOX_RUNTIME_BOOTSTRAPPING], serde_json::Value::Null ); assert_eq!(beta_stop["spec"]["operatingMode"], "Suspended"); @@ -8068,7 +8091,7 @@ mod tests { } #[test] - fn proxy_pod_topology_renders_credential_free_boundary_workload() { + fn sandbox_runtime_renders_credential_free_boundary_workload() { let params = SandboxPodParams { sandbox_runtime_image: "sandbox-runtime-image:latest", sandbox_id: "sandbox-123", @@ -9720,65 +9743,65 @@ mod tests { } #[test] - fn proxy_pod_control_availability_requires_an_available_replica() { + fn sandbox_runtime_control_availability_requires_an_available_replica() { let mut deployment = Deployment::default(); assert_eq!( - proxy_pod_control_availability_from_deployment(&deployment), - ProxyPodControlAvailability::Unavailable + sandbox_runtime_control_availability_from_deployment(&deployment), + SandboxRuntimeControlAvailability::Unavailable ); deployment.status = Some(k8s_openapi::api::apps::v1::DeploymentStatus { available_replicas: Some(1), ..Default::default() }); assert_eq!( - proxy_pod_control_availability_from_deployment(&deployment), - ProxyPodControlAvailability::Available + sandbox_runtime_control_availability_from_deployment(&deployment), + SandboxRuntimeControlAvailability::Available ); } #[test] - fn proxy_pod_readiness_transitions_bump_the_watched_cr() { - let unavailable = proxy_pod_readiness_transition_patch("42", "unavailable"); - let ready = proxy_pod_readiness_transition_patch("42", "ready"); + fn sandbox_runtime_readiness_transitions_bump_the_watched_cr() { + let unavailable = sandbox_runtime_readiness_transition_patch("42", "unavailable"); + let ready = sandbox_runtime_readiness_transition_patch("42", "ready"); assert_eq!(unavailable["metadata"]["resourceVersion"], "42"); assert_eq!(ready["metadata"]["resourceVersion"], "42"); assert_eq!( - unavailable["metadata"]["annotations"][ANNOTATION_PROXY_POD_READINESS], + unavailable["metadata"]["annotations"][ANNOTATION_SANDBOX_RUNTIME_READINESS], "unavailable" ); assert_eq!( - ready["metadata"]["annotations"][ANNOTATION_PROXY_POD_READINESS], + ready["metadata"]["annotations"][ANNOTATION_SANDBOX_RUNTIME_READINESS], "ready" ); assert_ne!(unavailable, ready); } #[test] - fn proxy_pod_bootstrap_completion_is_resource_version_guarded_and_publishes_ready() { - let patch = proxy_pod_bootstrap_completion_patch("42"); + fn sandbox_runtime_bootstrap_completion_is_resource_version_guarded_and_publishes_ready() { + let patch = sandbox_runtime_bootstrap_completion_patch("42"); assert_eq!(patch["metadata"]["resourceVersion"], "42"); assert_eq!( - patch["metadata"]["annotations"][ANNOTATION_PROXY_POD_BOOTSTRAPPING], + patch["metadata"]["annotations"][ANNOTATION_SANDBOX_RUNTIME_BOOTSTRAPPING], serde_json::Value::Null ); assert_eq!( - patch["metadata"]["annotations"][ANNOTATION_PROXY_POD_BOOTSTRAP_STARTED_AT], + patch["metadata"]["annotations"][ANNOTATION_SANDBOX_RUNTIME_BOOTSTRAP_STARTED_AT], serde_json::Value::Null ); assert_eq!( - patch["metadata"]["annotations"][ANNOTATION_PROXY_POD_BOOTSTRAP_OPERATION], + patch["metadata"]["annotations"][ANNOTATION_SANDBOX_RUNTIME_BOOTSTRAP_OPERATION], serde_json::Value::Null ); assert_eq!( - patch["metadata"]["annotations"][ANNOTATION_PROXY_POD_READINESS], + patch["metadata"]["annotations"][ANNOTATION_SANDBOX_RUNTIME_READINESS], "ready" ); } #[test] - fn proxy_pod_bootstrap_completion_waits_for_runtime_ready() { + fn sandbox_runtime_bootstrap_completion_waits_for_runtime_ready() { let resource = ApiResource::from_gvk(&GroupVersionKind::gvk( SANDBOX_GROUP, SANDBOX_VERSION_V1BETA1, @@ -9791,29 +9814,29 @@ mod tests { "conditions": [{"type": "Suspended", "status": "True", "observedGeneration": 7}] } }); - assert!(!proxy_pod_runtime_is_ready(&sandbox)); + assert!(!sandbox_runtime_runtime_is_ready(&sandbox)); sandbox.data["status"]["conditions"] = serde_json::json!([ {"type": "Suspended", "status": "False", "observedGeneration": 6}, {"type": "Ready", "status": "True", "observedGeneration": 6} ]); - assert!(!proxy_pod_runtime_is_ready(&sandbox)); + assert!(!sandbox_runtime_runtime_is_ready(&sandbox)); sandbox.data["status"]["conditions"] = serde_json::json!([ {"type": "Suspended", "status": "False", "observedGeneration": 7}, {"type": "Ready", "status": "True", "observedGeneration": 7} ]); - assert!(proxy_pod_runtime_is_ready(&sandbox)); + assert!(sandbox_runtime_runtime_is_ready(&sandbox)); sandbox.data["status"]["conditions"] = serde_json::json!([ {"type": "Suspended", "status": "True", "observedGeneration": 7}, {"type": "Ready", "status": "True", "observedGeneration": 7} ]); - assert!(!proxy_pod_runtime_is_ready(&sandbox)); + assert!(!sandbox_runtime_runtime_is_ready(&sandbox)); } #[test] - fn proxy_pod_readiness_is_downgraded_with_a_transient_reason() { + fn sandbox_runtime_readiness_is_downgraded_with_a_transient_reason() { let mut sandbox = Sandbox { status: Some(SandboxStatus { conditions: vec![SandboxCondition { @@ -9825,14 +9848,14 @@ mod tests { }), ..Default::default() }; - mark_proxy_pod_control_unavailable(&mut sandbox); + mark_sandbox_runtime_control_unavailable(&mut sandbox); let ready = &sandbox.status.unwrap().conditions[0]; assert_eq!(ready.status, "False"); assert_eq!(ready.reason, "DependenciesNotReady"); } #[test] - fn proxy_pod_bootstrap_does_not_publish_a_terminal_suspension() { + fn sandbox_runtime_bootstrap_does_not_publish_a_terminal_suspension() { let mut sandbox = Sandbox { status: Some(SandboxStatus { conditions: vec![SandboxCondition { @@ -9846,7 +9869,7 @@ mod tests { ..Default::default() }; - mark_proxy_pod_bootstrapping(&mut sandbox); + mark_sandbox_runtime_bootstrapping(&mut sandbox); let conditions = &sandbox.status.unwrap().conditions; assert!( @@ -9862,12 +9885,12 @@ mod tests { assert!(conditions.iter().any(|condition| { condition.r#type == "Bootstrapping" && condition.status == "True" - && condition.reason == "ProxyPodGenerationStarting" + && condition.reason == "SandboxRuntimeGenerationStarting" })); } #[test] - fn completed_proxy_pod_bootstrap_preserves_real_suspension() { + fn completed_sandbox_runtime_bootstrap_preserves_real_suspension() { let sandbox = Sandbox { status: Some(SandboxStatus { conditions: vec![SandboxCondition { @@ -9887,7 +9910,7 @@ mod tests { } #[test] - fn desired_proxy_pod_control_replicas_tracks_both_sandbox_apis() { + fn desired_sandbox_runtime_control_replicas_tracks_both_sandbox_apis() { let resource = ApiResource::from_gvk(&GroupVersionKind::gvk( SANDBOX_GROUP, SANDBOX_VERSION_V1BETA1, @@ -9895,14 +9918,14 @@ mod tests { )); let mut beta = DynamicObject::new("beta", &resource); beta.data = serde_json::json!({"spec": {"operatingMode": "Suspended"}}); - assert_eq!(desired_proxy_pod_control_replicas(&beta), 0); + assert_eq!(desired_sandbox_runtime_control_replicas(&beta), 0); beta.data = serde_json::json!({"spec": {"operatingMode": "Running"}}); - assert_eq!(desired_proxy_pod_control_replicas(&beta), 1); + assert_eq!(desired_sandbox_runtime_control_replicas(&beta), 1); let mut alpha = DynamicObject::new("alpha", &resource); alpha.data = serde_json::json!({"spec": {"replicas": 0}}); - assert_eq!(desired_proxy_pod_control_replicas(&alpha), 0); + assert_eq!(desired_sandbox_runtime_control_replicas(&alpha), 0); alpha.data = serde_json::json!({"spec": {"replicas": 1}}); - assert_eq!(desired_proxy_pod_control_replicas(&alpha), 1); + assert_eq!(desired_sandbox_runtime_control_replicas(&alpha), 1); } } diff --git a/crates/openshell-driver-kubernetes/src/isolation.rs b/crates/openshell-driver-kubernetes/src/isolation.rs index fee9b91c2..ea5c7e8d6 100644 --- a/crates/openshell-driver-kubernetes/src/isolation.rs +++ b/crates/openshell-driver-kubernetes/src/isolation.rs @@ -4,7 +4,7 @@ //! Kubernetes provisioning for the shared authenticated boundary protocol. //! //! This module deliberately contains no lifecycle, process, network, identity, -//! or wire implementation. The driver chooses the proxy-pod placement, binds +//! or wire implementation. The driver places the sandbox runtime, binds //! immutable Kubernetes resource identities, and provisions TCP coordinates; //! `openshell-isolation-interface` and `openshell-sandbox` provide the common //! control and boundary behavior. @@ -22,12 +22,12 @@ use k8s_openapi::apimachinery::pkg::util::intstr::IntOrString; use kube::core::ObjectMeta; use openshell_isolation_interface::contract::{DriverFenceEvidence, ResolvedWorkloadIdentity}; use openshell_sandbox_backend::boundary_protocol::{ - BoundaryConfig, BoundaryListener, BoundaryTopology, GatewayVerificationKey, + BoundaryConfig, BoundaryListener, GatewayVerificationKey, SandboxRuntimeDescriptor, SandboxTlsClientConfig, SandboxTlsServerConfig, SandboxTransport, }; -/// Registered RFC 0012 backend name for the proxy-pod topology. -pub const BACKEND_NAME: &str = "kubernetes-proxy-pod"; +/// Isolation backend implemented by the `OpenShell` sandbox runtime. +pub const BACKEND_NAME: &str = openshell_sandbox_backend::BACKEND_NAME; /// Label that binds the workload and control pods in one unique pair. pub const BOUNDARY_PAIR_LABEL: &str = "openshell.ai/boundary-pair"; @@ -40,11 +40,11 @@ const SUPERVISOR_ROLE: &str = "supervisor"; /// Driver-owned inputs for the workload pod's Kubernetes network fence. /// -/// This is the first phase of proxy-pod provisioning. The driver applies the +/// This is the first phase of sandbox-runtime provisioning. The driver applies the /// returned labels to the respective pods and creates the returned policy. It /// then observes the policy UID and resourceVersion and supplies both to -/// `KubernetesProxyPodBoundarySpec`. -pub struct KubernetesProxyPodNetworkFenceSpec { +/// `KubernetesSandboxRuntimeBoundarySpec`. +pub struct KubernetesSandboxRuntimeNetworkFenceSpec { pub namespace: String, pub policy_name: String, /// A unique, Kubernetes-label-safe value generated for this pod pair. @@ -53,13 +53,13 @@ pub struct KubernetesProxyPodNetworkFenceSpec { } /// Labels and policy needed to remove direct workload-pod egress. -pub struct KubernetesProxyPodNetworkFence { +pub struct KubernetesSandboxRuntimeNetworkFence { pub workload_labels: BTreeMap, pub control_labels: BTreeMap, pub workload_policy: NetworkPolicy, } -impl KubernetesProxyPodNetworkFenceSpec { +impl KubernetesSandboxRuntimeNetworkFenceSpec { /// Render a default-deny workload fence with one control-to-boundary path. /// /// Kubernetes `NetworkPolicy` is connection-aware: traffic returning over @@ -67,7 +67,7 @@ impl KubernetesProxyPodNetworkFenceSpec { /// workload pod has no egress rules. The control pod remains responsible /// for opening policy-approved upstream connections. #[must_use] - pub fn provision(self) -> KubernetesProxyPodNetworkFence { + pub fn provision(self) -> KubernetesSandboxRuntimeNetworkFence { let workload_labels = boundary_pair_labels(&self.pair_label_value, WORKLOAD_ROLE); let control_labels = boundary_pair_labels(&self.pair_label_value, SUPERVISOR_ROLE); @@ -107,7 +107,7 @@ impl KubernetesProxyPodNetworkFenceSpec { }), }; - KubernetesProxyPodNetworkFence { + KubernetesSandboxRuntimeNetworkFence { workload_labels, control_labels, workload_policy, @@ -126,9 +126,9 @@ fn boundary_pair_labels(pair: &str, role: &str) -> BTreeMap { /// /// The driver constructs this only after Kubernetes has assigned every UID and /// after it has observed the exact egress policy resource version. The workload -/// stays held until the matching boundary config and control topology have been +/// stays held until the matching boundary config and supervisor resources have been /// installed. -pub struct KubernetesProxyPodBoundarySpec { +pub struct KubernetesSandboxRuntimeBoundarySpec { pub boundary_id: String, pub generation: String, pub session_id: openshell_core::SandboxSessionId, @@ -151,17 +151,17 @@ pub struct KubernetesProxyPodBoundarySpec { pub child_env: HashMap, } -/// Protected workload-pod config and matching proxy-pod descriptor. -pub struct KubernetesProxyPodBoundaryProvisioning { +/// Protected workload-pod config and matching sandbox-runtime descriptor. +pub struct KubernetesSandboxRuntimeBoundaryProvisioning { pub boundary_config: BoundaryConfig, - pub topology: BoundaryTopology, + pub runtime_descriptor: SandboxRuntimeDescriptor, } -impl KubernetesProxyPodBoundarySpec { +impl KubernetesSandboxRuntimeBoundarySpec { /// Produce both sides of the common protocol from one observed Kubernetes /// resource set so a stale or recreated object cannot be attached. #[must_use] - pub fn provision(self) -> KubernetesProxyPodBoundaryProvisioning { + pub fn provision(self) -> KubernetesSandboxRuntimeBoundaryProvisioning { let resource_claims = BTreeMap::from([ ("kubernetes.namespace_uid".to_string(), self.namespace_uid), ( @@ -193,7 +193,7 @@ impl KubernetesProxyPodBoundarySpec { egress_isolated: true, egress_rule_count: 0, }; - KubernetesProxyPodBoundaryProvisioning { + KubernetesSandboxRuntimeBoundaryProvisioning { boundary_config: BoundaryConfig { boundary_id: self.boundary_id.clone(), generation: self.generation.clone(), @@ -213,7 +213,7 @@ impl KubernetesProxyPodBoundarySpec { driver_fence: driver_fence.clone(), child_env: self.child_env, }, - topology: BoundaryTopology { + runtime_descriptor: SandboxRuntimeDescriptor { boundary_id: self.boundary_id, generation: self.generation, session_id: self.session_id, @@ -235,8 +235,8 @@ impl KubernetesProxyPodBoundarySpec { mod tests { use super::*; - fn spec() -> KubernetesProxyPodBoundarySpec { - KubernetesProxyPodBoundarySpec { + fn spec() -> KubernetesSandboxRuntimeBoundarySpec { + KubernetesSandboxRuntimeBoundarySpec { boundary_id: "sandbox-1".to_string(), generation: "generation-1".to_string(), session_id: openshell_core::SandboxSessionId::new(), @@ -282,25 +282,25 @@ mod tests { assert_eq!( provisioned.boundary_config.resource_claims, - provisioned.topology.resource_claims + provisioned.runtime_descriptor.resource_claims ); assert_eq!( - provisioned.topology.resource_claims["kubernetes.sandbox_resource_uid"], + provisioned.runtime_descriptor.resource_claims["kubernetes.sandbox_resource_uid"], "sandbox-resource-uid" ); assert_eq!( - provisioned.topology.resource_claims["kubernetes.egress_policy_resource_version"], + provisioned.runtime_descriptor.resource_claims["kubernetes.egress_policy_resource_version"], "1945" ); assert_eq!( provisioned.boundary_config.driver_fence, - provisioned.topology.driver_fence + provisioned.runtime_descriptor.driver_fence ); assert!( provisioned - .topology + .runtime_descriptor .driver_fence - .validate_for_backend(BACKEND_NAME) + .validate() .is_ok() ); } @@ -320,14 +320,14 @@ mod tests { } ); assert_eq!( - provisioned.topology.transport, + provisioned.runtime_descriptor.transport, SandboxTransport::Tcp { authority: "os-boundary-sandbox.default.svc:5500".to_string(), addresses: vec!["10.42.0.7:5500".parse().expect("valid target")], } ); assert_eq!( - provisioned.topology.tls, + provisioned.runtime_descriptor.tls, SandboxTlsClientConfig { server_name: "boundary.sandbox.openshell".to_string(), trust_anchor_pem: "test-ca".to_string(), @@ -337,7 +337,7 @@ mod tests { #[test] fn network_fence_denies_all_workload_initiated_egress() { - let fence = KubernetesProxyPodNetworkFenceSpec { + let fence = KubernetesSandboxRuntimeNetworkFenceSpec { namespace: "sandbox-ns".to_string(), policy_name: "openshell-boundary-sandbox-1".to_string(), pair_label_value: "pair-1".to_string(), @@ -359,7 +359,7 @@ mod tests { #[test] fn network_fence_allows_only_paired_control_to_boundary_port() { - let fence = KubernetesProxyPodNetworkFenceSpec { + let fence = KubernetesSandboxRuntimeNetworkFenceSpec { namespace: "sandbox-ns".to_string(), policy_name: "openshell-boundary-sandbox-1".to_string(), pair_label_value: "pair-1".to_string(), diff --git a/crates/openshell-driver-kubernetes/src/lib.rs b/crates/openshell-driver-kubernetes/src/lib.rs index 8257b8372..e03906d12 100644 --- a/crates/openshell-driver-kubernetes/src/lib.rs +++ b/crates/openshell-driver-kubernetes/src/lib.rs @@ -6,12 +6,12 @@ pub mod driver; pub mod grpc; pub mod isolation; pub mod otel_tracing; -mod proxy_pod; +mod sandbox_runtime; pub use config::{ DEFAULT_GATEWAY_ID, DEFAULT_SANDBOX_SERVICE_ACCOUNT_NAME, DEFAULT_WORKSPACE_STORAGE_SIZE, - KubernetesComputeConfig, KubernetesProxyPodConfig, ManagedSshIngressConfig, WorkspaceMode, - managed_namespace_prefix, + KubernetesComputeConfig, KubernetesSandboxRuntimeConfig, ManagedSshIngressConfig, + WorkspaceMode, managed_namespace_prefix, }; pub use driver::{KubernetesComputeDriver, KubernetesDriverError}; pub use grpc::ComputeDriverService; diff --git a/crates/openshell-driver-kubernetes/src/main.rs b/crates/openshell-driver-kubernetes/src/main.rs index 4fcb9b805..b55fc203a 100644 --- a/crates/openshell-driver-kubernetes/src/main.rs +++ b/crates/openshell-driver-kubernetes/src/main.rs @@ -12,7 +12,7 @@ use openshell_core::VERSION; use openshell_core::proto::compute::v1::compute_driver_server::ComputeDriverServer; use openshell_driver_kubernetes::{ ComputeDriverService, DEFAULT_GATEWAY_ID, DEFAULT_SANDBOX_SERVICE_ACCOUNT_NAME, - KubernetesComputeConfig, KubernetesComputeDriver, KubernetesProxyPodConfig, + KubernetesComputeConfig, KubernetesComputeDriver, KubernetesSandboxRuntimeConfig, ManagedSshIngressConfig, WorkspaceMode, }; @@ -123,17 +123,17 @@ struct Args { #[arg( long, - env = "OPENSHELL_K8S_PROXY_POD_NETWORK_POLICY_ENFORCED", + env = "OPENSHELL_K8S_SANDBOX_RUNTIME_NETWORK_POLICY_ENFORCED", default_value_t = false )] - proxy_pod_network_policy_enforced: bool, + sandbox_runtime_network_policy_enforced: bool, #[arg( long, - env = "OPENSHELL_K8S_PROXY_POD_BOUNDARY_PORT", + env = "OPENSHELL_K8S_SANDBOX_RUNTIME_BOUNDARY_PORT", default_value_t = 5500 )] - proxy_pod_boundary_port: u16, + sandbox_runtime_boundary_port: u16, /// Corporate HTTP forward proxy for policy-approved TLS CONNECT egress. #[arg(long, env = "OPENSHELL_UPSTREAM_PROXY")] @@ -255,9 +255,9 @@ async fn main() -> Result<()> { .supervisor_image .unwrap_or_else(openshell_core::config::default_supervisor_image), supervisor_image_pull_policy: args.supervisor_image_pull_policy.unwrap_or_default(), - proxy_pod: KubernetesProxyPodConfig { - network_policy_enforced: args.proxy_pod_network_policy_enforced, - boundary_port: args.proxy_pod_boundary_port, + sandbox_runtime: KubernetesSandboxRuntimeConfig { + network_policy_enforced: args.sandbox_runtime_network_policy_enforced, + boundary_port: args.sandbox_runtime_boundary_port, }, https_proxy: args.https_proxy, no_proxy: args.no_proxy, diff --git a/crates/openshell-driver-kubernetes/src/proxy_pod.rs b/crates/openshell-driver-kubernetes/src/sandbox_runtime.rs similarity index 95% rename from crates/openshell-driver-kubernetes/src/proxy_pod.rs rename to crates/openshell-driver-kubernetes/src/sandbox_runtime.rs index afd0d3314..7b35b730b 100644 --- a/crates/openshell-driver-kubernetes/src/proxy_pod.rs +++ b/crates/openshell-driver-kubernetes/src/sandbox_runtime.rs @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 -//! Kubernetes resources owned by the RFC 0012 proxy-pod topology. +//! Kubernetes resources that place and protect the `OpenShell` sandbox runtime. use std::collections::BTreeMap; use std::path::Path; @@ -15,14 +15,14 @@ use kube::core::ObjectMeta; use rcgen::{CertificateParams, DnType, IsCa, KeyPair, KeyUsagePurpose}; use crate::isolation::{ - BOUNDARY_PAIR_LABEL, BOUNDARY_ROLE_LABEL, KubernetesProxyPodNetworkFence, - KubernetesProxyPodNetworkFenceSpec, + BOUNDARY_PAIR_LABEL, BOUNDARY_ROLE_LABEL, KubernetesSandboxRuntimeNetworkFence, + KubernetesSandboxRuntimeNetworkFenceSpec, }; pub const SANDBOX_SECRET_COMPONENT: &str = "sandbox-bootstrap"; pub const SUPERVISOR_SECRET_COMPONENT: &str = "supervisor-bootstrap"; pub const BOUNDARY_CONFIG_KEY: &str = "boundary.json"; -pub const TOPOLOGY_PAYLOAD_KEY: &str = "topology.json"; +pub const BACKEND_DESCRIPTOR_KEY: &str = "runtime-descriptor.json"; pub const BOUNDARY_CERTIFICATE_KEY: &str = "tls.crt"; pub const BOUNDARY_PRIVATE_KEY: &str = "tls.key"; pub const SUPERVISOR_AUTH_BUNDLE_KEY: &str = "auth.json"; @@ -32,7 +32,7 @@ pub const SANDBOX_BOOTSTRAP_INPUT_PATH: &str = "/.openshell/bootstrap-input"; pub const BOUNDARY_CONFIG_PATH: &str = "/.openshell/state/bootstrap/boundary.json"; pub const BOUNDARY_CERTIFICATE_PATH: &str = "/.openshell/state/bootstrap/tls.crt"; pub const BOUNDARY_PRIVATE_KEY_PATH: &str = "/.openshell/state/bootstrap/tls.key"; -pub const TOPOLOGY_PAYLOAD_PATH: &str = "/.openshell/supervisor/topology.json"; +pub const BACKEND_DESCRIPTOR_PATH: &str = "/.openshell/supervisor/runtime-descriptor.json"; pub const SUPERVISOR_AUTH_BUNDLE_PATH: &str = "/.openshell/supervisor/auth.json"; pub const PROXY_CA_CERTIFICATE_PATH: &str = "/.openshell/supervisor/proxy-ca.crt"; pub const PROXY_CA_PRIVATE_KEY_PATH: &str = "/.openshell/supervisor/proxy-ca.key"; @@ -64,7 +64,7 @@ pub fn generate_proxy_ca_material() -> Result { } #[derive(Clone, Debug, PartialEq, Eq)] -pub struct ProxyPodNames { +pub struct SandboxRuntimeNames { pub sandbox_secret: String, pub supervisor_secret: String, pub boundary_service: String, @@ -73,7 +73,7 @@ pub struct ProxyPodNames { pub control_policy: String, } -impl ProxyPodNames { +impl SandboxRuntimeNames { #[must_use] pub fn new(sandbox_id: &str) -> Self { let suffix = sandbox_id.to_ascii_lowercase(); @@ -112,11 +112,11 @@ pub fn pair_label_value(sandbox_id: &str) -> String { #[must_use] pub fn workload_fence( namespace: &str, - names: &ProxyPodNames, + names: &SandboxRuntimeNames, sandbox_id: &str, boundary_port: u16, -) -> KubernetesProxyPodNetworkFence { - KubernetesProxyPodNetworkFenceSpec { +) -> KubernetesSandboxRuntimeNetworkFence { + KubernetesSandboxRuntimeNetworkFenceSpec { namespace: namespace.to_string(), policy_name: names.workload_policy.clone(), pair_label_value: pair_label_value(sandbox_id), @@ -128,7 +128,7 @@ pub fn workload_fence( #[must_use] pub fn boundary_service( namespace: &str, - names: &ProxyPodNames, + names: &SandboxRuntimeNames, sandbox_id: &str, boundary_port: u16, owner: OwnerReference, @@ -154,7 +154,7 @@ pub fn boundary_service( #[must_use] pub fn control_deployment( namespace: &str, - names: &ProxyPodNames, + names: &SandboxRuntimeNames, sandbox_id: &str, sandbox_name: &str, gateway_id: &str, @@ -241,10 +241,8 @@ pub fn control_deployment( } let mut command = vec![ "/openshell-supervisor".to_string(), - "--topology-backend-name".to_string(), - crate::isolation::BACKEND_NAME.to_string(), - "--topology-payload-file".to_string(), - TOPOLOGY_PAYLOAD_PATH.to_string(), + "--backend-descriptor-file".to_string(), + BACKEND_DESCRIPTOR_PATH.to_string(), "--auth-bundle-file".to_string(), SUPERVISOR_AUTH_BUNDLE_PATH.to_string(), "--workdir".to_string(), @@ -361,7 +359,7 @@ pub fn control_deployment( #[must_use] pub fn control_egress_policy( namespace: &str, - names: &ProxyPodNames, + names: &SandboxRuntimeNames, sandbox_id: &str, owner: OwnerReference, ) -> NetworkPolicy { @@ -391,7 +389,7 @@ pub fn control_egress_policy( #[must_use] pub fn sandbox_bootstrap_secret( namespace: &str, - names: &ProxyPodNames, + names: &SandboxRuntimeNames, sandbox_id: &str, boundary_config: Vec, boundary_certificate: Vec, @@ -427,9 +425,9 @@ pub fn sandbox_bootstrap_secret( #[allow(clippy::too_many_arguments)] pub fn supervisor_bootstrap_secret( namespace: &str, - names: &ProxyPodNames, + names: &SandboxRuntimeNames, sandbox_id: &str, - topology_payload: Vec, + backend_descriptor: Vec, supervisor_auth_bundle: Vec, proxy_ca_certificate: Vec, proxy_ca_private_key: Vec, @@ -445,8 +443,8 @@ pub fn supervisor_bootstrap_secret( }, data: Some(BTreeMap::from([ ( - TOPOLOGY_PAYLOAD_KEY.to_string(), - ByteString(topology_payload), + BACKEND_DESCRIPTOR_KEY.to_string(), + ByteString(backend_descriptor), ), ( SUPERVISOR_AUTH_BUNDLE_KEY.to_string(), @@ -536,7 +534,7 @@ mod tests { #[test] fn service_selects_only_the_workload_boundary() { - let names = ProxyPodNames::new("4b67c0d0-1111-2222-3333-444444444444"); + let names = SandboxRuntimeNames::new("4b67c0d0-1111-2222-3333-444444444444"); let service = boundary_service("sandbox", &names, "pair", 5500, owner()); assert_eq!( service.spec.unwrap().selector.unwrap()[BOUNDARY_ROLE_LABEL], @@ -551,7 +549,7 @@ mod tests { #[test] fn control_policy_explicitly_allows_control_egress() { - let names = ProxyPodNames::new("pair"); + let names = SandboxRuntimeNames::new("pair"); let policy = control_egress_policy("sandbox", &names, "pair", owner()); let spec = policy.spec.unwrap(); assert_eq!(spec.policy_types.unwrap(), ["Egress"]); @@ -560,7 +558,7 @@ mod tests { #[test] fn control_deployment_is_singleton_ready_and_unprivileged() { - let names = ProxyPodNames::new("pair"); + let names = SandboxRuntimeNames::new("pair"); let deployment = control_deployment( "sandbox", &names, @@ -665,7 +663,7 @@ mod tests { #[test] fn bootstrap_secrets_are_immutable_and_split_by_trust_domain() { - let names = ProxyPodNames::new("pair"); + let names = SandboxRuntimeNames::new("pair"); let sandbox = sandbox_bootstrap_secret( "sandbox", &names, @@ -720,7 +718,7 @@ mod tests { PROXY_CA_CERTIFICATE_KEY.to_string(), PROXY_CA_PRIVATE_KEY.to_string(), SUPERVISOR_AUTH_BUNDLE_KEY.to_string(), - TOPOLOGY_PAYLOAD_KEY.to_string(), + BACKEND_DESCRIPTOR_KEY.to_string(), ]) ); } diff --git a/deploy/helm/openshell/README.md b/deploy/helm/openshell/README.md index cfbc7cee9..065986679 100644 --- a/deploy/helm/openshell/README.md +++ b/deploy/helm/openshell/README.md @@ -299,8 +299,8 @@ discovery endpoint or its TLS CA. | supervisor.image.pullPolicy | string | `nil` | Sandbox supervisor pull policy. Leave unset to use the Kubernetes image default. Prefer always, if_not_present, or never; the chart also accepts legacy Kubernetes spellings Always, IfNotPresent, and Never. | | supervisor.image.repository | string | `"ghcr.io/nvidia/openshell/supervisor"` | Supervisor image repository. Changing it uses the effective gateway image tag unless tag is also set. | | supervisor.image.tag | string | `""` | Supervisor image tag override. Empty uses the version pinned into the gateway unless repository is changed. | -| supervisor.proxyPod.boundaryPort | int | `5500` | Workload boundary TLS listener port. | -| supervisor.proxyPod.networkPolicyEnforced | bool | `false` | Required operator acknowledgement that the cluster CNI enforces NetworkPolicy. | +| supervisor.sandboxRuntime.boundaryPort | int | `5500` | Workload boundary TLS listener port. | +| supervisor.sandboxRuntime.networkPolicyEnforced | bool | `false` | Required operator acknowledgement that the cluster CNI enforces NetworkPolicy. | | tolerations | list | `[]` | Tolerations for the gateway pod. | | upstreamProxy | object | `{"authAllowInsecure":false,"authSecret":{"key":"","name":""},"connectByHostname":false,"noProxy":"","url":""}` | Operator-owned corporate forward proxy for policy-approved TLS egress from Kubernetes sandboxes. The workload cannot select or override it. | | upstreamProxy.authAllowInsecure | bool | `false` | Required when authSecret is configured because Basic auth to an HTTP proxy is cleartext. | diff --git a/deploy/helm/openshell/ci/values-proxy-pod.yaml b/deploy/helm/openshell/ci/values-sandbox-runtime.yaml similarity index 91% rename from deploy/helm/openshell/ci/values-proxy-pod.yaml rename to deploy/helm/openshell/ci/values-sandbox-runtime.yaml index 85535aef1..471ecc309 100644 --- a/deploy/helm/openshell/ci/values-proxy-pod.yaml +++ b/deploy/helm/openshell/ci/values-sandbox-runtime.yaml @@ -2,5 +2,5 @@ # SPDX-License-Identifier: Apache-2.0 supervisor: - proxyPod: + sandboxRuntime: networkPolicyEnforced: true diff --git a/deploy/helm/openshell/templates/gateway-config.yaml b/deploy/helm/openshell/templates/gateway-config.yaml index 78c96935c..3ee1619cb 100644 --- a/deploy/helm/openshell/templates/gateway-config.yaml +++ b/deploy/helm/openshell/templates/gateway-config.yaml @@ -218,9 +218,9 @@ data: gateway_namespace = {{ .Release.Namespace | quote }} gateway_pod_selector = { "app.kubernetes.io/name" = {{ include "openshell.name" . | quote }}, "app.kubernetes.io/instance" = {{ .Release.Name | quote }} } - [openshell.drivers.kubernetes.proxy_pod] - network_policy_enforced = {{ .Values.supervisor.proxyPod.networkPolicyEnforced }} - boundary_port = {{ .Values.supervisor.proxyPod.boundaryPort | default 5500 }} + [openshell.drivers.kubernetes.sandbox_runtime] + network_policy_enforced = {{ .Values.supervisor.sandboxRuntime.networkPolicyEnforced }} + boundary_port = {{ .Values.supervisor.sandboxRuntime.boundaryPort | default 5500 }} {{- if not $credentialDrivers }} diff --git a/deploy/helm/openshell/tests/clusterrole_test.yaml b/deploy/helm/openshell/tests/clusterrole_test.yaml index e05651aa8..40094b267 100644 --- a/deploy/helm/openshell/tests/clusterrole_test.yaml +++ b/deploy/helm/openshell/tests/clusterrole_test.yaml @@ -26,7 +26,7 @@ tests: resources: ["networkpolicies"] verbs: ["get", "create", "patch", "update"] - - it: preserves proxy-pod fence permissions when gateway isolation is disabled + - it: preserves sandbox-runtime fence permissions when gateway isolation is disabled set: server.drivers.kubernetes.workspaceMode: managed networkPolicy.enabled: false @@ -141,7 +141,7 @@ tests: resources: ["secrets"] any: true - - it: grants managed proxy-pod companion permissions + - it: grants managed sandbox-runtime companion permissions set: server.drivers.kubernetes.workspaceMode: managed asserts: @@ -158,7 +158,7 @@ tests: resources: ["secrets"] verbs: ["create"] - - it: grants operator proxy-pod companion permissions + - it: grants operator sandbox-runtime companion permissions set: server.drivers.kubernetes.workspaceMode: operator asserts: diff --git a/deploy/helm/openshell/values.yaml b/deploy/helm/openshell/values.yaml index de2039598..35129b47d 100644 --- a/deploy/helm/openshell/values.yaml +++ b/deploy/helm/openshell/values.yaml @@ -47,7 +47,7 @@ supervisor: pullPolicy: null # -- Supervisor image tag override. Empty uses the version pinned into the gateway unless repository is changed. tag: "" - proxyPod: + sandboxRuntime: # -- Required operator acknowledgement that the cluster CNI enforces NetworkPolicy. networkPolicyEnforced: false # -- Workload boundary TLS listener port. diff --git a/docs/kubernetes/openshift.mdx b/docs/kubernetes/openshift.mdx index a59367dc7..ba3199ca1 100644 --- a/docs/kubernetes/openshift.mdx +++ b/docs/kubernetes/openshift.mdx @@ -36,7 +36,7 @@ oc create ns openshell helm install openshell oci://ghcr.io/nvidia/openshell/helm-chart \ --version \ --namespace openshell \ - --set supervisor.proxyPod.networkPolicyEnforced=true + --set supervisor.sandboxRuntime.networkPolicyEnforced=true ``` The driver reads the namespace's `openshift.io/sa.scc.uid-range` annotation and diff --git a/docs/kubernetes/topology.mdx b/docs/kubernetes/sandbox-runtime.mdx similarity index 92% rename from docs/kubernetes/topology.mdx rename to docs/kubernetes/sandbox-runtime.mdx index 406e2606f..69caba91a 100644 --- a/docs/kubernetes/topology.mdx +++ b/docs/kubernetes/sandbox-runtime.mdx @@ -1,9 +1,9 @@ --- # SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: Apache-2.0 -title: "Kubernetes Sandbox Topology" -sidebar-title: "Topology" -description: "Understand the capability-free sandbox and supervisor topology used for Kubernetes workloads." +title: "Kubernetes Sandbox Runtime" +sidebar-title: "Sandbox runtime" +description: "Understand how Kubernetes places and protects the sandbox runtime and supervisor." keywords: "Generative AI, Cybersecurity, Kubernetes, Sandboxing, Network Policy, Seccomp, Landlock" position: 2 --- @@ -14,7 +14,7 @@ policy decisions, and upstream connections. ## Understand the Components -The Kubernetes driver always uses this topology: +The Kubernetes driver always uses this placement: ```mermaid flowchart LR @@ -76,7 +76,7 @@ policy-approved upstream destinations. Kubernetes policies are additive. Keep sandbox namespaces under administrative control so another principal cannot add permissive policies, create Pods with OpenShell labels, or read bootstrap Secrets. Set -`supervisor.proxyPod.networkPolicyEnforced: true` only after you verify that the +`supervisor.sandboxRuntime.networkPolicyEnforced: true` only after you verify that the cluster CNI enforces both ingress and egress policies for these namespaces. ## Bootstrap a Sandbox @@ -105,7 +105,7 @@ egress fence until Kubernetes confirms that the workload Pod is gone. ## Check Cluster Requirements -The topology requires the following cluster behavior: +This architecture requires the following cluster behavior: - Linux nodes and a container runtime that permits an unprivileged process to install a nested seccomp user-notification filter under `RuntimeDefault`. diff --git a/docs/kubernetes/setup.mdx b/docs/kubernetes/setup.mdx index 4203a2bd2..de6a4afaa 100644 --- a/docs/kubernetes/setup.mdx +++ b/docs/kubernetes/setup.mdx @@ -203,8 +203,8 @@ The most commonly changed values are: | `server.auth.allowUnauthenticatedUsers` | Accept user-facing calls without OIDC or mTLS credentials. Use only for trusted local development or a fully trusted access proxy. | | `server.enableLoopbackServiceHttp` | Enable local plaintext HTTP for loopback sandbox service URLs. Defaults to `true`. | | `pkiInitJob.serverDnsNames` / `certManager.serverDnsNames` | Additional gateway server DNS SANs. Wildcard SANs also enable sandbox service URLs under that domain. | -| `supervisor.proxyPod.networkPolicyEnforced` | Required acknowledgement that the cluster CNI enforces ingress and egress `NetworkPolicy` in sandbox namespaces. | -| `supervisor.proxyPod.boundaryPort` | Non-privileged TLS port used between paired supervisor and sandbox Pods. | +| `supervisor.sandboxRuntime.networkPolicyEnforced` | Required acknowledgement that the cluster CNI enforces ingress and egress `NetworkPolicy` in sandbox namespaces. | +| `supervisor.sandboxRuntime.boundaryPort` | Non-privileged TLS port used between paired supervisor and sandbox Pods. | | `upstreamProxy` | Operator-owned corporate HTTP forward proxy for policy-approved TLS egress. Refer to [Configure a Corporate Upstream Proxy](#configure-a-corporate-upstream-proxy). | Use a values file for repeatable deployments: @@ -311,7 +311,7 @@ The gateway exposes `/healthz` for process liveness and `/readyz` for dependency ## Next Steps -- Kubernetes sandboxes use a separate sandbox Pod and supervisor Deployment; refer to [Topology](/kubernetes/topology). +- Kubernetes sandboxes use a separate sandbox Pod and supervisor Deployment; refer to [Sandbox runtime](/kubernetes/sandbox-runtime). - To enable automatic certificate rotation with cert-manager, refer to [Managing Certificates](/kubernetes/managing-certificates). - To expose the gateway externally without port-forwarding, refer to [Ingress](/kubernetes/ingress). - To configure OIDC or reverse-proxy authentication, refer to [Access Control](/kubernetes/access-control). diff --git a/docs/reference/gateway-config.mdx b/docs/reference/gateway-config.mdx index 588d91b98..0b8d18f52 100644 --- a/docs/reference/gateway-config.mdx +++ b/docs/reference/gateway-config.mdx @@ -94,7 +94,7 @@ future version. To migrate an existing file: omit Docker or Podman `sandbox_pids_limit` to use OpenShell's default limit of 2048, and omit Podman `health_check_interval_secs` to disable health checks. Explicit zero values are invalid. -7. Remove local Docker, Podman, or VM `grpc_endpoint` when the topology-derived +7. Remove local Docker, Podman, or VM `grpc_endpoint` when the driver-derived callback is correct, or retain it as an explicit override. Kubernetes raw TOML requires an explicit endpoint; Helm derives one from the release's gateway Service. New VM root filesystems use an image-provided `sandbox` @@ -546,7 +546,7 @@ image_pull_secrets = ["regcred"] # supervisor_image = "ghcr.io/nvidia/openshell/supervisor:" supervisor_image_pull_policy = "if_not_present" -[openshell.drivers.kubernetes.proxy_pod] +[openshell.drivers.kubernetes.sandbox_runtime] # Set this only after verifying that the cluster CNI enforces both ingress and # egress NetworkPolicy in every sandbox namespace. network_policy_enforced = true @@ -619,7 +619,7 @@ enabled = true gateway_namespace = "openshell" gateway_pod_selector = { "app.kubernetes.io/name" = "openshell", "app.kubernetes.io/instance" = "openshell" } -[openshell.drivers.kubernetes.proxy_pod] +[openshell.drivers.kubernetes.sandbox_runtime] # Required acknowledgement that the cluster CNI enforces NetworkPolicy and the # sandbox namespaces prevent untrusted policy, pod, label, and Secret changes. network_policy_enforced = true @@ -699,7 +699,7 @@ image_pull_policy = "if_not_present" # Value assigned to the openshell.sandbox_namespace label on sandbox containers. sandbox_label = "docker-dev" # Optional override. When omitted, the gateway derives -# https://host.openshell.internal: for this topology. +# https://host.openshell.internal: for this driver. grpc_endpoint = "https://host.openshell.internal:17670" # Contains both /openshell-sandbox and /openshell-supervisor. Defaults to the # gateway version; override to pin a specific build. @@ -905,7 +905,7 @@ state_dir = "/var/lib/openshell/vm" driver_dir = "/usr/local/libexec/openshell" default_image = "ghcr.io/nvidia/openshell-community/sandboxes/base:latest" # Optional override. When omitted, the gateway derives -# https://host.openshell.internal: for the VM topology. +# https://host.openshell.internal: for the VM driver. grpc_endpoint = "https://host.openshell.internal:17670" # Empty falls back to default_image. bootstrap_image = "ghcr.io/nvidia/openshell-community/sandboxes/base:latest" diff --git a/docs/reference/sandbox-compute-drivers.mdx b/docs/reference/sandbox-compute-drivers.mdx index 0e3da6287..d97c36894 100644 --- a/docs/reference/sandbox-compute-drivers.mdx +++ b/docs/reference/sandbox-compute-drivers.mdx @@ -423,8 +423,8 @@ For maintainer-level implementation details, refer to the [Kubernetes driver REA | `client_tls_secret_name` | `server.tls.clientTlsSecretName` | Mount sandbox client TLS materials from a Kubernetes secret. | | `supervisor_image` | `supervisor.image.repository` / `supervisor.image.tag` | Override the trusted runtime image that provides the `openshell-sandbox` and `openshell-supervisor` binaries. The default repository with an empty tag uses the version pinned into the gateway. | | `supervisor_image_pull_policy` | `supervisor.image.pullPolicy` | Set the Kubernetes image pull policy for the supervisor image. | -| `proxy_pod.network_policy_enforced` | `supervisor.proxyPod.networkPolicyEnforced` | Acknowledge that the cluster CNI enforces ingress and egress `NetworkPolicy` in sandbox namespaces. This must be `true`. | -| `proxy_pod.boundary_port` | `supervisor.proxyPod.boundaryPort` | Set the non-privileged TLS port used between the paired supervisor and sandbox Pods. | +| `sandbox_runtime.network_policy_enforced` | `supervisor.sandboxRuntime.networkPolicyEnforced` | Acknowledge that the cluster CNI enforces ingress and egress `NetworkPolicy` in sandbox namespaces. This must be `true`. | +| `sandbox_runtime.boundary_port` | `supervisor.sandboxRuntime.boundaryPort` | Set the non-privileged TLS port used between the paired supervisor and sandbox Pods. | | `https_proxy` | `upstreamProxy.url` | Set the operator-owned `http://host:port` corporate forward proxy used for policy-approved TLS CONNECT egress. | | `no_proxy` | `upstreamProxy.noProxy` | Set destinations that bypass only the corporate proxy. OpenShell policy evaluation still applies. | | `proxy_auth_secret_name` | `upstreamProxy.authSecret.name` | Set the existing Secret name in the sandbox namespace that contains the proxy credential. The Secret mounts only in the supervisor Pod. | @@ -442,7 +442,8 @@ remain limited to the explicitly configured TLS and image-pull Secret names. The driver creates copies only in gateway-owned managed namespaces. Do not reuse the gateway ServiceAccount for unrelated workloads. -The Kubernetes driver always uses a capability-free proxy-pod topology. The +The Kubernetes driver always places the sandbox runtime in the workload Pod and +the supervisor in a separate Deployment. The workload Pod runs `openshell-sandbox` as the same non-root UID/GID as the agent and requests no added Linux capabilities. A separate Deployment runs `openshell-supervisor`, authenticates to the gateway with a sandbox JWT, and diff --git a/e2e/docker/Dockerfile.external-kubernetes-gateway b/e2e/docker/Dockerfile.external-kubernetes-gateway index 4a9de6ea2..1cc822dfc 100644 --- a/e2e/docker/Dockerfile.external-kubernetes-gateway +++ b/e2e/docker/Dockerfile.external-kubernetes-gateway @@ -22,7 +22,7 @@ ENV OPENSHELL_COMPUTE_DRIVER=kubernetes \ OPENSHELL_SUPERVISOR_IMAGE=${SUPERVISOR_IMAGE} \ OPENSHELL_SUPERVISOR_IMAGE_PULL_POLICY=if_not_present \ OPENSHELL_SUPERVISOR_SIDELOAD_METHOD=init-container \ - OPENSHELL_K8S_PROXY_POD_NETWORK_POLICY_ENFORCED=true + OPENSHELL_K8S_SANDBOX_RUNTIME_NETWORK_POLICY_ENFORCED=true USER 1000:1000 EXPOSE 8080 diff --git a/e2e/rust/tests/kubernetes_corporate_proxy.rs b/e2e/rust/tests/kubernetes_corporate_proxy.rs index e1c01dd56..3fc1c12a3 100644 --- a/e2e/rust/tests/kubernetes_corporate_proxy.rs +++ b/e2e/rust/tests/kubernetes_corporate_proxy.rs @@ -6,7 +6,7 @@ //! Kubernetes wiring coverage for authenticated corporate forward proxies. //! //! The shell wrapper configures the gateway before it starts, creates the -//! credential Secret, and enables proxy-pod topology. This test starts a proxy +//! credential Secret, and enables the Kubernetes sandbox runtime. This test starts a proxy //! and an HTTPS upstream on the host visible to sandbox pods, then proves that //! a permitted request uses authenticated CONNECT while a policy-denied port //! never reaches the proxy. diff --git a/e2e/with-kube-gateway.sh b/e2e/with-kube-gateway.sh index 366583d9e..e314cc331 100755 --- a/e2e/with-kube-gateway.sh +++ b/e2e/with-kube-gateway.sh @@ -263,10 +263,10 @@ cleanup() { echo "=== gateway sandbox records ===" "${OPENSHELL_BIN:-${ROOT}/target/debug/openshell}" \ sandbox list --all-workspaces --output json 2>&1 || true - echo "=== proxy-pod supervisor Deployments ===" + echo "=== sandbox-runtime supervisor Deployments ===" kctl -n "${NAMESPACE}" get deployments \ -l "openshell.ai/boundary-role=supervisor" -o yaml 2>&1 || true - echo "=== proxy-pod supervisor logs (last 200 lines each) ===" + echo "=== sandbox-runtime supervisor logs (last 200 lines each) ===" while IFS= read -r supervisor_pod; do [ -n "${supervisor_pod}" ] || continue echo "--- ${supervisor_pod} ---" @@ -284,7 +284,7 @@ cleanup() { kctl -n "${NAMESPACE}" logs "$(kube_workload_ref "${RELEASE_NAME}")" \ --since=20m \ --all-containers --prefix 2>&1 \ - | grep -Ei "sandbox phase changed|start_sandbox|stop_sandbox|supervisor session|proxy-pod|bootstrap" \ + | grep -Ei "sandbox phase changed|start_sandbox|stop_sandbox|supervisor session|sandbox-runtime|bootstrap" \ || true echo "=== gateway logs (last 200 lines) ===" kctl -n "${NAMESPACE}" logs \ diff --git a/skills/debug-openshell-cluster/SKILL.md b/skills/debug-openshell-cluster/SKILL.md index 3d944e344..ea0a1d4af 100644 --- a/skills/debug-openshell-cluster/SKILL.md +++ b/skills/debug-openshell-cluster/SKILL.md @@ -550,7 +550,8 @@ kubectl -n openshell get configmap openshell-config -o jsonpath='{.data.gateway\ kubectl -n get sandbox -o jsonpath='{.spec.template.spec.serviceAccountName}{"\n"}' ``` -Kubernetes always uses the proxy-pod topology. Helm must render +The Kubernetes driver creates a sandbox workload Pod and a separate supervisor +Deployment. Helm must render `network_policy_enforced = true`. This is an explicit operator assertion that the cluster CNI enforces Kubernetes NetworkPolicy; the Kubernetes API cannot attest enforcement. Run sandboxes only in a trusted namespace @@ -569,7 +570,7 @@ the sandbox over per-sandbox TLS with server-certificate verification plus bootstrap-token client authentication, and owns gateway policy, provider credentials, DNS, and mediated upstream connections. -Inspect the complete companion set when a proxy-pod sandbox remains Starting +Inspect all driver-managed resources when a Kubernetes sandbox remains Starting or loses readiness: ```bash diff --git a/tasks/test.toml b/tasks/test.toml index 8e789db0b..c5c65b2b0 100644 --- a/tasks/test.toml +++ b/tasks/test.toml @@ -190,9 +190,9 @@ run = [ "OPENSHELL_CONFORMANCE_BIN=\"${OPENSHELL_CONFORMANCE_BIN:-$PWD/target/debug/openshell-conformance}\" AGENT_SANDBOX_VERSION=v0.4.6 e2e/rust/e2e-kubernetes.sh", ] -["e2e:kubernetes:proxy-pod"] -description = "Run Kubernetes e2e with the sandbox/supervisor proxy-pod topology" -env = { OPENSHELL_E2E_KUBE_EXTRA_VALUES = "deploy/helm/openshell/ci/values-proxy-pod.yaml" } +["e2e:kubernetes:isolation"] +description = "Run Kubernetes e2e with the workload network fence and separate supervisor" +env = { OPENSHELL_E2E_KUBE_EXTRA_VALUES = "deploy/helm/openshell/ci/values-sandbox-runtime.yaml" } depends = ["e2e:conformance:build"] run = "OPENSHELL_CONFORMANCE_BIN=\"${OPENSHELL_CONFORMANCE_BIN:-$PWD/target/debug/openshell-conformance}\" e2e/rust/e2e-kubernetes.sh" @@ -210,13 +210,13 @@ run = "OPENSHELL_CONFORMANCE_BIN=\"${OPENSHELL_CONFORMANCE_BIN:-$PWD/target/debu ["e2e:kubernetes:workspace-managed"] description = "Run Kubernetes e2e with managed workspace mode (auto-created per-workspace namespaces)" -env = { OPENSHELL_E2E_KUBE_EXTRA_VALUES = "deploy/helm/openshell/ci/values-proxy-pod.yaml:deploy/helm/openshell/ci/values-workspace-managed.yaml", OPENSHELL_E2E_KUBE_IMAGE_PULL_SECRET = "e2e-regcred", OPENSHELL_E2E_KUBE_TEST = "workspace_namespace_managed", OPENSHELL_E2E_KUBERNETES_FEATURES = "e2e,e2e-kubernetes,e2e-kubernetes-workspace-managed" } +env = { OPENSHELL_E2E_KUBE_EXTRA_VALUES = "deploy/helm/openshell/ci/values-sandbox-runtime.yaml:deploy/helm/openshell/ci/values-workspace-managed.yaml", OPENSHELL_E2E_KUBE_IMAGE_PULL_SECRET = "e2e-regcred", OPENSHELL_E2E_KUBE_TEST = "workspace_namespace_managed", OPENSHELL_E2E_KUBERNETES_FEATURES = "e2e,e2e-kubernetes,e2e-kubernetes-workspace-managed" } depends = ["e2e:conformance:build"] run = "OPENSHELL_CONFORMANCE_BIN=\"${OPENSHELL_CONFORMANCE_BIN:-$PWD/target/debug/openshell-conformance}\" e2e/rust/e2e-kubernetes.sh" ["e2e:kubernetes:workspace-operator"] description = "Run Kubernetes e2e with operator workspace mode (pre-provisioned per-workspace namespaces)" -env = { OPENSHELL_E2E_KUBE_EXTRA_VALUES = "deploy/helm/openshell/ci/values-proxy-pod.yaml:deploy/helm/openshell/ci/values-workspace-operator.yaml", OPENSHELL_E2E_KUBE_TEST = "workspace_namespace_operator", OPENSHELL_E2E_KUBERNETES_FEATURES = "e2e,e2e-kubernetes,e2e-kubernetes-workspace-operator" } +env = { OPENSHELL_E2E_KUBE_EXTRA_VALUES = "deploy/helm/openshell/ci/values-sandbox-runtime.yaml:deploy/helm/openshell/ci/values-workspace-operator.yaml", OPENSHELL_E2E_KUBE_TEST = "workspace_namespace_operator", OPENSHELL_E2E_KUBERNETES_FEATURES = "e2e,e2e-kubernetes,e2e-kubernetes-workspace-operator" } depends = ["e2e:conformance:build"] run = "OPENSHELL_CONFORMANCE_BIN=\"${OPENSHELL_CONFORMANCE_BIN:-$PWD/target/debug/openshell-conformance}\" e2e/rust/e2e-kubernetes.sh"