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
2 changes: 2 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,13 @@ These pipelines connect skills into end-to-end workflows. Individual skill files
| `crates/openshell-ocsf/` | OCSF logging | OCSF v1.7.0 event types, builders, shorthand/JSONL formatters, tracing layers |
| `crates/openshell-otel/` | OpenTelemetry support | Shared OTLP trace provider, resource, and tracing-layer construction |
| `crates/openshell-core/` | Shared core | Common types, configuration, error handling |
| `crates/openshell-extension-core/` | Extension core | Shared extension identity, JWT claims, bearer-token rotation, and TLS transport primitives |
| `crates/openshell-sdk/` | Shared client SDK | Async Rust gateway client (gRPC transport, TLS, OIDC refresh, edge tunnel); consumed by CLI, TUI, and `@openshell/sdk` |
| `crates/openshell-providers/` | Provider management | Credential provider backends |
| `crates/openshell-tui/` | Terminal UI | Ratatui-based dashboard for monitoring |
| `crates/openshell-driver-kubernetes-secrets/` | Kubernetes Secrets credential driver | In-process `CredentialDriver` backend for OpenShell-managed K8s Secret storage |
| `crates/openshell-driver-vault/` | Vault credential driver | In-process `CredentialDriver` backend for Vault-compatible KV storage |
| `crates/openshell-driver-db-credstore/` | Database credential driver | In-process `CredentialDriver` backend for gateway database credential storage |
| `crates/openshell-driver-kubernetes/` | Kubernetes compute driver | In-process `ComputeDriver` backend for K8s sandbox pods |
| `crates/openshell-driver-docker/` | Docker compute driver | In-process `ComputeDriver` backend for local Docker sandbox containers |
| `crates/openshell-driver-podman/` | Podman compute driver | In-process `ComputeDriver` backend for local Podman sandbox containers |
Expand Down
1 change: 0 additions & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,6 @@ Skills live in `.agents/skills/`. Your agent's harness can discover and load the
| Contributing | `create-github-issue` | Create well-structured GitHub issues |
| Contributing | `create-github-pr` | Create pull requests with proper conventions |
| Reviewing | `review-github-pr` | Summarize PR diffs and key design decisions |
| Reviewing | `review-security-changes` | Review code changes for security vulnerabilities and boundary regressions |
| Reviewing | `review-security-issue` | Assess security issues for severity and remediation |
| Reviewing | `fix-security-issue` | Implement an approved security remediation plan |
| Reviewing | `watch-github-actions` | Monitor CI pipeline status and logs |
Expand Down
24 changes: 22 additions & 2 deletions Cargo.lock

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

7 changes: 7 additions & 0 deletions architecture/gateway.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,13 @@ until deliberately added to this allowlist. Interception remains centralized:
allowlisting a unary RPC does not require method-specific gateway
instrumentation.

Remote extension clients share `openshell-extension-core` transport and bearer
primitives. When gateway JWT signing is configured, the gateway mints
short-lived, exact-audience EdDSA credentials for middleware and interceptors,
rotates their in-memory slots without rebuilding clients, and publishes the
public verification key at `/.well-known/jwks.json`. HTTPS extensions can pin
an operator-provided CA while retaining endpoint-hostname verification.

Each configured interceptor selects a binding policy. `dynamic` accepts valid
manifest declarations and preserves the compatibility behavior. `allowlist`
enables only operator-configured RPCs and phases, while `exact` requires the
Expand Down
7 changes: 7 additions & 0 deletions architecture/sandbox.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,13 @@ generation and preserves the last-known-good generation if preparation fails.
Policy-only updates reuse the connected registry, so an external middleware
outage cannot block unrelated policy changes.

For authenticated operator middleware, the supervisor requests credentials by
registration name through `RefreshSandboxToken`. The gateway resolves names
against the effective policy and mints exact-audience credentials. The
supervisor keeps them in refreshable in-memory slots outside stable middleware
configuration, so rotation neither changes `config_revision` nor reconnects
the registry. Public custom-CA PEM travels with the stable registration.

Middleware cannot observe injected credentials or mutate supervisor-owned
credential, routing, or framing headers. Body transformations are re-evaluated
against body-aware L7 policy before later stages or the upstream can observe
Expand Down
1 change: 1 addition & 0 deletions crates/openshell-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ license.workspace = true
repository.workspace = true

[dependencies]
openshell-extension-core = { path = "../openshell-extension-core" }
glob = { workspace = true }
prost = { workspace = true }
prost-types = { workspace = true }
Expand Down
35 changes: 35 additions & 0 deletions crates/openshell-core/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
//! Configuration management for `OpenShell` components.

use serde::{Deserialize, Serialize};
use std::borrow::Cow;
use std::collections::BTreeMap;
use std::fmt;
#[cfg(unix)]
Expand Down Expand Up @@ -630,6 +631,14 @@ pub struct GatewayInterceptorConfig {
/// Interceptor gRPC endpoint. Supports `http://`, `https://`, and
/// `unix://` endpoints.
pub grpc_endpoint: String,
/// Optional PEM trust-root bundle for an HTTPS endpoint. The gateway
/// loads this file during interceptor initialization.
#[serde(default)]
pub tls_ca_cert_path: Option<PathBuf>,
/// Exact JWT audience for this service. When omitted, a kind-scoped value
/// is derived from the configured registration name.
#[serde(default)]
pub audience: Option<String>,
/// Deterministic service ordering. Lower values run first.
#[serde(default)]
pub order: i32,
Expand All @@ -655,6 +664,19 @@ pub struct GatewayInterceptorConfig {
pub bindings: Vec<GatewayInterceptorBindingOverride>,
}

impl GatewayInterceptorConfig {
/// Resolve the configured JWT audience to its deterministic default.
pub fn resolved_audience(&self) -> Cow<'_, str> {
self.audience
.as_deref()
.filter(|audience| !audience.is_empty())
.map_or_else(
|| Cow::Owned(format!("urn:openshell:extension:interceptor:{}", self.name)),
Cow::Borrowed,
)
}
}

/// Operator policy for authorizing interceptor manifest bindings.
#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
Expand Down Expand Up @@ -1205,6 +1227,19 @@ mod tests {
defaulted.binding_policy,
GatewayInterceptorBindingPolicy::Dynamic
);
assert_eq!(
defaulted.resolved_audience(),
"urn:openshell:extension:interceptor:governance"
);
let explicitly_empty = GatewayInterceptorConfig {
name: "governance".to_string(),
audience: Some(String::new()),
..GatewayInterceptorConfig::default()
};
assert_eq!(
explicitly_empty.resolved_audience(),
"urn:openshell:extension:interceptor:governance"
);
assert_eq!(allowlist, GatewayInterceptorBindingPolicy::Allowlist);
assert_eq!(exact, GatewayInterceptorBindingPolicy::Exact);
}
Expand Down
Loading