refactor(proto): negotiate supervisor config delivery with feature flags

Replace the exact-match protocol_revision handshake with a
SupervisorHello.supports_config_snapshots capability, matching the
existing supports_provider_readiness pattern. Peers no longer reject
unknown revisions, so gateway rollback and mixed-version HA replicas keep
supervisor sessions alive. SessionAccepted no longer carries a revision.

Document SandboxConfigSnapshot and ProviderEnvironmentSnapshot as the
canonical configuration shapes, with the GetSandboxConfig and
GetSandboxProviderEnvironment responses as polling projections. State
ProviderEnvironmentValue invariants and number its expiration_time
field 3 instead of reserving a field that never shipped.

Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>
This commit is contained in:
Piotr Mlocek
2026-10-06 10:13:17 -07:00
parent b819f5cac8
commit e3a0fb87e9
12 changed files with 108 additions and 194 deletions
-15
View File
@@ -99,21 +99,6 @@ pub fn all_workspaces_selector() -> WorkspaceSelector {
}
}
/// Exact protocol revision required between a gateway and its supervisor.
///
/// The supervisor stream is an internal, version-locked deployment contract.
/// Bump this when either peer can no longer honor the previous stream
/// semantics.
pub const SUPERVISOR_PROTOCOL_REVISION: u32 = 1;
/// Revision implied by peers built before the handshake existed. Proto3 leaves
/// the field unset, so such peers report zero.
///
/// Sandboxes keep their supervisor binary until they are recreated, so a
/// gateway upgrade must keep serving them for one release. Remove this
/// allowance once every supported release sends an explicit revision.
pub const LEGACY_SUPERVISOR_PROTOCOL_REVISION: u32 = 0;
#[cfg(test)]
mod tests {
use std::collections::HashMap;
@@ -1055,13 +1055,9 @@ mod tests {
state: &Arc<ServerState>,
sandbox_id: &str,
) -> (SupervisorStreamHarness, String) {
let mut harness = connect_supervisor_stream(
state,
sandbox_id,
openshell_core::proto::SUPERVISOR_PROTOCOL_REVISION,
)
.await
.unwrap();
let mut harness = connect_supervisor_stream(state, sandbox_id, true)
.await
.unwrap();
let first = tokio::time::timeout(Duration::from_secs(5), harness.inbound.message())
.await
.unwrap()
@@ -1322,13 +1318,9 @@ mod tests {
async fn legacy_supervisor_keeps_polling_when_gateway_shadow_push_is_enabled() {
let state = push_state().await;
put_sandbox(&state, "legacy-sandbox", &[]).await;
let mut harness = connect_supervisor_stream(
&state,
"legacy-sandbox",
openshell_core::proto::LEGACY_SUPERVISOR_PROTOCOL_REVISION,
)
.await
.unwrap();
let mut harness = connect_supervisor_stream(&state, "legacy-sandbox", false)
.await
.unwrap();
let first = harness.inbound.message().await.unwrap().unwrap();
let Some(gateway_message::Payload::SessionAccepted(accepted)) = first.payload else {
panic!("expected SessionAccepted");
@@ -1394,11 +1386,7 @@ mod tests {
put_sandbox(&state, "sandbox", &["provider"]).await;
let (resolve_hit, _release_resolve) = state.credentials.gate_next_resolve();
tokio::time::timeout(Duration::from_secs(10), async {
let connect = connect_supervisor_stream(
&state,
"sandbox",
openshell_core::proto::SUPERVISOR_PROTOCOL_REVISION,
);
let connect = connect_supervisor_stream(&state, "sandbox", true);
let (response, hit) = tokio::join!(connect, resolve_hit);
hit.expect("bootstrap must reach the stalled credential driver");
let mut harness = response.unwrap();
+4 -4
View File
@@ -983,12 +983,12 @@ pub mod test_support {
}
/// Serve `state` on loopback and open a supervisor stream whose hello
/// carries the given protocol revision. Returns the gRPC status when the
/// gateway rejects the stream before accepting it.
/// advertises the given configuration snapshot support. Returns the gRPC
/// status when the gateway rejects the stream before accepting it.
pub async fn connect_supervisor_stream(
state: &Arc<ServerState>,
sandbox_id: &str,
protocol_revision: u32,
supports_config_snapshots: bool,
) -> Result<SupervisorStreamHarness, tonic::Status> {
let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
let address = listener.local_addr().unwrap();
@@ -1008,7 +1008,7 @@ pub mod test_support {
payload: Some(supervisor_message::Payload::Hello(SupervisorHello {
sandbox_id: sandbox_id.into(),
instance_id: "instance".into(),
protocol_revision,
supports_config_snapshots,
connection_epoch: 0,
supports_provider_readiness: false,
})),
@@ -7,7 +7,6 @@ use crate::config_update_operation::CONFIG_UPDATE_OPERATION_OBJECT_TYPE;
use crate::grpc::test_support::{authed_request, test_server_state};
use crate::persistence::WriteCondition;
use crate::storage_proto::StoredConfigUpdateOperation;
use openshell_core::proto::SUPERVISOR_PROTOCOL_REVISION;
use openshell_core::proto::SandboxSpec;
use openshell_core::proto::datamodel::v1::ObjectMeta;
use prost::Message;
@@ -71,7 +70,7 @@ fn hello() -> SupervisorHello {
SupervisorHello {
sandbox_id: Uuid::new_v4().to_string(),
instance_id: Uuid::new_v4().to_string(),
protocol_revision: SUPERVISOR_PROTOCOL_REVISION,
supports_config_snapshots: true,
connection_epoch: 0,
supports_provider_readiness: true,
}
+1 -1
View File
@@ -136,7 +136,7 @@ mod tests {
// SandboxProvisioning in both closures. Old rows decode false and empty;
// decoding or deadline updates cannot claim an existing attempt.
const PUBLIC_RPC_SCHEMA_SHA256: &str =
"fe0408efcdc1d6e485586223fe8f14ce6a5747959aa2d646c94be57e1fc8c665";
"26279ae924037ff79c531625b3051ed750eb502d4b9c63e0653f6f0deafc35a1";
const DURABLE_SCHEMA_SHA256: &str =
"76487ab369fc3a4b03a179bb5e7ea6be8d20e380ad5563075dff8ee50e539406";
const PUBLIC_DURABLE_OVERLAP_SHA256: &str =
@@ -27,7 +27,6 @@ use openshell_core::proto::{
config_update, gateway_message, open_shell_client, peer_relay_frame, relay_open,
supervisor_message,
};
use openshell_core::proto::{LEGACY_SUPERVISOR_PROTOCOL_REVISION, SUPERVISOR_PROTOCOL_REVISION};
use openshell_core::transport_errors::is_expected_transport_close_status;
use crate::ServerState;
@@ -2094,7 +2093,6 @@ pub async fn handle_connect_supervisor(
if sandbox_id.is_empty() {
return Err(Status::invalid_argument("sandbox_id is required"));
}
validate_protocol_revision(&sandbox_id, hello.protocol_revision)?;
if let Some(principal) = principal.as_ref() {
crate::auth::guard::ensure_sandbox_principal_scope(principal, &sandbox_id)?;
}
@@ -2102,7 +2100,7 @@ pub async fn handle_connect_supervisor(
// record, so registration detects publications the bootstrap may miss.
let captured_seq = (state.config.config_delivery_mode
== openshell_core::config::ConfigDeliveryMode::Push
&& hello.protocol_revision == SUPERVISOR_PROTOCOL_REVISION)
&& hello.supports_config_snapshots)
.then(|| state.config_delivery.current_seq());
let sandbox = require_persisted_sandbox(&state.store, &sandbox_id).await?;
@@ -2218,7 +2216,6 @@ async fn establish_supervisor_session(
payload: Some(gateway_message::Payload::SessionAccepted(SessionAccepted {
session_id: session_id.clone(),
bootstrap,
protocol_revision: SUPERVISOR_PROTOCOL_REVISION,
heartbeat_interval: openshell_core::time::duration_from_std(Duration::from_secs(
u64::from(HEARTBEAT_INTERVAL_SECS),
))
@@ -2429,23 +2426,6 @@ async fn establish_supervisor_session(
Ok(Response::new(stream))
}
fn validate_protocol_revision(sandbox_id: &str, supervisor_revision: u32) -> Result<(), Status> {
match supervisor_revision {
SUPERVISOR_PROTOCOL_REVISION => Ok(()),
LEGACY_SUPERVISOR_PROTOCOL_REVISION => {
counter!("openshell_supervisor_protocol_legacy_sessions_total").increment(1);
warn!(
sandbox_id = %sandbox_id,
"supervisor session: supervisor predates the protocol handshake; recreate the sandbox before the next gateway upgrade"
);
Ok(())
}
other => Err(Status::failed_precondition(format!(
"supervisor protocol revision mismatch: gateway requires {SUPERVISOR_PROTOCOL_REVISION}, supervisor offered {other}"
))),
}
}
pub async fn handle_report_main_process_exit(
state: &Arc<ServerState>,
request: Request<ReportMainProcessExitRequest>,
@@ -2735,7 +2715,6 @@ mod tests {
sandbox_config: Some(SandboxConfigSnapshot::default()),
provider_environment: Some(ProviderEnvironmentSnapshot::default()),
}),
protocol_revision: SUPERVISOR_PROTOCOL_REVISION,
})),
};
let updates = [
@@ -2796,12 +2775,6 @@ mod tests {
assert_eq!(decoded, result);
}
#[test]
fn supervisor_protocol_revision_accepts_current_and_legacy_peers() {
assert!(validate_protocol_revision("sb-1", SUPERVISOR_PROTOCOL_REVISION).is_ok());
assert!(validate_protocol_revision("sb-1", LEGACY_SUPERVISOR_PROTOCOL_REVISION).is_ok());
}
async fn state_with_sandbox(sandbox_id: &str) -> Arc<ServerState> {
let state = crate::grpc::test_support::test_server_state().await;
state
@@ -2823,22 +2796,19 @@ mod tests {
}
#[tokio::test]
async fn legacy_supervisor_without_protocol_revision_is_accepted() {
async fn supervisor_without_config_snapshots_is_accepted() {
let state = state_with_sandbox("sb-legacy").await;
let mut harness = crate::grpc::test_support::connect_supervisor_stream(
&state,
"sb-legacy",
LEGACY_SUPERVISOR_PROTOCOL_REVISION,
)
.await
.expect("legacy supervisor must connect");
let mut harness =
crate::grpc::test_support::connect_supervisor_stream(&state, "sb-legacy", false)
.await
.expect("supervisor without configuration snapshots must connect");
let Some(gateway_message::Payload::SessionAccepted(accepted)) =
first_gateway_message(&mut harness).await.payload
else {
panic!("expected SessionAccepted");
};
assert_eq!(accepted.protocol_revision, SUPERVISOR_PROTOCOL_REVISION);
assert!(accepted.bootstrap.is_none());
assert!(
state
.supervisor_sessions
@@ -2846,32 +2816,6 @@ mod tests {
);
}
#[tokio::test]
async fn unknown_supervisor_protocol_revision_is_rejected() {
let state = state_with_sandbox("sb-future").await;
let Err(status) = crate::grpc::test_support::connect_supervisor_stream(
&state,
"sb-future",
SUPERVISOR_PROTOCOL_REVISION + 1,
)
.await
else {
panic!("mismatched revision must be rejected");
};
assert_eq!(status.code(), tonic::Code::FailedPrecondition);
assert!(status.message().contains("revision mismatch"));
assert!(!state.supervisor_sessions.has_session("sb-future"));
}
#[test]
fn supervisor_protocol_revision_rejects_unknown_peers() {
let error =
validate_protocol_revision("sb-1", SUPERVISOR_PROTOCOL_REVISION + 1).unwrap_err();
assert_eq!(error.code(), tonic::Code::FailedPrecondition);
assert!(error.message().contains("revision mismatch"));
}
fn register_push_session(
registry: &SupervisorSessionRegistry,
sandbox_id: &str,
@@ -21,7 +21,6 @@ use openshell_core::proto::{
RelayOpenResult, ReportMainProcessExitRequest, SupervisorHeartbeat, SupervisorHello,
SupervisorMessage, TcpRelayTarget, gateway_message, relay_open, supervisor_message,
};
use openshell_core::proto::{LEGACY_SUPERVISOR_PROTOCOL_REVISION, SUPERVISOR_PROTOCOL_REVISION};
use openshell_isolation_interface::contract::{BoundaryLoopbackConnector, LoopbackTarget};
use openshell_ocsf::{
ActivityId, BaseEventBuilder, ConnectionInfo, Endpoint, EventContext, NetworkActivityBuilder,
@@ -414,7 +413,7 @@ async fn run_single_session(
payload: Some(supervisor_message::Payload::Hello(SupervisorHello {
sandbox_id: config.sandbox_id.clone(),
instance_id: config.instance_id.clone(),
protocol_revision: SUPERVISOR_PROTOCOL_REVISION,
supports_config_snapshots: true,
connection_epoch,
supports_provider_readiness: true,
})),
@@ -448,7 +447,6 @@ async fn run_single_session(
.as_ref()
.and_then(|value| openshell_core::time::duration_to_std(value).ok())
.map_or(5, |value| value.as_secs().max(5));
validate_gateway_protocol_revision(accepted.protocol_revision)?;
if let Some(updates) = &config.session_id_updates {
updates.send_replace(Some(accepted.session_id.clone()));
}
@@ -511,24 +509,6 @@ async fn run_single_session(
}
}
fn validate_gateway_protocol_revision(
gateway_revision: u32,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
match gateway_revision {
SUPERVISOR_PROTOCOL_REVISION => Ok(()),
LEGACY_SUPERVISOR_PROTOCOL_REVISION => {
warn!(
"supervisor session: gateway predates the protocol handshake; upgrade the gateway before pinning newer supervisor images"
);
Ok(())
}
other => Err(format!(
"supervisor protocol revision mismatch: supervisor requires {SUPERVISOR_PROTOCOL_REVISION}, gateway offered {other}"
)
.into()),
}
}
/// Report the canonical process result and wait for durable handling.
pub async fn report_main_process_exit(
endpoint: &str,
@@ -962,19 +942,6 @@ fn normalize_tcp_target_host(target: &TcpRelayTarget) -> Result<String, String>
mod target_tests {
use super::*;
#[test]
fn gateway_protocol_revision_accepts_current_and_legacy_peers() {
assert!(validate_gateway_protocol_revision(SUPERVISOR_PROTOCOL_REVISION).is_ok());
assert!(validate_gateway_protocol_revision(LEGACY_SUPERVISOR_PROTOCOL_REVISION).is_ok());
}
#[test]
fn gateway_protocol_revision_rejects_unknown_peers() {
let error = validate_gateway_protocol_revision(SUPERVISOR_PROTOCOL_REVISION + 1)
.expect_err("version skew must be rejected");
assert!(error.to_string().contains("revision mismatch"));
}
fn tcp(host: &str, port: u32) -> TcpRelayTarget {
TcpRelayTarget {
host: host.to_string(),
+27 -16
View File
@@ -424,6 +424,8 @@ service OpenShell {
}
// Get sandbox settings by id (called by sandbox entrypoint and poll loop).
// Polling projection of the sandbox configuration delivered over
// ConnectSupervisor; removed with the polling RPCs.
rpc GetSandboxConfig(openshell.sandbox.v1.GetSandboxConfigRequest)
returns (openshell.sandbox.v1.GetSandboxConfigResponse) {
option (openshell.options.v1.authorization) = {
@@ -509,7 +511,9 @@ service OpenShell {
option (openshell.options.v1.authorization) = { auth_mode: "sandbox" };
}
// Get provider environment for a sandbox (called by sandbox supervisor at startup).
// Get provider environment for a sandbox (called by the sandbox supervisor at
// startup and by its poll loop). Polling projection of the provider
// environment delivered over ConnectSupervisor; removed with the polling RPCs.
rpc GetSandboxProviderEnvironment(GetSandboxProviderEnvironmentRequest)
returns (GetSandboxProviderEnvironmentResponse) {
option (openshell.options.v1.authorization) = {
@@ -547,8 +551,9 @@ service OpenShell {
// The supervisor opens this stream at startup and keeps it alive for the
// sandbox lifetime. The gateway uses it to coordinate relay channels for
// SSH connect, ExecSandbox, targetable sandbox services, and configuration
// delivery. Peers must report the same exact protocol_revision during the
// handshake. Raw service bytes flow over RelayStream calls (separate HTTP/2
// delivery. Supervisors advertise optional stream features in
// SupervisorHello, and the gateway sends only payloads the supervisor
// advertises. Raw service bytes flow over RelayStream calls (separate HTTP/2
// streams on the same connection), not over this stream.
rpc ConnectSupervisor(stream SupervisorMessage) returns (stream GatewayMessage) {
option (openshell.options.v1.authorization) = {
@@ -2632,7 +2637,9 @@ message StaticCredentialBinding {
string workload_credential_handle = 3;
}
// Get sandbox provider environment response.
// Polling projection of ProviderEnvironmentSnapshot. The parallel maps below
// are keyed by the same environment variable names. Removed with the polling
// RPCs; new provider environment fields belong on the snapshot first.
message GetSandboxProviderEnvironmentResponse {
reserved 3;
reserved "credential_expires_at_ms";
@@ -2664,25 +2671,29 @@ message GetSandboxProviderEnvironmentResponse {
map<string, string> files = 10;
}
// Receivers treat UNSPECIFIED and unknown values as secret.
enum ProviderEnvironmentValueClassification {
PROVIDER_ENVIRONMENT_VALUE_CLASSIFICATION_UNSPECIFIED = 0;
PROVIDER_ENVIRONMENT_VALUE_CLASSIFICATION_NON_SECRET = 1;
PROVIDER_ENVIRONMENT_VALUE_CLASSIFICATION_STATIC_CREDENTIAL = 2;
}
// One environment value and all metadata that shares its key.
// One environment value and all metadata that shares its key. Names are unique
// within a snapshot. static_credential_binding is present exactly when
// classification is STATIC_CREDENTIAL. Snapshots that violate either rule are
// invalid.
message ProviderEnvironmentValue {
reserved 3;
reserved "expires_at_ms";
string name = 1;
string value = 2 [(openshell.options.v1.secret) = true];
ProviderEnvironmentValueClassification classification = 4;
StaticCredentialBinding static_credential_binding = 5;
// Absolute expiration time. Absence means no expiry.
google.protobuf.Timestamp expiration_time = 103;
google.protobuf.Timestamp expiration_time = 3;
}
// Complete provider environment state delivered to a supervisor. Dynamic
// Complete provider environment state delivered to a supervisor. This is the
// canonical provider environment shape; GetSandboxProviderEnvironmentResponse
// is its polling projection and is removed with the polling RPCs. Dynamic
// credentials are endpoint selectors rather than environment values and stay
// in their own collection.
message ProviderEnvironmentSnapshot {
@@ -3049,9 +3060,9 @@ message SupervisorHello {
uint64 connection_epoch = 3;
// The supervisor can report credential, policy, and launch-environment installation.
bool supports_provider_readiness = 4;
// Exact internal stream protocol revision implemented by this supervisor.
// Zero identifies a supervisor built before the handshake existed.
uint32 protocol_revision = 5;
// The supervisor accepts ConfigBootstrap and ConfigUpdate payloads on this
// stream. Gateways send configuration payloads only when this is set.
bool supports_config_snapshots = 5;
}
// Gateway accepts the supervisor session.
@@ -3060,11 +3071,11 @@ message SessionAccepted {
reserved "heartbeat_interval_secs";
// Gateway-assigned session ID for this connection.
string session_id = 1;
// Complete gateway-owned configuration. During the staged rollout this may
// be omitted only when the gateway cannot build the projection.
// Complete gateway-owned configuration. Sent only to supervisors that set
// SupervisorHello.supports_config_snapshots. During the staged rollout this
// may also be omitted when push delivery is disabled or the gateway cannot
// build the projection.
ConfigBootstrap bootstrap = 3;
// Exact internal stream protocol revision implemented by this gateway.
uint32 protocol_revision = 4;
// Recommended heartbeat interval.
google.protobuf.Duration heartbeat_interval = 102;
}
+7 -2
View File
@@ -400,7 +400,11 @@ enum PolicySource {
POLICY_SOURCE_GLOBAL = 2;
}
// Complete effective sandbox configuration delivered to a supervisor.
// Complete effective sandbox configuration delivered to a supervisor. Fields
// have the same meaning as the identically numbered GetSandboxConfigResponse
// fields, including fail-closed handling of an unknown
// policy_validation_failure_mode and treating absent configuration_admitted as
// not admitted. Move those field comments here when the polling RPC is removed.
message SandboxConfigSnapshot {
SandboxPolicy policy = 1;
uint32 version = 2;
@@ -420,7 +424,8 @@ message SandboxConfigSnapshot {
string configuration_error = 16;
}
// Response containing effective sandbox settings and policy.
// Response containing effective sandbox settings and policy. Polling
// projection of SandboxConfigSnapshot; removed with the polling RPCs.
message GetSandboxConfigResponse {
// The sandbox policy configuration.
SandboxPolicy policy = 1;
+30 -30
View File
@@ -729,6 +729,7 @@ func (ProviderProfileCategory) EnumDescriptor() ([]byte, []int) {
return file_openshell_proto_rawDescGZIP(), []int{10}
}
// Receivers treat UNSPECIFIED and unknown values as secret.
type ProviderEnvironmentValueClassification int32
const (
@@ -11213,7 +11214,9 @@ func (x *StaticCredentialBinding) GetWorkloadCredentialHandle() string {
return ""
}
// Get sandbox provider environment response.
// Polling projection of ProviderEnvironmentSnapshot. The parallel maps below
// are keyed by the same environment variable names. Removed with the polling
// RPCs; new provider environment fields belong on the snapshot first.
type GetSandboxProviderEnvironmentResponse struct {
state protoimpl.MessageState `protogen:"open.v1"`
// Provider credential environment variables.
@@ -11346,7 +11349,10 @@ func (x *GetSandboxProviderEnvironmentResponse) GetFiles() map[string]string {
return nil
}
// One environment value and all metadata that shares its key.
// One environment value and all metadata that shares its key. Names are unique
// within a snapshot. static_credential_binding is present exactly when
// classification is STATIC_CREDENTIAL. Snapshots that violate either rule are
// invalid.
type ProviderEnvironmentValue struct {
state protoimpl.MessageState `protogen:"open.v1"`
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
@@ -11354,7 +11360,7 @@ type ProviderEnvironmentValue struct {
Classification ProviderEnvironmentValueClassification `protobuf:"varint,4,opt,name=classification,proto3,enum=openshell.v1.ProviderEnvironmentValueClassification" json:"classification,omitempty"`
StaticCredentialBinding *StaticCredentialBinding `protobuf:"bytes,5,opt,name=static_credential_binding,json=staticCredentialBinding,proto3" json:"static_credential_binding,omitempty"`
// Absolute expiration time. Absence means no expiry.
ExpirationTime *timestamppb.Timestamp `protobuf:"bytes,103,opt,name=expiration_time,json=expirationTime,proto3" json:"expiration_time,omitempty"`
ExpirationTime *timestamppb.Timestamp `protobuf:"bytes,3,opt,name=expiration_time,json=expirationTime,proto3" json:"expiration_time,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
@@ -11424,7 +11430,9 @@ func (x *ProviderEnvironmentValue) GetExpirationTime() *timestamppb.Timestamp {
return nil
}
// Complete provider environment state delivered to a supervisor. Dynamic
// Complete provider environment state delivered to a supervisor. This is the
// canonical provider environment shape; GetSandboxProviderEnvironmentResponse
// is its polling projection and is removed with the polling RPCs. Dynamic
// credentials are endpoint selectors rather than environment values and stay
// in their own collection.
type ProviderEnvironmentSnapshot struct {
@@ -13659,11 +13667,11 @@ type SupervisorHello struct {
ConnectionEpoch uint64 `protobuf:"varint,3,opt,name=connection_epoch,json=connectionEpoch,proto3" json:"connection_epoch,omitempty"`
// The supervisor can report credential, policy, and launch-environment installation.
SupportsProviderReadiness bool `protobuf:"varint,4,opt,name=supports_provider_readiness,json=supportsProviderReadiness,proto3" json:"supports_provider_readiness,omitempty"`
// Exact internal stream protocol revision implemented by this supervisor.
// Zero identifies a supervisor built before the handshake existed.
ProtocolRevision uint32 `protobuf:"varint,5,opt,name=protocol_revision,json=protocolRevision,proto3" json:"protocol_revision,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
// The supervisor accepts ConfigBootstrap and ConfigUpdate payloads on this
// stream. Gateways send configuration payloads only when this is set.
SupportsConfigSnapshots bool `protobuf:"varint,5,opt,name=supports_config_snapshots,json=supportsConfigSnapshots,proto3" json:"supports_config_snapshots,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *SupervisorHello) Reset() {
@@ -13724,11 +13732,11 @@ func (x *SupervisorHello) GetSupportsProviderReadiness() bool {
return false
}
func (x *SupervisorHello) GetProtocolRevision() uint32 {
func (x *SupervisorHello) GetSupportsConfigSnapshots() bool {
if x != nil {
return x.ProtocolRevision
return x.SupportsConfigSnapshots
}
return 0
return false
}
// Gateway accepts the supervisor session.
@@ -13736,11 +13744,11 @@ type SessionAccepted struct {
state protoimpl.MessageState `protogen:"open.v1"`
// Gateway-assigned session ID for this connection.
SessionId string `protobuf:"bytes,1,opt,name=session_id,json=sessionId,proto3" json:"session_id,omitempty"`
// Complete gateway-owned configuration. During the staged rollout this may
// be omitted only when the gateway cannot build the projection.
// Complete gateway-owned configuration. Sent only to supervisors that set
// SupervisorHello.supports_config_snapshots. During the staged rollout this
// may also be omitted when push delivery is disabled or the gateway cannot
// build the projection.
Bootstrap *ConfigBootstrap `protobuf:"bytes,3,opt,name=bootstrap,proto3" json:"bootstrap,omitempty"`
// Exact internal stream protocol revision implemented by this gateway.
ProtocolRevision uint32 `protobuf:"varint,4,opt,name=protocol_revision,json=protocolRevision,proto3" json:"protocol_revision,omitempty"`
// Recommended heartbeat interval.
HeartbeatInterval *durationpb.Duration `protobuf:"bytes,102,opt,name=heartbeat_interval,json=heartbeatInterval,proto3" json:"heartbeat_interval,omitempty"`
unknownFields protoimpl.UnknownFields
@@ -13791,13 +13799,6 @@ func (x *SessionAccepted) GetBootstrap() *ConfigBootstrap {
return nil
}
func (x *SessionAccepted) GetProtocolRevision() uint32 {
if x != nil {
return x.ProtocolRevision
}
return 0
}
func (x *SessionAccepted) GetHeartbeatInterval() *durationpb.Duration {
if x != nil {
return x.HeartbeatInterval
@@ -19683,13 +19684,13 @@ const file_openshell_proto_rawDesc = "" +
"\n" +
"FilesEntry\x12\x10\n" +
"\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
"\x05value\x18\x02 \x01(\tR\x05value:\x028\x01J\x04\b\x03\x10\x04R\x18credential_expires_at_ms\"\xe5\x02\n" +
"\x05value\x18\x02 \x01(\tR\x05value:\x028\x01J\x04\b\x03\x10\x04R\x18credential_expires_at_ms\"\xd0\x02\n" +
"\x18ProviderEnvironmentValue\x12\x12\n" +
"\x04name\x18\x01 \x01(\tR\x04name\x12\x1a\n" +
"\x05value\x18\x02 \x01(\tB\x04\x88\xb5\x18\x01R\x05value\x12\\\n" +
"\x0eclassification\x18\x04 \x01(\x0e24.openshell.v1.ProviderEnvironmentValueClassificationR\x0eclassification\x12a\n" +
"\x19static_credential_binding\x18\x05 \x01(\v2%.openshell.v1.StaticCredentialBindingR\x17staticCredentialBinding\x12C\n" +
"\x0fexpiration_time\x18g \x01(\v2\x1a.google.protobuf.TimestampR\x0eexpirationTimeJ\x04\b\x03\x10\x04R\rexpires_at_ms\"\xaa\x05\n" +
"\x0fexpiration_time\x18\x03 \x01(\v2\x1a.google.protobuf.TimestampR\x0eexpirationTime\"\xaa\x05\n" +
"\x1bProviderEnvironmentSnapshot\x122\n" +
"\x15provider_env_revision\x18\x01 \x01(\x04R\x13providerEnvRevision\x12>\n" +
"\x06values\x18\x02 \x03(\v2&.openshell.v1.ProviderEnvironmentValueR\x06values\x12r\n" +
@@ -19877,20 +19878,19 @@ const file_openshell_proto_rawDesc = "" +
"\vrelay_close\x18\x05 \x01(\v2\x18.openshell.v1.RelayCloseH\x00R\n" +
"relayClose\x12A\n" +
"\rconfig_update\x18\x06 \x01(\v2\x1a.openshell.v1.ConfigUpdateH\x00R\fconfigUpdateB\t\n" +
"\apayload\"\xe9\x01\n" +
"\apayload\"\xf8\x01\n" +
"\x0fSupervisorHello\x12\x1d\n" +
"\n" +
"sandbox_id\x18\x01 \x01(\tR\tsandboxId\x12\x1f\n" +
"\vinstance_id\x18\x02 \x01(\tR\n" +
"instanceId\x12)\n" +
"\x10connection_epoch\x18\x03 \x01(\x04R\x0fconnectionEpoch\x12>\n" +
"\x1bsupports_provider_readiness\x18\x04 \x01(\bR\x19supportsProviderReadiness\x12+\n" +
"\x11protocol_revision\x18\x05 \x01(\rR\x10protocolRevision\"\x83\x02\n" +
"\x1bsupports_provider_readiness\x18\x04 \x01(\bR\x19supportsProviderReadiness\x12:\n" +
"\x19supports_config_snapshots\x18\x05 \x01(\bR\x17supportsConfigSnapshots\"\xd6\x01\n" +
"\x0fSessionAccepted\x12\x1d\n" +
"\n" +
"session_id\x18\x01 \x01(\tR\tsessionId\x12;\n" +
"\tbootstrap\x18\x03 \x01(\v2\x1d.openshell.v1.ConfigBootstrapR\tbootstrap\x12+\n" +
"\x11protocol_revision\x18\x04 \x01(\rR\x10protocolRevision\x12H\n" +
"\tbootstrap\x18\x03 \x01(\v2\x1d.openshell.v1.ConfigBootstrapR\tbootstrap\x12H\n" +
"\x12heartbeat_interval\x18f \x01(\v2\x19.google.protobuf.DurationR\x11heartbeatIntervalJ\x04\b\x02\x10\x03R\x17heartbeat_interval_secs\"\xc3\x01\n" +
"\x0fConfigBootstrap\x12R\n" +
"\x0esandbox_config\x18\x01 \x01(\v2+.openshell.sandbox.v1.SandboxConfigSnapshotR\rsandboxConfig\x12\\\n" +
+16 -6
View File
@@ -215,6 +215,8 @@ type OpenShellClient interface {
// Delete a custom provider type profile by id.
DeleteProviderProfile(ctx context.Context, in *DeleteProviderProfileRequest, opts ...grpc.CallOption) (*DeleteProviderProfileResponse, error)
// Get sandbox settings by id (called by sandbox entrypoint and poll loop).
// Polling projection of the sandbox configuration delivered over
// ConnectSupervisor; removed with the polling RPCs.
GetSandboxConfig(ctx context.Context, in *sandboxv1.GetSandboxConfigRequest, opts ...grpc.CallOption) (*sandboxv1.GetSandboxConfigResponse, error)
// Get gateway-global settings (read-only runtime configuration; any
// authenticated user may read these without requiring Platform Admin).
@@ -239,7 +241,9 @@ type OpenShellClient interface {
ReportProviderReadiness(ctx context.Context, in *ReportProviderReadinessRequest, opts ...grpc.CallOption) (*ReportProviderReadinessResponse, error)
// Register startup and acknowledge an exact validated runtime configuration.
ReportSandboxConfiguration(ctx context.Context, in *ReportSandboxConfigurationRequest, opts ...grpc.CallOption) (*ReportSandboxConfigurationResponse, error)
// Get provider environment for a sandbox (called by sandbox supervisor at startup).
// Get provider environment for a sandbox (called by the sandbox supervisor at
// startup and by its poll loop). Polling projection of the provider
// environment delivered over ConnectSupervisor; removed with the polling RPCs.
GetSandboxProviderEnvironment(ctx context.Context, in *GetSandboxProviderEnvironmentRequest, opts ...grpc.CallOption) (*GetSandboxProviderEnvironmentResponse, error)
// Exchange a stored provider subject token for an intermediate token scoped
// to the calling supervisor's SPIFFE identity.
@@ -253,8 +257,9 @@ type OpenShellClient interface {
// The supervisor opens this stream at startup and keeps it alive for the
// sandbox lifetime. The gateway uses it to coordinate relay channels for
// SSH connect, ExecSandbox, targetable sandbox services, and configuration
// delivery. Peers must report the same exact protocol_revision during the
// handshake. Raw service bytes flow over RelayStream calls (separate HTTP/2
// delivery. Supervisors advertise optional stream features in
// SupervisorHello, and the gateway sends only payloads the supervisor
// advertises. Raw service bytes flow over RelayStream calls (separate HTTP/2
// streams on the same connection), not over this stream.
ConnectSupervisor(ctx context.Context, opts ...grpc.CallOption) (grpc.BidiStreamingClient[SupervisorMessage, GatewayMessage], error)
// Persist the canonical main process result before the supervisor exits.
@@ -1323,6 +1328,8 @@ type OpenShellServer interface {
// Delete a custom provider type profile by id.
DeleteProviderProfile(context.Context, *DeleteProviderProfileRequest) (*DeleteProviderProfileResponse, error)
// Get sandbox settings by id (called by sandbox entrypoint and poll loop).
// Polling projection of the sandbox configuration delivered over
// ConnectSupervisor; removed with the polling RPCs.
GetSandboxConfig(context.Context, *sandboxv1.GetSandboxConfigRequest) (*sandboxv1.GetSandboxConfigResponse, error)
// Get gateway-global settings (read-only runtime configuration; any
// authenticated user may read these without requiring Platform Admin).
@@ -1347,7 +1354,9 @@ type OpenShellServer interface {
ReportProviderReadiness(context.Context, *ReportProviderReadinessRequest) (*ReportProviderReadinessResponse, error)
// Register startup and acknowledge an exact validated runtime configuration.
ReportSandboxConfiguration(context.Context, *ReportSandboxConfigurationRequest) (*ReportSandboxConfigurationResponse, error)
// Get provider environment for a sandbox (called by sandbox supervisor at startup).
// Get provider environment for a sandbox (called by the sandbox supervisor at
// startup and by its poll loop). Polling projection of the provider
// environment delivered over ConnectSupervisor; removed with the polling RPCs.
GetSandboxProviderEnvironment(context.Context, *GetSandboxProviderEnvironmentRequest) (*GetSandboxProviderEnvironmentResponse, error)
// Exchange a stored provider subject token for an intermediate token scoped
// to the calling supervisor's SPIFFE identity.
@@ -1361,8 +1370,9 @@ type OpenShellServer interface {
// The supervisor opens this stream at startup and keeps it alive for the
// sandbox lifetime. The gateway uses it to coordinate relay channels for
// SSH connect, ExecSandbox, targetable sandbox services, and configuration
// delivery. Peers must report the same exact protocol_revision during the
// handshake. Raw service bytes flow over RelayStream calls (separate HTTP/2
// delivery. Supervisors advertise optional stream features in
// SupervisorHello, and the gateway sends only payloads the supervisor
// advertises. Raw service bytes flow over RelayStream calls (separate HTTP/2
// streams on the same connection), not over this stream.
ConnectSupervisor(grpc.BidiStreamingServer[SupervisorMessage, GatewayMessage]) error
// Persist the canonical main process result before the supervisor exits.
+7 -2
View File
@@ -1970,7 +1970,11 @@ func (x *EffectiveSetting) GetScope() SettingScope {
return SettingScope_SETTING_SCOPE_UNSPECIFIED
}
// Complete effective sandbox configuration delivered to a supervisor.
// Complete effective sandbox configuration delivered to a supervisor. Fields
// have the same meaning as the identically numbered GetSandboxConfigResponse
// fields, including fail-closed handling of an unknown
// policy_validation_failure_mode and treating absent configuration_admitted as
// not admitted. Move those field comments here when the polling RPC is removed.
type SandboxConfigSnapshot struct {
state protoimpl.MessageState `protogen:"open.v1"`
Policy *SandboxPolicy `protobuf:"bytes,1,opt,name=policy,proto3" json:"policy,omitempty"`
@@ -2135,7 +2139,8 @@ func (x *SandboxConfigSnapshot) GetConfigurationError() string {
return ""
}
// Response containing effective sandbox settings and policy.
// Response containing effective sandbox settings and policy. Polling
// projection of SandboxConfigSnapshot; removed with the polling RPCs.
type GetSandboxConfigResponse struct {
state protoimpl.MessageState `protogen:"open.v1"`
// The sandbox policy configuration.