mirror of
https://github.com/NVIDIA/OpenShell.git
synced 2026-11-07 08:55:37 +08:00
* feat(isolation): add RFC 0012 backend contract Signed-off-by: Drew Newberry <385+drew@users.noreply.github.com> * refactor(isolation): name the interface crate explicitly Signed-off-by: Drew Newberry <anewberry@nvidia.com> * feat(isolation): expose trusted host gateway Signed-off-by: Drew Newberry <anewberry@nvidia.com> * docs(agents): inventory the MXC driver Signed-off-by: Drew Newberry <anewberry@nvidia.com> * feat(isolation): add mediated DNS transport Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(isolation): tighten interface error and digest contracts Signed-off-by: Drew Newberry <anewberry@nvidia.com> * docs(isolation): remove unrelated driver inventory Signed-off-by: Drew Newberry <anewberry@nvidia.com> * feat(isolation): define capability-free launch contract Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(isolation): seal confirmed boundary state Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(isolation): validate confirmation for external backend implementations Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(isolation): clarify mediated DNS identity Signed-off-by: Drew Newberry <anewberry@nvidia.com> * refactor(isolation): generalize loopback connector Signed-off-by: Drew Newberry <anewberry@nvidia.com> * refactor(isolation): unify typed network mediation Signed-off-by: Drew Newberry <anewberry@nvidia.com> * feat(isolation): bind launches to sandbox sessions Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(mxc): initialize extended sandbox status Signed-off-by: Drew Newberry <anewberry@nvidia.com> * feat(isolation): add boundary protocol and Linux primitives Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(isolation): harden signals and separate process status from transport Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(isolation): validate remote confirmation through public contract Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(isolation): validate wire state and propagate snapshot failures Signed-off-by: Drew Newberry <anewberry@nvidia.com> * test(isolation): import owned agent specification explicitly Signed-off-by: Drew Newberry <anewberry@nvidia.com> * docs(isolation): describe mediated DNS channel Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(isolation): bound mediation attach without nested retries Signed-off-by: Drew Newberry <anewberry@nvidia.com> * refactor(isolation): generalize loopback protocol Signed-off-by: Drew Newberry <anewberry@nvidia.com> * feat(isolation): add transport-neutral session authentication Signed-off-by: Drew Newberry <anewberry@nvidia.com> * refactor(isolation): separate sandbox backend protocol Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(isolation): harden runtime boundary controls Signed-off-by: Drew Newberry <anewberry@nvidia.com> * feat(isolation): add terminal boundary operation Signed-off-by: Drew Newberry <anewberry@nvidia.com> * feat(isolation): split supervisor and sandbox runtimes Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(sandbox): harden boundary isolation and lifecycle ownership Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(sandbox): reject private root redirects and adopt typed errors Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(sandbox): preserve accept thread ownership on musl Signed-off-by: Drew Newberry <anewberry@nvidia.com> * test(sandbox): isolate credential probes from filtered threads Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(sandbox): return retained exec exit status to independent waiters Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(sandbox): bound network mediation and preserve socket authorization Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(sandbox): bound control admission and retire stale mediation Signed-off-by: Drew Newberry <anewberry@nvidia.com> * ci(e2e): select migrated drivers per stack layer Signed-off-by: Drew Newberry <anewberry@nvidia.com> * refactor(sandbox): implement loopback connector Signed-off-by: Drew Newberry <anewberry@nvidia.com> * feat(isolation): authenticate the Sandbox Protocol Signed-off-by: Drew Newberry <anewberry@nvidia.com> * feat(supervisor): rotate launch-scoped authentication Signed-off-by: Drew Newberry <anewberry@nvidia.com> * refactor(sandbox): consume dedicated backend crate Signed-off-by: Drew Newberry <anewberry@nvidia.com> * test(sandbox): align topology session fixture Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(sandbox): align projected bootstrap bundle Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(auth): validate refreshed credentials before rotation Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(sandbox): fail closed across supervisor disconnects Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(isolation): repair rebased sandbox CI Signed-off-by: Drew Newberry <anewberry@nvidia.com> * build(runtime): publish separate sandbox and supervisor images Signed-off-by: Drew Newberry <anewberry@nvidia.com> * feat(config): configure the sandbox runtime image Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(ci): validate sandbox binary linkage Signed-off-by: Drew Newberry <anewberry@nvidia.com> * refactor(isolation): use backend and runtime terminology Signed-off-by: Drew Newberry <anewberry@nvidia.com> * refactor(sandbox): use a scratch runtime image Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(ci): refresh schema and dependency policy Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(sandbox): bind reconnects to supervisor process Signed-off-by: Drew Newberry <anewberry@nvidia.com> * docs: align runtime split operational guidance Signed-off-by: Drew Newberry <anewberry@nvidia.com> * chore(security): document Kubernetes runtime RBAC Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(isolation): enforce runtime lifecycle invariants Signed-off-by: Drew Newberry <anewberry@nvidia.com> * feat(compute): identify sandbox start generations Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(server): restore sandbox launch sessions Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(isolation): support authenticated runtime replacement Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(auth): bind sandbox session successors Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(auth): retry pending sandbox successors Signed-off-by: Drew Newberry <anewberry@nvidia.com> * feat(vm): run the supervisor outside the guest workload Signed-off-by: Drew Newberry <anewberry@nvidia.com> * refactor(vm): use sandbox backend protocol Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(vm): repair rebase integration Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(vm): use unified build toolchain Signed-off-by: Drew Newberry <anewberry@nvidia.com> * refactor(vm): use sandbox runtime terminology Signed-off-by: Drew Newberry <anewberry@nvidia.com> * refactor(vm): own guest network bootstrap Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(vm): expose guest init version Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(vm): select native supervisor artifacts Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(vm): guard guest init Linux symbols Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(vm): scope Linux test imports Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(vm): avoid guest interface casts Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(vm): reconcile admitted sandbox identity Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(vm): share resolved sandbox identity Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(vm): surface host supervisor failures Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(vm): include guest logs on supervisor exit Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(vm): rotate and clean runtime generations Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(vm): make sandbox starts generation-aware Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(vm): rotate restored sandbox sessions Signed-off-by: Drew Newberry <anewberry@nvidia.com> * refactor(vm): keep shared paths in the base layer Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(vm): bind sandbox session lineage Signed-off-by: Drew Newberry <anewberry@nvidia.com> * feat(docker): isolate workloads behind the host supervisor Signed-off-by: Drew Newberry <anewberry@nvidia.com> * feat(docker): rotate launch-scoped authentication Signed-off-by: Drew Newberry <anewberry@nvidia.com> * refactor(docker): use sandbox backend protocol Signed-off-by: Drew Newberry <anewberry@nvidia.com> * refactor(docker): use host networking for supervisor Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(docker): preserve host gateway alias resolution Signed-off-by: Drew Newberry <anewberry@nvidia.com> * feat(docker): use separate sandbox and supervisor images Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(docker): restore startup validation after rebase Signed-off-by: Drew Newberry <anewberry@nvidia.com> * refactor(docker): name the sandbox runtime directly Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(docker): narrow supervisor CA runtime storage Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(docker): close companion isolation gaps Signed-off-by: Drew Newberry <anewberry@nvidia.com> * test(docker): align mediated network expectations Signed-off-by: Drew Newberry <anewberry@nvidia.com> * test(docker): exercise mediated network paths Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(docker): attach supervisor to managed network Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(docker): defer supervisor recovery until gateway is ready Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(docker): make sandbox starts generation-aware Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(docker): rotate restored sandbox sessions Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(docker): preserve workloads during session rotation Signed-off-by: Drew Newberry <anewberry@nvidia.com> * refactor(docker): remove unrelated configuration RFC changes Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(docker): bind sandbox session lineage Signed-off-by: Drew Newberry <anewberry@nvidia.com> * feat(kubernetes): add proxy-pod isolation topology Signed-off-by: Drew Newberry <anewberry@nvidia.com> * refactor(kubernetes): use sandbox backend protocol Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(kubernetes): use stable sandbox service authority Signed-off-by: Drew Newberry <anewberry@nvidia.com> * feat(kubernetes): split sandbox and supervisor images Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(kubernetes): adapt proxy pods to current runtime APIs Signed-off-by: Drew Newberry <anewberry@nvidia.com> * refactor(kubernetes): describe the single runtime placement Signed-off-by: Drew Newberry <anewberry@nvidia.com> * refactor(kubernetes): simplify sandbox orchestration Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(kubernetes): validate deployment prerequisites Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(kubernetes): update Trivy Helm profile inventory Signed-off-by: Drew Newberry <anewberry@nvidia.com> * test(kubernetes): update Trivy scan inventory count Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(kubernetes): reuse preloaded runtime images in e2e Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(kubernetes): type and clean runtime resources Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(kubernetes): make sandbox restarts recoverable Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(kubernetes): rotate restored sandbox sessions Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(kubernetes): preserve supervisor egress Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(kubernetes): bind sandbox session lineage Signed-off-by: Drew Newberry <anewberry@nvidia.com> * feat(podman): adopt isolated sandbox and supervisor containers Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(podman): stage bootstrap archives at named volume destinations Signed-off-by: Drew Newberry <anewberry@nvidia.com> * feat(podman): rotate launch-scoped authentication Signed-off-by: Drew Newberry <anewberry@nvidia.com> * refactor(podman): use sandbox backend protocol Signed-off-by: Drew Newberry <anewberry@nvidia.com> * refactor(podman): use host networking for supervisor Signed-off-by: Drew Newberry <anewberry@nvidia.com> * feat(podman): split sandbox and supervisor images Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(podman): repair rebase integration Signed-off-by: Drew Newberry <anewberry@nvidia.com> * refactor(podman): name the sandbox runtime directly Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(podman): provision supervisor CA runtime storage Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(podman): address isolation review findings Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(podman): inspect Debian supervisor provenance Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(podman): use libpod-compatible tmpfs options Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(podman): bind verified sandbox runtime binary Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(podman): provide external driver data directory Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(podman): start sandbox before joining user namespace Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(podman): separate supervisor user namespace Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(podman): make sandbox starts generation-aware Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(podman): rotate restored sandbox sessions Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(podman): bind sandbox session lineage Signed-off-by: Drew Newberry <anewberry@nvidia.com> * perf(isolation): add TCP and DNS benchmark harnesses Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(perf): align benchmark timing and supported protocols Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(perf): report TCP benchmark metrics accurately Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(perf): cancel failed worker startup Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(docker): build matching local supervisor image Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(podman): make local sandbox smoke test runnable Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(kubernetes): wire local sandbox runtime image Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(kubernetes): narrow sandbox service RBAC Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(ci): validate split runtime artifacts Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(isolation): harden runtime session handling Signed-off-by: Drew Newberry <anewberry@nvidia.com> * feat(supervisor): add standalone network proxy role Signed-off-by: Drew Newberry <anewberry@nvidia.com> * docs(rfc): remove implementation companion notes Signed-off-by: Drew Newberry <anewberry@nvidia.com> * refactor(vm): standardize runtime release name Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(vm): pin renamed runtime artifacts Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(auth): persist sandbox runtime identity Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(runtime): restore branch validation Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(isolation): reconcile main after rebase Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(network): close unframed HTTP 1.0 responses Signed-off-by: Drew Newberry <anewberry@nvidia.com> * chore(isolation): preserve upstream OCSF updates Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(security): close credential and TLS replay paths Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(auth): make sandbox refresh retries idempotent Signed-off-by: Drew Newberry <anewberry@nvidia.com> --------- Signed-off-by: Drew Newberry <385+drew@users.noreply.github.com> Signed-off-by: Drew Newberry <anewberry@nvidia.com>
448 lines
18 KiB
Rust
448 lines
18 KiB
Rust
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
//! Kubernetes provisioning for the shared authenticated boundary protocol.
|
|
//!
|
|
//! This module deliberately contains no lifecycle, process, network, identity,
|
|
//! 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.
|
|
|
|
use std::collections::{BTreeMap, HashMap};
|
|
use std::net::{IpAddr, SocketAddr};
|
|
use std::path::PathBuf;
|
|
|
|
use k8s_openapi::api::networking::v1::{
|
|
NetworkPolicy, NetworkPolicyEgressRule, NetworkPolicyIngressRule, NetworkPolicyPeer,
|
|
NetworkPolicyPort, NetworkPolicySpec,
|
|
};
|
|
use k8s_openapi::apimachinery::pkg::apis::meta::v1::LabelSelector;
|
|
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, GatewayVerificationKey, SandboxRuntimeDescriptor,
|
|
SandboxTlsClientConfig, SandboxTlsServerConfig, SandboxTransport,
|
|
};
|
|
|
|
/// Isolation backend implemented by the `OpenShell` sandbox runtime.
|
|
pub const BACKEND_NAME: &str = openshell_sandbox_backend::BACKEND_NAME;
|
|
|
|
/// Label that binds the workload and supervisor pods in one unique pair.
|
|
pub const BOUNDARY_PAIR_LABEL: &str = "openshell.ai/boundary-pair";
|
|
|
|
/// Label distinguishing the two pods in a sandbox generation.
|
|
pub const BOUNDARY_ROLE_LABEL: &str = "openshell.ai/boundary-role";
|
|
|
|
const WORKLOAD_ROLE: &str = "workload";
|
|
const SUPERVISOR_ROLE: &str = "supervisor";
|
|
|
|
/// Driver-owned inputs for the workload pod's Kubernetes network fence.
|
|
///
|
|
/// 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
|
|
/// `KubernetesSandboxRuntimeBoundarySpec`.
|
|
pub struct KubernetesSandboxRuntimeNetworkFenceSpec {
|
|
pub namespace: String,
|
|
pub policy_name: String,
|
|
pub supervisor_policy_name: String,
|
|
pub boundary_port: u16,
|
|
}
|
|
|
|
/// Labels and policy needed to remove direct workload-pod egress.
|
|
pub struct KubernetesSandboxRuntimeNetworkFence {
|
|
pub workload_labels: BTreeMap<String, String>,
|
|
pub control_labels: BTreeMap<String, String>,
|
|
pub workload_policy: NetworkPolicy,
|
|
pub supervisor_policy: NetworkPolicy,
|
|
}
|
|
|
|
impl KubernetesSandboxRuntimeNetworkFenceSpec {
|
|
/// Render the namespace-wide workload fence.
|
|
///
|
|
/// Kubernetes `NetworkPolicy` is connection-aware: traffic returning over
|
|
/// the control-initiated boundary connection is allowed even though the
|
|
/// workload pod has no egress rules. The control pod remains responsible
|
|
/// for opening policy-approved upstream connections.
|
|
#[must_use]
|
|
pub fn provision(self) -> KubernetesSandboxRuntimeNetworkFence {
|
|
let workload_labels = role_labels(WORKLOAD_ROLE);
|
|
let control_labels = role_labels(SUPERVISOR_ROLE);
|
|
|
|
let workload_policy = NetworkPolicy {
|
|
metadata: ObjectMeta {
|
|
name: Some(self.policy_name),
|
|
namespace: Some(self.namespace.clone()),
|
|
..Default::default()
|
|
},
|
|
spec: Some(NetworkPolicySpec {
|
|
pod_selector: LabelSelector {
|
|
match_labels: Some(workload_labels.clone()),
|
|
..Default::default()
|
|
},
|
|
policy_types: Some(vec!["Ingress".to_string(), "Egress".to_string()]),
|
|
// Any trusted OpenShell supervisor in this namespace may
|
|
// reach a sandbox listener. The Sandbox Protocol enforces the
|
|
// exact sandbox, generation, and Pod UID binding.
|
|
ingress: Some(vec![NetworkPolicyIngressRule {
|
|
from: Some(vec![NetworkPolicyPeer {
|
|
pod_selector: Some(LabelSelector {
|
|
match_labels: Some(control_labels.clone()),
|
|
..Default::default()
|
|
}),
|
|
..Default::default()
|
|
}]),
|
|
ports: Some(vec![NetworkPolicyPort {
|
|
port: Some(IntOrString::Int(i32::from(self.boundary_port))),
|
|
protocol: Some("TCP".to_string()),
|
|
..Default::default()
|
|
}]),
|
|
}]),
|
|
// An explicit empty list selects the pod for egress and allows
|
|
// no new workload-initiated connections, including DNS and the
|
|
// Kubernetes API. Reply traffic for allowed ingress remains
|
|
// permitted by conforming NetworkPolicy implementations.
|
|
egress: Some(Vec::new()),
|
|
}),
|
|
};
|
|
|
|
// Namespace-wide default-deny policies are additive with this rule.
|
|
// Select only OpenShell supervisor pods and explicitly allow their
|
|
// policy-approved DNS and upstream connections.
|
|
let supervisor_policy = NetworkPolicy {
|
|
metadata: ObjectMeta {
|
|
name: Some(self.supervisor_policy_name),
|
|
namespace: Some(self.namespace),
|
|
..Default::default()
|
|
},
|
|
spec: Some(NetworkPolicySpec {
|
|
pod_selector: LabelSelector {
|
|
match_labels: Some(control_labels.clone()),
|
|
..Default::default()
|
|
},
|
|
policy_types: Some(vec!["Egress".to_string()]),
|
|
egress: Some(vec![NetworkPolicyEgressRule::default()]),
|
|
..Default::default()
|
|
}),
|
|
};
|
|
|
|
KubernetesSandboxRuntimeNetworkFence {
|
|
workload_labels,
|
|
control_labels,
|
|
workload_policy,
|
|
supervisor_policy,
|
|
}
|
|
}
|
|
}
|
|
|
|
fn role_labels(role: &str) -> BTreeMap<String, String> {
|
|
BTreeMap::from([(BOUNDARY_ROLE_LABEL.to_string(), role.to_string())])
|
|
}
|
|
|
|
/// Driver-owned inputs that bind one workload/supervisor pair to one boundary.
|
|
///
|
|
/// The driver constructs this only after Kubernetes has assigned every UID and
|
|
/// after it has observed the namespace workload-policy resource version. The
|
|
/// workload stays held until the matching boundary config and supervisor
|
|
/// resources have been installed.
|
|
pub struct KubernetesSandboxRuntimeBoundarySpec {
|
|
pub boundary_id: String,
|
|
pub generation: String,
|
|
pub session_id: openshell_core::SandboxSessionId,
|
|
pub session_rotation: openshell_core::jwt::SessionRotation,
|
|
pub auth_epoch: openshell_core::jwt::CredentialEpoch,
|
|
pub gateway_id: String,
|
|
pub verification_keys: Vec<GatewayVerificationKey>,
|
|
pub namespace_uid: String,
|
|
pub sandbox_resource_uid: String,
|
|
pub workload_pod_uid: String,
|
|
pub workload_pod_uid_path: PathBuf,
|
|
pub supervisor_pod_uid: String,
|
|
pub egress_policy_uid: String,
|
|
pub egress_policy_resource_version: String,
|
|
pub boundary_listener: SocketAddr,
|
|
pub control_authority: String,
|
|
pub control_address: SocketAddr,
|
|
pub sandbox_tls: SandboxTlsServerConfig,
|
|
pub supervisor_tls: SandboxTlsClientConfig,
|
|
pub host_gateway_ip: Option<IpAddr>,
|
|
pub workload_identity: ResolvedWorkloadIdentity,
|
|
pub child_env: HashMap<String, String>,
|
|
}
|
|
|
|
/// Protected workload-pod config and matching sandbox-runtime descriptor.
|
|
pub struct KubernetesSandboxRuntimeBoundaryProvisioning {
|
|
pub boundary_config: BoundaryConfig,
|
|
pub runtime_descriptor: SandboxRuntimeDescriptor,
|
|
}
|
|
|
|
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) -> KubernetesSandboxRuntimeBoundaryProvisioning {
|
|
let resource_claims = BTreeMap::from([
|
|
("kubernetes.namespace_uid".to_string(), self.namespace_uid),
|
|
(
|
|
"kubernetes.sandbox_resource_uid".to_string(),
|
|
self.sandbox_resource_uid,
|
|
),
|
|
(
|
|
"kubernetes.workload_pod_uid".to_string(),
|
|
self.workload_pod_uid,
|
|
),
|
|
(
|
|
"kubernetes.supervisor_pod_uid".to_string(),
|
|
self.supervisor_pod_uid,
|
|
),
|
|
(
|
|
"kubernetes.egress_policy_uid".to_string(),
|
|
self.egress_policy_uid,
|
|
),
|
|
(
|
|
"kubernetes.egress_policy_resource_version".to_string(),
|
|
self.egress_policy_resource_version,
|
|
),
|
|
]);
|
|
let driver_fence = DriverFenceEvidence::Kubernetes {
|
|
network_policy_uid: resource_claims["kubernetes.egress_policy_uid"].clone(),
|
|
network_policy_resource_version:
|
|
resource_claims["kubernetes.egress_policy_resource_version"].clone(),
|
|
ingress_isolated: true,
|
|
egress_isolated: true,
|
|
egress_rule_count: 0,
|
|
};
|
|
KubernetesSandboxRuntimeBoundaryProvisioning {
|
|
boundary_config: BoundaryConfig {
|
|
boundary_id: self.boundary_id.clone(),
|
|
generation: self.generation.clone(),
|
|
session_id: self.session_id,
|
|
session_rotation: self.session_rotation,
|
|
auth_epoch: self.auth_epoch,
|
|
gateway_id: self.gateway_id,
|
|
verification_keys: self.verification_keys,
|
|
listener: BoundaryListener::TlsTcp {
|
|
address: self.boundary_listener,
|
|
tls: self.sandbox_tls,
|
|
},
|
|
resource_claims: resource_claims.clone(),
|
|
resource_claim_files: BTreeMap::from([(
|
|
"kubernetes.workload_pod_uid".to_string(),
|
|
self.workload_pod_uid_path,
|
|
)]),
|
|
workload_identity: self.workload_identity.clone(),
|
|
driver_fence: driver_fence.clone(),
|
|
child_env: self.child_env,
|
|
},
|
|
runtime_descriptor: SandboxRuntimeDescriptor {
|
|
boundary_id: self.boundary_id,
|
|
generation: self.generation,
|
|
session_id: self.session_id,
|
|
workload_identity: self.workload_identity,
|
|
transport: SandboxTransport::Tcp {
|
|
authority: self.control_authority,
|
|
addresses: vec![self.control_address],
|
|
},
|
|
tls: self.supervisor_tls,
|
|
host_gateway_ip: self.host_gateway_ip,
|
|
resource_claims,
|
|
driver_fence,
|
|
},
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
fn spec() -> KubernetesSandboxRuntimeBoundarySpec {
|
|
KubernetesSandboxRuntimeBoundarySpec {
|
|
boundary_id: "sandbox-1".to_string(),
|
|
generation: "generation-1".to_string(),
|
|
session_id: openshell_core::SandboxSessionId::new(),
|
|
session_rotation: openshell_core::jwt::SessionRotation::new(1).unwrap(),
|
|
auth_epoch: openshell_core::jwt::CredentialEpoch::new(1).unwrap(),
|
|
gateway_id: "gateway-1".to_string(),
|
|
verification_keys: vec![GatewayVerificationKey {
|
|
key_id: "key-1".to_string(),
|
|
public_key_pem: "public-key".to_string(),
|
|
}],
|
|
namespace_uid: "namespace-uid".to_string(),
|
|
sandbox_resource_uid: "sandbox-resource-uid".to_string(),
|
|
workload_pod_uid: "pod-uid".to_string(),
|
|
workload_pod_uid_path: PathBuf::from("/.openshell/pod-identity/uid"),
|
|
supervisor_pod_uid: "supervisor-pod-uid".to_string(),
|
|
egress_policy_uid: "network-policy-uid".to_string(),
|
|
egress_policy_resource_version: "1945".to_string(),
|
|
boundary_listener: "0.0.0.0:5500".parse().expect("valid listener"),
|
|
control_authority: "os-boundary-sandbox.default.svc:5500".to_string(),
|
|
control_address: "10.42.0.7:5500".parse().expect("valid target"),
|
|
sandbox_tls: SandboxTlsServerConfig {
|
|
certificate_chain_path: PathBuf::from("/run/boundary/tls.crt"),
|
|
private_key_path: PathBuf::from("/run/boundary/tls.key"),
|
|
},
|
|
supervisor_tls: SandboxTlsClientConfig {
|
|
server_name: "boundary.sandbox.openshell".to_string(),
|
|
trust_anchor_pem: "test-ca".to_string(),
|
|
},
|
|
host_gateway_ip: Some("10.42.0.1".parse().expect("valid gateway IP")),
|
|
workload_identity: ResolvedWorkloadIdentity::new(
|
|
1000,
|
|
1000,
|
|
vec![1000],
|
|
"kubernetes-config".to_string(),
|
|
"sandbox:sandbox-resource-uid".to_string(),
|
|
)
|
|
.unwrap(),
|
|
child_env: HashMap::new(),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn provisioning_binds_identical_kubernetes_resource_claims() {
|
|
let provisioned = spec().provision();
|
|
|
|
assert_eq!(
|
|
provisioned.boundary_config.resource_claims,
|
|
provisioned.runtime_descriptor.resource_claims
|
|
);
|
|
assert_eq!(
|
|
provisioned.runtime_descriptor.resource_claims["kubernetes.sandbox_resource_uid"],
|
|
"sandbox-resource-uid"
|
|
);
|
|
assert_eq!(
|
|
provisioned.runtime_descriptor.resource_claims["kubernetes.egress_policy_resource_version"],
|
|
"1945"
|
|
);
|
|
assert_eq!(
|
|
provisioned.boundary_config.driver_fence,
|
|
provisioned.runtime_descriptor.driver_fence
|
|
);
|
|
assert!(
|
|
provisioned
|
|
.runtime_descriptor
|
|
.driver_fence
|
|
.validate()
|
|
.is_ok()
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn provisioning_uses_one_shared_tcp_protocol_across_pods() {
|
|
let provisioned = spec().provision();
|
|
|
|
assert_eq!(
|
|
provisioned.boundary_config.listener,
|
|
BoundaryListener::TlsTcp {
|
|
address: "0.0.0.0:5500".parse().expect("valid listener"),
|
|
tls: SandboxTlsServerConfig {
|
|
certificate_chain_path: PathBuf::from("/run/boundary/tls.crt"),
|
|
private_key_path: PathBuf::from("/run/boundary/tls.key"),
|
|
},
|
|
}
|
|
);
|
|
assert_eq!(
|
|
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.runtime_descriptor.tls,
|
|
SandboxTlsClientConfig {
|
|
server_name: "boundary.sandbox.openshell".to_string(),
|
|
trust_anchor_pem: "test-ca".to_string(),
|
|
}
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn network_fence_denies_all_workload_initiated_egress() {
|
|
let fence = KubernetesSandboxRuntimeNetworkFenceSpec {
|
|
namespace: "sandbox-ns".to_string(),
|
|
policy_name: "openshell-boundary-sandbox-1".to_string(),
|
|
supervisor_policy_name: "openshell-sandbox-supervisors".to_string(),
|
|
boundary_port: 5500,
|
|
}
|
|
.provision();
|
|
|
|
let policy_spec = fence.workload_policy.spec.expect("policy has a spec");
|
|
assert_eq!(
|
|
policy_spec.policy_types,
|
|
Some(vec!["Ingress".to_string(), "Egress".to_string()])
|
|
);
|
|
assert_eq!(policy_spec.egress, Some(Vec::new()));
|
|
assert_eq!(
|
|
policy_spec.pod_selector.match_labels,
|
|
Some(fence.workload_labels)
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn network_fence_allows_namespace_supervisors_to_boundary_port() {
|
|
let fence = KubernetesSandboxRuntimeNetworkFenceSpec {
|
|
namespace: "sandbox-ns".to_string(),
|
|
policy_name: "openshell-boundary-sandbox-1".to_string(),
|
|
supervisor_policy_name: "openshell-sandbox-supervisors".to_string(),
|
|
boundary_port: 5500,
|
|
}
|
|
.provision();
|
|
|
|
let policy_spec = fence.workload_policy.spec.expect("policy has a spec");
|
|
let ingress = policy_spec
|
|
.ingress
|
|
.expect("policy has ingress rules")
|
|
.pop()
|
|
.expect("policy has one ingress rule");
|
|
let peer = ingress
|
|
.from
|
|
.expect("rule has peers")
|
|
.pop()
|
|
.expect("rule has one peer");
|
|
assert_eq!(
|
|
peer.pod_selector
|
|
.expect("peer has a pod selector")
|
|
.match_labels,
|
|
Some(fence.control_labels)
|
|
);
|
|
assert!(peer.namespace_selector.is_none());
|
|
|
|
let port = ingress
|
|
.ports
|
|
.expect("rule has ports")
|
|
.pop()
|
|
.expect("rule has one port");
|
|
assert_eq!(port.protocol.as_deref(), Some("TCP"));
|
|
assert_eq!(port.port, Some(IntOrString::Int(5500)));
|
|
}
|
|
|
|
#[test]
|
|
fn network_fence_keeps_supervisor_egress_available() {
|
|
let fence = KubernetesSandboxRuntimeNetworkFenceSpec {
|
|
namespace: "sandbox-ns".to_string(),
|
|
policy_name: "openshell-sandbox-workloads".to_string(),
|
|
supervisor_policy_name: "openshell-sandbox-supervisors".to_string(),
|
|
boundary_port: 5500,
|
|
}
|
|
.provision();
|
|
|
|
let policy_spec = fence
|
|
.supervisor_policy
|
|
.spec
|
|
.expect("supervisor policy has a spec");
|
|
assert_eq!(policy_spec.policy_types, Some(vec!["Egress".to_string()]));
|
|
assert_eq!(
|
|
policy_spec.pod_selector.match_labels,
|
|
Some(fence.control_labels)
|
|
);
|
|
assert_eq!(
|
|
policy_spec.egress,
|
|
Some(vec![NetworkPolicyEgressRule::default()])
|
|
);
|
|
}
|
|
}
|