Gateway Configuration File

View as Markdown

The OpenShell gateway reads its configuration from a TOML file when --config or OPENSHELL_GATEWAY_CONFIG is set. When neither is set, the gateway reads $XDG_CONFIG_HOME/openshell/gateway.toml if that file exists. If no config file exists, the gateway starts from built-in defaults. Gateway process flags and gateway OPENSHELL_* environment variables override the file. Compute driver settings live in the driver TOML tables. See RFC 0003 for the full schema.

Source Precedence

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

database_url is env-only. The loader rejects it when it appears in the file. When OPENSHELL_DB_URL is unset, the gateway stores its SQLite database under $XDG_STATE_HOME/openshell/gateway/openshell.db.

Package-Managed Locations

Package-managed gateways do not require a TOML file. Create one at the package’s optional config location when you need to override built-in defaults. Set OPENSHELL_GATEWAY_CONFIG in the launch environment to use a different file.

PackageOptional Gateway TOML location
Homebrew$XDG_CONFIG_HOME/openshell/gateway.toml when it exists, otherwise an existing Homebrew prefix config such as /opt/homebrew/var/openshell/gateway.toml.
Debian/Ubuntu$XDG_CONFIG_HOME/openshell/gateway.toml, usually ~/.config/openshell/gateway.toml for the systemd user service.
Fedora/RHEL RPM$XDG_CONFIG_HOME/openshell/gateway.toml, usually ~/.config/openshell/gateway.toml for the systemd user service.
Snap$SNAP_COMMON/gateway.toml, usually /var/snap/openshell/common/gateway.toml.

Layout

The file is rooted at [openshell]. Gateway-wide settings live under [openshell.gateway]. Each compute driver owns its own [openshell.drivers.<name>] table. Shared keys set at gateway scope are inherited into driver tables when not overridden.

1[openshell]
2version = 1
3
4[openshell.gateway]
5# ... gateway-wide settings ...
6
7[openshell.gateway.tls]
8# ... gateway listener TLS ...
9
10[openshell.gateway.oidc]
11# ... JWT bearer auth ...
12
13[openshell.drivers.kubernetes]
14# ... driver-specific settings ...

Full Example

A complete gateway configuration covering every section. Trim to the fields you need.

1# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2# SPDX-License-Identifier: Apache-2.0
3
4[openshell]
5version = 1
6
7[openshell.gateway]
8bind_address = "0.0.0.0:8080"
9health_bind_address = "0.0.0.0:8081"
10metrics_bind_address = "0.0.0.0:9090"
11
12log_level = "info"
13
14# When empty, the gateway auto-detects Kubernetes, then Podman, then Docker.
15# VM is never auto-detected and requires an explicit entry here.
16compute_drivers = ["kubernetes"]
17
18sandbox_namespace = "openshell"
19ssh_session_ttl_secs = 3600
20
21# Subject Alternative Names baked into the gateway server certificate.
22# Wildcard DNS SANs (e.g. "*.dev.openshell.localhost") also enable sandbox
23# service URLs under that domain.
24server_sans = ["openshell", "*.dev.openshell.localhost"]
25# Allow plaintext HTTP routing for loopback sandbox service URLs.
26enable_loopback_service_http = true
27
28# Set true only for local plaintext gateways or trusted TLS termination.
29disable_tls = false
30
31# Shared driver defaults. These inherit into [openshell.drivers.<name>] tables
32# when the driver-specific table does not override them.
33default_image = "ghcr.io/nvidia/openshell/sandbox:latest"
34# Defaults to the gateway version; override to pin a specific build.
35# supervisor_image = "ghcr.io/nvidia/openshell/supervisor:<version>"
36client_tls_secret_name = "openshell-client-tls"
37service_account_name = "openshell-sandbox"
38host_gateway_ip = "10.0.0.1"
39enable_user_namespaces = false
40sa_token_ttl_secs = 3600
41guest_tls_ca = "/etc/openshell/certs/ca.pem"
42guest_tls_cert = "/etc/openshell/certs/client.pem"
43guest_tls_key = "/etc/openshell/certs/client-key.pem"
44
45# Optional gRPC rate limit. Both values must be positive to enable the limit.
46# Set either value to 0, or omit both, to disable rate limiting.
47grpc_rate_limit_requests = 120
48grpc_rate_limit_window_seconds = 60
49
50# Optional exact provider-profile source composition. When omitted, the
51# gateway uses builtin + user.
52provider_profile_sources = [
53 { type = "builtin" },
54 { type = "user" },
55]
56
57# Gateway listener TLS (distinct from the per-driver guest_tls_*).
58[openshell.gateway.tls]
59cert_path = "/etc/openshell/certs/gateway.pem"
60key_path = "/etc/openshell/certs/gateway-key.pem"
61client_ca_path = "/etc/openshell/certs/client-ca.pem"
62require_client_auth = false
63
64[openshell.gateway.gateway_jwt]
65signing_key_path = "/etc/openshell/jwt/signing.pem"
66public_key_path = "/etc/openshell/jwt/public.pem"
67kid_path = "/etc/openshell/jwt/kid"
68gateway_id = "openshell"
69# Omit or set to 0 only for local single-player Docker, Podman, or VM gateways.
70ttl_secs = 3600
71
72[openshell.gateway.auth]
73allow_unauthenticated_users = false
74
75[openshell.gateway.mtls_auth]
76enabled = false
77
78[openshell.gateway.oidc]
79issuer = "https://idp.example.com/realms/openshell"
80audience = "openshell-cli"
81jwks_ttl_secs = 3600
82roles_claim = "realm_access.roles"
83admin_role = "openshell-admin"
84user_role = "openshell-user"
85scopes_claim = ""
86
87[[openshell.gateway.interceptors]]
88name = "quota"
89grpc_endpoint = "unix:///run/openshell/interceptors/quota.sock"
90order = 10
91failure_policy = "fail_closed"
92binding_policy = "allowlist"
93timeout = "500ms"
94max_response_bytes = 1048576
95max_patches = 32
96
97[[openshell.gateway.interceptors.bindings]]
98rpc = "openshell.v1.OpenShell/CreateSandbox"
99phases = ["modify_operation", "validate"]
100failure_policy = "fail_closed"
101
102[[openshell.gateway.interceptors.bindings]]
103rpc = "openshell.v1.OpenShell/UpdateConfig"
104phases = ["validate"]

Local Docker, Podman, and VM gateways can also set [openshell.gateway.mtls_auth] enabled = true to authenticate CLI callers from verified client certificates. Kubernetes deployments must leave this unset and use OIDC or a trusted access proxy; the Helm chart does not render this table.

[openshell.gateway.gateway_jwt] ttl_secs controls gateway-minted sandbox JWT lifetime. When omitted, it defaults to 0: the token exp claim and expires_at_ms response field become 0, and the sandbox JWT does not expire. Use that default only for local single-player Docker, Podman, or VM gateways. Kubernetes and other shared deployments should set a positive TTL; Helm renders 3600 seconds by default, and the gateway logs a warning when a Kubernetes gateway uses 0.

[openshell.gateway.auth] allow_unauthenticated_users = true is an unsafe local-development and trusted-proxy escape hatch. It accepts user-facing CLI/API calls without OIDC or mTLS credentials while sandbox supervisors still authenticate with gateway-minted sandbox JWTs. Leave it false for shared and production gateways.

[[openshell.gateway.interceptors]] configures gateway-side interceptor services. The gateway calls each service’s Describe RPC at startup, validates its declared OpenShell RPC bindings against the compiled service descriptor, and applies matching phases from a central gRPC middleware path. Interceptors can target only methods in the gateway’s built-in allowlist of unary mutation RPCs. New RPCs are non-interceptable until they are deliberately added to that allowlist; adding one does not require handler-specific interceptor code. Request bodies are exposed as protobuf JSON objects. Fields marked secret in the protobuf schema are recursively omitted from requests and post-commit responses. Interceptors cannot patch an omitted field or a containing object.

binding_policy controls how the manifest and operator binding configuration combine:

  • dynamic enables valid manifest bindings and treats configured entries as optional narrowing overrides. This is the compatibility default. The gateway logs a startup warning because the interceptor controls its non-secret RPC authority.
  • allowlist enables only configured RPC selectors and phases. The gateway ignores and logs extra manifest declarations, but fails startup when the manifest omits a configured RPC or phase.
  • exact requires the configured and manifest RPC selectors and phases to match exactly.

Bindings under allowlist and exact require rpc or service plus method, and a nonempty phases list. They match by RPC rather than manifest binding ID. Duplicate selectors, id, and disabled are invalid in these modes; omit a binding to disable it. Binding failure policy comes from the binding, then the configured service, then defaults to fail_closed. Manifest failure policies do not override strict-mode operator configuration.

Bindings that include post_commit must resolve to failure_policy = "fail_open"; the gateway rejects fail-closed post-commit configuration at startup. Post-commit evaluation is observational. If response observation or interceptor evaluation fails after a handler succeeds, the gateway logs and counts the failure but returns the committed response unchanged.

provider_profile_sources selects the exact ordered provider-profile source set. Omit it to use the built-in and user-managed sources. Use { type = "builtin" }, { type = "user" }, and { type = "interceptor", name = "<configured-interceptor-name>" } entries to compose a catalog. An interceptor entry must reference a configured service whose manifest advertises provider_profiles = true. Selecting only an interceptor makes that catalog authoritative; include local sources explicitly to compose them. Empty or duplicate source lists, unknown interceptor names, and duplicate normalized profile IDs fail closed. Source order controls collection and diagnostics, not override precedence.

For an authoritative interceptor catalog, select only that interceptor:

1[openshell.gateway]
2provider_profile_sources = [
3 { type = "interceptor", name = "provider-governance" },
4]

The gateway validates snapshot structure and provider-profile semantics. It treats a configured interceptor as the source trust boundary and does not verify signature, hash, or key annotations in profile payloads.

failure_policy accepts fail_closed or fail_open. timeout accepts ms and s suffixes. In dynamic mode, binding overrides may select a manifest binding by id, rpc, or service plus method; they can disable a binding, narrow its phases, or override its failure policy.

image_pull_policy is intentionally not a shared gateway key. Kubernetes and Docker use Always, IfNotPresent, or Never. Podman uses always, missing, never, or newer. Set it inside the relevant driver table.

Driver References

Each example is a complete TOML file for one compute driver. The examples repeat [openshell] and [openshell.gateway] so they stay copyable, and the driver tables list the accepted driver-specific keys. Driver-specific values override inherited gateway defaults. The gateway rejects unknown driver fields after inheritance is merged.

Kubernetes

The gateway runs as a Pod and creates sandbox Pods in another namespace. mTLS material for sandboxes is delivered through a Kubernetes Secret rather than host-side file paths.

1[openshell]
2version = 1
3
4[openshell.gateway]
5bind_address = "0.0.0.0:8080"
6health_bind_address = "0.0.0.0:8081"
7metrics_bind_address = "0.0.0.0:9090"
8log_level = "info"
9compute_drivers = ["kubernetes"]
10
11[openshell.gateway.tls]
12cert_path = "/etc/openshell-tls/server/tls.crt"
13key_path = "/etc/openshell-tls/server/tls.key"
14client_ca_path = "/etc/openshell-tls/client-ca/ca.crt"
15
16[openshell.drivers.kubernetes]
17namespace = "agents"
18service_account_name = "openshell-sandbox"
19default_image = "ghcr.io/nvidia/openshell/sandbox:latest"
20image_pull_policy = "IfNotPresent"
21image_pull_secrets = ["regcred"]
22# Defaults to the gateway version; override to pin a specific build.
23# supervisor_image = "ghcr.io/nvidia/openshell/supervisor:<version>"
24supervisor_image_pull_policy = "IfNotPresent"
25# Use the image volume on Kubernetes >= 1.35 (GA in 1.36); switch to "init-container"
26# on older clusters or where the ImageVolume feature gate is off.
27supervisor_sideload_method = "image-volume"
28# "combined" runs the existing single supervisor container with full process,
29# filesystem, and network enforcement in the agent container. "sidecar" moves
30# pod-level network enforcement and gateway session handling into a network sidecar.
31topology = "combined"
32grpc_endpoint = "https://openshell-gateway.agents.svc:8080"
33ssh_socket_path = "/run/openshell/ssh.sock"
34client_tls_secret_name = "openshell-client-tls"
35host_gateway_ip = "10.0.0.1"
36enable_user_namespaces = false
37app_armor_profile = "Unconfined"
38workspace_default_storage_size = "10Gi"
39# Kubernetes RuntimeClass applied to sandbox pods when the API request does
40# not specify one. Empty (default) = omit the field, using the cluster default.
41# default_runtime_class_name = "kata-containers"
42# Kubelet clamps projected tokens below 600 seconds. The driver caps values at 86400.
43sa_token_ttl_secs = 3600
44# Optional SPIFFE Workload API socket mounted into sandbox pods for dynamic
45# provider token grants. Use an absolute path under a dedicated directory;
46# shared roots such as /run, /var, /tmp, and /etc are rejected.
47# Supervisor-to-gateway auth still uses gateway JWTs.
48provider_spiffe_workload_api_socket_path = "/spiffe-workload-api/spire-agent.sock"
49# Explicit sandbox UID/GID for the supervisor container securityContext and
50# PVC init container. When unset, the driver auto-detects from OpenShift SCC
51# namespace annotations (openshift.io/sa.scc.uid-range) if present, falling
52# back to 1000 on non-OpenShift clusters.
53# sandbox_uid = 1500
54# sandbox_gid = 1500
55
56[openshell.drivers.kubernetes.sidecar]
57# UID used by relaxed long-running network sidecars. Strict process/binary-aware
58# sidecars run as UID 0 so Kubernetes grants the required /proc inspection
59# capabilities into the effective set. In sidecar topology the network init
60# container installs nftables rules that exempt the effective sidecar UID.
61proxy_uid = 1337
62# Keep process/binary-aware network policy enabled in sidecar topology. Set
63# false to run the sidecar as proxy_uid, drop the sidecar's extra /proc
64# inspection capabilities, and enforce endpoint/L7 policy without matching
65# policy.binaries.
66process_binary_aware_network_policy = true

Docker

Sandboxes run as containers on a local bridge network. The supervisor binary is bind-mounted from the host (no in-cluster image pull required); guest mTLS material is supplied as host paths.

1[openshell]
2version = 1
3
4[openshell.gateway]
5bind_address = "127.0.0.1:17670"
6log_level = "info"
7compute_drivers = ["docker"]
8
9[openshell.drivers.docker]
10default_image = "ghcr.io/nvidia/openshell/sandbox:latest"
11# Docker vocabulary: Always | IfNotPresent | Never. Empty behaves like IfNotPresent.
12image_pull_policy = "IfNotPresent"
13sandbox_namespace = "docker-dev"
14# Empty auto-detects https://host.openshell.internal:<gateway-port> when guest TLS is set.
15grpc_endpoint = "https://host.openshell.internal:17670"
16# Skip the image-pull-and-extract step by pointing at a locally built binary.
17supervisor_bin = "/usr/local/libexec/openshell/openshell-sandbox"
18# When supervisor_bin is omitted, Docker extracts /openshell-sandbox from this image.
19# Defaults to the gateway version; override to pin a specific build.
20# supervisor_image = "ghcr.io/nvidia/openshell/supervisor:<version>"
21guest_tls_ca = "/etc/openshell/certs/ca.pem"
22guest_tls_cert = "/etc/openshell/certs/client.pem"
23guest_tls_key = "/etc/openshell/certs/client-key.pem"
24network_name = "openshell-docker"
25host_gateway_ip = "172.17.0.1"
26ssh_socket_path = "/run/openshell/ssh.sock"
27# Unsafe operator override. Host bind mounts, including Docker local-driver
28# bind-backed volumes, expose gateway-host paths inside sandboxes and can
29# negate OpenShell isolation and filesystem controls.
30enable_bind_mounts = false
31# Set to 0 to leave Docker's runtime default unchanged.
32sandbox_pids_limit = 2048

Podman

Sandboxes run as Podman containers on a user-mode bridge network. The supervisor image is mounted read-only via Podman’s type=image mount; guest mTLS material is supplied as host paths.

1[openshell]
2version = 1
3
4[openshell.gateway]
5bind_address = "127.0.0.1:17670"
6log_level = "info"
7compute_drivers = ["podman"]
8
9[openshell.drivers.podman]
10# Rootless socket path. For root Podman use /run/podman/podman.sock.
11socket_path = "/run/user/1000/podman/podman.sock"
12default_image = "ghcr.io/nvidia/openshell/sandbox:latest"
13image_pull_policy = "missing" # always | missing | never | newer
14grpc_endpoint = "https://host.containers.internal:17670"
15# The gateway overwrites gateway_port from bind_address at runtime.
16gateway_port = 17670
17network_name = "openshell"
18# Omit for the platform default: empty on Linux, 192.168.127.254 on macOS Podman machine.
19# Set "" to force Podman's host-gateway resolver.
20# host_gateway_ip = "192.168.127.254"
21sandbox_ssh_socket_path = "/run/openshell/ssh.sock"
22stop_timeout_secs = 10
23# Defaults to the gateway version; override to pin a specific build.
24# supervisor_image = "ghcr.io/nvidia/openshell/supervisor:<version>"
25guest_tls_ca = "/etc/openshell/certs/ca.pem"
26guest_tls_cert = "/etc/openshell/certs/client.pem"
27guest_tls_key = "/etc/openshell/certs/client-key.pem"
28# Unsafe operator override. Host bind mounts, including Podman local-driver
29# bind-backed volumes, expose gateway-host paths inside sandboxes and can
30# negate OpenShell isolation and filesystem controls.
31enable_bind_mounts = false
32# Set to 0 to leave Podman's runtime default unchanged.
33sandbox_pids_limit = 2048
34# Health check interval in seconds. Lower values detect readiness faster
35# but increase process churn (each check spawns a conmon subprocess).
36# Set to 0 to disable health checks entirely. Default: 10.
37health_check_interval_secs = 10

MicroVM

Each sandbox runs inside its own libkrun microVM managed by the standalone openshell-driver-vm subprocess. Use this driver when you want stronger isolation than container namespaces alone.

1[openshell]
2version = 1
3
4[openshell.gateway]
5bind_address = "127.0.0.1:17670"
6log_level = "info"
7# VM is never auto-detected; an explicit entry here is required.
8compute_drivers = ["vm"]
9
10[openshell.drivers.vm]
11state_dir = "/var/lib/openshell/vm"
12# Where the gateway looks for the openshell-driver-vm subprocess binary.
13driver_dir = "/usr/local/libexec/openshell"
14default_image = "ghcr.io/nvidia/openshell/sandbox:latest"
15grpc_endpoint = "https://host.containers.internal:17670"
16# Empty falls back to default_image.
17bootstrap_image = "ghcr.io/nvidia/openshell/sandbox:latest"
18krun_log_level = 1
19vcpus = 2
20mem_mib = 2048
21overlay_disk_mib = 4096
22guest_tls_ca = "/var/lib/openshell/guest-tls/ca.pem"
23guest_tls_cert = "/var/lib/openshell/guest-tls/client.pem"
24guest_tls_key = "/var/lib/openshell/guest-tls/client-key.pem"
25# Resolved sandbox UID/GID for the rootfs /etc/passwd entry.
26# Defaults to 10001 when unset; matching GID is used if sandbox_gid is empty.
27# sandbox_uid = 20001

Extension Driver

Extension drivers run outside the gateway and expose the compute_driver.proto gRPC service on a Unix socket. Use a non-reserved driver name; built-in names such as vm, docker, podman, and kubernetes cannot be selected through unmanaged socket endpoints. The selected driver name is the key used for driver-owned sandbox config such as template.driver_config.<name>.

1[openshell]
2version = 1
3
4[openshell.gateway]
5bind_address = "127.0.0.1:17670"
6log_level = "info"
7compute_drivers = ["kyma"]
8
9[openshell.drivers.kyma]
10socket_path = "/run/openshell/kyma-compute-driver.sock"