Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 62 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ nix = { version = "0.29", features = ["signal", "process", "user", "fs", "term"]
serde = { version = "1", features = ["derive"] }
serde_json = "1"
serde_yml = "0.0.12"
toml = "0.8"
apollo-parser = "0.8.5"

# HTTP client
Expand Down
31 changes: 31 additions & 0 deletions architecture/gateway.md
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,37 @@ pre-created Secrets) disable the Helm hook via `pkiInitJob.enabled=false`.
The chart also ships a `certManager.*` path that produces equivalent Secrets
through cert-manager `Issuer`/`Certificate` resources.

## Configuration

The gateway reads its configuration from three sources, merged in this
precedence (highest first):

```
CLI flag > OPENSHELL_* env var > TOML file > built-in default
```

The TOML file is opt-in via `--config <PATH>` / `OPENSHELL_GATEWAY_CONFIG`.
When unset, the gateway behaves exactly as before — CLI flags and env vars
drive every setting. See `examples/gateway/gateway.example.toml` for a
worked example and RFC 0003 for the full schema.

`database_url` is env-only and rejected when present in the file
(`OPENSHELL_DB_URL` / `--db-url`).

### Driver inheritance

`[openshell.gateway]` carries a small set of values (`default_image`,
`supervisor_image`, `image_pull_policy`, `guest_tls_ca/cert/key`,
`client_tls_secret_name`, `host_gateway_ip`, `enable_user_namespaces`) that
are inherited into each driver's `[openshell.drivers.<name>]` table when
the driver-specific table does not override them. The allowlist is
per-driver so a gateway-wide default cannot land in a driver that does not
understand it (e.g. `client_tls_secret_name` is K8s-only).

Driver-specific values that are not part of the inheritance allowlist
(e.g. K8s `namespace`, Podman `socket_path`, VM `vcpus`) only come from
the driver's own table.

## Operational Constraints

- Gateway TLS and client certificate distribution are deployment concerns owned
Expand Down
1 change: 1 addition & 0 deletions crates/openshell-driver-docker/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ futures = { workspace = true }
tokio-stream = { workspace = true }
tracing = { workspace = true }
bytes = { workspace = true }
serde = { workspace = true }
bollard = { version = "0.20" }
tar = "0.4"
tempfile = "3"
Expand Down
16 changes: 15 additions & 1 deletion crates/openshell-driver-docker/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,8 @@ pub trait SupervisorReadiness: Send + Sync + 'static {
}

/// Gateway-local configuration for the Docker compute driver.
#[derive(Debug, Clone, Default)]
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(default, deny_unknown_fields)]
pub struct DockerComputeConfig {
/// Optional override for the Linux `openshell-sandbox` binary mounted into containers.
pub supervisor_bin: Option<PathBuf>,
Expand All @@ -151,6 +152,19 @@ pub struct DockerComputeConfig {
pub network_name: String,
}

impl Default for DockerComputeConfig {
fn default() -> Self {
Self {
supervisor_bin: None,
supervisor_image: None,
guest_tls_ca: None,
guest_tls_cert: None,
guest_tls_key: None,
network_name: DEFAULT_DOCKER_NETWORK_NAME.to_string(),
}
}
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct DockerGuestTlsPaths {
pub(crate) ca: PathBuf,
Expand Down
1 change: 1 addition & 0 deletions crates/openshell-driver-kubernetes/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ tokio-stream = { workspace = true }
kube = { workspace = true }
kube-runtime = { workspace = true }
k8s-openapi = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
clap = { workspace = true }
tracing = { workspace = true }
Expand Down
32 changes: 30 additions & 2 deletions crates/openshell-driver-kubernetes/src/config.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

use openshell_core::config::{
DEFAULT_IMAGE_PULL_POLICY, DEFAULT_K8S_NAMESPACE, DEFAULT_SSH_HANDSHAKE_SKEW_SECS,
DEFAULT_SUPERVISOR_IMAGE,
};
use serde::{Deserialize, Serialize};

/// How the supervisor binary is delivered into sandbox pods.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum SupervisorSideloadMethod {
/// Mount the supervisor OCI image directly as a read-only volume
/// (requires Kubernetes >= v1.33 with the `ImageVolume` feature gate,
Expand Down Expand Up @@ -37,7 +44,8 @@ impl std::str::FromStr for SupervisorSideloadMethod {
}
}

#[derive(Debug, Clone)]
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(default, deny_unknown_fields)]
pub struct KubernetesComputeConfig {
pub namespace: String,
pub default_image: String,
Expand All @@ -59,3 +67,23 @@ pub struct KubernetesComputeConfig {
pub host_gateway_ip: String,
pub enable_user_namespaces: bool,
}

impl Default for KubernetesComputeConfig {
fn default() -> Self {
Self {
namespace: DEFAULT_K8S_NAMESPACE.to_string(),
default_image: String::new(),
image_pull_policy: DEFAULT_IMAGE_PULL_POLICY.to_string(),
supervisor_image: DEFAULT_SUPERVISOR_IMAGE.to_string(),
supervisor_image_pull_policy: String::new(),
supervisor_sideload_method: SupervisorSideloadMethod::default(),
grpc_endpoint: String::new(),
ssh_socket_path: "/run/openshell/ssh.sock".to_string(),
ssh_handshake_secret: String::new(),
ssh_handshake_skew_secs: DEFAULT_SSH_HANDSHAKE_SKEW_SECS,
client_tls_secret_name: String::new(),
host_gateway_ip: String::new(),
enable_user_namespaces: false,
}
}
}
14 changes: 11 additions & 3 deletions crates/openshell-driver-kubernetes/src/driver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2308,10 +2308,18 @@ mod tests {

#[test]
fn log_level_propagates_as_env_var_to_sandbox_pod() {
let spec = SandboxSpec { log_level: "debug".to_string(), ..SandboxSpec::default() };
let spec = SandboxSpec {
log_level: "debug".to_string(),
..SandboxSpec::default()
};
let cr = sandbox_to_k8s_spec(Some(&spec), &SandboxPodParams::default());
let env = cr["spec"]["podTemplate"]["spec"]["containers"][0]["env"].as_array().unwrap();
assert!(env.iter().any(|e| e["name"] == "OPENSHELL_LOG_LEVEL" && e["value"] == "debug"));
let env = cr["spec"]["podTemplate"]["spec"]["containers"][0]["env"]
.as_array()
.unwrap();
assert!(
env.iter()
.any(|e| e["name"] == "OPENSHELL_LOG_LEVEL" && e["value"] == "debug")
);
assert!(cr["spec"].get("logLevel").is_none());
}
}
3 changes: 2 additions & 1 deletion crates/openshell-driver-podman/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,8 @@ impl FromStr for ImagePullPolicy {
}
}

#[derive(Clone)]
#[derive(Clone, serde::Serialize, serde::Deserialize)]
#[serde(default, deny_unknown_fields)]
pub struct PodmanComputeConfig {
/// Path to the Podman API Unix socket.
/// Default: `$XDG_RUNTIME_DIR/podman/podman.sock` (Linux),
Expand Down
1 change: 1 addition & 0 deletions crates/openshell-server/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ bytes = { workspace = true }
pin-project-lite = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
toml = { workspace = true }
tokio-stream = { workspace = true }
sqlx = { workspace = true }
reqwest = { workspace = true }
Expand Down
Loading
Loading