mirror of
https://github.com/NVIDIA/OpenShell.git
synced 2026-10-25 06:42:52 +08:00
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:
@@ -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();
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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;
|
||||
|
||||
@@ -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" +
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user