Skip to content
Draft
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
8 changes: 4 additions & 4 deletions crates/openshell-cli/src/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -675,8 +675,8 @@ fn is_loopback_gateway_endpoint(endpoint: &str) -> bool {
/// would serve this endpoint.
///
/// Loopback endpoints (`localhost`, `127.0.0.1`, `::1`) resolve to the
/// `"openshell"` gateway name, matching the convention used by
/// `init-pki.sh` and the TLS cert resolver in `tls.rs`.
/// `"openshell"` gateway name, matching the convention used by local
/// `openshell-gateway generate-certs` and the TLS cert resolver in `tls.rs`.
fn mtls_certs_exist_for_endpoint(name: &str, endpoint: &str) -> bool {
let cert_name = if is_loopback_gateway_endpoint(endpoint) {
"openshell"
Expand Down Expand Up @@ -901,7 +901,7 @@ pub async fn gateway_add(

// Derive a gateway name from the hostname when none is provided.
// Loopback endpoints use the canonical "openshell" name, matching the
// convention in init-pki.sh and default_tls_dir.
// convention in local cert generation and default_tls_dir.
let derived_name;
let name = if let Some(n) = name {
n
Expand Down Expand Up @@ -7136,7 +7136,7 @@ mod tests {
});

// Loopback endpoints derive the canonical "openshell" gateway
// name, matching init-pki.sh and default_tls_dir conventions.
// name, matching local cert generation and default_tls_dir conventions.
let metadata = load_gateway_metadata("openshell").expect("load stored gateway");
assert_eq!(metadata.auth_mode.as_deref(), Some("plaintext"));
assert!(!metadata.is_remote);
Expand Down
8 changes: 4 additions & 4 deletions crates/openshell-core/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ use std::str::FromStr;
pub const DEFAULT_SSH_PORT: u16 = 2222;

/// Default gateway server port.
pub const DEFAULT_SERVER_PORT: u16 = 8080;
pub const DEFAULT_SERVER_PORT: u16 = 17670;

/// Default container stop timeout in seconds (SIGTERM → SIGKILL).
pub const DEFAULT_STOP_TIMEOUT_SECS: u32 = 10;
Expand All @@ -34,7 +34,7 @@ pub const DEFAULT_DOCKER_NETWORK_NAME: &str = "openshell-docker";
pub const DEFAULT_SERVICE_ROUTING_DOMAIN: &str = "openshell.localhost";

/// Default OCI image for the openshell-sandbox supervisor binary.
pub const DEFAULT_SUPERVISOR_IMAGE: &str = "openshell/supervisor:latest";
pub const DEFAULT_SUPERVISOR_IMAGE: &str = "ghcr.io/nvidia/openshell/supervisor:latest";

/// CDI device identifier for requesting all NVIDIA GPUs.
pub const CDI_GPU_DEVICE_ALL: &str = "nvidia.com/gpu=all";
Expand Down Expand Up @@ -451,7 +451,7 @@ impl Default for ServiceRoutingConfig {
}

fn default_bind_address() -> SocketAddr {
"127.0.0.1:8080".parse().expect("valid default address")
"127.0.0.1:17670".parse().expect("valid default address")
}

fn default_service_routing_domains() -> Vec<String> {
Expand Down Expand Up @@ -557,7 +557,7 @@ mod tests {

#[test]
fn config_defaults_to_loopback_bind_address() {
let expected: SocketAddr = "127.0.0.1:8080".parse().expect("valid address");
let expected: SocketAddr = "127.0.0.1:17670".parse().expect("valid address");
assert_eq!(Config::new(None).bind_address, expected);
}

Expand Down
27 changes: 27 additions & 0 deletions crates/openshell-core/src/paths.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,24 @@ pub fn openshell_config_dir() -> Result<PathBuf> {
Ok(xdg_config_dir()?.join("openshell"))
}

/// Resolve the XDG state base directory.
///
/// Returns `$XDG_STATE_HOME` if set, otherwise `$HOME/.local/state`.
pub fn xdg_state_dir() -> Result<PathBuf> {
if let Ok(path) = std::env::var("XDG_STATE_HOME") {
return Ok(PathBuf::from(path));
}
let home = std::env::var("HOME")
.into_diagnostic()
.wrap_err("HOME is not set")?;
Ok(PathBuf::from(home).join(".local").join("state"))
}

/// The top-level `OpenShell` state directory: `$XDG_STATE_HOME/openshell/`.
pub fn openshell_state_dir() -> Result<PathBuf> {
Ok(xdg_state_dir()?.join("openshell"))
}

/// Resolve the XDG data base directory.
///
/// Returns `$XDG_DATA_HOME` if set, otherwise `$HOME/.local/share`.
Expand Down Expand Up @@ -130,6 +148,15 @@ mod tests {
);
}

#[test]
fn openshell_state_dir_appends_openshell() {
let dir = openshell_state_dir().unwrap();
assert!(
dir.ends_with("openshell"),
"expected path ending with 'openshell', got: {dir:?}"
);
}

#[cfg(unix)]
#[test]
fn create_dir_restricted_sets_0o700() {
Expand Down
26 changes: 18 additions & 8 deletions crates/openshell-driver-docker/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -252,12 +252,6 @@ impl DockerComputeDriver {
docker_config: &DockerComputeConfig,
supervisor_readiness: Arc<dyn SupervisorReadiness>,
) -> CoreResult<Self> {
if docker_config.grpc_endpoint.trim().is_empty() {
return Err(Error::config(
"grpc_endpoint is required when using the docker compute driver",
));
}

let docker = Docker::connect_with_local_defaults()
.map_err(|err| Error::execution(format!("failed to create Docker client: {err}")))?;
let version = docker.version().await.map_err(|err| {
Expand All @@ -281,14 +275,24 @@ impl DockerComputeDriver {
let host_gateway_ip = parse_optional_host_gateway_ip(&docker_config.host_gateway_ip)?;
let gateway_route =
docker_gateway_route(&info, bridge_gateway_ip, gateway_port, host_gateway_ip);
let mut docker_config = docker_config.clone();
if docker_config.grpc_endpoint.trim().is_empty() {
let scheme = if docker_guest_tls_configured(&docker_config) {
"https"
} else {
"http"
};
docker_config.grpc_endpoint =
format!("{scheme}://{HOST_OPENSHELL_INTERNAL}:{gateway_port}");
}
let grpc_endpoint = docker_container_openshell_endpoint(
&docker_config.grpc_endpoint,
HOST_OPENSHELL_INTERNAL,
gateway_port,
);
let daemon_arch = normalize_docker_arch(version.arch.as_deref().unwrap_or_default());
let supervisor_bin = resolve_supervisor_bin(&docker, docker_config, &daemon_arch).await?;
let guest_tls = docker_guest_tls_paths(docker_config)?;
let supervisor_bin = resolve_supervisor_bin(&docker, &docker_config, &daemon_arch).await?;
let guest_tls = docker_guest_tls_paths(&docker_config)?;

let driver = Self {
docker: Arc::new(docker),
Expand Down Expand Up @@ -2009,6 +2013,12 @@ pub(crate) fn validate_linux_elf_binary(path: &Path) -> CoreResult<()> {
Ok(())
}

fn docker_guest_tls_configured(docker_config: &DockerComputeConfig) -> bool {
docker_config.guest_tls_ca.is_some()
&& docker_config.guest_tls_cert.is_some()
&& docker_config.guest_tls_key.is_some()
}

pub(crate) fn docker_guest_tls_paths(
docker_config: &DockerComputeConfig,
) -> CoreResult<Option<DockerGuestTlsPaths>> {
Expand Down
10 changes: 5 additions & 5 deletions crates/openshell-driver-docker/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,23 +74,23 @@ fn container_visible_endpoint_rewrites_loopback_hosts() {
HOST_OPENSHELL_INTERNAL,
DEFAULT_SERVER_PORT,
),
"https://host.openshell.internal:8080/"
"https://host.openshell.internal:17670/"
);
assert_eq!(
docker_container_openshell_endpoint(
"http://127.0.0.1:8080",
HOST_OPENSHELL_INTERNAL,
DEFAULT_SERVER_PORT,
),
"http://host.openshell.internal:8080/"
"http://host.openshell.internal:17670/"
);
assert_eq!(
docker_container_openshell_endpoint(
"https://gateway.internal:8443",
HOST_OPENSHELL_INTERNAL,
DEFAULT_SERVER_PORT,
),
"https://host.openshell.internal:8080/"
"https://host.openshell.internal:17670/"
);
}

Expand Down Expand Up @@ -273,7 +273,7 @@ fn docker_gateway_route_uses_bridge_gateway_for_linux_docker() {
assert_eq!(
route,
DockerGatewayRoute::Bridge {
bind_address: "172.18.0.1:8080".parse().unwrap(),
bind_address: "172.18.0.1:17670".parse().unwrap(),
host_alias_ip: IpAddr::V4(Ipv4Addr::new(172, 18, 0, 1)),
}
);
Expand Down Expand Up @@ -303,7 +303,7 @@ fn docker_gateway_route_prefers_configured_host_gateway_ip() {
assert_eq!(
route,
DockerGatewayRoute::Bridge {
bind_address: "172.20.0.4:8080".parse().unwrap(),
bind_address: "172.20.0.4:17670".parse().unwrap(),
host_alias_ip: IpAddr::V4(Ipv4Addr::new(172, 20, 0, 4)),
}
);
Expand Down
13 changes: 6 additions & 7 deletions crates/openshell-driver-podman/NETWORKING.md
Original file line number Diff line number Diff line change
Expand Up @@ -357,9 +357,9 @@ Supervisor proxy in container netns

The Podman driver auto-detects the callback endpoint scheme based on whether
TLS client certificates are configured. When the RPM's auto-generated PKI is in
place, the endpoint is `https://host.containers.internal:8080` and the
place, the endpoint is `https://host.containers.internal:17670` and the
supervisor connects with mTLS. Without TLS configuration, it falls back to
`http://host.containers.internal:8080`.
`http://host.containers.internal:<gateway-port>`.

```text
Supervisor in container netns
Expand All @@ -382,10 +382,9 @@ Gateway
9. Same gRPC channel reused for RelayStream calls
```

The gateway binds to `0.0.0.0` by default in the RPM packaging. mTLS prevents
unauthenticated access even though the gateway is reachable from the network.
Client certificates are auto-generated by `init-pki.sh` on first start and
bind-mounted into sandbox containers by the Podman driver.
The gateway binds to `127.0.0.1:17670` by default in the RPM packaging. Client
certificates are auto-generated by `openshell-gateway generate-certs` on first
start and bind-mounted into sandbox containers by the Podman driver.

## Differences from the Kubernetes Driver

Expand All @@ -412,7 +411,7 @@ published ports, or the supervisor relay.

| Port | Component | Purpose |
|---|---|---|
| `8080` | Gateway | gRPC and HTTP multiplexed default server port. |
| `17670` | Gateway | Default local gRPC and HTTP multiplexed server port. |
| `2222` | Sandbox | Container port mapping default for the SSH compatibility port. |
| `3128` | Sandbox proxy | HTTP CONNECT proxy inside the sandbox network model. |
| `0` | Host | Ephemeral host port requested for the container SSH compatibility port. |
14 changes: 7 additions & 7 deletions crates/openshell-driver-podman/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,10 +120,10 @@ connection back to the gateway. On SELinux systems, the bind mounts include
Podman's shared relabel option so the container process can read the files.

The RPM packaging auto-generates a self-signed PKI on first start via
`init-pki.sh`. Client certs are placed in the CLI auto-discovery directory
(`~/.config/openshell/gateways/openshell/mtls/`) so the CLI connects with mTLS
without manual configuration. See `deploy/rpm/CONFIGURATION.md` for the full
RPM configuration reference.
`openshell-gateway generate-certs`. Client certs are placed in the CLI
auto-discovery directory (`~/.config/openshell/gateways/openshell/mtls/`) so
the CLI connects with mTLS without manual configuration. See
`deploy/rpm/CONFIGURATION.md` for the full RPM configuration reference.

## Network Model

Expand All @@ -134,7 +134,7 @@ the supervisor for sandbox process isolation.
```mermaid
graph TB
subgraph Host
GW["Gateway Server<br/>127.0.0.1:8080"]
GW["Gateway Server<br/>127.0.0.1:17670"]
PS["Podman Socket"]
end

Expand Down Expand Up @@ -289,11 +289,11 @@ Podman resources after out-of-band container removal or label drift.
| `OPENSHELL_SANDBOX_IMAGE` | `--sandbox-image` | From gateway config | Default OCI image for sandboxes. |
| `OPENSHELL_SANDBOX_IMAGE_PULL_POLICY` | `--sandbox-image-pull-policy` | `missing` | Pull policy: `always`, `missing`, `never`, or `newer`. |
| `OPENSHELL_GRPC_ENDPOINT` | `--grpc-endpoint` | Auto-detected via `host.containers.internal` | Gateway gRPC endpoint for sandbox callbacks. |
| `OPENSHELL_GATEWAY_PORT` | `--gateway-port` | `8080` | Gateway port used for endpoint auto-detection by the standalone binary. |
| `OPENSHELL_GATEWAY_PORT` | `--gateway-port` | `17670` | Gateway port used for endpoint auto-detection by the standalone binary. |
| `OPENSHELL_NETWORK_NAME` | `--network-name` | `openshell` | Podman bridge network name. |
| `OPENSHELL_SANDBOX_SSH_SOCKET_PATH` | `--sandbox-ssh-socket-path` | `/run/openshell/ssh.sock` | Supervisor Unix socket path in `PodmanComputeConfig`. |
| `OPENSHELL_STOP_TIMEOUT` | `--stop-timeout` | `10` | Container stop timeout in seconds. |
| `OPENSHELL_SUPERVISOR_IMAGE` | `--supervisor-image` | `openshell/supervisor:latest` through the gateway, required standalone | OCI image containing the supervisor binary. |
| `OPENSHELL_SUPERVISOR_IMAGE` | `--supervisor-image` | `ghcr.io/nvidia/openshell/supervisor:latest` through the gateway, required standalone | OCI image containing the supervisor binary. |
| `OPENSHELL_PODMAN_TLS_CA` | `--podman-tls-ca` | unset | Host path to the CA certificate mounted for sandbox mTLS. |
| `OPENSHELL_PODMAN_TLS_CERT` | `--podman-tls-cert` | unset | Host path to the client certificate mounted for sandbox mTLS. |
| `OPENSHELL_PODMAN_TLS_KEY` | `--podman-tls-key` | unset | Host path to the client private key mounted for sandbox mTLS. |
Expand Down
2 changes: 1 addition & 1 deletion crates/openshell-driver-podman/src/container.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1036,7 +1036,7 @@ mod tests {
let vol = &image_volumes[0];
assert_eq!(
vol["source"].as_str(),
Some("openshell/supervisor:latest"),
Some("ghcr.io/nvidia/openshell/supervisor:latest"),
"image volume source should be the supervisor image"
);
assert_eq!(
Expand Down
8 changes: 4 additions & 4 deletions crates/openshell-server/src/certgen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@
//! - **Kubernetes mode** (default): create two `kubernetes.io/tls` Secrets
//! in the supplied namespace. Used by the Helm pre-install hook. Requires
//! `--namespace`, `--server-secret-name`, `--client-secret-name`.
//! - **Local mode** (`--output-dir <DIR>`): write PEMs to a filesystem layout
//! matching `deploy/rpm/init-pki.sh`. Used by the RPM systemd unit's
//! `ExecStartPre`. Also copies client materials to
//! - **Local mode** (`--output-dir <DIR>`): write PEMs to the local package
//! filesystem layout. Used by systemd units' `ExecStartPre`. Also copies
//! client materials to
//! `$XDG_CONFIG_HOME/openshell/gateways/openshell/mtls/` so the local CLI
//! picks them up automatically.
//!
Expand Down Expand Up @@ -216,7 +216,7 @@ enum LocalAction {
Create,
}

/// Layout under `<dir>` matches `deploy/rpm/init-pki.sh`:
/// Layout under `<dir>`:
///
/// ```text
/// <dir>/ca.crt
Expand Down
Loading
Loading