diff --git a/Cargo.lock b/Cargo.lock index cd0cfdf0fe..8f4305854d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4293,6 +4293,7 @@ dependencies = [ "tracing", "tracing-subscriber", "url", + "wiremock", ] [[package]] diff --git a/crates/openshell-cli/Cargo.toml b/crates/openshell-cli/Cargo.toml index 5c92eefe5b..19e59185b1 100644 --- a/crates/openshell-cli/Cargo.toml +++ b/crates/openshell-cli/Cargo.toml @@ -102,3 +102,4 @@ serde_json = { workspace = true } temp-env = "0.3" tokio-stream = { workspace = true } url = { workspace = true } +wiremock = "0.6" diff --git a/crates/openshell-cli/src/main.rs b/crates/openshell-cli/src/main.rs index 5f20fc7f3c..e51f261293 100644 --- a/crates/openshell-cli/src/main.rs +++ b/crates/openshell-cli/src/main.rs @@ -594,6 +594,13 @@ enum Commands { command: Option, }, + /// Manage delegated identity credential records. + #[command(help_template = SUBCOMMAND_HELP_TEMPLATE)] + DelegatedCredential { + #[command(subcommand)] + command: Option, + }, + /// Manage workspaces. #[command(alias = "ws", after_help = WORKSPACE_EXAMPLES, help_template = SUBCOMMAND_HELP_TEMPLATE)] Workspace { @@ -1288,6 +1295,62 @@ impl ProfileCommands { } } +#[derive(Subcommand, Debug)] +enum DelegatedCredentialCommands { + /// List delegated identity credentials. + #[command(help_template = LEAF_HELP_TEMPLATE, next_help_heading = "FLAGS")] + List { + /// Maximum number of credentials to return in this page. + #[arg(long, default_value_t = 100)] + page_size: i32, + + /// Opaque continuation token from a previous page. + #[arg(long, default_value = "")] + page_token: String, + + /// Output only credential IDs. + #[arg(long, conflicts_with = "output")] + ids: bool, + + /// Output format. + #[arg(short = 'o', long = "output", value_enum, default_value_t = OutputFormat::Table, conflicts_with = "ids")] + output: OutputFormat, + }, + + /// Show delegated identity credential status. + #[command(help_template = LEAF_HELP_TEMPLATE, next_help_heading = "FLAGS")] + Status { + /// Delegated identity credential ID. + id: String, + + /// Output format. + #[arg(short = 'o', long = "output", value_enum, default_value_t = OutputFormat::Table)] + output: OutputFormat, + }, + + /// Revoke a delegated identity credential. + #[command(help_template = LEAF_HELP_TEMPLATE, next_help_heading = "FLAGS")] + Revoke { + /// Delegated identity credential ID. + id: String, + + /// Expected resource version for compare-and-swap updates. + #[arg(long = "resource-version", default_value_t = 0)] + resource_version: u64, + }, + + /// Delete a delegated identity credential record. + #[command(help_template = LEAF_HELP_TEMPLATE, next_help_heading = "FLAGS")] + Delete { + /// Delegated identity credential ID. + id: String, + + /// Expected resource version for compare-and-swap deletes. + #[arg(long = "resource-version", default_value_t = 0)] + resource_version: u64, + }, +} + // ----------------------------------------------------------------------- // Gateway commands (replaces the old `cluster` / `cluster admin` groups) // ----------------------------------------------------------------------- @@ -1528,6 +1591,12 @@ enum SandboxCommands { #[arg(long = "provider")] providers: Vec, + /// Allow the sandbox to use a separately authorized copy of your identity for a bounded duration. + /// Opens a browser authorization flow when no usable gateway-owned grant exists. Accepts + /// positive durations with m, h, or d suffixes, for example 30m, 8h, or 7d. + #[arg(long = "use-my-identity-for", value_name = "DURATION")] + delegate_identity_for: Option, + /// Path to a custom sandbox policy YAML file. /// Overrides the built-in default and the `OPENSHELL_SANDBOX_POLICY` env var. #[arg(long, value_hint = ValueHint::FilePath)] @@ -1845,6 +1914,41 @@ enum SandboxCommands { /// Manage reusable sandbox workload templates. #[command(subcommand)] Template(SandboxTemplateCommands), + + /// Manage sandbox delegated identity. + #[command(subcommand, name = "delegated-identity")] + DelegatedIdentity(SandboxDelegatedIdentityCommands), +} + +#[derive(Subcommand, Debug)] +enum SandboxDelegatedIdentityCommands { + /// Show delegated identity status for a sandbox. + #[command(help_template = LEAF_HELP_TEMPLATE, next_help_heading = "FLAGS")] + Status { + /// Sandbox name. + #[arg(add = ArgValueCompleter::new(completers::complete_sandbox_names))] + name: String, + }, + + /// Withdraw delegated identity from a sandbox. + #[command(help_template = LEAF_HELP_TEMPLATE, next_help_heading = "FLAGS")] + Withdraw { + /// Sandbox name. + #[arg(add = ArgValueCompleter::new(completers::complete_sandbox_names))] + name: String, + }, + + /// Set delegated identity expiry to now plus the requested duration. + #[command(help_template = LEAF_HELP_TEMPLATE, next_help_heading = "FLAGS")] + Extend { + /// Sandbox name. + #[arg(add = ArgValueCompleter::new(completers::complete_sandbox_names))] + name: String, + + /// New authorization window, for example 24h. + #[arg(long = "for", value_name = "DURATION")] + duration: String, + }, } /// Common observation flags; the deadline starts after the mutation is saved. @@ -3367,6 +3471,7 @@ async fn run_async() -> Result<()> { memory, driver_config_json, providers, + delegate_identity_for, policy, forward, expose, @@ -3463,6 +3568,7 @@ async fn run_async() -> Result<()> { driver_config_json: driver_config_json.as_deref(), editor, providers: &providers, + delegate_identity_for: delegate_identity_for.as_deref(), policy: policy.as_deref(), forward, expose, @@ -3813,6 +3919,37 @@ async fn run_async() -> Result<()> { .await?; } }, + SandboxCommands::DelegatedIdentity(command) => match command { + SandboxDelegatedIdentityCommands::Status { name } => { + run::sandbox_delegated_identity_status( + endpoint, + &name, + &cli.workspace, + &tls, + ) + .await?; + } + SandboxDelegatedIdentityCommands::Withdraw { name } => { + run::sandbox_delegated_identity_withdraw( + endpoint, + &name, + &cli.workspace, + &tls, + ) + .await?; + } + SandboxDelegatedIdentityCommands::Extend { name, duration } => { + run::sandbox_delegated_identity_extend( + endpoint, + &ctx.name, + &name, + &duration, + &cli.workspace, + &tls, + ) + .await?; + } + }, } } } @@ -4075,6 +4212,61 @@ async fn run_async() -> Result<()> { apply_auth(&mut tls, &ctx.name)?; command.run(endpoint, &cli.workspace, &tls).await?; } + Some(Commands::DelegatedCredential { + command: Some(command), + }) => { + let ctx = resolve_gateway(&cli.gateway, &cli.gateway_endpoint)?; + let endpoint = &ctx.endpoint; + let mut tls = tls.with_gateway_name(&ctx.name); + apply_auth(&mut tls, &ctx.name)?; + + match command { + DelegatedCredentialCommands::List { + page_size, + page_token, + ids, + output, + } => { + run::delegated_identity_credential_list( + endpoint, + page_size, + &page_token, + ids, + output.as_str(), + &tls, + ) + .await?; + } + DelegatedCredentialCommands::Status { id, output } => { + run::delegated_identity_credential_status(endpoint, &id, output.as_str(), &tls) + .await?; + } + DelegatedCredentialCommands::Revoke { + id, + resource_version, + } => { + run::delegated_identity_credential_revoke( + endpoint, + &id, + resource_version, + &tls, + ) + .await?; + } + DelegatedCredentialCommands::Delete { + id, + resource_version, + } => { + run::delegated_identity_credential_delete( + endpoint, + &id, + resource_version, + &tls, + ) + .await?; + } + } + } Some(Commands::Term { theme }) => { let ctx = resolve_gateway(&cli.gateway, &cli.gateway_endpoint)?; let mut tls = tls.with_gateway_name(&ctx.name); @@ -4214,6 +4406,13 @@ async fn run_async() -> Result<()> { .print_help() .expect("Failed to print help"); } + Some(Commands::DelegatedCredential { command: None }) => { + Cli::command() + .find_subcommand_mut("delegated-credential") + .expect("delegated-credential subcommand exists") + .print_help() + .expect("Failed to print help"); + } Some(Commands::Gateway { command: None }) => { Cli::command() .find_subcommand_mut("gateway") @@ -5170,6 +5369,76 @@ mod tests { }) if ids == ["custom-api", "custom-alt"])); } + #[test] + fn delegated_credential_commands_parse() { + let list = Cli::try_parse_from([ + "openshell", + "delegated-credential", + "list", + "--page-size", + "25", + "--page-token", + "next-credential-page", + "--ids", + ]) + .expect("delegated credential list should parse"); + match list.command { + Some(Commands::DelegatedCredential { + command: + Some(DelegatedCredentialCommands::List { + page_size, + page_token, + ids, + output, + }), + }) => { + assert_eq!(page_size, 25); + assert_eq!(page_token, "next-credential-page"); + assert!(ids); + assert_eq!(output, OutputFormat::Table); + } + command => panic!("unexpected delegated credential list command: {command:?}"), + } + + let status = Cli::try_parse_from([ + "openshell", + "delegated-credential", + "status", + "delegated-identity-123", + "-o", + "json", + ]) + .expect("delegated credential status should parse"); + assert!(matches!( + status.command, + Some(Commands::DelegatedCredential { + command: Some(DelegatedCredentialCommands::Status { + id, + output: OutputFormat::Json, + }) + }) if id == "delegated-identity-123" + )); + + let revoke = Cli::try_parse_from([ + "openshell", + "delegated-credential", + "revoke", + "delegated-identity-123", + "--resource-version", + "7", + ]) + .expect("delegated credential revoke should parse"); + assert!(matches!( + revoke.command, + Some(Commands::DelegatedCredential { + command: Some(DelegatedCredentialCommands::Revoke { + id, + resource_version: 7, + }) + }) if id == "delegated-identity-123" + )); + } + #[test] fn profile_import_and_lint_require_exactly_one_source() { for verb in ["import", "lint"] { @@ -6363,6 +6632,31 @@ mod tests { } } + #[test] + fn sandbox_create_use_my_identity_for_parses() { + let cli = Cli::try_parse_from([ + "openshell", + "sandbox", + "create", + "--use-my-identity-for", + "8h", + ]) + .expect("sandbox create --use-my-identity-for should parse"); + match cli.command { + Some(Commands::Sandbox { + command: + Some(SandboxCommands::Create { + delegate_identity_for, + .. + }), + .. + }) => { + assert_eq!(delegate_identity_for.as_deref(), Some("8h")); + } + other => panic!("expected SandboxCommands::Create, got: {other:?}"), + } + } + /// `--approval-mode ` is rejected by clap's value parser, so the /// CLI can't smuggle through a future-mode value that the gateway /// doesn't yet know about. diff --git a/crates/openshell-cli/src/oidc_auth.rs b/crates/openshell-cli/src/oidc_auth.rs index 7e362bf420..749aff8126 100644 --- a/crates/openshell-cli/src/oidc_auth.rs +++ b/crates/openshell-cli/src/oidc_auth.rs @@ -239,7 +239,7 @@ pub async fn oidc_browser_auth_flow( () = tokio::time::sleep(AUTH_TIMEOUT) => { return Err(miette::miette!( "OIDC authentication timed out after {} seconds.\n\ - Try again with: openshell gateway login", + Please retry the command.", AUTH_TIMEOUT.as_secs() )); } diff --git a/crates/openshell-cli/src/run.rs b/crates/openshell-cli/src/run.rs index f87ee7b709..31ab023f37 100644 --- a/crates/openshell-cli/src/run.rs +++ b/crates/openshell-cli/src/run.rs @@ -38,7 +38,7 @@ pub use crate::commands::provider_readiness::{ProviderWaitOptions, sandbox_provi use crate::color::Colorize; use crate::policy_update::build_policy_update_plan; use crate::tls::{ - TlsOptions, grpc_client, owner_replica, retry_unrouted, routed_to, with_owner_hint, + GrpcClient, TlsOptions, grpc_client, owner_replica, retry_unrouted, routed_to, with_owner_hint, }; use futures::StreamExt; use miette::{IntoDiagnostic, Result, WrapErr, miette}; @@ -47,25 +47,32 @@ use openshell_bootstrap::{ }; use openshell_core::net::set_tcp_nodelay_best_effort; use openshell_core::proto::{ - ApproveAllDraftChunksRequest, ApproveDraftChunkRequest, BeginRootfsTarStagingRequest, - ClearDraftChunksRequest, CreateSandboxRequest, CreateSandboxTemplateRequest, - CreateSshSessionRequest, DeleteSandboxRequest, DeleteSandboxTemplateRequest, + ApproveAllDraftChunksRequest, ApproveDraftChunkRequest, AuthorizeDelegatedIdentityRequest, + BeginRootfsTarStagingRequest, ClearDraftChunksRequest, CreateSandboxRequest, + CreateSandboxTemplateRequest, CreateSshSessionRequest, DelegatedIdentityAuthorizationGrant, + DelegatedIdentityCredentialSummary, DelegatedIdentityRequest, + DeleteDelegatedIdentityCredentialRequest, DeleteSandboxRequest, DeleteSandboxTemplateRequest, DeleteServiceRequest, DeletionOutcome, EndpointResult, EndpointStatus, ExecSandboxRequest, - ExposeServiceRequest, GetCurrentUserRequest, GetDraftHistoryRequest, GetDraftPolicyRequest, - GetGatewayConfigRequest, GetSandboxConfigRequest, GetSandboxConfigResponse, - GetSandboxLogsRequest, GetSandboxPolicyStatusRequest, GetSandboxRequest, - GetSandboxTemplateRequest, GetServiceRequest, GpuResourceRequirements, - ListSandboxPoliciesRequest, ListSandboxTemplatesRequest, ListSandboxesRequest, - ListServicesRequest, PolicySource, PolicyStatus, RejectDraftChunkRequest, ResourceRequirements, - RevokeSshSessionRequest, Sandbox, SandboxCondition, SandboxPhase, SandboxPolicy, - SandboxResources, SandboxRestartPolicy, SandboxServiceExposure, SandboxServiceLevel, - SandboxSpec, SandboxStartup, SandboxTemplate, SandboxWorkloadConfig, SandboxWorkloadTemplate, - SandboxWorkloadTemplateSpec, ServiceAuthorizationMode, ServiceEndpointResponse, SettingScope, - StartSandboxRequest, StopSandboxRequest, TcpForwardFrame, TcpForwardInit, TcpRelayTarget, - UpdateConfigRequest, WatchSandboxRequest, exec_sandbox_event, tcp_forward_init, + ExposeServiceRequest, ExtendSandboxDelegatedIdentityRequest, GetCurrentUserRequest, + GetDelegatedIdentityAuthorizationStatusRequest, GetDelegatedIdentityCredentialStatusRequest, + GetDraftHistoryRequest, GetDraftPolicyRequest, GetGatewayConfigRequest, + GetProviderProfileRequest, GetProviderRequest, GetSandboxConfigRequest, + GetSandboxConfigResponse, GetSandboxDelegatedIdentityStatusRequest, GetSandboxLogsRequest, + GetSandboxPolicyStatusRequest, GetSandboxRequest, GetSandboxTemplateRequest, GetServiceRequest, + GpuResourceRequirements, ListDelegatedIdentityCredentialsRequest, ListSandboxPoliciesRequest, + ListSandboxTemplatesRequest, ListSandboxesRequest, ListServicesRequest, PolicySource, + PolicyStatus, ProviderProfile, RejectDraftChunkRequest, ResourceRequirements, + RevokeDelegatedIdentityCredentialRequest, RevokeSshSessionRequest, Sandbox, SandboxCondition, + SandboxPhase, SandboxPolicy, SandboxResources, SandboxRestartPolicy, SandboxServiceExposure, + SandboxServiceLevel, SandboxSpec, SandboxStartup, SandboxTemplate, SandboxWorkloadConfig, + SandboxWorkloadTemplate, SandboxWorkloadTemplateSpec, ServiceAuthorizationMode, + ServiceEndpointResponse, SettingScope, StartSandboxRequest, StopSandboxRequest, + TcpForwardFrame, TcpForwardInit, TcpRelayTarget, UpdateConfigRequest, WatchSandboxRequest, + WithdrawSandboxDelegatedIdentityRequest, exec_sandbox_event, tcp_forward_init, }; use openshell_core::settings; use openshell_core::{ObjectId, ObjectName, ObjectWorkspace}; +use openshell_providers::normalize_profile_id; use prost::Message; use std::borrow::Cow; use std::collections::HashMap; @@ -103,6 +110,8 @@ pub use openshell_core::forward::{ ForwardSpec, find_forward_by_port, list_forwards, stop_forward, stop_forwards_for_sandbox, }; +const DELEGATED_IDENTITY_OIDC_SCOPES: &str = "openid offline_access"; + #[derive(Debug, PartialEq, Eq)] enum SandboxUploadPlan { GitAware { @@ -456,6 +465,7 @@ pub struct SandboxCreateConfig<'a> { pub detach: bool, pub suppress_credential_warnings: bool, pub restart_policy: &'a str, + pub delegate_identity_for: Option<&'a str>, } impl Default for SandboxCreateConfig<'_> { @@ -486,12 +496,29 @@ impl Default for SandboxCreateConfig<'_> { detach: false, suppress_credential_warnings: false, restart_policy: "never", + delegate_identity_for: None, } } } /// Create a sandbox with default settings. -pub async fn sandbox_create( +pub fn sandbox_create<'a>( + server: &'a str, + gateway_name: &'a str, + config: SandboxCreateConfig<'a>, + workspace: &'a str, + tls: &'a TlsOptions, +) -> impl Future> + 'a { + Box::pin(sandbox_create_inner( + server, + gateway_name, + config, + workspace, + tls, + )) +} + +async fn sandbox_create_inner( server: &str, gateway_name: &str, config: SandboxCreateConfig<'_>, @@ -524,6 +551,7 @@ pub async fn sandbox_create( detach, suppress_credential_warnings, restart_policy, + delegate_identity_for, } = config; if editor.is_some() && !command.is_empty() { @@ -545,6 +573,10 @@ pub async fn sandbox_create( return Err(miette::miette!("--expose port must be in 1..=65535")); } + let delegated_identity_duration_ms = delegate_identity_for + .map(parse_delegated_identity_duration_ms) + .transpose()?; + // Plan every upload before provisioning so a rejected one leaves no sandbox. let upload_plans = uploads .iter() @@ -557,7 +589,25 @@ pub async fn sandbox_create( openshell_core::forward::check_port_available(spec)?; } - let mut client = grpc_client(server, tls).await.wrap_err_with(|| { + if template.is_some() + && (from.is_some() + || gpu_requirements.is_some() + || cpu.is_some() + || memory.is_some() + || driver_config_json.is_some() + || !environment.is_empty()) + { + return Err(miette::miette!( + "--template cannot be combined with inline workload flags" + )); + } + + let mut active_tls = if delegated_identity_duration_ms.is_some() { + ensure_delegated_identity_authorization(server, gateway_name, tls).await? + } else { + tls.clone() + }; + let mut client = grpc_client(server, &active_tls).await.wrap_err_with(|| { format!( "failed to connect to gateway '{gateway_name}' at {server}. \ Start the gateway service with the installed package manager, \ @@ -565,7 +615,7 @@ pub async fn sandbox_create( ) })?; let effective_server = server.to_string(); - let effective_tls = tls.clone(); + let mut effective_tls = active_tls.clone(); // Provider profiles are import-only, so the catalog lives on the gateway. // Its only consumer is the credential warning, which is advisory: an @@ -576,19 +626,6 @@ pub async fn sandbox_create( .unwrap_or_default(); warn_credential_env_vars(&environment, &profile_catalog, suppress_credential_warnings); - if template.is_some() - && (from.is_some() - || gpu_requirements.is_some() - || cpu.is_some() - || memory.is_some() - || driver_config_json.is_some() - || !environment.is_empty()) - { - return Err(miette::miette!( - "--template cannot be combined with inline workload flags" - )); - } - // Resolve the --from flag into a container image reference, or stage a // rootfs tar on the gateway and retain its staging token. Template creates // resolve workload shape on the gateway and skip --from. @@ -613,6 +650,9 @@ pub async fn sandbox_create( let configured_providers = ensure_required_providers(&mut client, providers, auto_providers_override, workspace) .await?; + if delegate_identity_for.is_none() { + warn_delegated_identity_profiles(&mut client, &configured_providers, workspace).await?; + } let policy = load_sandbox_policy(policy)?; let resource_limits = if template.is_none() { @@ -648,6 +688,12 @@ pub async fn sandbox_create( }; let resource_requirements = gpu_requirements.map(|gpu| ResourceRequirements { gpu: Some(gpu) }); + let delegated_identity = delegated_identity_duration_ms.map(delegated_identity_request); + if delegated_identity.is_some() { + active_tls = tls_with_current_oidc_login(gateway_name, tls).await?; + client = grpc_client(server, &active_tls).await?; + effective_tls = active_tls.clone(); + } let main_terminal = tty_override .unwrap_or_else(|| std::io::stdin().is_terminal() && std::io::stdout().is_terminal()); @@ -708,6 +754,7 @@ pub async fn sandbox_create( }) .into_iter() .collect(), + delegated_identity, }; let response = match client.create_sandbox(request).await { @@ -1372,7 +1419,7 @@ fn rootfs_tar_sources_supported_for_gateway(metadata: Option<&GatewayMetadata>) /// file. Returns the token to pass on `CreateSandbox`. async fn stage_rootfs_tar( gateway_name: &str, - client: &mut crate::tls::GrpcClient, + client: &mut GrpcClient, workspace: &str, tar_path: &Path, ) -> Result { @@ -1570,6 +1617,729 @@ pub async fn sandbox_sync_command( Ok(()) } +fn delegated_identity_request(duration_ms: i64) -> DelegatedIdentityRequest { + DelegatedIdentityRequest { + duration: u64::try_from(duration_ms).ok().and_then(|value| { + openshell_core::time::duration_from_std(Duration::from_millis(value)).ok() + }), + } +} + +async fn tls_with_current_oidc_login(gateway_name: &str, tls: &TlsOptions) -> Result { + let metadata = openshell_bootstrap::load_gateway_metadata(gateway_name) + .map_err(|_| miette::miette!("failed to load metadata for gateway '{gateway_name}'"))?; + if metadata.auth_mode.as_deref() != Some("oidc") { + return Err(miette::miette!( + "delegated identity requires an OIDC-authenticated gateway" + )); + } + let bundle = + crate::oidc_auth::ensure_valid_oidc_token_bundle(gateway_name, tls.gateway_insecure) + .await + .map_err(|error| { + miette::miette!("failed to load or refresh the normal CLI login: {error}") + })?; + let mut current = tls.clone(); + current.oidc_token = Some(bundle.access_token); + Ok(current) +} + +async fn ensure_delegated_identity_authorization( + server: &str, + gateway_name: &str, + tls: &TlsOptions, +) -> Result { + let mut current_tls = tls_with_current_oidc_login(gateway_name, tls).await?; + let mut client = grpc_client(server, ¤t_tls).await?; + let status = client + .get_delegated_identity_authorization_status( + GetDelegatedIdentityAuthorizationStatusRequest {}, + ) + .await + .map_err(|status| { + if status.code() == Code::Unimplemented { + miette::miette!( + "the gateway does not support separate delegated identity authorization; upgrade the gateway before using --use-my-identity-for" + ) + } else { + miette::miette!(status.to_string()) + } + })? + .into_inner(); + if status.usable { + return Ok(current_tls); + } + if !status.reauthorization_required { + return Err(miette::miette!( + "delegated identity authorization is unavailable: {}", + non_empty_or(&status.reason, "unknown reason") + )); + } + + let metadata = openshell_bootstrap::load_gateway_metadata(gateway_name) + .map_err(|_| miette::miette!("failed to load metadata for gateway '{gateway_name}'"))?; + let issuer = metadata.oidc_issuer.as_deref().ok_or_else(|| { + miette::miette!("gateway '{gateway_name}' has OIDC auth but no issuer URL") + })?; + let client_id = metadata + .oidc_client_id + .as_deref() + .unwrap_or("openshell-cli"); + let audience = metadata.oidc_audience.as_deref(); + let local_refresh_token = openshell_bootstrap::oidc_token::load_oidc_token(gateway_name) + .and_then(|bundle| bundle.refresh_token); + + eprintln!("Authorizing a separate, gateway-owned OAuth grant for delegated identity..."); + let delegated_bundle = crate::oidc_auth::oidc_browser_auth_flow( + issuer, + client_id, + audience, + Some(DELEGATED_IDENTITY_OIDC_SCOPES), + tls.gateway_insecure, + false, + ) + .await + .map_err(|error| miette::miette!("delegated identity authorization failed: {error}"))?; + let delegated_refresh_token = delegated_bundle.refresh_token.ok_or_else(|| { + miette::miette!( + "the identity provider did not issue a delegated refresh token; enable offline access for the OIDC client" + ) + })?; + if local_refresh_token.as_deref() == Some(delegated_refresh_token.as_str()) { + return Err(miette::miette!( + "the identity provider reused the normal CLI refresh token for delegated identity; configure the OIDC client to issue a distinct grant for each browser authorization" + )); + } + + // Browser authorization can outlive the normal access token. Rebuild the + // RPC client from the current local login before transferring the grant. + current_tls = tls_with_current_oidc_login(gateway_name, tls).await?; + if openshell_bootstrap::oidc_token::load_oidc_token(gateway_name) + .and_then(|bundle| bundle.refresh_token) + .as_deref() + == Some(delegated_refresh_token.as_str()) + { + return Err(miette::miette!( + "the identity provider reused the normal CLI refresh token for delegated identity; configure the OIDC client to issue a distinct grant for each browser authorization" + )); + } + client = grpc_client(server, ¤t_tls).await?; + client + .authorize_delegated_identity(AuthorizeDelegatedIdentityRequest { + grant: Some(DelegatedIdentityAuthorizationGrant { + issuer: delegated_bundle.issuer, + client_id: delegated_bundle.client_id, + refresh_token: delegated_refresh_token, + access_token: delegated_bundle.access_token, + scopes: DELEGATED_IDENTITY_OIDC_SCOPES.to_string(), + audience: audience.unwrap_or(client_id).to_string(), + }), + }) + .await + .map_err(|status| miette::miette!(status.to_string()))?; + eprintln!("Delegated identity authorization stored by the gateway."); + Ok(current_tls) +} + +fn parse_delegated_identity_duration_ms(value: &str) -> Result { + let value = value.trim(); + let (number, multiplier): (&str, i64) = match value.as_bytes().last().copied() { + Some(b'm') => (&value[..value.len() - 1], 60_000), + Some(b'h') => (&value[..value.len() - 1], 3_600_000), + Some(b'd') => (&value[..value.len() - 1], 86_400_000), + _ => { + return Err(miette::miette!( + "invalid delegated identity duration '{value}'; use a positive duration with m, h, or d suffix" + )); + } + }; + let amount = number.parse::().map_err(|_| { + miette::miette!( + "invalid delegated identity duration '{value}'; use a positive integer with m, h, or d suffix" + ) + })?; + if amount <= 0 { + return Err(miette::miette!( + "delegated identity duration must be greater than zero" + )); + } + amount + .checked_mul(multiplier) + .ok_or_else(|| miette::miette!("delegated identity duration is too large")) +} + +fn current_time_ms() -> i64 { + std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap_or_default() + .as_millis() + .try_into() + .unwrap_or(i64::MAX) +} + +async fn warn_delegated_identity_profiles( + client: &mut GrpcClient, + provider_names: &[String], + workspace: &str, +) -> Result<()> { + for provider_name in provider_names { + let provider = client + .get_provider(GetProviderRequest { + name: provider_name.clone(), + workspace_scope: Some(openshell_core::proto::workspace_selector(workspace)), + }) + .await + .map_err(|status| miette::miette!(status.to_string()))? + .into_inner() + .provider; + let Some(provider) = provider else { + continue; + }; + let profile_id = + normalize_profile_id(&provider.r#type).unwrap_or_else(|| provider.r#type.clone()); + let profile = client + .get_provider_profile(GetProviderProfileRequest { + id: profile_id, + workspace_scope: (!provider.profile_workspace.is_empty()).then(|| { + openshell_core::proto::workspace_selector(&provider.profile_workspace) + }), + }) + .await + .ok() + .and_then(|response| response.into_inner().profile); + let Some(profile) = profile else { + continue; + }; + if profile_uses_sandbox_delegated_identity(&profile) { + eprintln!( + "Provider '{provider_name}' uses sandbox delegated identity. Token exchange will fail because this sandbox was not created with delegated identity. Delete and recreate the sandbox with --use-my-identity-for= to enable it." + ); + } + } + Ok(()) +} + +fn profile_uses_sandbox_delegated_identity(profile: &ProviderProfile) -> bool { + profile.credentials.iter().any(|credential| { + credential + .token_grant + .as_ref() + .and_then(|grant| grant.subject_token.as_ref()) + .is_some_and(|subject| subject.source == "sandbox_delegated_identity") + }) +} + +pub async fn sandbox_delegated_identity_status( + server: &str, + name: &str, + workspace: &str, + tls: &TlsOptions, +) -> Result<()> { + let mut client = grpc_client(server, tls).await?; + let response = client + .get_sandbox_delegated_identity_status(GetSandboxDelegatedIdentityStatusRequest { + name: name.to_string(), + workspace_scope: Some(openshell_core::proto::workspace_selector(workspace)), + }) + .await + .map_err(|status| miette::miette!(status.to_string()))? + .into_inner(); + let Some(delegation) = response.delegated_identity else { + println!("Delegated identity: disabled"); + return Ok(()); + }; + let now_ms = proto_timestamp_millis(response.current_time.as_ref()); + let credential_revoked_at_ms = + proto_timestamp_millis(response.credential_revocation_time.as_ref()); + let withdrawn_at_ms = proto_timestamp_millis(delegation.withdrawal_time.as_ref()); + let delegated_until_ms = proto_timestamp_millis(delegation.delegation_expiration_time.as_ref()); + let status = sandbox_delegation_status( + response.credential_missing, + credential_revoked_at_ms, + withdrawn_at_ms, + delegated_until_ms, + now_ms, + ); + println!("Delegated identity: enabled"); + println!("Credential ID: {}", delegation.credential_id); + println!("Principal: {}", delegation.principal_subject); + println!("Status: {status}"); + match status { + "active" => println!( + "Valid for: {}", + format_remaining_duration(delegated_until_ms, now_ms) + ), + "withdrawn" => println!( + "Withdrawn: {}", + format_age(withdrawn_at_ms, now_ms) + ), + "revoked" => println!( + "Revoked: {}", + format_age(credential_revoked_at_ms, now_ms) + ), + "credential-missing" => println!("Credential: missing"), + "expired" => println!( + "Expired: {}", + format_age(delegated_until_ms, now_ms) + ), + _ => {} + } + Ok(()) +} + +pub async fn sandbox_delegated_identity_withdraw( + server: &str, + name: &str, + workspace: &str, + tls: &TlsOptions, +) -> Result<()> { + let mut client = grpc_client(server, tls).await?; + let response = client + .withdraw_sandbox_delegated_identity(WithdrawSandboxDelegatedIdentityRequest { + name: name.to_string(), + workspace_scope: Some(openshell_core::proto::workspace_selector(workspace)), + }) + .await + .map_err(|status| miette::miette!(status.to_string()))? + .into_inner(); + let action = if response.withdrawn { + "Withdrew" + } else { + "Already withdrawn" + }; + println!("{action} delegated identity for sandbox {name}"); + Ok(()) +} + +pub async fn sandbox_delegated_identity_extend( + server: &str, + gateway_name: &str, + name: &str, + duration: &str, + workspace: &str, + tls: &TlsOptions, +) -> Result<()> { + let duration_ms = parse_delegated_identity_duration_ms(duration)?; + let current_tls = ensure_delegated_identity_authorization(server, gateway_name, tls).await?; + let mut client = grpc_client(server, ¤t_tls).await?; + let delegated_identity = delegated_identity_request(duration_ms); + let response = client + .extend_sandbox_delegated_identity(ExtendSandboxDelegatedIdentityRequest { + name: name.to_string(), + delegated_identity: Some(delegated_identity), + workspace_scope: Some(openshell_core::proto::workspace_selector(workspace)), + }) + .await + .map_err(|status| miette::miette!(status.to_string()))? + .into_inner(); + let sandbox_name = response + .sandbox + .as_ref() + .map(ObjectName::object_name) + .filter(|name| !name.is_empty()) + .unwrap_or(name); + println!("Extended delegated identity for sandbox {sandbox_name}"); + Ok(()) +} + +pub async fn delegated_identity_credential_list( + server: &str, + page_size: i32, + page_token: &str, + ids_only: bool, + output: &str, + tls: &TlsOptions, +) -> Result<()> { + let mut client = grpc_client(server, tls).await?; + let response = client + .list_delegated_identity_credentials(ListDelegatedIdentityCredentialsRequest { + page_size, + page_token: page_token.to_string(), + }) + .await + .map_err(|status| miette::miette!(status.to_string()))? + .into_inner(); + let next_page_token = response.next_page_token; + let credentials = response.credentials; + + if crate::output::print_paginated_output_collection( + output, + "credentials", + &credentials, + &next_page_token, + delegated_identity_credential_to_json, + )? { + return Ok(()); + } + crate::output::print_next_page_token(&next_page_token); + + if credentials.is_empty() { + if !ids_only { + println!("No delegated identity credentials found."); + } + return Ok(()); + } + + if ids_only { + for credential in credentials { + println!("{}", delegated_credential_object_id(&credential)); + } + return Ok(()); + } + + print_delegated_identity_credential_table(&credentials, current_time_ms()); + Ok(()) +} + +pub async fn delegated_identity_credential_status( + server: &str, + id: &str, + output: &str, + tls: &TlsOptions, +) -> Result<()> { + let mut client = grpc_client(server, tls).await?; + let response = client + .get_delegated_identity_credential_status(GetDelegatedIdentityCredentialStatusRequest { + id: id.to_string(), + }) + .await + .map_err(|status| miette::miette!(status.to_string()))? + .into_inner(); + let credential = response + .credential + .ok_or_else(|| miette::miette!("delegated identity credential missing from response"))?; + let now_ms = proto_timestamp_millis(response.current_time.as_ref()); + let view = serde_json::json!({ + "credential": delegated_identity_credential_to_json(&credential), + "now_ms": now_ms, + }); + if crate::output::print_output_single(output, &view, Clone::clone)? { + return Ok(()); + } + + println!("{}", "Delegated identity credential:".cyan().bold()); + println!(); + print_delegated_identity_credential_detail(&credential, now_ms); + Ok(()) +} + +pub async fn delegated_identity_credential_revoke( + server: &str, + id: &str, + expected_resource_version: u64, + tls: &TlsOptions, +) -> Result<()> { + let mut client = grpc_client(server, tls).await?; + let response = client + .revoke_delegated_identity_credential(RevokeDelegatedIdentityCredentialRequest { + id: id.to_string(), + expected_resource_version, + }) + .await + .map_err(|status| miette::miette!(status.to_string()))? + .into_inner(); + if response.revoked { + println!("Revoked delegated identity credential {id}"); + } else { + println!("Delegated identity credential {id} was already revoked"); + } + Ok(()) +} + +pub async fn delegated_identity_credential_delete( + server: &str, + id: &str, + expected_resource_version: u64, + tls: &TlsOptions, +) -> Result<()> { + let mut client = grpc_client(server, tls).await?; + let response = client + .delete_delegated_identity_credential(DeleteDelegatedIdentityCredentialRequest { + id: id.to_string(), + expected_resource_version, + }) + .await + .map_err(|status| miette::miette!(status.to_string()))? + .into_inner(); + if response.deleted { + println!("Deleted delegated identity credential {id}"); + } else { + println!("Delegated identity credential {id} was not found"); + } + Ok(()) +} + +fn delegated_identity_credential_to_json( + credential: &DelegatedIdentityCredentialSummary, +) -> serde_json::Value { + let metadata = credential.metadata.as_ref(); + serde_json::json!({ + "id": delegated_credential_object_id(credential), + "name": delegated_credential_object_name(credential), + "workspace": delegated_credential_object_workspace(credential), + "resource_version": metadata.map(|meta| meta.resource_version).unwrap_or_default(), + "created_at_ms": metadata + .and_then(|meta| meta.created_time.as_ref()) + .and_then(|value| openshell_core::time::timestamp_to_millis(value).ok()) + .unwrap_or_default(), + "issuer": credential.issuer, + "client_id": credential.client_id, + "principal_subject": credential.principal_subject, + "access_token_present": credential.access_token_present, + "refresh_token_present": credential.refresh_token_present, + "access_token_expires_at_ms": proto_timestamp_millis(credential.access_token_expiration_time.as_ref()), + "scopes": credential.scopes, + "audience": credential.audience, + "last_refresh_at_ms": proto_timestamp_millis(credential.last_refresh_time.as_ref()), + "revoked_at_ms": proto_timestamp_millis(credential.revocation_time.as_ref()), + }) +} + +fn print_delegated_identity_credential_table( + credentials: &[DelegatedIdentityCredentialSummary], + now: i64, +) { + println!( + "{:<84} {:<40} {:<24} {:<10} {:<16} {:<14} {:>8}", + "ID", "PRINCIPAL", "CLIENT_ID", "STATUS", "ACCESS_VALID_FOR", "LAST_REFRESH", "RV" + ); + println!("{}", "-".repeat(208)); + for credential in credentials { + let resource_version = credential + .metadata + .as_ref() + .map(|meta| meta.resource_version) + .unwrap_or_default(); + println!( + "{:<84} {:<40} {:<24} {:<10} {:<16} {:<14} {:>8}", + delegated_credential_object_id(credential), + credential.principal_subject, + truncate_for_table(&credential.client_id, 24), + delegated_credential_status(credential, now), + credential_access_valid_for(credential, now), + format_optional_age( + proto_timestamp_millis(credential.last_refresh_time.as_ref()), + now + ), + resource_version, + ); + } +} + +fn print_delegated_identity_credential_detail( + credential: &DelegatedIdentityCredentialSummary, + now: i64, +) { + let metadata = credential.metadata.as_ref(); + println!( + " {:<22} {}", + "id:", + delegated_credential_object_id(credential) + ); + println!(" {:<22} {}", "issuer:", credential.issuer); + println!(" {:<22} {}", "client_id:", credential.client_id); + println!( + " {:<22} {}", + "principal_subject:", credential.principal_subject + ); + println!( + " {:<22} {}", + "resource_version:", + metadata + .map(|meta| meta.resource_version) + .unwrap_or_default() + ); + println!( + " {:<22} {}", + "created_at_ms:", + metadata + .and_then(|meta| meta.created_time.as_ref()) + .and_then(|value| openshell_core::time::timestamp_to_millis(value).ok()) + .unwrap_or_default() + ); + println!( + " {:<22} {}", + "access_token_present:", credential.access_token_present + ); + println!( + " {:<22} {}", + "refresh_token_present:", credential.refresh_token_present + ); + println!( + " {:<22} {}", + "status:", + delegated_credential_status(credential, now) + ); + println!( + " {:<22} {}", + "access_valid_for:", + credential_access_valid_for(credential, now) + ); + println!(" {:<22} {}", "scopes:", credential.scopes); + println!(" {:<22} {}", "audience:", credential.audience); + println!( + " {:<22} {}", + "last_refresh:", + format_optional_age( + proto_timestamp_millis(credential.last_refresh_time.as_ref()), + now + ) + ); + let revoked_at_ms = proto_timestamp_millis(credential.revocation_time.as_ref()); + if revoked_at_ms > 0 { + println!(" {:<22} {}", "revoked:", format_age(revoked_at_ms, now)); + } +} + +fn sandbox_delegation_status( + credential_missing: bool, + credential_revoked_at_ms: i64, + withdrawn_at_ms: i64, + delegated_until_ms: i64, + now_ms: i64, +) -> &'static str { + if credential_missing { + "credential-missing" + } else if credential_revoked_at_ms > 0 { + "revoked" + } else if withdrawn_at_ms > 0 { + "withdrawn" + } else if delegated_until_ms <= now_ms { + "expired" + } else { + "active" + } +} + +fn delegated_credential_status( + credential: &DelegatedIdentityCredentialSummary, + now: i64, +) -> &'static str { + let revoked_at_ms = proto_timestamp_millis(credential.revocation_time.as_ref()); + let access_token_expires_at_ms = + proto_timestamp_millis(credential.access_token_expiration_time.as_ref()); + if revoked_at_ms > 0 { + "revoked" + } else if access_token_expires_at_ms > 0 && access_token_expires_at_ms <= now { + "expired" + } else { + "active" + } +} + +fn delegated_credential_object_id(credential: &DelegatedIdentityCredentialSummary) -> &str { + credential + .metadata + .as_ref() + .map(|metadata| metadata.id.as_str()) + .unwrap_or_default() +} + +fn delegated_credential_object_name(credential: &DelegatedIdentityCredentialSummary) -> &str { + credential + .metadata + .as_ref() + .map(|metadata| metadata.name.as_str()) + .unwrap_or_default() +} + +fn delegated_credential_object_workspace(credential: &DelegatedIdentityCredentialSummary) -> &str { + credential + .metadata + .as_ref() + .map(|metadata| metadata.workspace.as_str()) + .unwrap_or_default() +} + +fn credential_access_valid_for( + credential: &DelegatedIdentityCredentialSummary, + now: i64, +) -> String { + let access_token_expires_at_ms = + proto_timestamp_millis(credential.access_token_expiration_time.as_ref()); + if delegated_credential_status(credential, now) != "active" { + "-".to_string() + } else if access_token_expires_at_ms == 0 { + "unknown".to_string() + } else { + format_remaining_duration(access_token_expires_at_ms, now) + } +} + +fn proto_timestamp_millis(value: Option<&prost_types::Timestamp>) -> i64 { + value + .and_then(|value| openshell_core::time::timestamp_to_millis(value).ok()) + .unwrap_or_default() +} + +fn format_remaining_duration(until_ms: i64, now_ms: i64) -> String { + if until_ms <= now_ms { + "-".to_string() + } else { + format_compact_duration_ms(until_ms.saturating_sub(now_ms)) + } +} + +fn format_optional_age(timestamp_ms: i64, now_ms: i64) -> String { + if timestamp_ms > 0 { + format_age(timestamp_ms, now_ms) + } else { + "never".to_string() + } +} + +fn format_age(timestamp_ms: i64, now_ms: i64) -> String { + if timestamp_ms <= 0 { + return "never".to_string(); + } + if timestamp_ms > now_ms { + return format!( + "in {}", + format_compact_duration_ms(timestamp_ms.saturating_sub(now_ms)) + ); + } + format!( + "{} ago", + format_compact_duration_ms(now_ms.saturating_sub(timestamp_ms)) + ) +} + +fn format_compact_duration_ms(duration_ms: i64) -> String { + let seconds = duration_ms.saturating_add(999) / 1000; + if seconds < 60 { + return format!("{}s", seconds.max(0)); + } + let minutes = seconds / 60; + if minutes < 60 { + return format!("{minutes}m"); + } + let hours = minutes / 60; + if hours < 48 { + return format!("{hours}h"); + } + let days = hours / 24; + format!("{days}d") +} + +fn truncate_for_table(value: &str, max_len: usize) -> String { + if value.len() <= max_len { + value.to_string() + } else if max_len <= 1 { + ".".to_string() + } else { + let prefix = value + .chars() + .take(max_len.saturating_sub(3)) + .collect::(); + format!("{prefix}...") + } +} + +/// Fetch a sandbox by name. +/// +/// Policy always comes from [`GetSandboxConfig`] (effective active policy, sandbox +/// or global). With `policy_only`, prints only that YAML to stdout; otherwise +/// prints sandbox metadata and the same policy with formatted YAML. pub async fn sandbox_get( server: &str, name: &str, @@ -2243,7 +3013,7 @@ pub async fn service_forward_tcp( } async fn create_forward_session_token( - client: &mut crate::tls::GrpcClient, + client: &mut GrpcClient, sandbox_name: &str, workspace: &str, ) -> std::result::Result { @@ -2269,7 +3039,7 @@ fn forward_requires_session_token(status: &Status) -> bool { /// Confirm the sandbox is still ready, returning the replica that owns it. async fn fetch_ready_sandbox_for_forward( - client: &mut crate::tls::GrpcClient, + client: &mut GrpcClient, name: &str, workspace: &str, ) -> Result> { @@ -2363,7 +3133,7 @@ fn parse_tcp_forward_spec(local: Option<&str>, default_port: u16) -> Result<(Str #[allow(clippy::too_many_arguments)] // one connection's sandbox, target, and authorization context async fn forward_one_tcp_connection( - client: &mut crate::tls::GrpcClient, + client: &mut GrpcClient, socket: tokio::net::TcpStream, sandbox_name: String, workspace: String, @@ -2449,7 +3219,7 @@ type OpenForwardTcpStream = ( ); async fn open_forward_tcp_stream( - client: &mut crate::tls::GrpcClient, + client: &mut GrpcClient, init: TcpForwardInit, replica: Option<&AsciiMetadataValue>, ) -> std::result::Result { @@ -2462,7 +3232,7 @@ async fn open_forward_tcp_stream( Ok((tx, response?.into_inner())) } -async fn revoke_forward_session_token(client: &mut crate::tls::GrpcClient, token: Option) { +async fn revoke_forward_session_token(client: &mut GrpcClient, token: Option) { if let Some(token) = token { let _ = client .revoke_ssh_session(RevokeSshSessionRequest { @@ -2576,7 +3346,7 @@ impl Drop for TaskGuard { #[allow(clippy::too_many_arguments)] async fn sandbox_exec_streaming_grpc( - mut client: crate::tls::GrpcClient, + mut client: GrpcClient, sandbox: &Sandbox, command: &[String], workdir: Option<&str>, @@ -4146,7 +4916,7 @@ pub async fn sandbox_start( } async fn wait_for_lifecycle_phase( - client: &mut crate::tls::GrpcClient, + client: &mut GrpcClient, sandbox: Sandbox, target: SandboxPhase, ) -> Result { @@ -7014,14 +7784,15 @@ fn format_endpoint(endpoint: &openshell_core::proto::NetworkEndpoint) -> String mod tests { use super::{ ForwardTcpConnectionError, PolicyGetView, ProvisioningStep, build_sandbox_resource_limits, - format_endpoint, format_log_line, forward_requires_session_token, git_sync_files, - has_main_process_result, parse_cli_setting_value, parse_credential_expiry_cli_value, - parse_driver_config_json, parse_env_from_pairs, parse_secret_material_env_pairs, - policy_revision_list_json, policy_revision_to_json, proto_execution_timeout, - provisioning_timeout_message, ready_false_condition_message, relay_local_socket, - resolve_from, rootfs_tar_sources_supported_for_gateway, sandbox_should_persist, - sandbox_upload_plan, service_endpoint_to_json, service_status_error, - service_url_for_gateway, workspace_member_to_json, + delegated_identity_request, format_endpoint, format_log_line, + forward_requires_session_token, git_sync_files, has_main_process_result, + parse_cli_setting_value, parse_credential_expiry_cli_value, + parse_delegated_identity_duration_ms, parse_driver_config_json, parse_env_from_pairs, + parse_secret_material_env_pairs, policy_revision_list_json, policy_revision_to_json, + proto_execution_timeout, provisioning_timeout_message, ready_false_condition_message, + relay_local_socket, resolve_from, rootfs_tar_sources_supported_for_gateway, + sandbox_should_persist, sandbox_upload_plan, service_endpoint_to_json, + service_status_error, service_url_for_gateway, workspace_member_to_json, }; use openshell_core::proto::TcpForwardFrame; @@ -7081,6 +7852,36 @@ mod tests { datamodel::v1::ObjectMeta, }; + #[test] + fn delegated_identity_lifecycle_request_contains_only_the_duration() { + let request = delegated_identity_request(60_000); + + assert_eq!( + openshell_core::time::duration_to_std(request.duration.as_ref().unwrap()) + .unwrap() + .as_millis(), + 60_000 + ); + } + + #[test] + fn delegated_identity_duration_is_validated_before_authorization() { + assert_eq!( + parse_delegated_identity_duration_ms("5m").expect("valid duration"), + 300_000 + ); + assert_eq!( + parse_delegated_identity_duration_ms("2h").expect("valid duration"), + 7_200_000 + ); + assert_eq!( + parse_delegated_identity_duration_ms("1d").expect("valid duration"), + 86_400_000 + ); + assert!(parse_delegated_identity_duration_ms("0m").is_err()); + assert!(parse_delegated_identity_duration_ms("5s").is_err()); + } + #[test] fn policy_revision_json_includes_revision_provenance() { let revision = SandboxPolicyRevision { diff --git a/crates/openshell-cli/tests/ensure_providers_integration.rs b/crates/openshell-cli/tests/ensure_providers_integration.rs index ba5f60c6cc..357b743713 100644 --- a/crates/openshell-cli/tests/ensure_providers_integration.rs +++ b/crates/openshell-cli/tests/ensure_providers_integration.rs @@ -176,6 +176,83 @@ impl OpenShell for TestOpenShell { Err(Status::unimplemented("unused")) } + async fn get_delegated_identity_authorization_status( + &self, + _request: tonic::Request< + openshell_core::proto::GetDelegatedIdentityAuthorizationStatusRequest, + >, + ) -> Result< + Response, + Status, + > { + Err(Status::unimplemented("unused")) + } + + async fn authorize_delegated_identity( + &self, + _request: tonic::Request, + ) -> Result, Status> { + Err(Status::unimplemented("unused")) + } + + async fn get_sandbox_delegated_identity_status( + &self, + _request: tonic::Request, + ) -> Result, Status> + { + Err(Status::unimplemented("unused")) + } + + async fn withdraw_sandbox_delegated_identity( + &self, + _request: tonic::Request, + ) -> Result, Status> + { + Err(Status::unimplemented("unused")) + } + + async fn extend_sandbox_delegated_identity( + &self, + _request: tonic::Request, + ) -> Result, Status> + { + Err(Status::unimplemented("unused")) + } + + async fn list_delegated_identity_credentials( + &self, + _request: tonic::Request, + ) -> Result, Status> + { + Err(Status::unimplemented("unused")) + } + + async fn get_delegated_identity_credential_status( + &self, + _request: tonic::Request< + openshell_core::proto::GetDelegatedIdentityCredentialStatusRequest, + >, + ) -> Result, Status> + { + Err(Status::unimplemented("unused")) + } + + async fn revoke_delegated_identity_credential( + &self, + _request: tonic::Request, + ) -> Result, Status> + { + Err(Status::unimplemented("unused")) + } + + async fn delete_delegated_identity_credential( + &self, + _request: tonic::Request, + ) -> Result, Status> + { + Err(Status::unimplemented("unused")) + } + async fn get_sandbox( &self, _request: tonic::Request, diff --git a/crates/openshell-cli/tests/mtls_integration.rs b/crates/openshell-cli/tests/mtls_integration.rs index 4428666184..228dcf25e9 100644 --- a/crates/openshell-cli/tests/mtls_integration.rs +++ b/crates/openshell-cli/tests/mtls_integration.rs @@ -130,6 +130,83 @@ impl OpenShell for TestOpenShell { Err(Status::unimplemented("unused")) } + async fn get_delegated_identity_authorization_status( + &self, + _request: tonic::Request< + openshell_core::proto::GetDelegatedIdentityAuthorizationStatusRequest, + >, + ) -> Result< + Response, + Status, + > { + Err(Status::unimplemented("unused")) + } + + async fn authorize_delegated_identity( + &self, + _request: tonic::Request, + ) -> Result, Status> { + Err(Status::unimplemented("unused")) + } + + async fn get_sandbox_delegated_identity_status( + &self, + _request: tonic::Request, + ) -> Result, Status> + { + Err(Status::unimplemented("unused")) + } + + async fn withdraw_sandbox_delegated_identity( + &self, + _request: tonic::Request, + ) -> Result, Status> + { + Err(Status::unimplemented("unused")) + } + + async fn extend_sandbox_delegated_identity( + &self, + _request: tonic::Request, + ) -> Result, Status> + { + Err(Status::unimplemented("unused")) + } + + async fn list_delegated_identity_credentials( + &self, + _request: tonic::Request, + ) -> Result, Status> + { + Err(Status::unimplemented("unused")) + } + + async fn get_delegated_identity_credential_status( + &self, + _request: tonic::Request< + openshell_core::proto::GetDelegatedIdentityCredentialStatusRequest, + >, + ) -> Result, Status> + { + Err(Status::unimplemented("unused")) + } + + async fn revoke_delegated_identity_credential( + &self, + _request: tonic::Request, + ) -> Result, Status> + { + Err(Status::unimplemented("unused")) + } + + async fn delete_delegated_identity_credential( + &self, + _request: tonic::Request, + ) -> Result, Status> + { + Err(Status::unimplemented("unused")) + } + async fn get_sandbox( &self, _request: tonic::Request, diff --git a/crates/openshell-cli/tests/provider_commands_integration.rs b/crates/openshell-cli/tests/provider_commands_integration.rs index 538eebf060..45e8104bd4 100644 --- a/crates/openshell-cli/tests/provider_commands_integration.rs +++ b/crates/openshell-cli/tests/provider_commands_integration.rs @@ -329,6 +329,83 @@ impl OpenShell for TestOpenShell { Err(Status::unimplemented("unused")) } + async fn get_delegated_identity_authorization_status( + &self, + _request: tonic::Request< + openshell_core::proto::GetDelegatedIdentityAuthorizationStatusRequest, + >, + ) -> Result< + Response, + Status, + > { + Err(Status::unimplemented("unused")) + } + + async fn authorize_delegated_identity( + &self, + _request: tonic::Request, + ) -> Result, Status> { + Err(Status::unimplemented("unused")) + } + + async fn get_sandbox_delegated_identity_status( + &self, + _request: tonic::Request, + ) -> Result, Status> + { + Err(Status::unimplemented("unused")) + } + + async fn withdraw_sandbox_delegated_identity( + &self, + _request: tonic::Request, + ) -> Result, Status> + { + Err(Status::unimplemented("unused")) + } + + async fn extend_sandbox_delegated_identity( + &self, + _request: tonic::Request, + ) -> Result, Status> + { + Err(Status::unimplemented("unused")) + } + + async fn list_delegated_identity_credentials( + &self, + _request: tonic::Request, + ) -> Result, Status> + { + Err(Status::unimplemented("unused")) + } + + async fn get_delegated_identity_credential_status( + &self, + _request: tonic::Request< + openshell_core::proto::GetDelegatedIdentityCredentialStatusRequest, + >, + ) -> Result, Status> + { + Err(Status::unimplemented("unused")) + } + + async fn revoke_delegated_identity_credential( + &self, + _request: tonic::Request, + ) -> Result, Status> + { + Err(Status::unimplemented("unused")) + } + + async fn delete_delegated_identity_credential( + &self, + _request: tonic::Request, + ) -> Result, Status> + { + Err(Status::unimplemented("unused")) + } + async fn get_sandbox( &self, request: tonic::Request, diff --git a/crates/openshell-cli/tests/sandbox_create_lifecycle_integration.rs b/crates/openshell-cli/tests/sandbox_create_lifecycle_integration.rs index 625d944fa4..b0a449446a 100644 --- a/crates/openshell-cli/tests/sandbox_create_lifecycle_integration.rs +++ b/crates/openshell-cli/tests/sandbox_create_lifecycle_integration.rs @@ -7,19 +7,23 @@ mod helpers; use helpers::TempDir; use helpers::{Environment, build_ca, build_client_cert, build_server_cert}; -use openshell_bootstrap::load_last_sandbox; +use openshell_bootstrap::oidc_token::OidcTokenBundle; +use openshell_bootstrap::{GatewayMetadata, load_last_sandbox}; use openshell_cli::run; use openshell_cli::tls::TlsOptions; use openshell_core::proto::open_shell_server::{OpenShell, OpenShellServer}; use openshell_core::proto::{ - AttachSandboxProviderRequest, AttachSandboxProviderResponse, CreateProviderRequest, - CreateSandboxRequest, CreateSandboxTemplateRequest, CreateSshSessionRequest, - CreateSshSessionResponse, DeleteProviderRequest, DeleteProviderResponse, DeleteSandboxRequest, - DeleteSandboxResponse, DeleteSandboxTemplateRequest, DetachSandboxProviderRequest, - DetachSandboxProviderResponse, ExchangeProviderSubjectTokenRequest, - ExchangeProviderSubjectTokenResponse, ExecSandboxEvent, ExecSandboxInput, ExecSandboxRequest, - GatewayMessage, GetGatewayConfigRequest, GetGatewayConfigResponse, GetProviderRequest, - GetSandboxConfigRequest, GetSandboxConfigResponse, GetSandboxProviderEnvironmentRequest, + AttachSandboxProviderRequest, AttachSandboxProviderResponse, AuthorizeDelegatedIdentityRequest, + AuthorizeDelegatedIdentityResponse, CreateProviderRequest, CreateSandboxRequest, + CreateSandboxTemplateRequest, CreateSshSessionRequest, CreateSshSessionResponse, + DeleteProviderRequest, DeleteProviderResponse, DeleteSandboxRequest, DeleteSandboxResponse, + DeleteSandboxTemplateRequest, DetachSandboxProviderRequest, DetachSandboxProviderResponse, + ExchangeProviderSubjectTokenRequest, ExchangeProviderSubjectTokenResponse, ExecSandboxEvent, + ExecSandboxInput, ExecSandboxRequest, GatewayMessage, + GetDelegatedIdentityAuthorizationStatusRequest, + GetDelegatedIdentityAuthorizationStatusResponse, GetGatewayConfigRequest, + GetGatewayConfigResponse, GetProviderRequest, GetSandboxConfigRequest, + GetSandboxConfigResponse, GetSandboxProviderEnvironmentRequest, GetSandboxProviderEnvironmentResponse, GetSandboxRequest, GetSandboxTemplateRequest, GpuResourceRequirements, HealthRequest, HealthResponse, ListProvidersRequest, ListProvidersResponse, ListSandboxProvidersRequest, ListSandboxProvidersResponse, @@ -44,6 +48,8 @@ use tokio::sync::{Mutex, Notify, mpsc}; use tokio_stream::wrappers::TcpListenerStream; use tonic::transport::{Certificate as TlsCertificate, Identity, Server, ServerTlsConfig}; use tonic::{Response, Status}; +use wiremock::matchers::{body_string_contains, method, path}; +use wiremock::{Mock, MockServer, ResponseTemplate}; fn selected_workspace( scope: &Option, @@ -92,6 +98,11 @@ struct SandboxState { template_get_requests: Arc>>, template_list_requests: Arc>>, template_delete_requests: Arc>>, + delegated_authorization_usable: Arc, + delegated_status_bearers: Arc>>, + delegated_authorize_bearers: Arc>>, + delegated_authorize_requests: Arc>>, + create_bearers: Arc>>, } #[derive(Clone, Default)] @@ -99,6 +110,14 @@ struct TestOpenShell { state: SandboxState, } +fn authorization_header(metadata: &tonic::metadata::MetadataMap) -> String { + metadata + .get("authorization") + .and_then(|value| value.to_str().ok()) + .unwrap_or_default() + .to_string() +} + #[tonic::async_trait] impl OpenShell for TestOpenShell { async fn peer_report_provider_readiness( @@ -171,6 +190,11 @@ impl OpenShell for TestOpenShell { &self, request: tonic::Request, ) -> Result, Status> { + self.state + .create_bearers + .lock() + .await + .push(authorization_header(request.metadata())); let request = request.into_inner(); let name = request.name.clone(); let service_urls = request @@ -234,6 +258,115 @@ impl OpenShell for TestOpenShell { Err(Status::unimplemented("unused")) } + async fn get_delegated_identity_authorization_status( + &self, + request: tonic::Request, + ) -> Result, Status> { + self.state + .delegated_status_bearers + .lock() + .await + .push(authorization_header(request.metadata())); + let usable = self + .state + .delegated_authorization_usable + .load(Ordering::SeqCst); + Ok(Response::new( + GetDelegatedIdentityAuthorizationStatusResponse { + usable, + reauthorization_required: !usable, + reason: if usable { + String::new() + } else { + "delegated identity authorization is missing".to_string() + }, + credential: None, + current_time: openshell_core::time::timestamp_from_millis(1).ok(), + }, + )) + } + + async fn authorize_delegated_identity( + &self, + request: tonic::Request, + ) -> Result, Status> { + self.state + .delegated_authorize_bearers + .lock() + .await + .push(authorization_header(request.metadata())); + self.state + .delegated_authorize_requests + .lock() + .await + .push(request.into_inner()); + self.state + .delegated_authorization_usable + .store(true, Ordering::SeqCst); + Ok(Response::new(AuthorizeDelegatedIdentityResponse { + credential: None, + current_time: openshell_core::time::timestamp_from_millis(1).ok(), + })) + } + + async fn get_sandbox_delegated_identity_status( + &self, + _request: tonic::Request, + ) -> Result, Status> + { + Err(Status::unimplemented("unused")) + } + + async fn withdraw_sandbox_delegated_identity( + &self, + _request: tonic::Request, + ) -> Result, Status> + { + Err(Status::unimplemented("unused")) + } + + async fn extend_sandbox_delegated_identity( + &self, + _request: tonic::Request, + ) -> Result, Status> + { + Err(Status::unimplemented("unused")) + } + + async fn list_delegated_identity_credentials( + &self, + _request: tonic::Request, + ) -> Result, Status> + { + Err(Status::unimplemented("unused")) + } + + async fn get_delegated_identity_credential_status( + &self, + _request: tonic::Request< + openshell_core::proto::GetDelegatedIdentityCredentialStatusRequest, + >, + ) -> Result, Status> + { + Err(Status::unimplemented("unused")) + } + + async fn revoke_delegated_identity_credential( + &self, + _request: tonic::Request, + ) -> Result, Status> + { + Err(Status::unimplemented("unused")) + } + + async fn delete_delegated_identity_credential( + &self, + _request: tonic::Request, + ) -> Result, Status> + { + Err(Status::unimplemented("unused")) + } + async fn get_sandbox( &self, request: tonic::Request, @@ -556,9 +689,27 @@ impl OpenShell for TestOpenShell { async fn get_provider( &self, - _request: tonic::Request, + request: tonic::Request, ) -> Result, Status> { - Err(Status::not_found("provider not found")) + let request = request.into_inner(); + let provider = self + .state + .providers + .lock() + .await + .iter() + .find(|provider| { + provider + .metadata + .as_ref() + .is_some_and(|metadata| metadata.name == request.name) + }) + .cloned() + .ok_or_else(|| Status::not_found("provider not found"))?; + Ok(Response::new(ProviderResponse { + provider: Some(provider), + ..Default::default() + })) } async fn list_providers( @@ -1287,6 +1438,12 @@ fn install_fake_ssh(dir: &TempDir) -> std::path::PathBuf { install_executable_script(dir, "ssh", "#!/bin/sh\nexit 0\n") } +fn install_fake_browser(dir: &TempDir) { + let script = "#!/bin/sh\nset -eu\nprintf '%s' \"$1\" > \"$OPENSHELL_TEST_BROWSER_URL_FILE\"\n"; + install_executable_script(dir, "xdg-open", script); + install_executable_script(dir, "open", script); +} + fn install_fake_pgrep_no_match(dir: &TempDir) -> std::path::PathBuf { install_executable_script(dir, "pgrep", "#!/bin/sh\nexit 1\n") } @@ -1688,6 +1845,227 @@ fn test_config() -> run::SandboxCreateConfig<'static> { } } +#[tokio::test] +async fn sandbox_create_transfers_a_separate_browser_grant_without_replacing_cli_login() { + let fake_ssh_dir = helpers::tempdir().unwrap(); + let xdg_dir = helpers::tempdir().unwrap(); + let browser_url_path = xdg_dir.path().join("browser-url"); + test_env_with( + &fake_ssh_dir, + &xdg_dir, + &[( + "OPENSHELL_TEST_BROWSER_URL_FILE", + browser_url_path.display().to_string(), + )], + ) + .run_async(async { + let oidc = MockServer::start().await; + let issuer = oidc.uri(); + Mock::given(method("GET")) + .and(path("/.well-known/openid-configuration")) + .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({ + "issuer": issuer, + "authorization_endpoint": format!("{issuer}/authorize"), + "token_endpoint": format!("{issuer}/token") + }))) + .expect(1) + .mount(&oidc) + .await; + Mock::given(method("POST")) + .and(path("/token")) + .and(body_string_contains("grant_type=authorization_code")) + .and(body_string_contains("code=delegated-code")) + .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({ + "access_token": "delegated-access-B", + "refresh_token": "delegated-refresh-B", + "token_type": "Bearer", + "expires_in": 3600 + }))) + .expect(1) + .mount(&oidc) + .await; + + let server = run_server().await; + install_fake_ssh(&fake_ssh_dir); + install_fake_browser(&fake_ssh_dir); + openshell_bootstrap::store_gateway_metadata( + "openshell", + &GatewayMetadata { + name: "openshell".to_string(), + gateway_endpoint: server.endpoint.clone(), + auth_mode: Some("oidc".to_string()), + oidc_issuer: Some(issuer.clone()), + oidc_client_id: Some("openshell-cli".to_string()), + oidc_audience: Some("openshell-api".to_string()), + oidc_scopes: Some("openid sandbox:write platform:admin".to_string()), + ..GatewayMetadata::default() + }, + ) + .unwrap(); + let cli_login = OidcTokenBundle { + access_token: "cli-access-A".to_string(), + refresh_token: Some("cli-refresh-A".to_string()), + expires_at: Some( + std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap() + .as_secs() + + 3600, + ), + issuer: issuer.clone(), + client_id: "openshell-cli".to_string(), + }; + openshell_bootstrap::oidc_token::store_oidc_token("openshell", &cli_login).unwrap(); + let refreshed_cli_login = OidcTokenBundle { + access_token: "cli-access-A2".to_string(), + refresh_token: Some("cli-refresh-A2".to_string()), + ..cli_login.clone() + }; + let status_state = server.openshell.state.clone(); + let refreshed_cli_login_for_update = refreshed_cli_login.clone(); + let normal_login_update = tokio::spawn(async move { + loop { + if !status_state + .delegated_status_bearers + .lock() + .await + .is_empty() + { + break; + } + tokio::time::sleep(Duration::from_millis(10)).await; + } + openshell_bootstrap::oidc_token::store_oidc_token( + "openshell", + &refreshed_cli_login_for_update, + ) + .expect("simulate a concurrent normal CLI token refresh"); + }); + let browser_callback = tokio::spawn(async move { + let deadline = Instant::now() + Duration::from_secs(10); + let browser_url = loop { + if let Ok(value) = fs::read_to_string(&browser_url_path) + && !value.is_empty() + { + break value; + } + assert!( + Instant::now() < deadline, + "timed out waiting for delegated browser authorization URL" + ); + tokio::time::sleep(Duration::from_millis(10)).await; + }; + let authorization_url = + url::Url::parse(&browser_url).expect("browser authorization URL"); + assert_eq!(authorization_url.path(), "/authorize"); + let query = authorization_url + .query_pairs() + .into_owned() + .collect::>(); + assert_eq!( + query.get("client_id").map(String::as_str), + Some("openshell-cli") + ); + assert_eq!( + query.get("scope").map(String::as_str), + Some("openid offline_access") + ); + assert_eq!( + query.get("audience").map(String::as_str), + Some("openshell-api") + ); + assert!(!query.contains_key("prompt")); + + let redirect_uri = query.get("redirect_uri").expect("redirect URI"); + let state = query.get("state").expect("authorization state"); + let mut callback_url = url::Url::parse(redirect_uri).expect("callback URL"); + callback_url + .query_pairs_mut() + .append_pair("code", "delegated-code") + .append_pair("state", state); + let response = reqwest::get(callback_url) + .await + .expect("send browser authorization callback"); + assert!(response.status().is_success()); + }); + + let mut config = test_config(); + config.name = Some("delegated-sandbox"); + config.detach = true; + config.delegate_identity_for = Some("5m"); + let exit_code = run::sandbox_create( + &server.endpoint, + "openshell", + config, + "default", + &test_tls(&server), + ) + .await + .expect("sandbox create with delegated identity"); + normal_login_update.await.expect("normal login update task"); + browser_callback.await.expect("browser callback task"); + assert_eq!(exit_code, 0); + + let stored_login = openshell_bootstrap::oidc_token::load_oidc_token("openshell") + .expect("normal CLI login remains stored"); + assert_eq!(stored_login.access_token, refreshed_cli_login.access_token); + assert_eq!( + stored_login.refresh_token, + refreshed_cli_login.refresh_token + ); + assert_eq!(stored_login.issuer, refreshed_cli_login.issuer); + assert_eq!(stored_login.client_id, refreshed_cli_login.client_id); + + let authorize_requests = server + .openshell + .state + .delegated_authorize_requests + .lock() + .await; + assert_eq!(authorize_requests.len(), 1); + let grant = authorize_requests[0] + .grant + .as_ref() + .expect("transferred delegated grant"); + assert_eq!(grant.access_token, "delegated-access-B"); + assert_eq!(grant.refresh_token, "delegated-refresh-B"); + assert_eq!(grant.scopes, "openid offline_access"); + assert_eq!(grant.audience, "openshell-api"); + drop(authorize_requests); + + assert_eq!( + *server.openshell.state.delegated_status_bearers.lock().await, + vec!["Bearer cli-access-A".to_string()] + ); + assert_eq!( + *server + .openshell + .state + .delegated_authorize_bearers + .lock() + .await, + vec!["Bearer cli-access-A2".to_string()] + ); + assert_eq!( + *server.openshell.state.create_bearers.lock().await, + vec!["Bearer cli-access-A2".to_string()] + ); + + let requests = create_requests(&server).await; + let delegated = requests[0] + .delegated_identity + .as_ref() + .expect("lifecycle duration"); + assert_eq!( + openshell_core::time::duration_to_std(delegated.duration.as_ref().unwrap()) + .unwrap() + .as_millis(), + 300_000 + ); + }) + .await; +} + #[tokio::test] async fn sandbox_delete_continues_after_entry_failure() { let server = run_server().await; @@ -3617,7 +3995,7 @@ fn write_oidc_test_credentials( let gateway_dir = xdg_dir.path().join("openshell/gateways/openshell"); fs::write( gateway_dir.join("metadata.json"), - serde_json::to_vec_pretty(&openshell_bootstrap::GatewayMetadata { + serde_json::to_vec_pretty(&GatewayMetadata { name: "openshell".to_string(), gateway_endpoint: server.endpoint.clone(), auth_mode: Some("oidc".to_string()), @@ -3630,7 +4008,7 @@ fn write_oidc_test_credentials( .unwrap(); fs::write( gateway_dir.join("oidc_token.json"), - serde_json::to_vec_pretty(&openshell_bootstrap::oidc_token::OidcTokenBundle { + serde_json::to_vec_pretty(&OidcTokenBundle { access_token: "cached-access-token".to_string(), refresh_token: Some("inactive-refresh-token".to_string()), expires_at: Some(expires_at), diff --git a/crates/openshell-cli/tests/sandbox_exec_streaming_integration.rs b/crates/openshell-cli/tests/sandbox_exec_streaming_integration.rs index c57e2d4ea5..de5aaf5db0 100644 --- a/crates/openshell-cli/tests/sandbox_exec_streaming_integration.rs +++ b/crates/openshell-cli/tests/sandbox_exec_streaming_integration.rs @@ -236,6 +236,15 @@ mock_gateway! { delete_sandbox(proto::DeleteSandboxRequest) -> proto::DeleteSandboxResponse; stop_sandbox(proto::StopSandboxRequest) -> proto::SandboxResponse; start_sandbox(proto::StartSandboxRequest) -> proto::SandboxResponse; + get_delegated_identity_authorization_status(proto::GetDelegatedIdentityAuthorizationStatusRequest) -> proto::GetDelegatedIdentityAuthorizationStatusResponse; + authorize_delegated_identity(proto::AuthorizeDelegatedIdentityRequest) -> proto::AuthorizeDelegatedIdentityResponse; + get_sandbox_delegated_identity_status(proto::GetSandboxDelegatedIdentityStatusRequest) -> proto::GetSandboxDelegatedIdentityStatusResponse; + withdraw_sandbox_delegated_identity(proto::WithdrawSandboxDelegatedIdentityRequest) -> proto::WithdrawSandboxDelegatedIdentityResponse; + extend_sandbox_delegated_identity(proto::ExtendSandboxDelegatedIdentityRequest) -> proto::ExtendSandboxDelegatedIdentityResponse; + list_delegated_identity_credentials(proto::ListDelegatedIdentityCredentialsRequest) -> proto::ListDelegatedIdentityCredentialsResponse; + get_delegated_identity_credential_status(proto::GetDelegatedIdentityCredentialStatusRequest) -> proto::GetDelegatedIdentityCredentialStatusResponse; + revoke_delegated_identity_credential(proto::RevokeDelegatedIdentityCredentialRequest) -> proto::RevokeDelegatedIdentityCredentialResponse; + delete_delegated_identity_credential(proto::DeleteDelegatedIdentityCredentialRequest) -> proto::DeleteDelegatedIdentityCredentialResponse; create_ssh_session(proto::CreateSshSessionRequest) -> proto::CreateSshSessionResponse; expose_service(proto::ExposeServiceRequest) -> proto::ServiceEndpointResponse; get_service(proto::GetServiceRequest) -> proto::ServiceEndpointResponse; diff --git a/crates/openshell-cli/tests/sandbox_name_fallback_integration.rs b/crates/openshell-cli/tests/sandbox_name_fallback_integration.rs index 94a185567d..57db6554f6 100644 --- a/crates/openshell-cli/tests/sandbox_name_fallback_integration.rs +++ b/crates/openshell-cli/tests/sandbox_name_fallback_integration.rs @@ -143,6 +143,83 @@ impl OpenShell for TestOpenShell { Err(Status::unimplemented("unused")) } + async fn get_delegated_identity_authorization_status( + &self, + _request: tonic::Request< + openshell_core::proto::GetDelegatedIdentityAuthorizationStatusRequest, + >, + ) -> Result< + Response, + Status, + > { + Err(Status::unimplemented("unused")) + } + + async fn authorize_delegated_identity( + &self, + _request: tonic::Request, + ) -> Result, Status> { + Err(Status::unimplemented("unused")) + } + + async fn get_sandbox_delegated_identity_status( + &self, + _request: tonic::Request, + ) -> Result, Status> + { + Err(Status::unimplemented("unused")) + } + + async fn withdraw_sandbox_delegated_identity( + &self, + _request: tonic::Request, + ) -> Result, Status> + { + Err(Status::unimplemented("unused")) + } + + async fn extend_sandbox_delegated_identity( + &self, + _request: tonic::Request, + ) -> Result, Status> + { + Err(Status::unimplemented("unused")) + } + + async fn list_delegated_identity_credentials( + &self, + _request: tonic::Request, + ) -> Result, Status> + { + Err(Status::unimplemented("unused")) + } + + async fn get_delegated_identity_credential_status( + &self, + _request: tonic::Request< + openshell_core::proto::GetDelegatedIdentityCredentialStatusRequest, + >, + ) -> Result, Status> + { + Err(Status::unimplemented("unused")) + } + + async fn revoke_delegated_identity_credential( + &self, + _request: tonic::Request, + ) -> Result, Status> + { + Err(Status::unimplemented("unused")) + } + + async fn delete_delegated_identity_credential( + &self, + _request: tonic::Request, + ) -> Result, Status> + { + Err(Status::unimplemented("unused")) + } + async fn get_sandbox( &self, request: tonic::Request, diff --git a/crates/openshell-core/src/config.rs b/crates/openshell-core/src/config.rs index d68562398c..97a7a6f299 100644 --- a/crates/openshell-core/src/config.rs +++ b/crates/openshell-core/src/config.rs @@ -46,6 +46,9 @@ pub const DEFAULT_DOCKER_NETWORK_NAME: &str = "openshell-docker"; /// Default domain used for browser-facing sandbox service URLs. pub const DEFAULT_SERVICE_ROUTING_DOMAIN: &str = "openshell.localhost"; +/// Default maximum delegated identity authorization window for one sandbox. +pub const DEFAULT_MAX_DELEGATED_IDENTITY_DURATION_SECS: u64 = 86_400; + /// Gateway posture when a sandbox rejects a candidate policy generation. #[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] @@ -286,6 +289,9 @@ pub struct Config { /// sandbox attempts, in seconds. Must be between 1 and 86400, inclusive. pub image_preparation_timeout_seconds: u32, + /// Maximum delegated identity authorization window for one sandbox. + pub max_delegated_identity_duration_secs: u64, + /// Maximum gRPC requests allowed per rate-limit window. /// /// When paired with [`Self::grpc_rate_limit_window_secs`], positive values @@ -918,6 +924,7 @@ impl Config { default_credential_driver: None, ssh_session_ttl_secs: default_ssh_session_ttl_secs(), image_preparation_timeout_seconds: 1800, + max_delegated_identity_duration_secs: DEFAULT_MAX_DELEGATED_IDENTITY_DURATION_SECS, grpc_rate_limit_requests: None, grpc_rate_limit_window_secs: None, service_routing: ServiceRoutingConfig::default(), @@ -1029,6 +1036,13 @@ impl Config { self } + /// Create a new configuration with the maximum delegated identity duration. + #[must_use] + pub const fn with_max_delegated_identity_duration_secs(mut self, secs: u64) -> Self { + self.max_delegated_identity_duration_secs = secs; + self + } + /// Set the gateway-wide gRPC request rate limit. #[must_use] pub const fn with_grpc_rate_limit( diff --git a/crates/openshell-core/src/oauth.rs b/crates/openshell-core/src/oauth.rs index 51dc40a9f3..e3ebc8f539 100644 --- a/crates/openshell-core/src/oauth.rs +++ b/crates/openshell-core/src/oauth.rs @@ -12,6 +12,7 @@ use std::fmt; use std::net::IpAddr; use std::time::Duration; +use miette::{IntoDiagnostic, WrapErr}; use serde::Deserialize; use crate::token_grant_failure::{ @@ -83,6 +84,15 @@ impl fmt::Debug for OAuthTokenResponse { } } +/// `OAuth2` refresh-token response. +#[derive(Debug, Clone)] +pub struct OAuthRefreshTokenResponse { + pub access_token: String, + pub refresh_token: Option, + pub expires_in: i64, + pub token_type: String, +} + #[derive(Deserialize)] struct RawTokenResponse { access_token: String, @@ -90,6 +100,8 @@ struct RawTokenResponse { expires_in: i64, #[serde(default)] token_type: String, + #[serde(default)] + refresh_token: Option, } #[derive(Deserialize)] @@ -247,6 +259,76 @@ pub async fn post_oauth_token_exchange( .await } +/// Refresh-token grant form fields. +pub struct RefreshTokenParams<'a> { + pub refresh_token: &'a str, + pub client_id: &'a str, + pub scopes: &'a [String], + pub allow_insecure_http: bool, +} + +/// POST an `OAuth2` refresh-token request to a token endpoint. +pub async fn post_oauth_refresh_token( + client: &reqwest::Client, + token_endpoint: &str, + params: &RefreshTokenParams<'_>, +) -> miette::Result { + let token_endpoint_url = + parse_token_endpoint_url_with_policy(token_endpoint, params.allow_insecure_http)?; + let mut form_params = vec![ + ("grant_type", "refresh_token"), + ("refresh_token", params.refresh_token), + ("client_id", params.client_id), + ]; + + let scope_param; + if !params.scopes.is_empty() { + scope_param = params.scopes.join(" "); + form_params.push(("scope", &scope_param)); + } + + let response = client + .post(token_endpoint_url) + .form(&form_params) + .send() + .await + .into_diagnostic() + .wrap_err_with(|| format!("failed to POST to token endpoint {token_endpoint}"))?; + + if !response.status().is_success() { + let status = response.status(); + let body = read_bounded_body(response) + .await + .map_err(|_| miette::miette!("token endpoint returned HTTP {status}"))?; + if status == reqwest::StatusCode::BAD_REQUEST + && serde_json::from_slice::(&body) + .ok() + .and_then(|error| error.error) + .as_deref() + == Some("invalid_grant") + { + return Err(miette::miette!( + "token endpoint returned HTTP {status}: error=invalid_grant" + )); + } + return Err(miette::miette!("token endpoint returned HTTP {status}")); + } + + let raw = response + .json::() + .await + .into_diagnostic() + .wrap_err("failed to parse token response as JSON")?; + validate_access_token(&raw.access_token) + .map_err(|_| miette::miette!("token endpoint returned an invalid access token"))?; + Ok(OAuthRefreshTokenResponse { + access_token: raw.access_token, + refresh_token: raw.refresh_token, + expires_in: raw.expires_in, + token_type: raw.token_type, + }) +} + pub fn effective_client_assertion_type(client_assertion_type: &str) -> &str { if client_assertion_type.trim().is_empty() { DEFAULT_CLIENT_ASSERTION_TYPE @@ -268,11 +350,21 @@ pub fn effective_token_type(token_type: &str) -> &str { /// HTTPS is accepted; plain HTTP is limited to loopback and Kubernetes service /// hosts and never authenticates a precise token-service error classification. pub fn parse_token_endpoint_url(token_endpoint: &str) -> Result { + parse_token_endpoint_url_with_policy(token_endpoint, false) +} + +fn parse_token_endpoint_url_with_policy( + token_endpoint: &str, + allow_insecure_http: bool, +) -> Result { let invalid = TokenGrantFailure::new( TokenGrantFailureKind::Configuration, TokenGrantStage::Configuration, ); let url = reqwest::Url::parse(token_endpoint).map_err(|_| invalid)?; + if allow_insecure_http && matches!(url.scheme(), "http" | "https") { + return Ok(url); + } if token_endpoint_transport_allowed(&url) { return Ok(url); } @@ -989,6 +1081,20 @@ mod tests { } } + #[test] + fn token_endpoint_url_can_explicitly_allow_plain_http_for_refresh() { + parse_token_endpoint_url("http://auth.example.com/token") + .expect_err("strict validation should reject arbitrary plain HTTP"); + + parse_token_endpoint_url_with_policy("http://auth.example.com/token", true) + .expect("explicit insecure refresh policy should allow HTTP"); + parse_token_endpoint_url_with_policy("https://auth.example.com/token", true) + .expect("explicit insecure refresh policy should allow HTTPS"); + + parse_token_endpoint_url_with_policy("ftp://auth.example.com/token", true) + .expect_err("non-HTTP token endpoints must still be rejected"); + } + #[test] fn validate_access_token_accepts_token68_values() { for token in [ diff --git a/crates/openshell-driver-db-credstore/src/lib.rs b/crates/openshell-driver-db-credstore/src/lib.rs index 85ef0a5a69..bc555d99dd 100644 --- a/crates/openshell-driver-db-credstore/src/lib.rs +++ b/crates/openshell-driver-db-credstore/src/lib.rs @@ -142,6 +142,15 @@ struct EncryptedBytes { } impl DbCredstoreCredentialDriver { + pub fn prepare_credential( + &self, + request: openshell_core::proto::credentials::v1::PrepareCredentialRequest, + ) -> Result { + EncryptedGatewayCredentialStoreCrypto::validate_credential_key(&request.credential_key)?; + EncryptedGatewayCredentialStoreCrypto::validate_provider_name(&request.provider)?; + self.crypto + .credential_handle(&EncryptedGatewayCredentialStoreCrypto::new_handle_id()?) + } pub const NAME: &'static str = DRIVER_NAME; pub const OBJECT_TYPE: &'static str = OBJECT_TYPE; @@ -1093,6 +1102,52 @@ mod tests { } } + #[tokio::test] + async fn prepared_handle_is_recoverable_before_secret_storage() { + let tmp = tempfile::tempdir().unwrap(); + let config = driver_config_for_key_encryption_key_path( + &tmp.path().join(DEFAULT_KEY_ENCRYPTION_KEY_FILE), + ); + let store = Arc::new(MemoryObjectStore::default()); + let driver = DbCredstoreCredentialDriver::from_config(store.clone(), &config).unwrap(); + let handle = driver + .prepare_credential( + openshell_core::proto::credentials::v1::PrepareCredentialRequest { + provider: "owner".into(), + credential_key: "token".into(), + workspace: "test-workspace".into(), + provider_id: "test-provider-id".into(), + object_id: "staged-operation".into(), + }, + ) + .unwrap(); + assert!(store.objects.lock().unwrap().is_empty()); + let deletion = DeleteCredentialRequest { + provider: "owner".into(), + credential_key: "token".into(), + handle: Some(handle.clone()), + workspace: "test-workspace".into(), + provider_id: "test-provider-id".into(), + }; + driver.delete_credential(deletion.clone()).await.unwrap(); + let stored = driver + .store_credential(request("owner", "token", "secret", Some(handle.clone()))) + .await + .unwrap(); + assert_eq!(stored.handle, handle.handle); + assert_eq!( + driver + .resolve_credentials(vec![resolve_request("token", "owner", "token", handle)]) + .await + .unwrap()[0] + .value, + "secret" + ); + driver.delete_credential(deletion.clone()).await.unwrap(); + driver.delete_credential(deletion).await.unwrap(); + assert!(store.objects.lock().unwrap().is_empty()); + } + #[tokio::test] async fn driver_stores_resolves_updates_and_deletes_encrypted_objects() { let tmp = tempfile::tempdir().unwrap(); diff --git a/crates/openshell-driver-kubernetes-secrets/src/lib.rs b/crates/openshell-driver-kubernetes-secrets/src/lib.rs index 4cd5f5ed7e..241744aee1 100644 --- a/crates/openshell-driver-kubernetes-secrets/src/lib.rs +++ b/crates/openshell-driver-kubernetes-secrets/src/lib.rs @@ -85,6 +85,31 @@ struct KubernetesSecretReference { } impl KubernetesSecretsCredentialDriver { + pub fn prepare_credential( + &self, + request: openshell_core::proto::credentials::v1::PrepareCredentialRequest, + ) -> Result { + let object_id = requested_object_id(&request.object_id, &request.provider_id)?; + if !is_secret_data_key(&request.credential_key) { + return Err(Status::invalid_argument("invalid credential key")); + } + let namespace = self.settings.namespace.clone(); + let name = managed_secret_name( + &request.workspace, + &request.provider_id, + &request.provider, + &request.credential_key, + object_id, + ); + Ok(CredentialHandle { + driver: Self::NAME.into(), + handle: format!("{HANDLE_VERSION}:{namespace}:{name}"), + metadata: std::collections::HashMap::from([( + OBJECT_ID_METADATA_KEY.into(), + object_id.into(), + )]), + }) + } pub const NAME: &'static str = "kubernetes-secrets"; pub async fn from_config(config: &toml::Table) -> CoreResult { @@ -436,6 +461,14 @@ impl Clone for KubernetesSecretsCredentialDriver { #[tonic::async_trait] impl CredentialDriver for CredentialDriverService { + async fn prepare_credential( + &self, + request: Request, + ) -> Result, Status> { + Ok(Response::new(StoreCredentialResponse { + handle: Some(self.driver.prepare_credential(request.into_inner())?), + })) + } async fn get_capabilities( &self, request: Request, diff --git a/crates/openshell-driver-podman/NETWORKING.md b/crates/openshell-driver-podman/NETWORKING.md index 37361395aa..c425368a67 100644 --- a/crates/openshell-driver-podman/NETWORKING.md +++ b/crates/openshell-driver-podman/NETWORKING.md @@ -22,7 +22,11 @@ or require `CAP_NET_ADMIN`. TCP opens, TCP byte streams, DNS requests/replies, and lifecycle operations share the authenticated gRPC channel. DNS is resolved and authorized by the -supervisor. General UDP is unsupported. +supervisor. The driver writes the sandbox-local policy DNS relay +(`127.0.0.53`) into `/etc/resolv.conf` before workload start and restart. +Podman resolver generation is disabled for the workload because `network=none` +skips DNS configuration, including explicit DNS servers. The host-networked +supervisor keeps the host resolver. General UDP is unsupported. The driver mounts a read-only, driver-owned `/etc/resolv.conf` into the workload with `nameserver 127.0.0.53`. This prevents the disconnected Podman @@ -40,6 +44,15 @@ host-loopback route. The upstream corporate proxy applies only to the supervisor. The gateway's SSH tunnel uses the supervisor relay over its private Unix socket, so the driver does not publish a supervisor port. +On native Linux, host-side SPIFFE Workload API TCP fixtures must advertise +host loopback to the supervisor, not a workload-side link-local gateway address. +The Podman E2E wrapper binds its provider SPIFFE fixture to `127.0.0.1`. + +Reserved host aliases such as `host.openshell.internal` resolve through +mediated DNS to supervisor loopback on Linux. When `host_gateway_ip` is set, +including the Podman Machine default on macOS, mediated DNS pins those aliases +to that configured address instead. + These runtime-managed network helpers are outside the workload trust boundary. Sharing the workload's user namespace preserves volume UID/GID mapping; it does not share the workload's PID, mount, or network namespaces. diff --git a/crates/openshell-driver-podman/src/container.rs b/crates/openshell-driver-podman/src/container.rs index 9f046ab465..4e5656ea21 100644 --- a/crates/openshell-driver-podman/src/container.rs +++ b/crates/openshell-driver-podman/src/container.rs @@ -352,6 +352,9 @@ pub struct ContainerSpec { /// via Podman's `host-gateway` magic so sandbox containers can reach /// the gateway server running on the host in rootless mode. hostadd: Vec, + /// Preserve the resolver file installed by the driver in the workload rootfs. + #[serde(rename = "use_image_resolve_conf")] + use_image_resolv_conf: bool, /// Search domains written to `/etc/resolv.conf` by Podman. dns_search: Vec, /// Resolver options written to `/etc/resolv.conf` by Podman. @@ -1390,9 +1393,7 @@ fn build_base_spec( // reach services on the host. `host.openshell.internal` is the driver- // neutral alias used by policies and e2e tests. hostadd: hostadd_entries(config), - // Preserve Podman's resolver defaults for both policy-DNS and ordinary - // sandboxes. Namespace-local capture supports UDP and TCP, so it must - // not depend on a libc-specific option or alter short-name searches. + use_image_resolv_conf: false, dns_search: Vec::new(), dns_option: Vec::new(), use_image_resolve_conf: None, @@ -1609,6 +1610,9 @@ pub fn build_isolation_specs( workload.networks.clear(); workload.portmappings.clear(); workload.hostadd.clear(); + // Podman does not generate resolv.conf for network=none, even with + // dns_server set. Bootstrap installs the sandbox-local resolver instead. + workload.use_image_resolv_conf = true; workload.secret_env.clear(); workload.secrets.clear(); workload.secrets.push(SecretMount { @@ -2098,6 +2102,41 @@ mod tests { .expect("existing /sandbox images keep their image-volume behavior"); } + #[test] + fn isolated_workload_uses_sandbox_local_policy_dns() { + let sandbox = DriverSandbox { + id: "dns-pair".into(), + name: "agent".into(), + ..Default::default() + }; + let config = PodmanComputeConfig::default(); + let identity = openshell_isolation_interface::contract::ResolvedWorkloadIdentity::new( + 1000, + 1001, + Vec::new(), + "image".into(), + "sha256:image".into(), + ) + .unwrap(); + let specs = build_isolation_specs(IsolationSpecInput { + sandbox: &sandbox, + config: &config, + token_secret: None, + resolver_secret: "resolver", + gpu_devices: None, + image: &resolved_image("image:latest", "sha256:image", "1000:1001", ""), + supervisor_bin: None, + tls_secrets: None, + identity: &identity, + }) + .unwrap(); + + let workload = serde_json::to_value(specs.workload).unwrap(); + let supervisor = serde_json::to_value(specs.supervisor).unwrap(); + assert_eq!(workload["use_image_resolve_conf"], true); + assert_eq!(supervisor["use_image_resolve_conf"], false); + } + fn json_struct(value: Value) -> prost_types::Struct { let Value::Object(object) = value else { panic!("expected JSON object"); diff --git a/crates/openshell-driver-vault/src/lib.rs b/crates/openshell-driver-vault/src/lib.rs index b2738e3602..2c9ceb08d0 100644 --- a/crates/openshell-driver-vault/src/lib.rs +++ b/crates/openshell-driver-vault/src/lib.rs @@ -123,6 +123,28 @@ struct KubernetesLoginAuth { } impl VaultCredentialDriver { + pub fn prepare_credential( + &self, + request: openshell_core::proto::credentials::v1::PrepareCredentialRequest, + ) -> Result { + let object_id = requested_object_id(&request.object_id, &request.provider_id)?; + let path = managed_secret_path( + &request.workspace, + &request.provider_id, + &request.provider, + &request.credential_key, + object_id, + ); + validate_secret_path(&path).map_err(Status::invalid_argument)?; + Ok(CredentialHandle { + driver: Self::NAME.into(), + handle: format!("{HANDLE_VERSION}:{path}"), + metadata: std::collections::HashMap::from([( + OBJECT_ID_METADATA_KEY.into(), + object_id.into(), + )]), + }) + } pub const NAME: &'static str = "vault"; pub fn from_config(config: &toml::Table) -> CoreResult { @@ -499,6 +521,14 @@ impl Clone for VaultCredentialDriver { #[tonic::async_trait] impl CredentialDriver for CredentialDriverService { + async fn prepare_credential( + &self, + request: Request, + ) -> Result, Status> { + Ok(Response::new(StoreCredentialResponse { + handle: Some(self.driver.prepare_credential(request.into_inner())?), + })) + } async fn get_capabilities( &self, request: Request, diff --git a/crates/openshell-gateway-interceptors/src/proto_json.rs b/crates/openshell-gateway-interceptors/src/proto_json.rs index f34c791adc..4f34432489 100644 --- a/crates/openshell-gateway-interceptors/src/proto_json.rs +++ b/crates/openshell-gateway-interceptors/src/proto_json.rs @@ -322,6 +322,7 @@ mod tests { await_main_process_attachment: false, workload_template: String::new(), service_exposures: Vec::new(), + delegated_identity: None, }; let bytes = request.encode_to_vec(); let json = codec @@ -407,6 +408,14 @@ mod tests { ("openshell.v1.TcpForwardInit", "authorization_token"), ("openshell.v1.SshSession", "token"), ("openshell.v1.ConfigureProviderRefreshRequest", "material"), + ( + "openshell.v1.DelegatedIdentityAuthorizationGrant", + "refresh_token", + ), + ( + "openshell.v1.DelegatedIdentityAuthorizationGrant", + "access_token", + ), ( "openshell.v1.GetSandboxProviderEnvironmentResponse", "environment", @@ -421,6 +430,41 @@ mod tests { } } + #[test] + fn delegated_identity_lifecycle_request_cannot_carry_oauth_material() { + let codec = ProtoJsonCodec::openshell().unwrap(); + let message = codec + .message_descriptor("openshell.v1.DelegatedIdentityRequest") + .unwrap(); + + for field_name in [ + "issuer", + "client_id", + "refresh_token", + "access_token", + "access_token_expires_at_ms", + "scopes", + "audience", + ] { + assert!(message.get_field_by_name(field_name).is_none()); + assert!( + message + .descriptor_proto() + .reserved_name + .iter() + .any(|name| name == field_name), + "{field_name} must remain reserved" + ); + } + assert_eq!( + message + .fields() + .map(|field| field.name().to_string()) + .collect::>(), + ["duration"] + ); + } + #[test] fn generic_sandbox_environment_remains_visible() { let codec = ProtoJsonCodec::openshell().unwrap(); diff --git a/crates/openshell-gateway-interceptors/src/runtime.rs b/crates/openshell-gateway-interceptors/src/runtime.rs index 21de44fb9c..6970685e72 100644 --- a/crates/openshell-gateway-interceptors/src/runtime.rs +++ b/crates/openshell-gateway-interceptors/src/runtime.rs @@ -1094,6 +1094,7 @@ mod tests { await_main_process_attachment: false, workload_template: String::new(), service_exposures: Vec::new(), + delegated_identity: None, }; let bytes = request.encode_to_vec(); diff --git a/crates/openshell-providers/src/profiles.rs b/crates/openshell-providers/src/profiles.rs index e289d4b80d..34e821f027 100644 --- a/crates/openshell-providers/src/profiles.rs +++ b/crates/openshell-providers/src/profiles.rs @@ -226,6 +226,7 @@ impl Eq for TokenGrantProfile {} #[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)] pub struct TokenGrantSubjectTokenProfile { pub source: String, + #[serde(default)] pub credential: String, #[serde(default, skip_serializing_if = "String::is_empty")] pub subject_token_type: String, @@ -1595,8 +1596,10 @@ fn token_grant_subject_token_to_proto( subject_token: &TokenGrantSubjectTokenProfile, ) -> ProviderCredentialTokenGrantSubjectToken { ProviderCredentialTokenGrantSubjectToken { - source: subject_token.source.clone(), - credential: subject_token.credential.clone(), + // Validation treats whitespace around these identifiers as insignificant. + // Lower canonical values so runtime exact matching uses the same semantics. + source: subject_token.source.trim().to_owned(), + credential: subject_token.credential.trim().to_owned(), subject_token_type: subject_token.subject_token_type.clone(), } } @@ -3313,31 +3316,43 @@ fn validate_token_grant_subject_token( return diagnostics; }; - let source_value = subject_token.source.trim(); - if source_value != "provider_credential" { - diagnostics.push(ProfileValidationDiagnostic::error( - source, - profile_id, - "credentials.token_grant.subject_token.source", - "subject_token.source must be provider_credential", - )); - } - let subject_credential = subject_token.credential.trim(); - if subject_credential.is_empty() { - diagnostics.push(ProfileValidationDiagnostic::error( - source, - profile_id, - "credentials.token_grant.subject_token.credential", - "subject_token.credential is required", - )); - } else if !credential_names.contains(subject_credential) { - diagnostics.push(ProfileValidationDiagnostic::error( - source, - profile_id, - "credentials.token_grant.subject_token.credential", - format!("unknown subject token credential: {subject_credential}"), - )); + match subject_token.source.trim() { + "provider_credential" => { + if subject_credential.is_empty() { + diagnostics.push(ProfileValidationDiagnostic::error( + source, + profile_id, + "credentials.token_grant.subject_token.credential", + "subject_token.credential is required", + )); + } else if !credential_names.contains(subject_credential) { + diagnostics.push(ProfileValidationDiagnostic::error( + source, + profile_id, + "credentials.token_grant.subject_token.credential", + format!("unknown subject token credential: {subject_credential}"), + )); + } + } + "sandbox_delegated_identity" => { + if !subject_credential.is_empty() { + diagnostics.push(ProfileValidationDiagnostic::error( + source, + profile_id, + "credentials.token_grant.subject_token.credential", + "sandbox_delegated_identity subject_token must not set credential", + )); + } + } + _ => { + diagnostics.push(ProfileValidationDiagnostic::error( + source, + profile_id, + "credentials.token_grant.subject_token.source", + "subject_token.source must be provider_credential or sandbox_delegated_identity", + )); + } } } ProviderCredentialTokenGrantType::Unspecified => { @@ -5484,6 +5499,97 @@ credentials: ); } + #[test] + fn token_exchange_subject_identifiers_are_normalized_during_lowering() { + for (source, credential, expected_credential) in [ + ( + "provider_credential", + " USER_OIDC_TOKEN ", + "USER_OIDC_TOKEN", + ), + ("sandbox_delegated_identity", " ", ""), + ] { + let yaml = format!( + r#" +id: normalized-token-exchange +display_name: Normalized Token Exchange +credentials: + - name: USER_OIDC_TOKEN + required: true + - name: access_token + auth_style: bearer + header_name: Authorization + token_grant: + grant_type: token_exchange + token_endpoint: https://keycloak.example.com/realms/openshell/protocol/openid-connect/token + subject_token: + source: " {source} " + credential: "{credential}" + subject_token_type: " urn:ietf:params:oauth:token-type:access_token " +"# + ); + let yaml_profile = parse_profile_yaml(&yaml).expect("profile should parse"); + let json = profile_to_json(&yaml_profile).expect("profile should serialize"); + let json_profile = parse_profile_json(&json).expect("JSON profile should parse"); + + for profile in [yaml_profile, json_profile] { + let diagnostics = + validate_profile_set(&[("normalized-profile".to_string(), profile.clone())]); + assert!( + diagnostics.is_empty(), + "unexpected diagnostics for {source}: {diagnostics:?}" + ); + + let proto = profile.to_proto(); + let subject_token = proto.credentials[1] + .token_grant + .as_ref() + .and_then(|grant| grant.subject_token.as_ref()) + .expect("subject token should lower"); + assert_eq!(subject_token.source, source); + assert_eq!(subject_token.credential, expected_credential); + assert_eq!( + subject_token.subject_token_type, + " urn:ietf:params:oauth:token-type:access_token ", + "lowering should not trim non-identifier fields" + ); + } + } + } + + #[test] + fn sandbox_delegated_identity_subject_token_defaults_credential() { + let profile = parse_profile_yaml( + r" +id: delegated-identity-token-exchange +display_name: Delegated Identity Token Exchange +credentials: + - name: access_token + auth_style: bearer + header_name: Authorization + token_grant: + grant_type: token_exchange + token_endpoint: https://keycloak.example.com/realms/openshell/protocol/openid-connect/token + subject_token: + source: sandbox_delegated_identity +", + ) + .expect("profile should parse without an explicit subject credential"); + + let subject_token = profile.credentials[0] + .token_grant + .as_ref() + .and_then(|grant| grant.subject_token.as_ref()) + .expect("subject token should parse"); + assert!(subject_token.credential.is_empty()); + + let diagnostics = validate_profile_set(&[("delegated.yaml".to_string(), profile)]); + assert!( + diagnostics.is_empty(), + "unexpected diagnostics: {diagnostics:?}" + ); + } + #[test] fn validate_profile_set_rejects_token_exchange_without_subject_token() { let profile = parse_profile_yaml( diff --git a/crates/openshell-sdk/src/client.rs b/crates/openshell-sdk/src/client.rs index 69b75ffbd2..b2bcd15ecf 100644 --- a/crates/openshell-sdk/src/client.rs +++ b/crates/openshell-sdk/src/client.rs @@ -1364,6 +1364,7 @@ fn create_sandbox_request(spec: SandboxSpec) -> proto::CreateSandboxRequest { ) as i32, }) .collect(), + delegated_identity: None, } } @@ -1405,6 +1406,7 @@ fn create_sandbox_from_template_request( ) as i32, }) .collect(), + delegated_identity: None, } } diff --git a/crates/openshell-sdk/tests/client_mock.rs b/crates/openshell-sdk/tests/client_mock.rs index 9784bea474..1fcda18dab 100644 --- a/crates/openshell-sdk/tests/client_mock.rs +++ b/crates/openshell-sdk/tests/client_mock.rs @@ -445,6 +445,69 @@ impl OpenShell for TestOpenShell { })) } + async fn get_delegated_identity_authorization_status( + &self, + _request: tonic::Request, + ) -> Result, Status> { + Err(Status::unimplemented("unused")) + } + + async fn authorize_delegated_identity( + &self, + _request: tonic::Request, + ) -> Result, Status> { + Err(Status::unimplemented("unused")) + } + + async fn get_sandbox_delegated_identity_status( + &self, + _request: tonic::Request, + ) -> Result, Status> { + Err(Status::unimplemented("unused")) + } + + async fn withdraw_sandbox_delegated_identity( + &self, + _request: tonic::Request, + ) -> Result, Status> { + Err(Status::unimplemented("unused")) + } + + async fn extend_sandbox_delegated_identity( + &self, + _request: tonic::Request, + ) -> Result, Status> { + Err(Status::unimplemented("unused")) + } + + async fn list_delegated_identity_credentials( + &self, + _request: tonic::Request, + ) -> Result, Status> { + Err(Status::unimplemented("unused")) + } + + async fn get_delegated_identity_credential_status( + &self, + _request: tonic::Request, + ) -> Result, Status> { + Err(Status::unimplemented("unused")) + } + + async fn revoke_delegated_identity_credential( + &self, + _request: tonic::Request, + ) -> Result, Status> { + Err(Status::unimplemented("unused")) + } + + async fn delete_delegated_identity_credential( + &self, + _request: tonic::Request, + ) -> Result, Status> { + Err(Status::unimplemented("unused")) + } + async fn get_sandbox( &self, request: tonic::Request, diff --git a/crates/openshell-server/proto/storage.proto b/crates/openshell-server/proto/storage.proto index 28e9e9e4a1..a3bddf7282 100644 --- a/crates/openshell-server/proto/storage.proto +++ b/crates/openshell-server/proto/storage.proto @@ -103,6 +103,41 @@ message StoredRefreshMaterialDeletion { openshell.datamodel.v1.CredentialHandle handle = 2; } +message DelegatedIdentityCredential { + openshell.datamodel.v1.ObjectMeta metadata = 1; + string issuer = 2; + string client_id = 3; + string principal_subject = 4; + reserved 5, 6; + reserved "refresh_token", "access_token"; + int64 access_token_expires_at_ms = 7; + string scopes = 8; + string audience = 9; + int64 last_refresh_at_ms = 10; + int64 revoked_at_ms = 11; + // Delegated OAuth material is stored through the active credential driver. + // This record contains only opaque driver handles under fixed, + // gateway-owned material names. + map secret_material_handles = 12; + // Superseded or revoked handles retained until credential-driver cleanup + // succeeds. Keeping retry references here prevents silent secret orphans + // across gateway restarts and temporary backend outages. + repeated StoredRefreshMaterialDeletion pending_secret_deletions = 13; + // Shared-store lease serializing refresh-token use across gateway replicas. + DelegatedIdentityRefreshLease refresh_lease = 14; +} + +message DelegatedIdentityRefreshLease { + string owner_id = 1; + int64 expires_at_ms = 2; +} + +message SandboxDelegatedIdentityRecord { + openshell.datamodel.v1.ObjectMeta metadata = 1; + string sandbox_id = 2; + openshell.v1.SandboxDelegatedIdentity delegated_identity = 3; +} + // Stored custom provider profile object. message StoredProviderProfile { openshell.datamodel.v1.ObjectMeta metadata = 1; diff --git a/crates/openshell-server/src/auth/descriptor_authz.rs b/crates/openshell-server/src/auth/descriptor_authz.rs index cbdadbdb29..1fa83aa35a 100644 --- a/crates/openshell-server/src/auth/descriptor_authz.rs +++ b/crates/openshell-server/src/auth/descriptor_authz.rs @@ -29,7 +29,11 @@ const AUTH_ONLY_METHODS: &[&str] = &["/openshell.v1.OpenShell/GetCurrentUser"]; /// /// Keep this list explicit so an incomplete authorization annotation cannot /// silently turn a future mutating RPC into a scope-only endpoint. -const SCOPE_ONLY_METHODS: &[&str] = &["/openshell.v1.OpenShell/GetGatewayConfig"]; +const SCOPE_ONLY_METHODS: &[&str] = &[ + "/openshell.v1.OpenShell/GetGatewayConfig", + "/openshell.v1.OpenShell/GetDelegatedIdentityAuthorizationStatus", + "/openshell.v1.OpenShell/AuthorizeDelegatedIdentity", +]; /// Per-method authorization entry decoded from proto annotations. #[derive(Debug, Clone)] diff --git a/crates/openshell-server/src/auth/oidc.rs b/crates/openshell-server/src/auth/oidc.rs index 5e876826b0..aa171d442f 100644 --- a/crates/openshell-server/src/auth/oidc.rs +++ b/crates/openshell-server/src/auth/oidc.rs @@ -503,6 +503,8 @@ fn parse_jwk(key: &JwkKey) -> Result<(String, DecodingKey, Algorithm), SkipReaso pub struct OidcClaims { pub sub: String, #[serde(default)] + pub exp: i64, + #[serde(default)] pub preferred_username: Option, #[serde(default)] #[allow(dead_code)] @@ -793,6 +795,11 @@ impl JwksCache { /// This is the authentication step — it verifies the caller's identity /// but does not check authorization (that's `authz::AuthzPolicy::check`). pub async fn validate_token(&self, token: &str) -> Result { + Ok(self.validate_token_details(token).await?.identity) + } + + /// Validate a JWT and return the derived identity plus verified token metadata. + pub async fn validate_token_details(&self, token: &str) -> Result { crate::install_jsonwebtoken_crypto_provider(); self.refresh_if_stale().await.map_err(|e| { @@ -851,6 +858,10 @@ impl JwksCache { })?; let mut claims = token_data.claims; + let expires_at_ms = claims.exp.saturating_mul(1000); + if expires_at_ms <= 0 { + return Err(Status::unauthenticated("invalid token: invalid exp")); + } claims.extract_roles(&self.config.roles_claim); let scopes = if self.config.scopes_claim.is_empty() { @@ -859,16 +870,25 @@ impl JwksCache { claims.extract_scopes(&self.config.scopes_claim) }; - Ok(Identity { - subject: claims.sub, - display_name: claims.preferred_username, - roles: claims.roles, - scopes, - provider: IdentityProvider::Oidc, + Ok(ValidatedOidcToken { + identity: Identity { + subject: claims.sub, + display_name: claims.preferred_username, + roles: claims.roles, + scopes, + provider: IdentityProvider::Oidc, + }, + expires_at_ms, }) } } +/// A verified OIDC access token and server-derived metadata. +pub struct ValidatedOidcToken { + pub identity: Identity, + pub expires_at_ms: i64, +} + /// Authenticator that validates `Authorization: Bearer ` headers against /// the configured OIDC issuer. /// diff --git a/crates/openshell-server/src/cli.rs b/crates/openshell-server/src/cli.rs index 19a22e5d16..4788a0ece3 100644 --- a/crates/openshell-server/src/cli.rs +++ b/crates/openshell-server/src/cli.rs @@ -600,6 +600,13 @@ fn prepare_server_config_with_drivers( config = config.with_ssh_session_ttl_secs(ttl); } + if let Some(max_secs) = file + .as_ref() + .and_then(|f| f.openshell.gateway.max_delegated_identity_duration_secs) + { + config = config.with_max_delegated_identity_duration_secs(max_secs); + } + if let Some(mode) = file .as_ref() .and_then(|f| f.openshell.gateway.policy_validation_failure_mode) diff --git a/crates/openshell-server/src/compute/mod.rs b/crates/openshell-server/src/compute/mod.rs index 754605d174..6febfd73e0 100644 --- a/crates/openshell-server/src/compute/mod.rs +++ b/crates/openshell-server/src/compute/mod.rs @@ -21,12 +21,15 @@ use crate::persistence::{ }; use crate::sandbox_index::SandboxIndex; use crate::sandbox_watch::SandboxWatchBus; +use crate::storage_proto::SandboxDelegatedIdentityRecord; use crate::supervisor_owner::{OWNER_TTL, SupervisorOwnerIndex}; use crate::supervisor_session::SupervisorSessionRegistry; use crate::tracing_bus::TracingLogBus; use futures::{Stream, StreamExt}; #[cfg(unix)] use hyper_util::rt::TokioIo; +use openshell_core::ObjectWorkspace; +use openshell_core::SetResourceVersion; use openshell_core::extension_protocol::{ ExtensionFamily, NegotiatedExtension, gateway_metadata, negotiate, }; @@ -50,7 +53,6 @@ use openshell_core::proto::{ ServiceEndpoint, SshSession, }; use openshell_core::telemetry::TelemetryComputeDriver; -use openshell_core::{ObjectLabels, ObjectWorkspace, SetResourceVersion}; use prost::Message; use std::collections::HashMap; use std::fmt; @@ -1181,6 +1183,7 @@ impl ComputeRuntime { sandbox_token, launch_authentication, await_main_process_attachment, + None, lifecycle_guard, mutation_guard, )) @@ -1194,6 +1197,7 @@ impl ComputeRuntime { sandbox_token: Option, launch_authentication: Option>, await_main_process_attachment: bool, + delegation: Option<&crate::delegated_identity::PreparedSandboxDelegatedIdentity>, lifecycle_guard: SandboxLifecycleGuard, mutation_guard: MutationGuard, ) -> Result { @@ -1205,6 +1209,7 @@ impl ComputeRuntime { launch_authentication, ..Default::default() }, + delegation, lifecycle_guard, mutation_guard, )) @@ -1230,18 +1235,21 @@ impl ComputeRuntime { sandbox_token, await_main_process_attachment, runtime_inputs, + None, lifecycle_guard, mutation_guard, )) .await } + #[allow(clippy::too_many_arguments)] pub(crate) async fn create_sandbox_with_runtime_inputs_and_guards( &self, sandbox: Sandbox, sandbox_token: Option, await_main_process_attachment: bool, runtime_inputs: SandboxCreateRuntimeInputs, + delegation: Option<&crate::delegated_identity::PreparedSandboxDelegatedIdentity>, lifecycle_guard: SandboxLifecycleGuard, mutation_guard: MutationGuard, ) -> Result { @@ -1282,44 +1290,10 @@ impl ComputeRuntime { set_rootfs_tar_path(&mut driver_sandbox, staged.path()); } - // Create with MustCreate condition to prevent duplicate creation race - self.sandbox_index.update_from_sandbox(&sandbox); - let labels_map = sandbox.object_labels(); - let labels_json = if labels_map.as_ref().is_none_or(HashMap::is_empty) { - None - } else { - Some( - serde_json::to_string(&labels_map) - .map_err(|e| Status::internal(format!("failed to serialize labels: {e}")))?, - ) - }; - let result = self - .store - .put_if( - Sandbox::object_type(), - &sandbox_id, - sandbox.object_name(), - sandbox.object_workspace(), - &sandbox.encode_to_vec(), - labels_json.as_deref(), - WriteCondition::MustCreate, - ) - .await - .map_err(|e| { - if matches!( - e, - crate::persistence::PersistenceError::UniqueViolation { .. } - ) { - Status::already_exists(format!( - "sandbox '{}' already exists", - sandbox.object_name() - )) - } else { - Status::internal(format!("persist sandbox failed: {e}")) - } - })?; + crate::delegated_identity::store_sandbox_with_delegation(&self.store, &sandbox, delegation) + .await?; if let Some(metadata) = sandbox.metadata.as_mut() { - metadata.resource_version = result.resource_version; + metadata.resource_version = 1; } // The parent now owns any staged key, including if this request is // cancelled. Keep the creation guard until its public identity and @@ -1343,6 +1317,7 @@ impl ComputeRuntime { } } .await; + self.sandbox_index.update_from_sandbox(&sandbox); drop(mutation_guard); let launch_authentication = match prepared { Ok(authentication) => authentication, @@ -1639,6 +1614,7 @@ impl ComputeRuntime { .map(|response| response.into_inner().deleted) } + #[cfg(test)] pub(crate) async fn stop_sandbox( &self, workspace: &str, @@ -1650,6 +1626,14 @@ impl ComputeRuntime { .await .map_err(|e| Status::internal(format!("fetch sandbox failed: {e}")))? .ok_or_else(|| Status::not_found("sandbox not found"))?; + self.stop_resolved_sandbox(&candidate).await + } + + /// Stop the exact sandbox resolved and authorized by the caller. + pub(crate) async fn stop_resolved_sandbox( + &self, + candidate: &Sandbox, + ) -> Result { let sandbox_id = candidate.object_id().to_string(); let sandbox_name = candidate.object_name().to_string(); let lifecycle_guard = self.lifecycle_gates.lock_for(&sandbox_id).await; @@ -1831,23 +1815,32 @@ impl ComputeRuntime { workspace: &str, name: &str, ) -> Result { - self.start_sandbox_authenticated(workspace, name, None) + let candidate = self + .store + .get_message_by_name::(workspace, name) .await + .map_err(|e| Status::internal(format!("fetch sandbox failed: {e}")))? + .ok_or_else(|| Status::not_found("sandbox not found"))?; + provisioning_operation::ensure_operation_settled(&candidate)?; + self.start_sandbox_authenticated(&candidate, None).await } + /// Start the exact sandbox resolved and authorized by the caller. pub(crate) async fn start_sandbox_authenticated( &self, - workspace: &str, - name: &str, + candidate: &Sandbox, authority: Option<&crate::auth::sandbox_jwt::SandboxSessionJwtAuthority>, ) -> Result { + let sandbox_id = candidate.object_id().to_string(); + let sandbox_name = candidate.object_name().to_string(); + // Reject an expired attempt before waiting on its busy lifecycle gate. + // Resolve by the authorized ID so name reuse cannot change the target. let candidate = self .store - .get_message_by_name::(workspace, name) + .get_message::(&sandbox_id) .await .map_err(|e| Status::internal(format!("fetch sandbox failed: {e}")))? .ok_or_else(|| Status::not_found("sandbox not found"))?; - provisioning_operation::ensure_operation_settled(&candidate)?; if provisioning_deadline::timed_out(&candidate) && candidate .status @@ -1859,8 +1852,6 @@ impl ComputeRuntime { "provisioning timeout cleanup is still pending; retry start after compute is reclaimed", )); } - let sandbox_id = candidate.object_id().to_string(); - let sandbox_name = candidate.object_name().to_string(); let lifecycle_guard = self.lifecycle_gates.lock_for(&sandbox_id).await; let sandbox_guard = self.lock_sandbox_for_lifecycle(&lifecycle_guard).await; let mut current = self @@ -1869,6 +1860,17 @@ impl ComputeRuntime { .await .map_err(|e| Status::internal(format!("fetch sandbox failed: {e}")))? .ok_or_else(|| Status::not_found("sandbox not found"))?; + if provisioning_deadline::timed_out(¤t) + && current + .status + .as_ref() + .and_then(|status| status.provisioning.as_ref()) + .is_some_and(|record| record.cleanup_completed_time.is_none()) + { + return Err(Status::failed_precondition( + "provisioning timeout cleanup is still pending; retry start after compute is reclaimed", + )); + } if current.object_name() != sandbox_name { return Err(Status::aborted( "sandbox name changed while the start request was waiting; retry explicitly", @@ -2583,21 +2585,6 @@ impl ComputeRuntime { .await } - pub(crate) async fn delete_sandbox_allow_missing( - &self, - workspace: &str, - name: &str, - allow_missing: bool, - ) -> Result { - self.delete_sandbox_with_options( - workspace, - name, - allow_missing, - SandboxDeletePreconditions::default(), - ) - .await - } - #[cfg(test)] pub(crate) async fn delete_sandbox_with_preconditions( &self, @@ -2609,22 +2596,7 @@ impl ComputeRuntime { .await } - pub(crate) async fn delete_sandbox_allow_missing_with_preconditions( - &self, - workspace: &str, - name: &str, - allow_missing: bool, - preconditions: SandboxDeletePreconditions, - ) -> Result { - if preconditions == SandboxDeletePreconditions::default() { - return self - .delete_sandbox_allow_missing(workspace, name, allow_missing) - .await; - } - self.delete_sandbox_with_options(workspace, name, allow_missing, preconditions) - .await - } - + #[cfg(test)] async fn delete_sandbox_with_options( &self, workspace: &str, @@ -3004,6 +2976,7 @@ impl ComputeRuntime { .await .map_err(|err| err.to_string())? else { + self.cleanup_sandbox_delegated_identity(sandbox_id).await?; self.cleanup_removed_sandbox_state(sandbox_id); return Ok(false); }; @@ -3052,10 +3025,12 @@ impl ComputeRuntime { .await { Ok(true) => { + self.cleanup_sandbox_delegated_identity(sandbox_id).await?; self.cleanup_removed_sandbox_state(sandbox_id); Ok(true) } Ok(false) => { + self.cleanup_sandbox_delegated_identity(sandbox_id).await?; self.cleanup_removed_sandbox_state(sandbox_id); Ok(false) } @@ -5472,6 +5447,7 @@ impl ComputeRuntime { .delete(Sandbox::object_type(), sandbox_id) .await .map_err(|e| e.to_string())?; + self.cleanup_sandbox_delegated_identity(sandbox_id).await?; self.cleanup_removed_sandbox_state(sandbox_id); Ok(()) } @@ -5498,7 +5474,6 @@ impl ComputeRuntime { .await?; self.cleanup_sandbox_service_endpoints(sandbox.object_id(), sandbox.object_workspace()) .await?; - self.store .delete_by_name( SANDBOX_SETTINGS_OBJECT_TYPE, @@ -5521,6 +5496,21 @@ impl ComputeRuntime { Ok(()) } + /// Remove the ownership marker only after the sandbox row is gone. + /// Keeping this separate from child-record cleanup ensures an interrupted + /// or version-conflicted delete cannot turn a delegated sandbox into an + /// ordinary workspace sandbox. + async fn cleanup_sandbox_delegated_identity(&self, sandbox_id: &str) -> Result<(), String> { + self.store + .delete( + SandboxDelegatedIdentityRecord::object_type(), + &crate::delegated_identity::sandbox_delegated_identity_record_id(sandbox_id), + ) + .await + .map_err(|e| format!("delete sandbox delegated identity: {e}"))?; + Ok(()) + } + /// Best-effort driver-side cleanup for a sandbox discovered gone /// out-of-band — a watch deletion event, or the periodic prune sweep /// finding no matching driver resource — rather than through an @@ -7628,6 +7618,7 @@ mod tests { use super::*; use futures::stream; + use openshell_core::proto::SandboxDelegatedIdentity; use openshell_core::proto::SandboxPolicy as PublicSandboxPolicy; use openshell_core::proto::compute::v1::{ CreateSandboxResponse, DeleteSandboxResponse, GetCapabilitiesResponse, GetSandboxRequest, @@ -10908,6 +10899,30 @@ mod tests { } async fn seed_sandbox_owned_records(runtime: &ComputeRuntime, sandbox: &Sandbox) -> SshSession { + let delegation_id = + crate::delegated_identity::sandbox_delegated_identity_record_id(sandbox.object_id()); + runtime + .store + .put_scoped_message( + &SandboxDelegatedIdentityRecord { + metadata: Some(openshell_core::proto::datamodel::v1::ObjectMeta { + id: delegation_id.clone(), + name: delegation_id, + workspace: sandbox.object_workspace().to_string(), + ..Default::default() + }), + sandbox_id: sandbox.object_id().to_string(), + delegated_identity: Some(SandboxDelegatedIdentity { + credential_id: "credential-owned".to_string(), + principal_subject: "delegator".to_string(), + delegation_expiration_time: None, + withdrawal_time: None, + }), + }, + sandbox.object_id(), + ) + .await + .unwrap(); runtime .store .put( @@ -10965,6 +10980,10 @@ mod tests { .delete(Sandbox::object_type(), sandbox.object_id()) .await .unwrap(); + runtime + .cleanup_sandbox_delegated_identity(sandbox.object_id()) + .await + .unwrap(); } async fn assert_sandbox_owned_records( @@ -10973,6 +10992,19 @@ mod tests { session: &SshSession, expected: bool, ) { + assert_eq!( + runtime + .store + .get_message::( + &crate::delegated_identity::sandbox_delegated_identity_record_id( + sandbox.object_id(), + ), + ) + .await + .unwrap() + .is_some(), + expected + ); assert_eq!( runtime .store @@ -11803,6 +11835,51 @@ mod tests { ); } + #[tokio::test] + async fn authorized_lifecycle_target_does_not_follow_reused_name() { + let driver = ControlledDriver::new(); + let runtime = test_runtime(driver.clone()).await; + // The handler authorized this snapshot before the original was removed. + let authorized = sandbox_record("sb-original", "reused-name", SandboxPhase::Stopped); + let replacement = sandbox_record("sb-replacement", "reused-name", SandboxPhase::Stopped); + runtime.store.put_message(&replacement).await.unwrap(); + let replacement = runtime + .store + .get_message::(replacement.object_id()) + .await + .unwrap() + .unwrap(); + + let error = runtime + .start_sandbox_authenticated(&authorized, None) + .await + .unwrap_err(); + assert_eq!(error.code(), Code::NotFound); + + let error = runtime + .stop_resolved_sandbox(&authorized) + .await + .unwrap_err(); + assert_eq!(error.code(), Code::NotFound); + + let deleted = runtime + .delete_sandbox_by_id(authorized.object_id(), authorized.object_name()) + .await + .unwrap(); + assert_eq!(deleted.sandbox_id, authorized.object_id()); + assert_eq!(driver.start_calls(), 0); + assert_eq!(driver.stop_calls(), 0); + assert_eq!( + runtime + .store + .get_message::(replacement.object_id()) + .await + .unwrap(), + Some(replacement), + "a lifecycle request must leave the replacement untouched" + ); + } + #[tokio::test] async fn stop_and_start_follow_durable_state_machine() { let driver = ControlledDriver::new(); @@ -12344,11 +12421,7 @@ mod tests { let authority = test_session_authority(); runtime - .start_sandbox_authenticated( - sandbox.object_workspace(), - sandbox.object_name(), - Some(&authority), - ) + .start_sandbox_authenticated(&sandbox, Some(&authority)) .await .unwrap(); @@ -12371,11 +12444,7 @@ mod tests { ); runtime - .start_sandbox_authenticated( - sandbox.object_workspace(), - sandbox.object_name(), - Some(&authority), - ) + .start_sandbox_authenticated(&sandbox, Some(&authority)) .await .unwrap(); @@ -12423,11 +12492,7 @@ mod tests { runtime.store.put_message(&sandbox).await.unwrap(); runtime - .start_sandbox_authenticated( - sandbox.object_workspace(), - sandbox.object_name(), - Some(&test_session_authority()), - ) + .start_sandbox_authenticated(&sandbox, Some(&test_session_authority())) .await .unwrap(); @@ -13987,6 +14052,60 @@ mod tests { )); } + #[tokio::test] + async fn failed_sandbox_record_delete_retains_delegated_identity_ownership() { + let directory = tempfile::tempdir().expect("temporary database directory"); + let database_url = format!("sqlite://{}", directory.path().join("gateway.db").display()); + let store = Arc::new(Store::connect(&database_url).await.expect("connect store")); + let pool = sqlx::SqlitePool::connect(&database_url) + .await + .expect("connect failure injector"); + sqlx::query( + "CREATE TRIGGER reject_test_sandbox_delete \ + BEFORE DELETE ON objects \ + WHEN OLD.object_type = 'sandbox' AND OLD.id = 'sb-1' \ + BEGIN SELECT RAISE(ABORT, 'injected sandbox deletion failure'); END", + ) + .execute(&pool) + .await + .expect("install persistence failure trigger"); + pool.close().await; + + let driver = ControlledDriver::new(); + let mut runtime = test_runtime(driver).await; + runtime.store = store; + let sandbox = sandbox_record("sb-1", "sandbox-a", SandboxPhase::Ready); + runtime.store.put_message(&sandbox).await.unwrap(); + seed_sandbox_owned_records(&runtime, &sandbox).await; + + runtime + .apply_deleted("sb-1") + .await + .expect_err("sandbox row deletion should fail"); + + assert!( + runtime + .store + .get_message::(sandbox.object_id()) + .await + .unwrap() + .is_some() + ); + assert!( + runtime + .store + .get_message::( + &crate::delegated_identity::sandbox_delegated_identity_record_id( + sandbox.object_id(), + ), + ) + .await + .unwrap() + .is_some(), + "ownership marker must remain while the sandbox row remains" + ); + } + #[tokio::test] async fn apply_deleted_releases_driver_resources_for_out_of_band_removal() { // Regression test for #2352: a container removed without going @@ -19026,11 +19145,14 @@ mod tests { "cleanup waits for the in-flight lifecycle operation" ); assert_eq!( - runtime - .start_sandbox("default", "busy-ttl") - .await - .unwrap_err() - .code(), + tokio::time::timeout( + Duration::from_secs(3), + runtime.start_sandbox("default", "busy-ttl"), + ) + .await + .expect("start rejects pending cleanup without waiting for the lifecycle gate") + .unwrap_err() + .code(), Code::FailedPrecondition ); drop(gate); diff --git a/crates/openshell-server/src/compute/tests/error_recovery.rs b/crates/openshell-server/src/compute/tests/error_recovery.rs index e612fa3f9e..57cb77e0e3 100644 --- a/crates/openshell-server/src/compute/tests/error_recovery.rs +++ b/crates/openshell-server/src/compute/tests/error_recovery.rs @@ -91,7 +91,7 @@ async fn failed_gateway_start_can_be_stopped_and_retried() { let authority = test_session_authority(); let starting = runtime - .start_sandbox_authenticated("default", "retained", Some(&authority)) + .start_sandbox_authenticated(&failed, Some(&authority)) .await .unwrap(); assert_eq!(starting.phase(), SandboxPhase::Starting as i32); diff --git a/crates/openshell-server/src/config_file.rs b/crates/openshell-server/src/config_file.rs index 915acdad39..f3ade00a1a 100644 --- a/crates/openshell-server/src/config_file.rs +++ b/crates/openshell-server/src/config_file.rs @@ -142,6 +142,8 @@ pub struct GatewayFileSection { #[serde(default)] pub image_preparation_timeout_seconds: Option, #[serde(default)] + pub max_delegated_identity_duration_secs: Option, + #[serde(default)] pub grpc_rate_limit_requests: Option, #[serde(default)] pub grpc_rate_limit_window_seconds: Option, diff --git a/crates/openshell-server/src/credentials.rs b/crates/openshell-server/src/credentials.rs index 148acaa176..c317e2856f 100644 --- a/crates/openshell-server/src/credentials.rs +++ b/crates/openshell-server/src/credentials.rs @@ -30,8 +30,9 @@ use openshell_core::extension_protocol::{ }; use openshell_core::proto::credentials::v1::{ DeleteCredentialRequest, GetCredentialDriverCapabilitiesRequest, - GetCredentialDriverCapabilitiesResponse, ResolveCredentialRequest, ResolveCredentialsRequest, - ResolvedCredential, StoreCredentialRequest, credential_driver_client::CredentialDriverClient, + GetCredentialDriverCapabilitiesResponse, PrepareCredentialRequest, ResolveCredentialRequest, + ResolveCredentialsRequest, ResolvedCredential, StoreCredentialRequest, + credential_driver_client::CredentialDriverClient, }; use openshell_core::proto::{CredentialHandle, Provider}; use openshell_core::{Config, Error, Result as CoreResult}; @@ -58,7 +59,7 @@ use crate::storage_proto::StoredRefreshMaterialDeletion; const DEFAULT_CREDENTIAL_DRIVER_STARTUP_TIMEOUT_SECS: u64 = 10; const DEFAULT_CREDENTIAL_DRIVER_RPC_TIMEOUT_SECS: u64 = 30; -const REFRESH_MATERIAL_CREDENTIAL_KEY_DOMAIN: &[u8] = +const SECRET_MATERIAL_CREDENTIAL_KEY_DOMAIN: &[u8] = b"openshell-refresh-material-credential-key-v1"; const COMMON_CREDENTIAL_DRIVER_FIELDS: &[&str] = &[ "transport", @@ -72,6 +73,14 @@ const CREDENTIAL_DRIVER_CONNECT_INTERVAL: Duration = Duration::from_millis(100); #[async_trait] pub trait CredentialDriver: std::fmt::Debug + Send + Sync { + async fn prepare_credential( + &self, + _request: PrepareCredentialRequest, + ) -> Result { + Err(Status::unimplemented( + "credential driver does not support recoverable secret staging", + )) + } async fn store_credential( &self, request: StoreCredentialRequest, @@ -124,6 +133,31 @@ pub struct RefreshMaterialScope<'a> { pub credential_key: &'a str, } +/// Gateway-owned secret material stored through the provider-shaped +/// credential-driver protocol. +/// +/// The owner fields form a reserved internal namespace. `key_namespace` is +/// hashed together with each fixed material name before it reaches a driver, +/// so callers cannot choose backend keys or paths directly. +#[derive(Debug, Clone, Copy)] +pub struct SecretMaterialScope<'a> { + pub owner_name: &'a str, + pub workspace: &'a str, + pub owner_id: &'a str, + pub key_namespace: &'a str, +} + +impl<'a> From> for SecretMaterialScope<'a> { + fn from(scope: RefreshMaterialScope<'a>) -> Self { + Self { + owner_name: scope.provider_name, + workspace: scope.workspace, + owner_id: scope.provider_id, + key_namespace: scope.credential_key, + } + } +} + #[derive(Debug, Clone)] pub struct CredentialRuntime { registry: CredentialDriverRegistry, @@ -133,6 +167,71 @@ pub struct CredentialRuntime { } impl CredentialRuntime { + pub async fn prepare_secret_material<'a>( + &self, + scope: SecretMaterialScope<'_>, + object_id: &str, + keys: impl Iterator, + ) -> Result, Status> { + let driver_name = self.registry.storage_owner_name(); + let driver = self.connected_driver(&driver_name)?; + let mut handles = HashMap::new(); + for key in keys { + let mut handle = driver + .prepare_credential(PrepareCredentialRequest { + provider: scope.owner_name.into(), + credential_key: secret_material_storage_key(scope.key_namespace, key), + workspace: scope.workspace.into(), + provider_id: scope.owner_id.into(), + object_id: object_id.into(), + }) + .await?; + if handle.handle.trim().is_empty() { + return Err(Status::internal( + "credential driver returned an empty prepared handle", + )); + } + handle.driver.clone_from(&driver_name); + handles.insert(key.clone(), handle); + } + Ok(handles) + } + + /// Writes only pre-journaled identities. Errors retain all identities in + /// the journal, including a backend write whose response was lost. + pub async fn store_prepared_secret_material( + &self, + scope: SecretMaterialScope<'_>, + object_id: &str, + material: &HashMap, + handles: &HashMap, + ) -> Result<(), Status> { + let results = futures::future::join_all(material.iter().map(|(key, value)| async move { + let handle = handles + .get(key) + .ok_or_else(|| Status::internal("missing prepared secret handle"))?; + let driver = self.connected_driver(&handle.driver)?; + let stored = driver + .store_credential(StoreCredentialRequest { + provider: scope.owner_name.into(), + workspace: scope.workspace.into(), + provider_id: scope.owner_id.into(), + object_id: object_id.into(), + credential_key: secret_material_storage_key(scope.key_namespace, key), + value: value.clone(), + existing_handle: Some(handle.clone()), + }) + .await?; + if stored.handle != handle.handle { + return Err(Status::internal( + "credential driver changed a prepared handle", + )); + } + Ok::<(), Status>(()) + })) + .await; + results.into_iter().collect() + } pub fn from_config(config: &Config) -> CoreResult { Self::from_config_with_optional_store(config, None) } @@ -420,28 +519,46 @@ impl CredentialRuntime { object_id: &str, material: &HashMap, existing_handles: &HashMap, + ) -> Result, Status> { + self.store_secret_material_with_object_id( + scope.into(), + object_id, + material, + existing_handles, + ) + .await + } + + /// Store gateway-only secret material through the active credential + /// driver. The returned handles are opaque to the owning subsystem. + pub async fn store_secret_material_with_object_id( + &self, + scope: SecretMaterialScope<'_>, + object_id: &str, + material: &HashMap, + existing_handles: &HashMap, ) -> Result, Status> { let mut values_by_storage_key = HashMap::with_capacity(material.len()); let mut handles_by_storage_key = HashMap::with_capacity(existing_handles.len()); let mut material_by_storage_key = HashMap::with_capacity(material.len()); for (material_key, value) in material { - let storage_key = refresh_material_storage_key(scope.credential_key, material_key); + let storage_key = secret_material_storage_key(scope.key_namespace, material_key); material_by_storage_key.insert(storage_key.clone(), material_key.clone()); values_by_storage_key.insert(storage_key, value.clone()); } for (material_key, handle) in existing_handles { handles_by_storage_key.insert( - refresh_material_storage_key(scope.credential_key, material_key), + secret_material_storage_key(scope.key_namespace, material_key), handle.clone(), ); } let stored = self .store_provider_credentials_with_object_id( - scope.provider_name, + scope.owner_name, scope.workspace, - scope.provider_id, + scope.owner_id, object_id, &values_by_storage_key, &handles_by_storage_key, @@ -455,7 +572,7 @@ impl CredentialRuntime { .map(|material_key| (material_key, handle)) .ok_or_else(|| { Status::internal( - "credential driver returned an unknown refresh material key", + "credential driver returned an unknown secret material key", ) }) }) @@ -466,6 +583,14 @@ impl CredentialRuntime { &self, scope: RefreshMaterialScope<'_>, handles: &HashMap, + ) -> Result, Status> { + self.resolve_secret_material(scope.into(), handles).await + } + + pub async fn resolve_secret_material( + &self, + scope: SecretMaterialScope<'_>, + handles: &HashMap, ) -> Result, Status> { if handles.is_empty() { return Ok(HashMap::new()); @@ -473,18 +598,18 @@ impl CredentialRuntime { let mut material_by_storage_key = HashMap::with_capacity(handles.len()); let mut storage_handles = HashMap::with_capacity(handles.len()); for (material_key, handle) in handles { - let storage_key = refresh_material_storage_key(scope.credential_key, material_key); + let storage_key = secret_material_storage_key(scope.key_namespace, material_key); material_by_storage_key.insert(storage_key.clone(), material_key.clone()); storage_handles.insert(storage_key, handle.clone()); } // Reuse the normal driver-batched resolver with a synthetic provider. - // Refresh inputs are gateway-only and have no provider-level expiry; + // Gateway-only secret inputs have no provider-level expiry; // passing zero below prevents an expired injectable credential from // suppressing resolution of an otherwise valid refresh-material handle. let provider = Provider { metadata: Some(openshell_core::proto::datamodel::v1::ObjectMeta { - id: scope.provider_id.to_string(), - name: scope.provider_name.to_string(), + id: scope.owner_id.to_string(), + name: scope.owner_name.to_string(), workspace: scope.workspace.to_string(), ..Default::default() }), @@ -501,7 +626,7 @@ impl CredentialRuntime { .map(|material_key| (material_key, value)) .ok_or_else(|| { Status::internal( - "credential driver resolved an unknown refresh material key", + "credential driver resolved an unknown secret material key", ) }) }) @@ -512,20 +637,29 @@ impl CredentialRuntime { &self, scope: RefreshMaterialScope<'_>, handles: &HashMap, + ) -> Result<(), Status> { + self.delete_secret_material_handles(scope.into(), handles) + .await + } + + pub async fn delete_secret_material_handles( + &self, + scope: SecretMaterialScope<'_>, + handles: &HashMap, ) -> Result<(), Status> { let storage_handles = handles .iter() .map(|(material_key, handle)| { ( - refresh_material_storage_key(scope.credential_key, material_key), + secret_material_storage_key(scope.key_namespace, material_key), handle.clone(), ) }) .collect(); self.delete_provider_credential_handles( - scope.provider_name, + scope.owner_name, scope.workspace, - scope.provider_id, + scope.owner_id, &storage_handles, ) .await @@ -535,24 +669,33 @@ impl CredentialRuntime { &self, scope: RefreshMaterialScope<'_>, deletions: &[StoredRefreshMaterialDeletion], + ) -> Result<(), Status> { + self.delete_secret_material_deletions(scope.into(), deletions) + .await + } + + pub async fn delete_secret_material_deletions( + &self, + scope: SecretMaterialScope<'_>, + deletions: &[StoredRefreshMaterialDeletion], ) -> Result<(), Status> { let futures = deletions.iter().map(|deletion| async move { if deletion.material_key.is_empty() { return Err(Status::failed_precondition( - "pending refresh-material deletion has no material key", + "pending secret-material deletion has no material key", )); } let handle = deletion.handle.clone().ok_or_else(|| { Status::failed_precondition( - "pending refresh-material deletion has no credential handle", + "pending secret-material deletion has no credential handle", ) })?; let storage_key = - refresh_material_storage_key(scope.credential_key, &deletion.material_key); + secret_material_storage_key(scope.key_namespace, &deletion.material_key); self.delete_provider_credential_handle( - scope.provider_name, + scope.owner_name, scope.workspace, - scope.provider_id, + scope.owner_id, &storage_key, handle, ) @@ -782,11 +925,14 @@ fn effective_credential_expiration_ms( } } -fn refresh_material_storage_key(credential_key: &str, material_key: &str) -> String { +fn secret_material_storage_key(key_namespace: &str, material_key: &str) -> String { let mut hasher = Sha256::new(); - hasher.update(REFRESH_MATERIAL_CREDENTIAL_KEY_DOMAIN); + // Preserve the original refresh-material domain and output prefix so + // existing provider-refresh handles remain resolvable after extracting + // this generic gateway-secret helper. + hasher.update(SECRET_MATERIAL_CREDENTIAL_KEY_DOMAIN); hasher.update([0]); - hasher.update(credential_key.as_bytes()); + hasher.update(key_namespace.as_bytes()); hasher.update([0]); hasher.update(material_key.as_bytes()); format!("openshell.refresh.{:x}", hasher.finalize()) @@ -1357,6 +1503,12 @@ fn default_credential_store_persistence_error_to_status( #[async_trait] impl CredentialDriver for KubernetesSecretsCredentialDriver { + async fn prepare_credential( + &self, + request: PrepareCredentialRequest, + ) -> Result { + Self::prepare_credential(self, request) + } async fn store_credential( &self, request: StoreCredentialRequest, @@ -1378,6 +1530,12 @@ impl CredentialDriver for KubernetesSecretsCredentialDriver { #[async_trait] impl CredentialDriver for DbCredstoreCredentialDriver { + async fn prepare_credential( + &self, + request: PrepareCredentialRequest, + ) -> Result { + Self::prepare_credential(self, request) + } async fn store_credential( &self, request: StoreCredentialRequest, @@ -1399,6 +1557,12 @@ impl CredentialDriver for DbCredstoreCredentialDriver { #[async_trait] impl CredentialDriver for VaultCredentialDriver { + async fn prepare_credential( + &self, + request: PrepareCredentialRequest, + ) -> Result { + Self::prepare_credential(self, request) + } async fn store_credential( &self, request: StoreCredentialRequest, @@ -1438,6 +1602,20 @@ impl RemoteCredentialDriver { #[cfg(unix)] #[async_trait] impl CredentialDriver for RemoteCredentialDriver { + async fn prepare_credential( + &self, + request: PrepareCredentialRequest, + ) -> Result { + let duration = Duration::from_secs(DEFAULT_CREDENTIAL_DRIVER_RPC_TIMEOUT_SECS); + let mut request = Request::new(request); + request.set_timeout(duration); + tokio::time::timeout(duration, self.client().prepare_credential(request)) + .await + .map_err(|_| Status::deadline_exceeded("credential handle preparation timed out"))?? + .into_inner() + .handle + .ok_or_else(|| Status::internal("credential driver returned no prepared handle")) + } async fn store_credential( &self, request: StoreCredentialRequest, @@ -1890,6 +2068,19 @@ impl TestStaticCredentialDriver { #[cfg(any(test, feature = "test-support"))] #[async_trait] impl CredentialDriver for TestStaticCredentialDriver { + async fn prepare_credential( + &self, + request: PrepareCredentialRequest, + ) -> Result { + Ok(CredentialHandle { + driver: Self::NAME.into(), + handle: format!( + "{}:{}:{}", + request.provider, request.credential_key, request.object_id + ), + metadata: HashMap::new(), + }) + } async fn store_credential( &self, request: StoreCredentialRequest, diff --git a/crates/openshell-server/src/delegated_identity.rs b/crates/openshell-server/src/delegated_identity.rs new file mode 100644 index 0000000000..4ff0a81fb7 --- /dev/null +++ b/crates/openshell-server/src/delegated_identity.rs @@ -0,0 +1,4503 @@ +// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +//! Gateway-scoped delegated OIDC identity credentials for sandbox token exchange. + +mod secret_operations; + +use crate::ServerState; +use crate::auth::principal::{Principal, UserPrincipal}; +use crate::auth::workspace_authz::{ + AuthGrant, MinWorkspaceRole, authorize_workspace, require_platform_admin, +}; +use crate::compute::MutationScope; +use crate::credentials::SecretMaterialScope; +use crate::pagination::{MAX_PAGE_SIZE, Pagination}; +use crate::persistence::{ + ObjectListQuery, ObjectType, PersistenceError, WriteCondition, current_time_ms, +}; +use crate::storage_proto::{ + DelegatedIdentityCredential, DelegatedIdentityRefreshLease, SandboxDelegatedIdentityRecord, + StoredRefreshMaterialDeletion, +}; +use openshell_core::proto::datamodel::v1::{ObjectMeta, WorkspaceSelector}; +use openshell_core::proto::{ + AuthorizeDelegatedIdentityRequest, AuthorizeDelegatedIdentityResponse, CredentialHandle, + DelegatedIdentityAuthorizationGrant, DelegatedIdentityCredentialSummary, + DelegatedIdentityRequest, DeleteDelegatedIdentityCredentialRequest, + DeleteDelegatedIdentityCredentialResponse, ExtendSandboxDelegatedIdentityRequest, + ExtendSandboxDelegatedIdentityResponse, GetDelegatedIdentityAuthorizationStatusRequest, + GetDelegatedIdentityAuthorizationStatusResponse, GetDelegatedIdentityCredentialStatusRequest, + GetDelegatedIdentityCredentialStatusResponse, GetSandboxDelegatedIdentityStatusRequest, + GetSandboxDelegatedIdentityStatusResponse, ListDelegatedIdentityCredentialsRequest, + ListDelegatedIdentityCredentialsResponse, RevokeDelegatedIdentityCredentialRequest, + RevokeDelegatedIdentityCredentialResponse, Sandbox, SandboxDelegatedIdentity, + WithdrawSandboxDelegatedIdentityRequest, WithdrawSandboxDelegatedIdentityResponse, +}; +use openshell_core::{GetResourceVersion, ObjectId, ObjectName, ObjectWorkspace}; +use serde::Deserialize; +use sha2::{Digest, Sha256}; +use std::collections::HashMap; +use std::sync::{Arc, LazyLock}; +use std::time::Duration; +use tonic::{Request, Response, Status}; + +const CREDENTIAL_OBJECT_TYPE: &str = "delegated_identity_credential"; +const SANDBOX_DELEGATION_OBJECT_TYPE: &str = "sandbox_delegated_identity"; +const GLOBAL_WORKSPACE: &str = ""; +const DELEGATED_IDENTITY_SECRET_OWNER: &str = "openshell-delegated-identity"; +const DELEGATED_IDENTITY_SECRET_NAMESPACE: &str = "delegated-identity"; +const ACCESS_TOKEN_MATERIAL_KEY: &str = "access_token"; +const REFRESH_TOKEN_MATERIAL_KEY: &str = "refresh_token"; +const REFRESH_SKEW_MS: i64 = 60_000; +const REFRESH_LEASE_DURATION_MS: i64 = 120_000; +const REFRESH_LEASE_RENEW_INTERVAL: Duration = Duration::from_secs(30); +const REFRESH_STEP_TIMEOUT: Duration = Duration::from_secs(30); +const REFRESH_MIN_LEASE_LIFETIME_MS: i64 = 60_000; +const REFRESH_LEASE_POLL_INTERVAL: Duration = Duration::from_millis(100); +const REFRESH_LEASE_WAIT_LIMIT: Duration = Duration::from_secs(125); +const SANDBOX_CREATE_CREDENTIAL_TOUCH_ATTEMPTS: usize = 4; +static DELEGATED_IDENTITY_HTTP_CLIENT: LazyLock> = + LazyLock::new(|| { + let _ = rustls::crypto::aws_lc_rs::default_provider().install_default(); + reqwest::Client::builder() + .timeout(REFRESH_STEP_TIMEOUT) + .connect_timeout(REFRESH_STEP_TIMEOUT) + .no_proxy() + .redirect(reqwest::redirect::Policy::none()) + .build() + .map_err(|err| format!("delegated identity HTTP client configuration failed: {err}")) + }); + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum DelegatedSandboxLifecycleOperation { + Start, + Stop, +} + +impl DelegatedSandboxLifecycleOperation { + fn as_str(self) -> &'static str { + match self { + Self::Start => "start", + Self::Stop => "stop", + } + } +} + +pub struct PreparedSandboxDelegatedIdentity { + pub record: SandboxDelegatedIdentityRecord, + pub credential: DelegatedIdentityCredential, +} + +impl ObjectType for DelegatedIdentityCredential { + fn object_type() -> &'static str { + CREDENTIAL_OBJECT_TYPE + } +} + +impl ObjectType for SandboxDelegatedIdentityRecord { + fn object_type() -> &'static str { + SANDBOX_DELEGATION_OBJECT_TYPE + } +} + +pub async fn prepare_for_sandbox_create( + state: &Arc, + principal: &Principal, + sandbox: &Sandbox, + request: Option, +) -> Result, Status> { + let Some(request) = request else { + return Ok(None); + }; + let user = require_user(principal)?; + let delegated_until_ms = delegated_until_from_duration(state, request.duration.as_ref())?; + let credential = get_user_credential(state, user).await?.ok_or_else(|| { + Status::failed_precondition( + "delegated identity authorization is missing; authorize it before creating the sandbox", + ) + })?; + ensure_delegated_credential_usable(&credential)?; + ensure_delegated_credential_material_present(&credential)?; + let credential_id = credential.object_id().to_string(); + let sandbox_id = sandbox.object_id().to_string(); + let record_id = sandbox_delegated_identity_record_id(&sandbox_id); + Ok(Some(PreparedSandboxDelegatedIdentity { + credential, + record: SandboxDelegatedIdentityRecord { + metadata: Some(ObjectMeta { + id: record_id.clone(), + name: record_id, + created_time: openshell_core::time::timestamp_from_millis(current_time_ms()).ok(), + labels: HashMap::new(), + resource_version: 0, + annotations: HashMap::new(), + workspace: sandbox.object_workspace().to_string(), + deletion_time: None, + }), + sandbox_id, + delegated_identity: Some(SandboxDelegatedIdentity { + credential_id, + principal_subject: user.identity.subject.clone(), + delegation_expiration_time: timestamp_from_positive_millis(delegated_until_ms), + withdrawal_time: None, + }), + }, + })) +} + +pub async fn store_sandbox_with_delegation( + store: &crate::persistence::Store, + sandbox: &Sandbox, + prepared: Option<&PreparedSandboxDelegatedIdentity>, +) -> Result<(), Status> { + use crate::persistence::AtomicObjectWrite; + let mut credential = prepared.map(|prepared| prepared.credential.clone()); + for attempt in 0..SANDBOX_CREATE_CREDENTIAL_TOUCH_ATTEMPTS { + let mut writes = vec![ + AtomicObjectWrite::message(sandbox, None) + .map_err(|error| sandbox_create_persistence_error(error, sandbox.object_name()))?, + ]; + if let (Some(prepared), Some(credential)) = (prepared, credential.as_ref()) { + // Touching the credential fences deletion's reference scan and CAS. + // The entire batch rolls back on a version conflict. + writes.push( + AtomicObjectWrite::message(credential, Some(credential.get_resource_version())) + .map_err(|error| { + sandbox_create_persistence_error(error, sandbox.object_name()) + })?, + ); + let mut delegation = AtomicObjectWrite::message(&prepared.record, None) + .map_err(|error| sandbox_create_persistence_error(error, sandbox.object_name()))?; + delegation.scope = Some(sandbox.object_id().into()); + writes.push(delegation); + } + match store.put_objects_atomic(&writes).await { + Ok(()) => return Ok(()), + Err(PersistenceError::Conflict { .. }) + if prepared.is_some() && attempt + 1 < SANDBOX_CREATE_CREDENTIAL_TOUCH_ATTEMPTS => + { + let prepared = prepared.expect("checked above"); + let latest = store + .get_message::(prepared.credential.object_id()) + .await + .map_err(|error| { + sandbox_create_persistence_error(error, sandbox.object_name()) + })? + .ok_or_else(|| { + Status::failed_precondition("delegated identity credential is missing") + })?; + if prepared + .record + .delegated_identity + .as_ref() + .is_some_and(|delegation| { + latest.principal_subject != delegation.principal_subject + }) + { + return Err(Status::failed_precondition( + "delegated identity credential principal changed", + )); + } + ensure_delegated_credential_usable(&latest)?; + ensure_delegated_credential_material_present(&latest)?; + credential = Some(latest); + tokio::time::sleep(Duration::from_millis(1_u64 << attempt)).await; + } + Err(error) => { + return Err(sandbox_create_persistence_error( + error, + sandbox.object_name(), + )); + } + } + } + unreachable!("bounded credential touch loop returns on its final attempt") +} + +fn sandbox_create_persistence_error(error: PersistenceError, sandbox_name: &str) -> Status { + match error { + PersistenceError::UniqueViolation { .. } => { + Status::already_exists(format!("sandbox '{sandbox_name}' already exists")) + } + PersistenceError::Conflict { .. } => { + Status::aborted("delegated credential changed during sandbox creation; retry") + } + error => Status::internal(format!("persist sandbox failed: {error}")), + } +} + +pub async fn resolve_subject_access_token( + state: &Arc, + sandbox: &Sandbox, +) -> Result<(String, i64, String), Status> { + let record = sandbox_delegated_identity_record(state, sandbox).await?; + let delegation = record + .as_ref() + .and_then(|record| record.delegated_identity.as_ref()) + .ok_or_else(|| { + Status::failed_precondition("sandbox was not created with delegated identity") + })?; + if delegation.withdrawal_time.is_some() { + return Err(Status::failed_precondition("delegated identity withdrawn")); + } + let now = current_time_ms(); + if timestamp_millis(delegation.delegation_expiration_time.as_ref()) <= now { + return Err(Status::failed_precondition("delegated identity expired")); + } + let credential = state + .store + .get_message::(&delegation.credential_id) + .await + .map_err(|e| Status::internal(format!("fetch delegated credential failed: {e}")))? + .ok_or_else(|| Status::failed_precondition("delegated credential missing"))?; + if credential.principal_subject != delegation.principal_subject { + return Err(Status::failed_precondition( + "delegated credential principal does not match sandbox delegation", + )); + } + ensure_delegated_credential_usable(&credential)?; + let resolved = refresh_if_needed(state, credential).await?; + let credential_id = resolved.credential.object_id().to_string(); + Ok(( + resolved.access_token, + resolved.credential.access_token_expires_at_ms, + credential_id, + )) +} + +pub async fn handle_authorization_status( + state: &Arc, + request: Request, +) -> Result, Status> { + let principal = crate::grpc::extract_principal(&request)?; + let user = require_user(&principal)?; + let now_ms = current_time_ms(); + let Some(credential) = get_user_credential(state, user).await? else { + return Ok(Response::new( + GetDelegatedIdentityAuthorizationStatusResponse { + usable: false, + reauthorization_required: true, + reason: "delegated identity authorization is missing".to_string(), + credential: None, + current_time: timestamp_from_positive_millis(now_ms), + }, + )); + }; + + if delegated_credential_is_deleting(&credential) { + return Ok(Response::new( + GetDelegatedIdentityAuthorizationStatusResponse { + usable: false, + reauthorization_required: false, + reason: "delegated identity credential is being deleted".to_string(), + credential: Some(delegated_credential_summary(credential)), + current_time: timestamp_from_positive_millis(now_ms), + }, + )); + } + if let Err(status) = ensure_delegated_credential_usable(&credential) + .and_then(|()| ensure_delegated_credential_material_present(&credential)) + { + return Ok(Response::new( + GetDelegatedIdentityAuthorizationStatusResponse { + usable: false, + reauthorization_required: true, + reason: status.message().to_string(), + credential: Some(delegated_credential_summary(credential)), + current_time: timestamp_from_positive_millis(now_ms), + }, + )); + } + + match refresh_if_needed(state, credential).await { + Ok(resolved) => Ok(Response::new( + GetDelegatedIdentityAuthorizationStatusResponse { + usable: true, + reauthorization_required: false, + reason: String::new(), + credential: Some(delegated_credential_summary(resolved.credential)), + current_time: timestamp_from_positive_millis(current_time_ms()), + }, + )), + Err(status) if delegated_refresh_requires_reauthorization(&status) => Ok(Response::new( + GetDelegatedIdentityAuthorizationStatusResponse { + usable: false, + reauthorization_required: true, + reason: status.message().to_string(), + credential: None, + current_time: timestamp_from_positive_millis(current_time_ms()), + }, + )), + Err(status) => Err(status), + } +} + +pub async fn handle_authorize( + state: &Arc, + request: Request, +) -> Result, Status> { + let principal = crate::grpc::extract_principal(&request)?; + let user = require_user(&principal)?; + let grant = request + .into_inner() + .grant + .ok_or_else(|| Status::invalid_argument("grant is required"))?; + let access_token_expires_at_ms = validate_authorization_grant(state, user, &grant).await?; + let credential = upsert_credential(state, user, grant, access_token_expires_at_ms).await?; + Ok(Response::new(AuthorizeDelegatedIdentityResponse { + credential: Some(delegated_credential_summary(credential)), + current_time: timestamp_from_positive_millis(current_time_ms()), + })) +} + +pub async fn handle_status( + state: &Arc, + request: Request, +) -> Result, Status> { + let principal = crate::grpc::extract_principal(&request)?; + let req = request.into_inner(); + let sandbox = + authorized_sandbox_by_name(state, &principal, req.workspace_scope.as_ref(), &req.name) + .await?; + let record = sandbox_delegated_identity_record(state, &sandbox).await?; + let delegation = record + .as_ref() + .and_then(|record| record.delegated_identity.as_ref()); + if delegation.is_some() { + ensure_delegator(&principal, delegation)?; + } + let (credential_revoked_at_ms, credential_missing) = + sandbox_delegated_identity_credential_status(state, delegation).await?; + Ok(Response::new(GetSandboxDelegatedIdentityStatusResponse { + delegated_identity: delegation.cloned(), + current_time: timestamp_from_positive_millis(current_time_ms()), + credential_revocation_time: timestamp_from_positive_millis(credential_revoked_at_ms), + credential_missing, + })) +} + +async fn sandbox_delegated_identity_credential_status( + state: &Arc, + delegation: Option<&SandboxDelegatedIdentity>, +) -> Result<(i64, bool), Status> { + let Some(delegation) = delegation else { + return Ok((0, true)); + }; + let credential = state + .store + .get_message::(&delegation.credential_id) + .await + .map_err(|e| Status::internal(format!("fetch delegated credential failed: {e}")))?; + Ok(delegated_credential_status_fields(credential.as_ref())) +} + +fn delegated_credential_status_fields( + credential: Option<&DelegatedIdentityCredential>, +) -> (i64, bool) { + credential.map_or((0, true), |credential| (credential.revoked_at_ms, false)) +} + +async fn sandbox_delegated_identity_record( + state: &Arc, + sandbox: &Sandbox, +) -> Result, Status> { + state + .store + .get_message::(&sandbox_delegated_identity_record_id( + sandbox.object_id(), + )) + .await + .map_err(|e| Status::internal(format!("fetch sandbox delegated identity failed: {e}"))) +} + +pub fn sandbox_delegated_identity_record_id(sandbox_id: &str) -> String { + format!("sandbox-delegated-identity-{sandbox_id}") +} + +pub async fn ensure_delegated_identity_sandbox_user( + state: &Arc, + principal: &Principal, + sandbox: &Sandbox, +) -> Result<(), Status> { + let record = sandbox_delegated_identity_record(state, sandbox).await?; + let Some(delegation) = record + .as_ref() + .and_then(|record| record.delegated_identity.as_ref()) + else { + return Ok(()); + }; + + match principal { + Principal::User(user) if user.identity.subject == delegation.principal_subject => Ok(()), + Principal::User(_) | Principal::Operator(_) | Principal::Peer(_) => { + Err(Status::permission_denied( + "delegated identity sandbox access denied: caller is not the delegating principal", + )) + } + Principal::Sandbox(_) => Ok(()), + Principal::Anonymous => Err(Status::unauthenticated( + "sandbox-scoped methods require an authenticated caller", + )), + } +} + +/// Enforce the operation-specific lifecycle policy for delegated sandboxes. +/// +/// The delegator may start or stop their sandbox. Workspace and Platform +/// Admins may stop it for containment, but cannot restart a workload acting on +/// another user's behalf. Ordinary sandboxes retain the workspace role model. +pub async fn ensure_delegated_identity_sandbox_lifecycle_operation( + state: &Arc, + principal: &Principal, + grant: AuthGrant, + sandbox: &Sandbox, + operation: DelegatedSandboxLifecycleOperation, +) -> Result<(), Status> { + let record = sandbox_delegated_identity_record(state, sandbox).await?; + let Some(delegation) = record + .as_ref() + .and_then(|record| record.delegated_identity.as_ref()) + else { + return Ok(()); + }; + + let is_delegator = matches!( + principal, + Principal::User(user) if user.identity.subject == delegation.principal_subject + ); + let is_containment_admin = matches!( + grant, + AuthGrant::PlatformAdmin | AuthGrant::Member(openshell_core::proto::WorkspaceRole::Admin) + ); + if is_delegator + || (operation == DelegatedSandboxLifecycleOperation::Stop && is_containment_admin) + { + return Ok(()); + } + + if matches!(principal, Principal::Anonymous) { + return Err(Status::unauthenticated( + "sandbox lifecycle methods require an authenticated caller", + )); + } + + tracing::info!( + sandbox_id = %sandbox.object_id(), + operation = operation.as_str(), + "delegated identity sandbox lifecycle access denied" + ); + Err(Status::permission_denied(format!( + "only the delegating principal may {} this delegated identity sandbox", + operation.as_str() + ))) +} + +pub async fn handle_withdraw( + state: &Arc, + request: Request, +) -> Result, Status> { + let principal = crate::grpc::extract_principal(&request)?; + let req = request.into_inner(); + let sandbox = + authorized_sandbox_by_name(state, &principal, req.workspace_scope.as_ref(), &req.name) + .await?; + let record = sandbox_delegated_identity_record(state, &sandbox).await?; + let delegation = record + .as_ref() + .and_then(|record| record.delegated_identity.as_ref()); + ensure_delegator(&principal, delegation)?; + let Some(record) = record else { + return Err(Status::invalid_argument( + "sandbox delegated identity is not enabled", + )); + }; + let now = current_time_ms(); + let mut changed = false; + state + .store + .update_message_cas::(record.object_id(), 0, |current| { + if let Some(delegation) = current.delegated_identity.as_mut() + && delegation.withdrawal_time.is_none() + { + delegation.withdrawal_time = timestamp_from_positive_millis(now); + changed = true; + } + }) + .await + .map_err(|e| crate::grpc::persistence_error_to_status(e, "withdraw delegated identity"))?; + Ok(Response::new(WithdrawSandboxDelegatedIdentityResponse { + sandbox: Some(sandbox), + withdrawn: changed, + })) +} + +pub async fn handle_extend( + state: &Arc, + request: Request, +) -> Result, Status> { + let principal = crate::grpc::extract_principal(&request)?; + let req = request.into_inner(); + let requested = req + .delegated_identity + .ok_or_else(|| Status::invalid_argument("delegated_identity is required"))?; + let sandbox = + authorized_sandbox_by_name(state, &principal, req.workspace_scope.as_ref(), &req.name) + .await?; + let _mutation_guard = state + .compute + .mutation_guard(MutationScope::sandbox( + sandbox.object_workspace(), + sandbox.object_id(), + )) + .await + .map_err(|error| { + crate::grpc::persistence_error_to_status( + error, + "acquire delegated identity mutation lock", + ) + })?; + let record = sandbox_delegated_identity_record(state, &sandbox).await?; + let delegation = record + .as_ref() + .and_then(|record| record.delegated_identity.as_ref()); + ensure_delegator(&principal, delegation)?; + let Some(record) = record else { + return Err(Status::invalid_argument( + "sandbox delegated identity is not enabled", + )); + }; + let user = require_user(&principal)?; + let delegated_until_ms = delegated_until_from_duration(state, requested.duration.as_ref())?; + let credential = get_user_credential(state, user).await?.ok_or_else(|| { + Status::failed_precondition( + "delegated identity authorization is missing; authorize it before extending the sandbox", + ) + })?; + ensure_delegated_credential_usable(&credential)?; + ensure_delegated_credential_material_present(&credential)?; + let mut updated = record.clone(); + if let Some(delegation) = updated.delegated_identity.as_mut() { + delegation.credential_id = credential.object_id().to_string(); + delegation.delegation_expiration_time = timestamp_from_positive_millis(delegated_until_ms); + delegation.withdrawal_time = None; + } + state + .store + .put_objects_atomic(&[ + crate::persistence::AtomicObjectWrite::message( + &credential, + Some(credential.get_resource_version()), + ) + .map_err(|error| { + crate::grpc::persistence_error_to_status(error, "encode delegated credential") + })?, + crate::persistence::AtomicObjectWrite::message( + &updated, + Some(record.get_resource_version()), + ) + .map_err(|error| { + crate::grpc::persistence_error_to_status(error, "encode delegation") + })?, + ]) + .await + .map_err(|error| { + crate::grpc::persistence_error_to_status(error, "extend delegated identity") + })?; + Ok(Response::new(ExtendSandboxDelegatedIdentityResponse { + sandbox: Some(sandbox), + })) +} + +pub async fn handle_list_credentials( + state: &Arc, + request: Request, +) -> Result, Status> { + let principal = crate::grpc::extract_principal(&request)?; + require_platform_admin(&state.admin_role, &principal)?; + let req = request.into_inner(); + let pagination = Pagination::new( + req.page_size, + &req.page_token, + "ListDelegatedIdentityCredentials", + &[], + )?; + let after = pagination.object_cursor()?; + let page = state + .store + .list_message_page::( + ObjectListQuery::AllWorkspaces, + after.as_ref(), + pagination.page_size(), + ) + .await + .map_err(|e| Status::internal(format!("list delegated credentials failed: {e}")))?; + let next_page_token = pagination.next_object_token(page.next_cursor.as_ref()); + let credentials = page + .messages + .into_iter() + .map(delegated_credential_summary) + .collect(); + Ok(Response::new(ListDelegatedIdentityCredentialsResponse { + credentials, + next_page_token, + })) +} + +pub async fn handle_get_credential_status( + state: &Arc, + request: Request, +) -> Result, Status> { + let principal = crate::grpc::extract_principal(&request)?; + require_platform_admin(&state.admin_role, &principal)?; + let req = request.into_inner(); + let credential = state + .store + .get_message::(&req.id) + .await + .map_err(|e| Status::internal(format!("fetch delegated credential failed: {e}")))? + .ok_or_else(|| Status::not_found("delegated credential not found"))?; + Ok(Response::new( + GetDelegatedIdentityCredentialStatusResponse { + credential: Some(delegated_credential_summary(credential)), + current_time: timestamp_from_positive_millis(current_time_ms()), + }, + )) +} + +fn delegated_credential_summary( + credential: DelegatedIdentityCredential, +) -> DelegatedIdentityCredentialSummary { + DelegatedIdentityCredentialSummary { + metadata: credential.metadata, + issuer: credential.issuer, + client_id: credential.client_id, + principal_subject: credential.principal_subject, + refresh_token_present: credential + .secret_material_handles + .contains_key(REFRESH_TOKEN_MATERIAL_KEY), + access_token_present: credential + .secret_material_handles + .contains_key(ACCESS_TOKEN_MATERIAL_KEY), + access_token_expiration_time: timestamp_from_positive_millis( + credential.access_token_expires_at_ms, + ), + scopes: credential.scopes, + audience: credential.audience, + last_refresh_time: timestamp_from_positive_millis(credential.last_refresh_at_ms), + revocation_time: timestamp_from_positive_millis(credential.revoked_at_ms), + } +} + +pub async fn handle_revoke_credential( + state: &Arc, + request: Request, +) -> Result, Status> { + let principal = crate::grpc::extract_principal(&request)?; + require_platform_admin(&state.admin_role, &principal)?; + let req = request.into_inner(); + let now = current_time_ms(); + let mut revoked = false; + let credential = state + .store + .update_message_cas::( + &req.id, + req.expected_resource_version, + |credential| { + if credential.revoked_at_ms == 0 { + credential.revoked_at_ms = now; + revoked = true; + } + move_active_handles_to_pending(credential); + }, + ) + .await + .map_err(|e| crate::grpc::persistence_error_to_status(e, "revoke delegated credential"))?; + let credential = match cleanup_pending_secret_deletions(state, credential).await { + Ok(cleaned) => cleaned, + Err(error) => { + tracing::warn!( + credential_id = %req.id, + error = %error, + "delegated credential revoked; secret cleanup will be retried" + ); + state + .store + .get_message::(&req.id) + .await + .map_err(|e| { + Status::internal(format!("fetch revoked delegated credential failed: {e}")) + })? + .ok_or_else(|| Status::not_found("delegated credential not found"))? + } + }; + let resource_version = credential + .metadata + .as_ref() + .map(|metadata| metadata.resource_version) + .unwrap_or_default(); + Ok(Response::new(RevokeDelegatedIdentityCredentialResponse { + revoked, + revocation_time: timestamp_from_positive_millis(credential.revoked_at_ms), + resource_version, + })) +} + +pub async fn handle_delete_credential( + state: &Arc, + request: Request, +) -> Result, Status> { + let principal = crate::grpc::extract_principal(&request)?; + require_platform_admin(&state.admin_role, &principal)?; + let req = request.into_inner(); + let _mutation_guard = state + .compute + .mutation_guard(MutationScope::Global) + .await + .map_err(|error| { + crate::grpc::persistence_error_to_status( + error, + "acquire delegated identity mutation lock", + ) + })?; + let credential = state + .store + .get_message::(&req.id) + .await + .map_err(|e| Status::internal(format!("fetch delegated credential failed: {e}")))?; + let Some(credential) = credential else { + return Ok(Response::new(DeleteDelegatedIdentityCredentialResponse { + deleted: false, + })); + }; + let is_tombstoned = credential + .metadata + .as_ref() + .is_some_and(|metadata| metadata.deletion_time.is_some()); + if !is_tombstoned { + ensure_expected_resource_version(&credential, req.expected_resource_version)?; + ensure_no_active_delegation_references(state, &req.id).await?; + } + let expected_resource_version = if is_tombstoned || req.expected_resource_version == 0 { + credential.get_resource_version() + } else { + req.expected_resource_version + }; + let deleted = + tombstone_and_delete_credential(state, &req.id, expected_resource_version).await?; + Ok(Response::new(DeleteDelegatedIdentityCredentialResponse { + deleted, + })) +} + +fn ensure_expected_resource_version( + credential: &DelegatedIdentityCredential, + expected_resource_version: u64, +) -> Result<(), Status> { + if expected_resource_version == 0 + || credential.get_resource_version() == expected_resource_version + { + return Ok(()); + } + Err(Status::aborted( + "delegated credential resource version conflict", + )) +} + +async fn ensure_no_active_delegation_references( + state: &Arc, + credential_id: &str, +) -> Result<(), Status> { + let now = current_time_ms(); + let records = state + .store + .collect_messages::(ObjectListQuery::AllWorkspaces) + .await + .map_err(|e| { + Status::internal(format!( + "list sandbox delegated identity references failed: {e}" + )) + })?; + if records.iter().any(|record| { + record + .delegated_identity + .as_ref() + .is_some_and(|delegation| { + delegation.credential_id == credential_id + && delegation.withdrawal_time.is_none() + && timestamp_millis(delegation.delegation_expiration_time.as_ref()) > now + }) + }) { + return Err(Status::failed_precondition( + "delegated credential is referenced by an active sandbox delegation", + )); + } + Ok(()) +} + +async fn tombstone_and_delete_credential( + state: &Arc, + credential_id: &str, + expected_resource_version: u64, +) -> Result { + let now = current_time_ms(); + let credential = state + .store + .update_message_cas::( + credential_id, + expected_resource_version, + |credential| { + if let Some(metadata) = credential.metadata.as_mut() + && metadata.deletion_time.is_none() + { + metadata.deletion_time = openshell_core::time::timestamp_from_millis(now).ok(); + } + if credential.revoked_at_ms == 0 { + credential.revoked_at_ms = now; + } + move_active_handles_to_pending(credential); + }, + ) + .await + .map_err(|error| { + crate::grpc::persistence_error_to_status(error, "delete delegated credential") + })?; + + state + .credentials + .delete_secret_material_deletions( + delegated_secret_scope(credential_id), + &credential.pending_secret_deletions, + ) + .await + .map_err(|error| { + Status::new( + error.code(), + format!( + "delegated credential is disabled, but secret cleanup failed and will be retried: {}", + error.message() + ), + ) + })?; + + state + .store + .delete_if( + DelegatedIdentityCredential::object_type(), + credential_id, + credential.get_resource_version(), + ) + .await + .map_err(|error| { + crate::grpc::persistence_error_to_status(error, "delete delegated credential") + }) +} + +fn move_active_handles_to_pending(credential: &mut DelegatedIdentityCredential) { + credential.refresh_lease = None; + let handles = std::mem::take(&mut credential.secret_material_handles); + enqueue_handle_deletions(&mut credential.pending_secret_deletions, handles); +} + +fn enqueue_handle_deletions( + pending: &mut Vec, + handles: HashMap, +) { + pending.extend(handles.into_iter().map(|(material_key, handle)| { + StoredRefreshMaterialDeletion { + material_key, + handle: Some(handle), + } + })); +} + +async fn cleanup_pending_secret_deletions( + state: &Arc, + credential: DelegatedIdentityCredential, +) -> Result { + if credential.pending_secret_deletions.is_empty() { + return Ok(credential); + } + state + .credentials + .delete_secret_material_deletions( + delegated_secret_scope(credential.object_id()), + &credential.pending_secret_deletions, + ) + .await?; + state + .store + .update_message_cas::( + credential.object_id(), + credential.get_resource_version(), + |current| current.pending_secret_deletions.clear(), + ) + .await + .map_err(|error| { + crate::grpc::persistence_error_to_status( + error, + "record delegated credential secret cleanup", + ) + }) +} + +async fn cleanup_pending_secret_deletions_best_effort( + state: &Arc, + credential: DelegatedIdentityCredential, +) -> DelegatedIdentityCredential { + let retained = credential.clone(); + match cleanup_pending_secret_deletions(state, credential).await { + Ok(cleaned) => cleaned, + Err(error) => { + tracing::warn!( + credential_id = %retained.object_id(), + error = %error, + "failed to clean up superseded delegated credential material; retrying later" + ); + retained + } + } +} + +fn require_user(principal: &Principal) -> Result<&UserPrincipal, Status> { + match principal { + Principal::User(user) => Ok(user), + _ => Err(Status::permission_denied( + "delegated identity requires an authenticated user principal", + )), + } +} + +fn ensure_delegator( + principal: &Principal, + delegation: Option<&SandboxDelegatedIdentity>, +) -> Result<(), Status> { + let user = require_user(principal)?; + let delegation = delegation.ok_or_else(|| { + Status::failed_precondition("sandbox was not created with delegated identity") + })?; + if delegation.principal_subject != user.identity.subject { + return Err(Status::permission_denied( + "only the delegating principal may manage this sandbox delegated identity", + )); + } + Ok(()) +} + +fn delegated_until_from_duration( + state: &Arc, + duration: Option<&prost_types::Duration>, +) -> Result { + let duration = duration + .ok_or_else(|| Status::invalid_argument("delegated_identity.duration is required"))?; + let duration = openshell_core::time::duration_to_std(duration).map_err(|error| { + Status::invalid_argument(format!("invalid delegated identity duration: {error}")) + })?; + let duration_ms = i64::try_from(duration.as_millis()) + .map_err(|_| Status::invalid_argument("delegated identity duration is too large"))?; + if duration_ms <= 0 { + return Err(Status::invalid_argument( + "delegated_identity.duration must be greater than zero", + )); + } + let max_ms = i64::try_from(state.config.max_delegated_identity_duration_secs) + .unwrap_or(i64::MAX / 1000) + .saturating_mul(1000); + if duration_ms > max_ms { + return Err(Status::failed_precondition(format!( + "delegated identity duration exceeds gateway maximum of {} seconds", + state.config.max_delegated_identity_duration_secs + ))); + } + Ok(current_time_ms().saturating_add(duration_ms)) +} + +fn timestamp_from_positive_millis(value: i64) -> Option { + (value > 0) + .then(|| openshell_core::time::timestamp_from_millis(value).ok()) + .flatten() +} + +fn timestamp_millis(value: Option<&prost_types::Timestamp>) -> i64 { + value + .and_then(|value| openshell_core::time::timestamp_to_millis(value).ok()) + .unwrap_or_default() +} + +fn configured_delegated_issuer(state: &Arc) -> Result<&str, Status> { + state + .config + .oidc + .as_ref() + .map(|oidc| oidc.issuer.trim_end_matches('/')) + .ok_or_else(|| { + Status::failed_precondition( + "delegated identity requires gateway OIDC authentication to be configured", + ) + }) +} + +async fn validate_authorization_grant( + state: &Arc, + user: &UserPrincipal, + grant: &DelegatedIdentityAuthorizationGrant, +) -> Result { + if grant.issuer.trim().is_empty() { + return Err(Status::invalid_argument("grant.issuer is required")); + } + if grant.issuer.trim_end_matches('/') != configured_delegated_issuer(state)? { + return Err(Status::invalid_argument( + "grant.issuer must match the gateway OIDC issuer", + )); + } + if grant.client_id.trim().is_empty() { + return Err(Status::invalid_argument("grant.client_id is required")); + } + if grant.refresh_token.trim().is_empty() { + return Err(Status::invalid_argument("grant.refresh_token is required")); + } + if grant.access_token.trim().is_empty() { + return Err(Status::invalid_argument("grant.access_token is required")); + } + validate_delegated_access_token_subject(state, user, grant).await +} + +async fn validate_delegated_access_token_subject( + state: &Arc, + user: &UserPrincipal, + grant: &DelegatedIdentityAuthorizationGrant, +) -> Result { + validate_delegated_access_token_subject_value( + state, + &grant.access_token, + &user.identity.subject, + ) + .await +} + +async fn validate_delegated_access_token_subject_value( + state: &Arc, + access_token: &str, + expected_subject: &str, +) -> Result { + let cache = state.oidc_cache.as_ref().ok_or_else(|| { + Status::failed_precondition( + "delegated identity requires gateway OIDC token validation to be configured", + ) + })?; + let validated = cache.validate_token_details(access_token).await?; + ensure_delegated_token_subject_matches(&validated.identity.subject, expected_subject)?; + Ok(validated.expires_at_ms) +} + +fn ensure_delegated_token_subject_matches( + token_subject: &str, + caller_subject: &str, +) -> Result<(), Status> { + if token_subject == caller_subject { + Ok(()) + } else { + Err(Status::permission_denied( + "delegated_identity.access_token subject must match the authenticated caller", + )) + } +} + +async fn authorized_sandbox_by_name( + state: &Arc, + principal: &Principal, + workspace_scope: Option<&WorkspaceSelector>, + name: &str, +) -> Result { + if name.trim().is_empty() { + return Err(Status::invalid_argument("name is required")); + } + let authz = authorize_workspace( + &state.store, + &state.admin_role, + principal, + crate::auth::workspace_authz::selected_workspace_name(workspace_scope)?, + MinWorkspaceRole::User, + ) + .await?; + let workspace = + crate::grpc::workspace::resolve_workspace(state.store.as_ref(), &authz.workspace) + .await? + .name; + state + .store + .get_message_by_name::(&workspace, name) + .await + .map_err(|e| Status::internal(format!("fetch sandbox failed: {e}")))? + .ok_or_else(|| Status::not_found("sandbox not found")) +} + +async fn upsert_credential( + state: &Arc, + user: &UserPrincipal, + grant: DelegatedIdentityAuthorizationGrant, + access_token_expires_at_ms: i64, +) -> Result { + let id = delegated_credential_id(&grant.issuer, &user.identity.subject); + let now = current_time_ms(); + let existing = state + .store + .get_message::(&id) + .await + .map_err(|e| Status::internal(format!("fetch delegated credential failed: {e}")))?; + if existing.as_ref().is_some_and(|credential| { + credential + .metadata + .as_ref() + .is_some_and(|metadata| metadata.deletion_time.is_some()) + }) { + return Err(Status::failed_precondition( + "delegated identity credential is being deleted", + )); + } + + let material = HashMap::from([ + (REFRESH_TOKEN_MATERIAL_KEY.to_string(), grant.refresh_token), + (ACCESS_TOKEN_MATERIAL_KEY.to_string(), grant.access_token), + ]); + let staged_handles = store_staged_secret_material(state, &id, &material).await?; + let mut credential = DelegatedIdentityCredential { + metadata: Some(ObjectMeta { + id: id.clone(), + name: id.clone(), + created_time: openshell_core::time::timestamp_from_millis(now).ok(), + labels: HashMap::new(), + resource_version: 0, + annotations: HashMap::new(), + workspace: GLOBAL_WORKSPACE.to_string(), + deletion_time: None, + }), + issuer: grant.issuer, + client_id: grant.client_id, + principal_subject: user.identity.subject.clone(), + access_token_expires_at_ms, + scopes: grant.scopes, + audience: grant.audience, + last_refresh_at_ms: now, + revoked_at_ms: 0, + secret_material_handles: staged_handles.clone(), + pending_secret_deletions: Vec::new(), + refresh_lease: None, + }; + + let write_condition = if let Some(existing) = existing { + let write_condition = delegated_credential_upsert_condition(&existing); + credential.metadata = existing.metadata; + credential.pending_secret_deletions = existing.pending_secret_deletions; + enqueue_handle_deletions( + &mut credential.pending_secret_deletions, + existing.secret_material_handles, + ); + write_condition + } else { + WriteCondition::MustCreate + }; + let expected_version = match write_condition { + WriteCondition::MatchResourceVersion(version) => Some(version), + WriteCondition::MustCreate => None, + WriteCondition::Unconditional => { + return Err(Status::internal( + "unconditional delegated credential publication", + )); + } + }; + let result = async { + let publication = secret_operations::publication(state, &staged_handles).await?; + let credential_write = + crate::persistence::AtomicObjectWrite::message(&credential, expected_version).map_err( + |error| { + crate::grpc::persistence_error_to_status(error, "encode delegated credential") + }, + )?; + state + .store + .put_objects_atomic(&[credential_write, publication]) + .await + .map_err(|error| { + crate::grpc::persistence_error_to_status(error, "persist delegated credential") + }) + } + .await; + match result { + Ok(()) => {} + Err(error) => { + cleanup_staged_secret_material(state, &id, &staged_handles).await; + return Err(error); + } + } + if let Some(metadata) = credential.metadata.as_mut() { + metadata.resource_version = expected_version.unwrap_or(0) + 1; + } + credential = cleanup_pending_secret_deletions_best_effort(state, credential).await; + Ok(credential) +} + +fn delegated_credential_upsert_condition(existing: &DelegatedIdentityCredential) -> WriteCondition { + WriteCondition::MatchResourceVersion(existing.get_resource_version()) +} + +fn ensure_delegated_credential_usable( + credential: &DelegatedIdentityCredential, +) -> Result<(), Status> { + if credential.revoked_at_ms > 0 { + return Err(Status::failed_precondition( + "delegated identity credential is revoked", + )); + } + if delegated_credential_is_deleting(credential) { + return Err(Status::failed_precondition( + "delegated identity credential is being deleted", + )); + } + Ok(()) +} + +fn delegated_credential_is_deleting(credential: &DelegatedIdentityCredential) -> bool { + credential + .metadata + .as_ref() + .is_some_and(|metadata| metadata.deletion_time.is_some()) +} + +fn ensure_delegated_credential_material_present( + credential: &DelegatedIdentityCredential, +) -> Result<(), Status> { + if !credential + .secret_material_handles + .contains_key(ACCESS_TOKEN_MATERIAL_KEY) + || !credential + .secret_material_handles + .contains_key(REFRESH_TOKEN_MATERIAL_KEY) + { + return Err(Status::failed_precondition( + "delegated identity authorization is incomplete", + )); + } + Ok(()) +} + +fn delegated_credential_id(issuer: &str, principal_subject: &str) -> String { + let mut hasher = Sha256::new(); + hasher.update(issuer.trim_end_matches('/')); + hasher.update(b"\0"); + hasher.update(principal_subject); + format!("delegated-identity-{:x}", hasher.finalize()) +} + +async fn get_user_credential( + state: &Arc, + user: &UserPrincipal, +) -> Result, Status> { + let id = delegated_credential_id(configured_delegated_issuer(state)?, &user.identity.subject); + state + .store + .get_message::(&id) + .await + .map_err(|error| Status::internal(format!("fetch delegated credential failed: {error}"))) +} + +fn delegated_secret_scope(credential_id: &str) -> SecretMaterialScope<'_> { + SecretMaterialScope { + owner_name: DELEGATED_IDENTITY_SECRET_OWNER, + workspace: GLOBAL_WORKSPACE, + owner_id: credential_id, + key_namespace: DELEGATED_IDENTITY_SECRET_NAMESPACE, + } +} + +async fn store_staged_secret_material( + state: &Arc, + credential_id: &str, + material: &HashMap, +) -> Result, Status> { + secret_operations::stage(state, credential_id, material).await +} + +async fn cleanup_staged_secret_material( + state: &Arc, + _credential_id: &str, + handles: &HashMap, +) { + if handles.is_empty() { + return; + } + secret_operations::abort(state, handles).await; +} + +struct DelegatedSecretMaterial { + access_token: String, + refresh_token: String, +} + +#[derive(Debug)] +struct ResolvedDelegatedAccessToken { + credential: DelegatedIdentityCredential, + access_token: String, +} + +async fn resolve_delegated_secret_material( + state: &Arc, + credential: &DelegatedIdentityCredential, +) -> Result { + let mut material = state + .credentials + .resolve_secret_material( + delegated_secret_scope(credential.object_id()), + &credential.secret_material_handles, + ) + .await?; + let access_token = material.remove(ACCESS_TOKEN_MATERIAL_KEY).ok_or_else(|| { + Status::failed_precondition("delegated credential access token is missing") + })?; + let refresh_token = material.remove(REFRESH_TOKEN_MATERIAL_KEY).ok_or_else(|| { + Status::failed_precondition("delegated credential refresh token is missing") + })?; + Ok(DelegatedSecretMaterial { + access_token, + refresh_token, + }) +} + +pub fn spawn_credential_cleanup_worker(state: Arc, interval: Duration) { + tracing::info!( + interval_seconds = interval.as_secs(), + "delegated credential cleanup worker started" + ); + tokio::spawn(async move { + let mut ticker = tokio::time::interval(interval); + ticker.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Delay); + loop { + ticker.tick().await; + if let Err(error) = run_credential_cleanup_tick(&state).await { + tracing::warn!( + error = %error, + "delegated credential cleanup worker tick failed" + ); + } + } + }); +} + +async fn run_credential_cleanup_tick(state: &Arc) -> Result<(), Status> { + secret_operations::tick(state).await?; + let mut cursor = None; + loop { + let page = state + .store + .list_message_page::( + ObjectListQuery::AllWorkspaces, + cursor.as_ref(), + MAX_PAGE_SIZE, + ) + .await + .map_err(|error| { + Status::internal(format!( + "list delegated credentials for cleanup failed: {error}" + )) + })?; + for credential in &page.messages { + let deleting = credential + .metadata + .as_ref() + .is_some_and(|metadata| metadata.deletion_time.is_some()); + if deleting { + if let Err(error) = tombstone_and_delete_credential( + state, + credential.object_id(), + credential.get_resource_version(), + ) + .await + { + tracing::warn!( + credential_id = %credential.object_id(), + error = %error, + "failed to finalize delegated credential deletion; retrying later" + ); + } + } else if !credential.pending_secret_deletions.is_empty() + && let Err(error) = + cleanup_pending_secret_deletions(state, credential.clone()).await + { + tracing::warn!( + credential_id = %credential.object_id(), + error = %error, + "failed to retry delegated credential secret cleanup" + ); + } + } + let Some(next_cursor) = page.next_cursor else { + return Ok(()); + }; + cursor = Some(next_cursor); + } +} + +async fn refresh_if_needed( + state: &Arc, + credential: DelegatedIdentityCredential, +) -> Result { + ensure_delegated_credential_usable(&credential)?; + let mut credential = cleanup_pending_secret_deletions_best_effort(state, credential).await; + let wait_started = tokio::time::Instant::now(); + + loop { + ensure_delegated_credential_usable(&credential)?; + ensure_delegated_credential_material_present(&credential)?; + let now = current_time_ms(); + if credential.access_token_expires_at_ms > 0 + && credential.access_token_expires_at_ms.saturating_sub(now) > REFRESH_SKEW_MS + { + let material = resolve_delegated_secret_material(state, &credential).await?; + return Ok(ResolvedDelegatedAccessToken { + credential, + access_token: material.access_token, + }); + } + + match try_acquire_refresh_lease(state, &credential).await? { + RefreshLeaseAttempt::Acquired { + credential, + owner_id, + } => { + return refresh_with_owned_lease(state, *credential, &owner_id).await; + } + RefreshLeaseAttempt::Held => { + if wait_started.elapsed() >= REFRESH_LEASE_WAIT_LIMIT { + return Err(Status::deadline_exceeded( + "timed out waiting for delegated credential refresh", + )); + } + tokio::time::sleep(REFRESH_LEASE_POLL_INTERVAL).await; + credential = state + .store + .get_message::(credential.object_id()) + .await + .map_err(|error| { + Status::internal(format!( + "reload delegated credential while waiting for refresh failed: {error}" + )) + })? + .ok_or_else(|| { + Status::failed_precondition("delegated identity credential is missing") + })?; + } + } + } +} + +enum RefreshLeaseAttempt { + Acquired { + credential: Box, + owner_id: String, + }, + Held, +} + +async fn try_acquire_refresh_lease( + state: &Arc, + credential: &DelegatedIdentityCredential, +) -> Result { + let now = current_time_ms(); + if credential + .refresh_lease + .as_ref() + .is_some_and(|lease| lease.expires_at_ms > now) + { + return Ok(RefreshLeaseAttempt::Held); + } + + let owner_id = uuid::Uuid::new_v4().to_string(); + let owner_id_for_update = owner_id.clone(); + let result = state + .store + .update_message_cas::( + credential.object_id(), + credential.get_resource_version(), + move |current| { + current.refresh_lease = Some(DelegatedIdentityRefreshLease { + owner_id: owner_id_for_update.clone(), + expires_at_ms: now.saturating_add(REFRESH_LEASE_DURATION_MS), + }); + }, + ) + .await; + match result { + Ok(credential) => Ok(RefreshLeaseAttempt::Acquired { + credential: Box::new(credential), + owner_id, + }), + Err(PersistenceError::Conflict { .. }) => Ok(RefreshLeaseAttempt::Held), + Err(error) => Err(crate::grpc::persistence_error_to_status( + error, + "acquire delegated credential refresh lease", + )), + } +} + +async fn refresh_with_owned_lease( + state: &Arc, + credential: DelegatedIdentityCredential, + owner_id: &str, +) -> Result { + let credential_id = credential.object_id().to_string(); + let renewal = async { + let mut ticker = tokio::time::interval_at( + tokio::time::Instant::now() + REFRESH_LEASE_RENEW_INTERVAL, + REFRESH_LEASE_RENEW_INTERVAL, + ); + ticker.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Delay); + loop { + ticker.tick().await; + if let Err(status) = renew_refresh_lease(state, &credential_id, owner_id).await { + break Err::<(), Status>(status); + } + } + }; + refresh_with_owned_lease_and_renewal(state, credential, owner_id, renewal).await +} + +async fn refresh_with_owned_lease_and_renewal( + state: &Arc, + credential: DelegatedIdentityCredential, + owner_id: &str, + renewal: impl Future>, +) -> Result { + let (failure_tx, failure_rx) = tokio::sync::watch::channel(None); + let mut operation = Box::pin(refresh_with_owned_lease_inner( + state, + credential.clone(), + owner_id, + &failure_rx, + )); + let mut renewal = Box::pin(renewal); + // No detached task: cancellation drops renewal along with the operation. + let result = tokio::select! { + result = &mut operation => result, + result = &mut renewal => { + if let Err(status) = result { + failure_tx.send_replace(Some(status)); + } + // Do not cancel a potentially partial credential write. The operation + // observes this failure before exchange/commit and compensates staging. + operation.await + } + }; + if result.is_err() { + release_refresh_lease_best_effort(state, credential.object_id(), owner_id).await; + } + let mut resolved = result?; + // Commit has cleared the lease; backend cleanup no longer needs renewal. + resolved.credential = + cleanup_pending_secret_deletions_best_effort(state, resolved.credential).await; + Ok(resolved) +} + +async fn renew_refresh_lease( + state: &Arc, + credential_id: &str, + owner_id: &str, +) -> Result<(), Status> { + tokio::time::timeout(REFRESH_STEP_TIMEOUT, async { + loop { + let current = state + .store + .get_message::(credential_id) + .await + .map_err(|error| { + crate::grpc::persistence_error_to_status( + error, + "reload delegated credential for lease renewal", + ) + })? + .ok_or_else(|| { + Status::failed_precondition("delegated identity credential is missing") + })?; + ensure_delegated_credential_usable(¤t)?; + ensure_refresh_lease_owner(¤t, owner_id)?; + let mut renewal_error = None; + let result = state + .store + .update_message_cas::( + credential_id, + current.get_resource_version(), + |credential| { + // The CAS helper reloads before running this closure; do + // not revive a lease that expired during that read. + if let Err(status) = ensure_refresh_lease_owner(credential, owner_id) { + renewal_error = Some(status); + return; + } + // Only extend this owner's lease; merge all other concurrent fields. + if let Some(lease) = credential.refresh_lease.as_mut() { + lease.expires_at_ms = + current_time_ms().saturating_add(REFRESH_LEASE_DURATION_MS); + } + }, + ) + .await; + match result { + Ok(renewed) => { + if let Some(status) = renewal_error { + return Err(status); + } + ensure_refresh_lease_owner(&renewed, owner_id)?; + // Reserve at least two request timeouts, including margin + // for a delayed CAS response, before allowing exchange. + if renewed.refresh_lease.as_ref().is_none_or(|lease| { + lease.expires_at_ms.saturating_sub(current_time_ms()) + < REFRESH_MIN_LEASE_LIFETIME_MS + }) { + return Err(Status::deadline_exceeded( + "delegated credential refresh lease has insufficient lifetime", + )); + } + return Ok(()); + } + Err(PersistenceError::Conflict { .. }) => {} + Err(error) => { + return Err(crate::grpc::persistence_error_to_status( + error, + "renew delegated credential refresh lease", + )); + } + } + } + }) + .await + .map_err(|_| { + Status::deadline_exceeded("delegated credential refresh lease renewal timed out") + })? +} + +fn ensure_refresh_renewal_healthy( + failure: &tokio::sync::watch::Receiver>, +) -> Result<(), Status> { + failure + .borrow() + .as_ref() + .map_or(Ok(()), |status| Err(status.clone())) +} + +async fn refresh_with_owned_lease_inner( + state: &Arc, + credential: DelegatedIdentityCredential, + owner_id: &str, + renewal_failure: &tokio::sync::watch::Receiver>, +) -> Result { + ensure_refresh_lease_owner(&credential, owner_id)?; + let material = tokio::time::timeout( + REFRESH_STEP_TIMEOUT, + resolve_delegated_secret_material(state, &credential), + ) + .await + .map_err(|_| Status::deadline_exceeded("resolve delegated credential material timed out"))??; + let client = delegated_identity_http_client()?; + let token_endpoint = discover_token_endpoint(client, &credential.issuer).await?; + let scopes = credential + .scopes + .split_whitespace() + .filter(|scope| !scope.is_empty()) + .map(ToString::to_string) + .collect::>(); + ensure_refresh_renewal_healthy(renewal_failure)?; + // Reload and renew immediately before the irreversible exchange. A full + // lease leaves margin beyond the HTTP client's 30-second request timeout. + renew_refresh_lease(state, credential.object_id(), owner_id).await?; + ensure_refresh_renewal_healthy(renewal_failure)?; + let refreshed = openshell_core::oauth::post_oauth_refresh_token( + client, + &token_endpoint, + &openshell_core::oauth::RefreshTokenParams { + refresh_token: &material.refresh_token, + client_id: &credential.client_id, + scopes: &scopes, + allow_insecure_http: delegated_refresh_allows_insecure_http( + &credential.issuer, + &token_endpoint, + ), + }, + ) + .await + .map_err(|e| Status::failed_precondition(delegated_refresh_error_message(&e.to_string())))?; + let expires_at_ms = tokio::time::timeout( + REFRESH_STEP_TIMEOUT, + validate_delegated_access_token_subject_value( + state, + &refreshed.access_token, + &credential.principal_subject, + ), + ) + .await + .map_err(|_| Status::deadline_exceeded("delegated access token validation timed out"))??; + ensure_refresh_renewal_healthy(renewal_failure)?; + let refreshed_at_ms = current_time_ms(); + let refreshed_access_token = refreshed.access_token; + let mut replacement_material = HashMap::from([( + ACCESS_TOKEN_MATERIAL_KEY.to_string(), + refreshed_access_token.clone(), + )]); + if let Some(refresh_token) = refreshed.refresh_token { + replacement_material.insert(REFRESH_TOKEN_MATERIAL_KEY.to_string(), refresh_token); + } + let credential_id = credential.object_id().to_string(); + let staged_handles = + store_staged_secret_material(state, &credential_id, &replacement_material).await?; + let updated = match commit_refreshed_material( + state, + &credential_id, + owner_id, + &staged_handles, + expires_at_ms, + refreshed_at_ms, + renewal_failure, + ) + .await + { + Ok(updated) => updated, + Err(status) => { + cleanup_staged_secret_material(state, &credential_id, &staged_handles).await; + return Err(status); + } + }; + Ok(ResolvedDelegatedAccessToken { + credential: updated, + access_token: refreshed_access_token, + }) +} + +async fn commit_refreshed_material( + state: &Arc, + credential_id: &str, + owner_id: &str, + staged_handles: &HashMap, + expires_at_ms: i64, + refreshed_at_ms: i64, + renewal_failure: &tokio::sync::watch::Receiver>, +) -> Result { + let deadline = tokio::time::Instant::now() + REFRESH_STEP_TIMEOUT; + loop { + if tokio::time::Instant::now() >= deadline { + return Err(Status::deadline_exceeded( + "delegated credential refresh commit retries timed out", + )); + } + let current = tokio::time::timeout_at( + deadline, + state + .store + .get_message::(credential_id), + ) + .await + .map_err(|_| { + Status::deadline_exceeded("reload delegated credential before refresh commit timed out") + })? + .map_err(|error| { + Status::internal(format!( + "reload delegated credential before committing refresh failed: {error}" + )) + })? + .ok_or_else(|| Status::failed_precondition("delegated identity credential was deleted"))?; + ensure_delegated_credential_usable(¤t)?; + ensure_refresh_renewal_healthy(renewal_failure)?; + ensure_refresh_lease_owner(¤t, owner_id)?; + let version = current.get_resource_version(); + let mut updated = current; + for (material_key, handle) in staged_handles { + if let Some(previous) = updated + .secret_material_handles + .insert(material_key.clone(), handle.clone()) + { + updated + .pending_secret_deletions + .push(StoredRefreshMaterialDeletion { + material_key: material_key.clone(), + handle: Some(previous), + }); + } + } + let publication = secret_operations::publication(state, staged_handles).await?; + ensure_refresh_renewal_healthy(renewal_failure)?; + ensure_refresh_lease_owner(&updated, owner_id)?; + updated.access_token_expires_at_ms = expires_at_ms; + updated.last_refresh_at_ms = refreshed_at_ms; + updated.refresh_lease = None; + let write = crate::persistence::AtomicObjectWrite::message(&updated, Some(version)) + .map_err(|error| { + crate::grpc::persistence_error_to_status(error, "encode refreshed credential") + })?; + let result = state.store.put_objects_atomic(&[write, publication]).await; + match result { + Ok(()) => { + if let Some(metadata) = updated.metadata.as_mut() { + metadata.resource_version = version + 1; + } + return Ok(updated); + } + Err(PersistenceError::Conflict { .. }) => {} + Err(error) => { + return Err(crate::grpc::persistence_error_to_status( + error, + "refresh delegated credential", + )); + } + } + } +} + +fn ensure_refresh_lease_owner( + credential: &DelegatedIdentityCredential, + owner_id: &str, +) -> Result<(), Status> { + if credential + .refresh_lease + .as_ref() + .is_some_and(|lease| lease.owner_id == owner_id && lease.expires_at_ms > current_time_ms()) + { + return Ok(()); + } + Err(Status::aborted( + "delegated credential refresh lease ownership changed or expired", + )) +} + +async fn release_refresh_lease_best_effort( + state: &Arc, + credential_id: &str, + owner_id: &str, +) { + loop { + let current = match state + .store + .get_message::(credential_id) + .await + { + Ok(Some(current)) => current, + Ok(None) => return, + Err(error) => { + tracing::warn!( + credential_id, + error = %error, + "failed to reload delegated credential while releasing refresh lease" + ); + return; + } + }; + if current + .refresh_lease + .as_ref() + .is_none_or(|lease| lease.owner_id != owner_id) + { + return; + } + let result = state + .store + .update_message_cas::( + credential_id, + current.get_resource_version(), + |credential| credential.refresh_lease = None, + ) + .await; + match result { + Ok(_) => return, + Err(PersistenceError::Conflict { .. }) => {} + Err(error) => { + tracing::warn!( + credential_id, + error = %error, + "failed to release delegated credential refresh lease" + ); + return; + } + } + } +} + +fn delegated_identity_http_client() -> Result<&'static reqwest::Client, Status> { + DELEGATED_IDENTITY_HTTP_CLIENT + .as_ref() + .map_err(|err| Status::internal(err.clone())) +} + +fn delegated_refresh_allows_insecure_http(issuer: &str, token_endpoint: &str) -> bool { + let Ok(issuer) = reqwest::Url::parse(issuer) else { + return false; + }; + let Ok(token_endpoint) = reqwest::Url::parse(token_endpoint) else { + return false; + }; + issuer.scheme() == "http" + && token_endpoint.scheme() == "http" + && issuer.host_str() == token_endpoint.host_str() + && issuer.port_or_known_default() == token_endpoint.port_or_known_default() +} + +fn delegated_refresh_error_message(error: &str) -> String { + let mut message = format!("delegated credential refresh failed: {error}"); + if inactive_refresh_token_error(error) { + message.push_str( + "; the stored delegated identity refresh token is no longer active. \ + Retry sandbox create or delegated-identity extend to authorize a new delegated grant.", + ); + } + message +} + +fn delegated_refresh_requires_reauthorization(status: &Status) -> bool { + status.code() == tonic::Code::FailedPrecondition + && (status.message().contains("no longer active") + || status.message().contains("authorization is incomplete")) +} + +fn inactive_refresh_token_error(error: &str) -> bool { + let error = error.to_ascii_lowercase(); + error.contains("invalid_grant") +} + +#[derive(Debug, Deserialize)] +struct OidcDiscovery { + issuer: String, + token_endpoint: String, +} + +async fn discover_token_endpoint(client: &reqwest::Client, issuer: &str) -> Result { + let normalized = issuer.trim_end_matches('/'); + let url = format!("{normalized}/.well-known/openid-configuration"); + let response = client + .get(url) + .send() + .await + .map_err(|e| Status::failed_precondition(format!("OIDC discovery failed: {e}")))?; + if response.status().is_redirection() { + return Err(Status::failed_precondition(format!( + "OIDC discovery failed: redirect response {} is not allowed", + response.status() + ))); + } + let discovery = response + .error_for_status() + .map_err(|e| Status::failed_precondition(format!("OIDC discovery failed: {e}")))? + .json::() + .await + .map_err(|e| Status::failed_precondition(format!("OIDC discovery parse failed: {e}")))?; + if discovery.issuer.trim_end_matches('/') != normalized { + return Err(Status::failed_precondition( + "OIDC discovery issuer does not match delegated credential issuer", + )); + } + Ok(discovery.token_endpoint) +} + +#[cfg(test)] +mod tests { + use super::{ + delegated_credential_id, delegated_credential_status_fields, delegated_credential_summary, + delegated_credential_upsert_condition, delegated_refresh_allows_insecure_http, + delegated_refresh_error_message, ensure_delegated_credential_usable, + ensure_delegated_token_subject_matches, ensure_expected_resource_version, + sandbox_delegated_identity_record_id, timestamp_from_positive_millis, timestamp_millis, + }; + use crate::grpc::test_support::{authed_request, test_server_state}; + use crate::persistence::{ObjectType, Store, WriteCondition, current_time_ms}; + use crate::sandbox_index::SandboxIndex; + use crate::sandbox_watch::SandboxWatchBus; + use crate::storage_proto::{ + DelegatedIdentityCredential, DelegatedIdentityRefreshLease, SandboxDelegatedIdentityRecord, + StoredRefreshMaterialDeletion, + }; + use crate::supervisor_session::SupervisorSessionRegistry; + use crate::tracing_bus::TracingLogBus; + use openshell_core::proto::datamodel::v1::ObjectMeta; + use openshell_core::proto::{ + AuthorizeDelegatedIdentityRequest, DelegatedIdentityAuthorizationGrant, + DelegatedIdentityRequest, DeleteDelegatedIdentityCredentialRequest, + ExtendSandboxDelegatedIdentityRequest, GetDelegatedIdentityAuthorizationStatusRequest, + GetSandboxDelegatedIdentityStatusRequest, ListDelegatedIdentityCredentialsRequest, + RevokeDelegatedIdentityCredentialRequest, Sandbox, SandboxDelegatedIdentity, SandboxSpec, + SandboxStatus, workspace_selector, + }; + use openshell_core::{Config, GetResourceVersion, ObjectId, OidcConfig}; + use prost::Message as _; + use std::collections::HashMap; + use std::sync::{Arc, LazyLock}; + use tonic::Code; + use tonic::Request; + + const TEST_KID: &str = "test-signing-key"; + const TEST_AUDIENCE: &str = "openshell-cli"; + + #[tokio::test] + async fn sandbox_delegation_is_atomic_and_fences_credential_deletion() { + let state = test_server_state().await; + let credential = put_test_credential(&state, "atomic-credential").await; + let version = credential.get_resource_version(); + let sandbox = Sandbox { + metadata: Some(ObjectMeta { + id: "atomic-sandbox".into(), + name: "atomic-sandbox".into(), + workspace: "default".into(), + ..Default::default() + }), + ..Default::default() + }; + let prepared = super::PreparedSandboxDelegatedIdentity { + credential, + record: SandboxDelegatedIdentityRecord { + metadata: Some(ObjectMeta { + id: sandbox_delegated_identity_record_id("atomic-sandbox"), + name: sandbox_delegated_identity_record_id("atomic-sandbox"), + workspace: "default".into(), + ..Default::default() + }), + sandbox_id: "atomic-sandbox".into(), + delegated_identity: Some(SandboxDelegatedIdentity { + credential_id: "atomic-credential".into(), + principal_subject: "user-1".into(), + delegation_expiration_time: timestamp_from_positive_millis( + current_time_ms() + 60_000, + ), + withdrawal_time: None, + }), + }, + }; + super::store_sandbox_with_delegation(&state.store, &sandbox, Some(&prepared)) + .await + .unwrap(); + assert!( + state + .store + .get_message::("atomic-sandbox") + .await + .unwrap() + .is_some() + ); + assert!( + state + .store + .get_message::(prepared.record.object_id()) + .await + .unwrap() + .is_some() + ); + // A second create prepared from the same credential version reloads + // and revalidates that credential after the first create touches it. + let mut second_sandbox = sandbox.clone(); + let second_metadata = second_sandbox.metadata.as_mut().unwrap(); + second_metadata.id = "atomic-sandbox-2".into(); + second_metadata.name = "atomic-sandbox-2".into(); + let mut second_record = prepared.record.clone(); + second_record.sandbox_id = second_sandbox.object_id().into(); + let second_record_id = sandbox_delegated_identity_record_id(second_sandbox.object_id()); + let second_record_metadata = second_record.metadata.as_mut().unwrap(); + second_record_metadata.id = second_record_id.clone(); + second_record_metadata.name = second_record_id.clone(); + let second_prepared = super::PreparedSandboxDelegatedIdentity { + credential: prepared.credential.clone(), + record: second_record, + }; + super::store_sandbox_with_delegation(&state.store, &second_sandbox, Some(&second_prepared)) + .await + .unwrap(); + assert!( + state + .store + .get_message::(&second_record_id) + .await + .unwrap() + .is_some() + ); + // A delete that scanned references before creation cannot tombstone the + // old credential version after the atomic creation commits. + let error = super::tombstone_and_delete_credential(&state, "atomic-credential", version) + .await + .unwrap_err(); + assert_eq!(error.code(), Code::Aborted); + assert!( + super::ensure_no_active_delegation_references(&state, "atomic-credential") + .await + .is_err() + ); + + // Duplicate sandbox insertion rolls back the credential touch too. + let mut prepared = prepared; + prepared.credential = state + .store + .get_message::("atomic-credential") + .await + .unwrap() + .unwrap(); + let latest_version = prepared.credential.get_resource_version(); + assert!( + super::store_sandbox_with_delegation(&state.store, &sandbox, Some(&prepared)) + .await + .is_err() + ); + assert_eq!( + state + .store + .get_message::("atomic-credential") + .await + .unwrap() + .unwrap() + .get_resource_version(), + latest_version + ); + } + + #[tokio::test] + async fn credential_revocation_prevents_prepared_sandbox_and_delegation_creation() { + let state = test_server_state().await; + let credential = put_test_credential(&state, "revoked-before-create").await; + let sandbox = Sandbox { + metadata: Some(ObjectMeta { + id: "uncommitted-sandbox".into(), + name: "uncommitted-sandbox".into(), + workspace: "default".into(), + ..Default::default() + }), + ..Default::default() + }; + let record_id = sandbox_delegated_identity_record_id(sandbox.object_id()); + let prepared = super::PreparedSandboxDelegatedIdentity { + credential: credential.clone(), + record: SandboxDelegatedIdentityRecord { + metadata: Some(ObjectMeta { + id: record_id.clone(), + name: record_id.clone(), + workspace: "default".into(), + ..Default::default() + }), + sandbox_id: sandbox.object_id().into(), + delegated_identity: None, + }, + }; + state + .store + .update_message_cas::( + credential.object_id(), + credential.get_resource_version(), + |value| value.revoked_at_ms = current_time_ms(), + ) + .await + .unwrap(); + assert!( + super::store_sandbox_with_delegation(&state.store, &sandbox, Some(&prepared)) + .await + .is_err() + ); + assert!( + state + .store + .get_message::(sandbox.object_id()) + .await + .unwrap() + .is_none() + ); + assert!( + state + .store + .get_message::(&record_id) + .await + .unwrap() + .is_none() + ); + } + + static TEST_RSA_KEY: LazyLock = LazyLock::new(TestRsaKey::generate); + + struct TestRsaKey { + private_pem: String, + modulus_b64: String, + exponent_b64: String, + } + + impl TestRsaKey { + fn generate() -> Self { + use base64::Engine as _; + use rsa::pkcs1::EncodeRsaPrivateKey as _; + use rsa::traits::PublicKeyParts as _; + + let private = rsa::RsaPrivateKey::new(&mut rsa::rand_core::OsRng, 2048) + .expect("generate RSA test key"); + let b64 = base64::engine::general_purpose::URL_SAFE_NO_PAD; + Self { + private_pem: private + .to_pkcs1_pem(rsa::pkcs1::LineEnding::LF) + .expect("encode RSA private key as PEM") + .to_string(), + modulus_b64: b64.encode(private.n().to_bytes_be()), + exponent_b64: b64.encode(private.e().to_bytes_be()), + } + } + } + + #[tokio::test] + async fn delegation_duration_is_bounded_and_anchored_to_gateway_clock() { + let state = test_server_state().await; + let max_duration_ms = i64::try_from(state.config.max_delegated_identity_duration_secs) + .expect("test maximum fits in i64") + .saturating_mul(1000); + let before = current_time_ms(); + let maximum = openshell_core::time::duration_from_std(std::time::Duration::from_millis( + u64::try_from(max_duration_ms).unwrap(), + )) + .unwrap(); + + let delegated_until_ms = super::delegated_until_from_duration(&state, Some(&maximum)) + .expect("the exact gateway maximum should be accepted"); + + let after = current_time_ms(); + assert!(delegated_until_ms >= before.saturating_add(max_duration_ms)); + assert!(delegated_until_ms <= after.saturating_add(max_duration_ms)); + + let zero_duration = prost_types::Duration::default(); + let zero = super::delegated_until_from_duration(&state, Some(&zero_duration)) + .expect_err("a zero duration should be rejected"); + assert_eq!(zero.code(), Code::InvalidArgument); + + let too_long_duration = + openshell_core::time::duration_from_std(std::time::Duration::from_millis( + u64::try_from(max_duration_ms.saturating_add(1)).unwrap(), + )) + .unwrap(); + let too_long = super::delegated_until_from_duration(&state, Some(&too_long_duration)) + .expect_err("a duration over the gateway maximum should be rejected"); + assert_eq!(too_long.code(), Code::FailedPrecondition); + } + + #[test] + fn delegated_refresh_allows_insecure_http_only_for_same_http_origin() { + assert!(delegated_refresh_allows_insecure_http( + "http://keycloak.127.0.0.1.sslip.io:9090/realms/openshell", + "http://keycloak.127.0.0.1.sslip.io:9090/realms/openshell/protocol/openid-connect/token", + )); + assert!(!delegated_refresh_allows_insecure_http( + "https://idp.example.com/realms/openshell", + "http://idp.example.com/realms/openshell/protocol/openid-connect/token", + )); + assert!(!delegated_refresh_allows_insecure_http( + "http://idp.example.com/realms/openshell", + "http://metadata.internal/token", + )); + assert!(!delegated_refresh_allows_insecure_http( + "not an issuer url", + "http://idp.example.com/token", + )); + } + + #[test] + fn delegated_refresh_error_message_explains_inactive_session_recovery() { + let message = delegated_refresh_error_message( + "token grant failed with status 400 Bad Request: error=invalid_grant; error_description=Session not active", + ); + + assert!(message.contains("delegated credential refresh failed")); + assert!(message.contains("stored delegated identity refresh token is no longer active")); + assert!(message.contains("authorize a new delegated grant")); + } + + #[tokio::test] + async fn delegated_oidc_discovery_does_not_follow_redirects() { + use wiremock::matchers::{method, path}; + use wiremock::{Mock, ResponseTemplate}; + + let redirect_target = wiremock::MockServer::start().await; + Mock::given(method("GET")) + .and(path("/.well-known/openid-configuration")) + .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({ + "issuer": redirect_target.uri(), + "token_endpoint": format!("{}/token", redirect_target.uri()), + }))) + .mount(&redirect_target) + .await; + + let issuer = wiremock::MockServer::start().await; + Mock::given(method("GET")) + .and(path("/.well-known/openid-configuration")) + .respond_with(ResponseTemplate::new(307).insert_header( + "Location", + format!("{}/.well-known/openid-configuration", redirect_target.uri()), + )) + .mount(&issuer) + .await; + + let status = super::discover_token_endpoint( + super::delegated_identity_http_client().expect("delegated identity HTTP client"), + &issuer.uri(), + ) + .await + .expect_err("OIDC discovery redirect must be rejected"); + + assert_eq!(status.code(), Code::FailedPrecondition); + assert!(status.message().contains("redirect response 307")); + assert!(status.message().contains("not allowed")); + assert!( + redirect_target + .received_requests() + .await + .expect("redirect target records requests") + .is_empty(), + "discovery redirect target must not receive a request" + ); + } + + #[tokio::test] + async fn delegated_refresh_does_not_replay_token_body_across_redirects() { + use wiremock::matchers::{method, path}; + use wiremock::{Mock, ResponseTemplate}; + + let redirect_target = wiremock::MockServer::start().await; + Mock::given(method("POST")) + .and(path("/token")) + .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({ + "access_token": "unexpected-token", + "token_type": "Bearer", + "expires_in": 3600, + }))) + .mount(&redirect_target) + .await; + + let token_server = wiremock::MockServer::start().await; + Mock::given(method("POST")) + .and(path("/token")) + .respond_with( + ResponseTemplate::new(308) + .insert_header("Location", format!("{}/token", redirect_target.uri())), + ) + .mount(&token_server) + .await; + + let error = openshell_core::oauth::post_oauth_refresh_token( + super::delegated_identity_http_client().expect("delegated identity HTTP client"), + &format!("{}/token", token_server.uri()), + &openshell_core::oauth::RefreshTokenParams { + refresh_token: "refresh-secret", + client_id: "openshell-cli", + scopes: &[], + allow_insecure_http: true, + }, + ) + .await + .expect_err("refresh redirect must be rejected"); + + let message = error.to_string(); + assert!(message.contains("308 Permanent Redirect")); + assert!(!message.contains("refresh-secret")); + assert!( + redirect_target + .received_requests() + .await + .expect("redirect target records requests") + .is_empty(), + "refresh redirect target must not receive the token form" + ); + } + + #[tokio::test] + async fn delegated_http_client_bypasses_ambient_proxy() { + use wiremock::matchers::{method, path}; + use wiremock::{Mock, ResponseTemplate}; + + const CHILD_ENV: &str = "OPENSHELL_TEST_DELEGATED_HTTP_PROXY_CHILD"; + const ISSUER_ENV: &str = "OPENSHELL_TEST_DELEGATED_HTTP_DIRECT_ISSUER"; + + if std::env::var_os(CHILD_ENV).is_some() { + let issuer = std::env::var(ISSUER_ENV).expect("direct issuer is set for child"); + let token_endpoint = super::discover_token_endpoint( + super::delegated_identity_http_client().expect("delegated identity HTTP client"), + &issuer, + ) + .await + .expect("delegated client should bypass ambient proxy"); + assert_eq!(token_endpoint, format!("{issuer}/token")); + return; + } + + let direct = wiremock::MockServer::start().await; + let issuer = direct.uri(); + Mock::given(method("GET")) + .and(path("/.well-known/openid-configuration")) + .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({ + "issuer": issuer, + "token_endpoint": format!("{issuer}/token"), + }))) + .mount(&direct) + .await; + + let proxy = wiremock::MockServer::start().await; + Mock::given(wiremock::matchers::any()) + .respond_with(ResponseTemplate::new(502)) + .mount(&proxy) + .await; + + let executable = std::env::current_exe().expect("current test executable"); + let output = tokio::process::Command::new(executable) + .arg("--exact") + .arg("delegated_identity::tests::delegated_http_client_bypasses_ambient_proxy") + .arg("--nocapture") + .env(CHILD_ENV, "1") + .env(ISSUER_ENV, &issuer) + .env("HTTP_PROXY", proxy.uri()) + .env("HTTPS_PROXY", proxy.uri()) + .env("ALL_PROXY", proxy.uri()) + .env("http_proxy", proxy.uri()) + .env("https_proxy", proxy.uri()) + .env("all_proxy", proxy.uri()) + .env("NO_PROXY", "") + .env("no_proxy", "") + .output() + .await + .expect("run delegated HTTP proxy child test"); + + assert!( + output.status.success(), + "child test failed:\nstdout:\n{}\nstderr:\n{}", + String::from_utf8_lossy(&output.stdout), + String::from_utf8_lossy(&output.stderr) + ); + assert_eq!( + direct + .received_requests() + .await + .expect("direct server records requests") + .len(), + 1, + "delegated client should connect directly to the issuer" + ); + assert!( + proxy + .received_requests() + .await + .expect("proxy records requests") + .is_empty(), + "ambient proxy must not receive delegated OIDC traffic" + ); + } + + #[test] + fn revoked_delegated_credential_is_not_usable_without_reauthorization() { + let active = DelegatedIdentityCredential { + revoked_at_ms: 0, + ..Default::default() + }; + ensure_delegated_credential_usable(&active).expect("active credential is reusable"); + + let revoked = DelegatedIdentityCredential { + revoked_at_ms: 42, + ..Default::default() + }; + let status = ensure_delegated_credential_usable(&revoked) + .expect_err("revoked credential must stay revoked"); + + assert_eq!(status.code(), Code::FailedPrecondition); + assert!(status.message().contains("credential is revoked")); + } + + #[test] + fn delegated_access_token_subject_must_match_authenticated_caller() { + ensure_delegated_token_subject_matches("user-a", "user-a") + .expect("matching subject should be accepted"); + + let status = ensure_delegated_token_subject_matches("user-b", "user-a") + .expect_err("mismatched subject must be rejected"); + + assert_eq!(status.code(), Code::PermissionDenied); + assert!(status.message().contains("subject must match")); + } + + #[test] + fn delegated_credential_resource_version_is_enforced_when_requested() { + let credential = DelegatedIdentityCredential { + metadata: Some(ObjectMeta { + id: "delegated-identity-test".to_string(), + name: "delegated-identity-test".to_string(), + resource_version: 7, + workspace: String::new(), + labels: HashMap::new(), + annotations: HashMap::new(), + ..Default::default() + }), + ..Default::default() + }; + + ensure_expected_resource_version(&credential, 0) + .expect("zero should use the current resource version"); + ensure_expected_resource_version(&credential, 7) + .expect("matching resource version should pass"); + let status = ensure_expected_resource_version(&credential, 42) + .expect_err("mismatched resource version should fail"); + assert_eq!(status.code(), Code::Aborted); + } + + #[test] + fn admin_credential_response_uses_non_secret_summary() { + let credential = DelegatedIdentityCredential { + issuer: "https://issuer.example.com".to_string(), + client_id: "openshell-cli".to_string(), + principal_subject: "user-1".to_string(), + secret_material_handles: HashMap::from([ + ( + super::REFRESH_TOKEN_MATERIAL_KEY.to_string(), + openshell_core::proto::CredentialHandle { + driver: "test-static".to_string(), + handle: "opaque-refresh-handle".to_string(), + ..Default::default() + }, + ), + ( + super::ACCESS_TOKEN_MATERIAL_KEY.to_string(), + openshell_core::proto::CredentialHandle { + driver: "test-static".to_string(), + handle: "opaque-access-handle".to_string(), + ..Default::default() + }, + ), + ]), + access_token_expires_at_ms: 123, + scopes: "openid profile".to_string(), + audience: "api://resource".to_string(), + last_refresh_at_ms: 42, + revoked_at_ms: 0, + ..Default::default() + }; + + let summary = delegated_credential_summary(credential); + + assert!(summary.refresh_token_present); + assert!(summary.access_token_present); + assert_eq!(summary.issuer, "https://issuer.example.com"); + assert_eq!(summary.principal_subject, "user-1"); + assert_eq!( + timestamp_millis(summary.access_token_expiration_time.as_ref()), + 123 + ); + } + + #[test] + fn delegated_credential_upsert_uses_existing_resource_version_for_cas() { + let credential = DelegatedIdentityCredential { + metadata: Some(ObjectMeta { + id: "delegated-identity-test".to_string(), + name: "delegated-identity-test".to_string(), + resource_version: 7, + workspace: String::new(), + labels: HashMap::new(), + annotations: HashMap::new(), + ..Default::default() + }), + ..Default::default() + }; + + assert!(matches!( + delegated_credential_upsert_condition(&credential), + WriteCondition::MatchResourceVersion(7) + )); + } + + #[test] + fn sandbox_status_reports_revoked_backing_credential() { + let credential = DelegatedIdentityCredential { + metadata: Some(ObjectMeta { + id: "delegated-identity-test".to_string(), + name: "delegated-identity-test".to_string(), + resource_version: 7, + workspace: String::new(), + labels: HashMap::new(), + annotations: HashMap::new(), + ..Default::default() + }), + revoked_at_ms: 42, + ..Default::default() + }; + + let (revoked_at_ms, missing) = delegated_credential_status_fields(Some(&credential)); + assert_eq!(revoked_at_ms, 42); + assert!(!missing); + assert_eq!(delegated_credential_status_fields(None), (0, true)); + } + + #[tokio::test] + async fn delegated_refresh_rejects_access_token_for_different_subject() { + let server = wiremock::MockServer::start().await; + mount_test_oidc_issuer(&server).await; + let issuer = server.uri(); + let state = test_server_state_with_oidc(issuer.clone()).await; + let original_access_token = + mint_test_access_token(&issuer, "alice", current_time_secs() + 3600); + let mismatched_access_token = + mint_test_access_token(&issuer, "bob", current_time_secs() + 3600); + let credential = DelegatedIdentityCredential { + metadata: Some(ObjectMeta { + id: "delegated-identity-refresh".to_string(), + name: "delegated-identity-refresh".to_string(), + workspace: String::new(), + labels: HashMap::new(), + annotations: HashMap::new(), + ..Default::default() + }), + issuer: issuer.clone(), + client_id: TEST_AUDIENCE.to_string(), + principal_subject: "alice".to_string(), + access_token_expires_at_ms: current_time_ms() - 1, + scopes: "openid profile".to_string(), + ..Default::default() + }; + let mut credential = credential; + credential.secret_material_handles = super::store_staged_secret_material( + &state, + credential.object_id(), + &HashMap::from([ + ( + super::REFRESH_TOKEN_MATERIAL_KEY.to_string(), + "refresh-token".to_string(), + ), + ( + super::ACCESS_TOKEN_MATERIAL_KEY.to_string(), + original_access_token.clone(), + ), + ]), + ) + .await + .unwrap(); + state.store.put_message(&credential).await.unwrap(); + let credential = state + .store + .get_message::("delegated-identity-refresh") + .await + .unwrap() + .unwrap(); + mount_refresh_token_response(&server, &mismatched_access_token).await; + + let status = super::refresh_if_needed(&state, credential) + .await + .expect_err("mismatched refreshed subject must be rejected"); + + assert_eq!(status.code(), Code::PermissionDenied); + assert!(status.message().contains("subject must match")); + let stored = state + .store + .get_message::("delegated-identity-refresh") + .await + .unwrap() + .unwrap(); + let material = super::resolve_delegated_secret_material(&state, &stored) + .await + .unwrap(); + assert_eq!(material.access_token, original_access_token); + assert_eq!(stored.principal_subject, "alice"); + } + + #[tokio::test] + async fn delegated_refresh_rotates_driver_handles_and_deletes_superseded_material() { + let server = wiremock::MockServer::start().await; + mount_test_oidc_issuer(&server).await; + let issuer = server.uri(); + let state = test_server_state_with_oidc(issuer.clone()).await; + let original_access_token = + mint_test_access_token(&issuer, "alice", current_time_secs() - 1); + let refreshed_access_token = + mint_test_access_token(&issuer, "alice", current_time_secs() + 3600); + let mut credential = DelegatedIdentityCredential { + metadata: Some(ObjectMeta { + id: "delegated-identity-rotate".to_string(), + name: "delegated-identity-rotate".to_string(), + workspace: String::new(), + labels: HashMap::new(), + annotations: HashMap::new(), + ..Default::default() + }), + issuer: issuer.clone(), + client_id: TEST_AUDIENCE.to_string(), + principal_subject: "alice".to_string(), + access_token_expires_at_ms: current_time_ms() - 1, + scopes: "openid profile".to_string(), + ..Default::default() + }; + credential.secret_material_handles = super::store_staged_secret_material( + &state, + credential.object_id(), + &HashMap::from([ + ( + super::REFRESH_TOKEN_MATERIAL_KEY.to_string(), + "original-refresh-token".to_string(), + ), + ( + super::ACCESS_TOKEN_MATERIAL_KEY.to_string(), + original_access_token, + ), + ]), + ) + .await + .unwrap(); + let original_handles = credential.secret_material_handles.clone(); + state.store.put_message(&credential).await.unwrap(); + let credential = state + .store + .get_message::(credential.object_id()) + .await + .unwrap() + .unwrap(); + mount_refresh_token_rotation_response( + &server, + &refreshed_access_token, + "rotated-refresh-token", + ) + .await; + + let resolved = super::refresh_if_needed(&state, credential) + .await + .expect("delegated credential should refresh"); + + assert_eq!(resolved.access_token, refreshed_access_token); + assert!(resolved.credential.pending_secret_deletions.is_empty()); + assert_ne!( + resolved.credential.secret_material_handles, + original_handles + ); + assert_eq!(state.credentials.stored_credential_count(), Some(2)); + let material = super::resolve_delegated_secret_material(&state, &resolved.credential) + .await + .unwrap(); + assert_eq!(material.refresh_token, "rotated-refresh-token"); + assert_eq!(material.access_token, refreshed_access_token); + } + + #[tokio::test] + async fn delegated_refresh_lease_serializes_gateway_replicas() { + use wiremock::matchers::{method, path}; + use wiremock::{Mock, ResponseTemplate}; + + let server = wiremock::MockServer::start().await; + mount_test_oidc_issuer(&server).await; + let issuer = server.uri(); + let state = test_server_state_with_oidc(issuer.clone()).await; + let refreshed_access_token = + mint_test_access_token(&issuer, "alice", current_time_secs() + 3600); + Mock::given(method("POST")) + .and(path("/token")) + .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({ + "access_token": refreshed_access_token.clone(), + "refresh_token": "rotated-refresh-token", + "token_type": "Bearer", + "expires_in": 3600, + }))) + .expect(1) + .mount(&server) + .await; + + let mut credential = DelegatedIdentityCredential { + metadata: Some(ObjectMeta { + id: "delegated-identity-shared-refresh".to_string(), + name: "delegated-identity-shared-refresh".to_string(), + workspace: String::new(), + labels: HashMap::new(), + annotations: HashMap::new(), + ..Default::default() + }), + issuer, + client_id: TEST_AUDIENCE.to_string(), + principal_subject: "alice".to_string(), + access_token_expires_at_ms: current_time_ms() - 1, + scopes: "offline_access".to_string(), + ..Default::default() + }; + credential.secret_material_handles = super::store_staged_secret_material( + &state, + credential.object_id(), + &HashMap::from([ + ( + super::REFRESH_TOKEN_MATERIAL_KEY.to_string(), + "single-use-refresh-token".to_string(), + ), + ( + super::ACCESS_TOKEN_MATERIAL_KEY.to_string(), + "expired-access-token".to_string(), + ), + ]), + ) + .await + .unwrap(); + state.store.put_message(&credential).await.unwrap(); + let credential = state + .store + .get_message::(credential.object_id()) + .await + .unwrap() + .unwrap(); + + let second_compute = crate::compute::new_test_runtime(Arc::clone(&state.store)).await; + let second_state = Arc::new(crate::ServerState::new_with_credentials( + state.config.clone(), + Arc::clone(&state.store), + second_compute, + SandboxIndex::new(), + SandboxWatchBus::new(), + TracingLogBus::new(), + Arc::new(SupervisorSessionRegistry::new()), + state.oidc_cache.clone(), + state.credentials.clone(), + )); + let first_credential = credential.clone(); + let (first, second) = tokio::join!( + super::refresh_if_needed(&state, first_credential), + super::refresh_if_needed(&second_state, credential), + ); + + assert_eq!(first.unwrap().access_token, refreshed_access_token); + assert_eq!(second.unwrap().access_token, refreshed_access_token); + server.verify().await; + } + + #[tokio::test] + async fn refresh_lease_renewal_preserves_latest_credential_fields() { + let state = test_server_state().await; + let credential = put_test_credential(&state, "delegated-lease-renewal").await; + let original_expiry = current_time_ms() + 60_000; + let current = state + .store + .update_message_cas::( + credential.object_id(), + credential.get_resource_version(), + |credential| { + credential.refresh_lease = Some(DelegatedIdentityRefreshLease { + owner_id: "owner-a".to_string(), + expires_at_ms: original_expiry, + }); + credential.last_refresh_at_ms = 1234; + credential + .metadata + .as_mut() + .unwrap() + .annotations + .insert("concurrent-change".to_string(), "preserved".to_string()); + credential + .pending_secret_deletions + .push(StoredRefreshMaterialDeletion::default()); + }, + ) + .await + .unwrap(); + + super::renew_refresh_lease(&state, credential.object_id(), "owner-a") + .await + .expect("live owner should renew"); + let mut renewed = state + .store + .get_message::(credential.object_id()) + .await + .unwrap() + .unwrap(); + assert!(renewed.get_resource_version() > current.get_resource_version()); + assert!(renewed.refresh_lease.as_ref().unwrap().expires_at_ms > original_expiry); + // Apart from version and lease expiry, renewal must leave the record untouched. + renewed.metadata.as_mut().unwrap().resource_version = current.get_resource_version(); + renewed.refresh_lease = current.refresh_lease.clone(); + assert_eq!(renewed, current); + } + + #[tokio::test] + async fn refresh_lease_renewal_cannot_revive_expiry_or_overwrite_takeover() { + let state = test_server_state().await; + let mut credential = put_test_credential(&state, "delegated-lease-lost").await; + for (owner_id, expires_at_ms) in [ + ("owner-a", current_time_ms() - 1), + ("owner-b", current_time_ms() + 120_000), + ] { + credential = state + .store + .update_message_cas::( + credential.object_id(), + credential.get_resource_version(), + |credential| { + credential.refresh_lease = Some(DelegatedIdentityRefreshLease { + owner_id: owner_id.to_string(), + expires_at_ms, + }); + }, + ) + .await + .unwrap(); + let status = super::renew_refresh_lease(&state, credential.object_id(), "owner-a") + .await + .expect_err("expired or replaced lease cannot be renewed"); + assert_eq!(status.code(), Code::Aborted); + let stored = state + .store + .get_message::(credential.object_id()) + .await + .unwrap() + .unwrap(); + assert_eq!(stored, credential); + } + } + + #[tokio::test] + async fn refresh_lease_renewal_rejects_revoked_credentials() { + let state = test_server_state().await; + let credential = put_test_credential(&state, "delegated-lease-revoked").await; + let credential = state + .store + .update_message_cas::( + credential.object_id(), + credential.get_resource_version(), + |credential| { + credential.refresh_lease = Some(DelegatedIdentityRefreshLease { + owner_id: "owner-a".to_string(), + expires_at_ms: current_time_ms() + 120_000, + }); + credential.revoked_at_ms = current_time_ms(); + }, + ) + .await + .unwrap(); + let status = super::renew_refresh_lease(&state, credential.object_id(), "owner-a") + .await + .expect_err("revocation must prevent renewal"); + assert_eq!(status.code(), Code::FailedPrecondition); + assert_eq!( + state + .store + .get_message::(credential.object_id()) + .await + .unwrap() + .unwrap(), + credential + ); + } + + #[tokio::test] + async fn live_refresh_renews_while_staging_and_compensates_renewal_failure() { + for (takeover, renewal_failure) in [(false, false), (true, false), (false, true)] { + let server = wiremock::MockServer::start().await; + mount_test_oidc_issuer(&server).await; + let issuer = server.uri(); + let state = test_server_state_with_oidc(issuer.clone()).await; + let access_token = mint_test_access_token(&issuer, "alice", current_time_secs() + 3600); + mount_refresh_token_rotation_response(&server, &access_token, "rotated-refresh").await; + let credential = + put_test_credential_for_subject(&state, "delegated-live-renewal", "alice").await; + let credential = state + .store + .update_message_cas::( + credential.object_id(), + credential.get_resource_version(), + |credential| { + credential.issuer = issuer.clone(); + credential.client_id = TEST_AUDIENCE.to_string(); + credential.access_token_expires_at_ms = current_time_ms() - 1; + }, + ) + .await + .unwrap(); + let credential_id = credential.object_id().to_string(); + let original_handles = credential.secret_material_handles.clone(); + let super::RefreshLeaseAttempt::Acquired { + credential, + owner_id, + } = super::try_acquire_refresh_lease(&state, &credential) + .await + .unwrap() + else { + panic!("initial owner should acquire lease") + }; + let (store_started, release_store) = state.credentials.gate_next_store(); + let (renew_now_tx, renew_now_rx) = tokio::sync::oneshot::channel(); + let (renewed_tx, renewed_rx) = tokio::sync::oneshot::channel(); + let task_state = Arc::clone(&state); + let task_credential_id = credential_id.clone(); + let refresh = tokio::spawn(async move { + let renewal = async { + renew_now_rx.await.expect("test should trigger renewal"); + let result = if renewal_failure { + Err(tonic::Status::deadline_exceeded("test renewal timeout")) + } else { + super::renew_refresh_lease(&task_state, &task_credential_id, &owner_id) + .await + }; + renewed_tx + .send(result.clone()) + .expect("test should observe renewal"); + result?; + std::future::pending::>().await + }; + super::refresh_with_owned_lease_and_renewal( + &task_state, + *credential, + &owner_id, + renewal, + ) + .await + }); + store_started + .await + .expect("refresh should reach replacement staging"); + let current = state + .store + .get_message::(&credential_id) + .await + .unwrap() + .unwrap(); + let short_expiry = current_time_ms() + 10_000; + let current = state + .store + .update_message_cas::( + &credential_id, + current.get_resource_version(), + |credential| { + let lease = credential.refresh_lease.as_mut().unwrap(); + lease.expires_at_ms = short_expiry; + if takeover { + lease.owner_id = "owner-b".to_string(); + } + credential + .metadata + .as_mut() + .unwrap() + .annotations + .insert("concurrent-change".to_string(), "preserved".to_string()); + }, + ) + .await + .unwrap(); + renew_now_tx + .send(()) + .expect("trigger renewal during staging"); + let renewal_result = renewed_rx.await.expect("renewal should complete"); + if takeover || renewal_failure { + assert_eq!( + renewal_result.unwrap_err().code(), + if takeover { + Code::Aborted + } else { + Code::DeadlineExceeded + } + ); + } else { + // A storage gate and shortened lease exercise renewal without + // sleeping for the production lease duration. + renewal_result.expect("live owner should renew while storage is blocked"); + let renewed = state + .store + .get_message::(&credential_id) + .await + .unwrap() + .unwrap(); + assert!(renewed.refresh_lease.as_ref().unwrap().expires_at_ms > short_expiry); + assert!(matches!( + super::try_acquire_refresh_lease(&state, &renewed) + .await + .unwrap(), + super::RefreshLeaseAttempt::Held + )); + } + release_store.send(()).expect("release replacement storage"); + let result = tokio::time::timeout(std::time::Duration::from_secs(5), refresh) + .await + .expect("refresh should finish") + .unwrap(); + let stored = state + .store + .get_message::(&credential_id) + .await + .unwrap() + .unwrap(); + assert_eq!( + stored + .metadata + .as_ref() + .unwrap() + .annotations + .get("concurrent-change"), + Some(&"preserved".to_string()) + ); + if takeover || renewal_failure { + assert_eq!( + result.unwrap_err().code(), + if takeover { + Code::Aborted + } else { + Code::DeadlineExceeded + } + ); + assert_eq!(stored.secret_material_handles, original_handles); + if takeover { + assert_eq!(stored.refresh_lease, current.refresh_lease); + } else { + assert!(stored.refresh_lease.is_none()); + } + } else { + assert_eq!(result.unwrap().access_token, access_token); + assert!(stored.refresh_lease.is_none()); + assert_ne!(stored.secret_material_handles, original_handles); + } + assert_eq!( + state.credentials.stored_credential_count(), + Some(original_handles.len()) + ); + } + } + + #[tokio::test] + async fn delegated_refresh_recovers_an_expired_lease() { + use wiremock::matchers::{method, path}; + use wiremock::{Mock, ResponseTemplate}; + + let server = wiremock::MockServer::start().await; + mount_test_oidc_issuer(&server).await; + let issuer = server.uri(); + let state = test_server_state_with_oidc(issuer.clone()).await; + let refreshed_access_token = + mint_test_access_token(&issuer, "alice", current_time_secs() + 3600); + Mock::given(method("POST")) + .and(path("/token")) + .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({ + "access_token": refreshed_access_token.clone(), + "refresh_token": "recovered-refresh-token", + "token_type": "Bearer", + "expires_in": 3600, + }))) + .expect(1) + .mount(&server) + .await; + + let mut credential = DelegatedIdentityCredential { + metadata: Some(ObjectMeta { + id: "delegated-identity-expired-lease".to_string(), + name: "delegated-identity-expired-lease".to_string(), + workspace: String::new(), + labels: HashMap::new(), + annotations: HashMap::new(), + ..Default::default() + }), + issuer, + client_id: TEST_AUDIENCE.to_string(), + principal_subject: "alice".to_string(), + access_token_expires_at_ms: current_time_ms() - 1, + scopes: "openid offline_access".to_string(), + refresh_lease: Some(DelegatedIdentityRefreshLease { + owner_id: "crashed-gateway".to_string(), + expires_at_ms: current_time_ms() - 1, + }), + ..Default::default() + }; + credential.secret_material_handles = super::store_staged_secret_material( + &state, + credential.object_id(), + &HashMap::from([ + ( + super::REFRESH_TOKEN_MATERIAL_KEY.to_string(), + "stale-owner-refresh-token".to_string(), + ), + ( + super::ACCESS_TOKEN_MATERIAL_KEY.to_string(), + "expired-access-token".to_string(), + ), + ]), + ) + .await + .unwrap(); + state.store.put_message(&credential).await.unwrap(); + let credential = state + .store + .get_message::(credential.object_id()) + .await + .unwrap() + .unwrap(); + + let resolved = super::refresh_if_needed(&state, credential) + .await + .expect("expired lease should be recoverable"); + + assert_eq!(resolved.access_token, refreshed_access_token); + assert!(resolved.credential.refresh_lease.is_none()); + server.verify().await; + } + + #[tokio::test] + async fn revocation_during_refresh_prevents_credential_repopulation() { + let server = wiremock::MockServer::start().await; + mount_test_oidc_issuer(&server).await; + let issuer = server.uri(); + let state = test_server_state_with_oidc(issuer.clone()).await; + let refreshed_access_token = + mint_test_access_token(&issuer, "alice", current_time_secs() + 3600); + mount_refresh_token_rotation_response( + &server, + &refreshed_access_token, + "rotated-refresh-token", + ) + .await; + let mut credential = DelegatedIdentityCredential { + metadata: Some(ObjectMeta { + id: "delegated-identity-revoke-during-refresh".to_string(), + name: "delegated-identity-revoke-during-refresh".to_string(), + workspace: String::new(), + labels: HashMap::new(), + annotations: HashMap::new(), + ..Default::default() + }), + issuer, + client_id: TEST_AUDIENCE.to_string(), + principal_subject: "alice".to_string(), + access_token_expires_at_ms: current_time_ms() - 1, + scopes: "offline_access".to_string(), + ..Default::default() + }; + credential.secret_material_handles = super::store_staged_secret_material( + &state, + credential.object_id(), + &HashMap::from([ + ( + super::REFRESH_TOKEN_MATERIAL_KEY.to_string(), + "single-use-refresh-token".to_string(), + ), + ( + super::ACCESS_TOKEN_MATERIAL_KEY.to_string(), + "expired-access-token".to_string(), + ), + ]), + ) + .await + .unwrap(); + state.store.put_message(&credential).await.unwrap(); + let credential = state + .store + .get_message::(credential.object_id()) + .await + .unwrap() + .unwrap(); + let credential_id = credential.object_id().to_string(); + let (store_started, release_store) = state.credentials.gate_next_store(); + let task_state = Arc::clone(&state); + let refresh = + tokio::spawn(async move { super::refresh_if_needed(&task_state, credential).await }); + store_started + .await + .expect("replacement material staging should start"); + + let current = state + .store + .get_message::(&credential_id) + .await + .unwrap() + .unwrap(); + state + .store + .update_message_cas::( + &credential_id, + current.get_resource_version(), + |credential| { + credential.revoked_at_ms = current_time_ms(); + super::move_active_handles_to_pending(credential); + }, + ) + .await + .unwrap(); + release_store.send(()).expect("release replacement staging"); + + let status = refresh + .await + .expect("refresh task should finish") + .expect_err("revocation must win over an in-flight refresh"); + assert_eq!(status.code(), Code::FailedPrecondition); + assert!(status.message().contains("revoked")); + let stored = state + .store + .get_message::(&credential_id) + .await + .unwrap() + .unwrap(); + assert!(stored.revoked_at_ms > 0); + assert!(stored.secret_material_handles.is_empty()); + assert!(stored.refresh_lease.is_none()); + } + + #[tokio::test] + async fn delegated_upsert_cas_failure_deletes_staged_driver_material() { + let state = test_server_state().await; + let user = test_user("alice"); + let issuer = "https://issuer.example.com"; + let client_id = "openshell-cli-cas"; + let credential_id = delegated_credential_id(issuer, "alice"); + let request = DelegatedIdentityAuthorizationGrant { + issuer: issuer.to_string(), + client_id: client_id.to_string(), + refresh_token: "cas-refresh-secret".to_string(), + access_token: "cas-access-secret".to_string(), + scopes: "openid".to_string(), + audience: TEST_AUDIENCE.to_string(), + }; + let (store_started, release_store) = state.credentials.gate_next_store(); + let task_state = Arc::clone(&state); + let task = tokio::spawn(async move { + super::upsert_credential(&task_state, &user, request, current_time_ms() + 600_000).await + }); + store_started.await.expect("staged storage should start"); + state + .store + .put_message(&DelegatedIdentityCredential { + metadata: Some(ObjectMeta { + id: credential_id.clone(), + name: credential_id.clone(), + workspace: String::new(), + labels: HashMap::new(), + annotations: HashMap::new(), + ..Default::default() + }), + issuer: issuer.to_string(), + client_id: client_id.to_string(), + principal_subject: "alice".to_string(), + ..Default::default() + }) + .await + .unwrap(); + release_store.send(()).expect("release staged storage"); + + let status = task + .await + .expect("upsert task should finish") + .expect_err("competing create should fail delegated credential CAS"); + + assert_eq!(status.code(), Code::Internal); + assert!(status.message().contains("persist delegated credential")); + assert_eq!(state.credentials.stored_credential_count(), Some(0)); + assert!(!status.message().contains("cas-refresh-secret")); + assert!(!status.message().contains("cas-access-secret")); + } + + #[tokio::test] + async fn delegated_upsert_cas_cleanup_failure_persists_retry_handles() { + let state = test_server_state().await; + let user = test_user("alice"); + let issuer = "https://issuer.example.com"; + let client_id = "openshell-cli-cas-retry"; + let credential_id = delegated_credential_id(issuer, "alice"); + let request = DelegatedIdentityAuthorizationGrant { + issuer: issuer.to_string(), + client_id: client_id.to_string(), + refresh_token: "cas-retry-refresh-secret".to_string(), + access_token: "cas-retry-access-secret".to_string(), + scopes: "openid".to_string(), + audience: TEST_AUDIENCE.to_string(), + }; + let (store_started, release_store) = state.credentials.gate_next_store(); + let task_state = Arc::clone(&state); + let task = tokio::spawn(async move { + super::upsert_credential(&task_state, &user, request, current_time_ms() + 600_000).await + }); + store_started.await.expect("staged storage should start"); + state + .store + .put_message(&DelegatedIdentityCredential { + metadata: Some(ObjectMeta { + id: credential_id.clone(), + name: credential_id.clone(), + workspace: String::new(), + labels: HashMap::new(), + annotations: HashMap::new(), + ..Default::default() + }), + issuer: issuer.to_string(), + client_id: client_id.to_string(), + principal_subject: "alice".to_string(), + ..Default::default() + }) + .await + .unwrap(); + state.credentials.fail_next_delete(); + release_store.send(()).expect("release staged storage"); + task.await + .expect("upsert task should finish") + .expect_err("competing create should fail delegated credential CAS"); + + let stored = state + .store + .get_message::(&credential_id) + .await + .unwrap() + .unwrap(); + // The failed candidate is owned by its operation, independently of + // whichever credential won publication. + assert!(stored.pending_secret_deletions.is_empty()); + assert_eq!(state.credentials.stored_credential_count(), Some(1)); + + super::run_credential_cleanup_tick(&state).await.unwrap(); + let stored = state + .store + .get_message::(&credential_id) + .await + .unwrap() + .unwrap(); + assert!(stored.pending_secret_deletions.is_empty()); + assert_eq!(state.credentials.stored_credential_count(), Some(0)); + } + + #[tokio::test] + async fn delegated_reauthorization_retains_failed_superseded_cleanup_for_retry() { + let state = test_server_state().await; + let user = test_user("alice"); + let request = + |refresh_token: &str, access_token: &str| DelegatedIdentityAuthorizationGrant { + issuer: "https://issuer.example.com".to_string(), + client_id: "openshell-cli-reauthorize".to_string(), + refresh_token: refresh_token.to_string(), + access_token: access_token.to_string(), + scopes: "openid".to_string(), + audience: TEST_AUDIENCE.to_string(), + }; + let original = super::upsert_credential( + &state, + &user, + request("old-refresh", "old-access"), + current_time_ms() + 600_000, + ) + .await + .unwrap(); + let original_handles = original.secret_material_handles; + state.credentials.fail_next_delete(); + + let replacement = super::upsert_credential( + &state, + &user, + request("new-refresh", "new-access"), + current_time_ms() + 600_000, + ) + .await + .expect("replacement should commit before cleanup"); + + assert_eq!(replacement.pending_secret_deletions.len(), 2); + assert_ne!(replacement.secret_material_handles, original_handles); + let material = super::resolve_delegated_secret_material(&state, &replacement) + .await + .unwrap(); + assert_eq!(material.refresh_token, "new-refresh"); + assert_eq!(material.access_token, "new-access"); + + super::run_credential_cleanup_tick(&state).await.unwrap(); + let stored = state + .store + .get_message::(replacement.object_id()) + .await + .unwrap() + .unwrap(); + assert!(stored.pending_secret_deletions.is_empty()); + assert_eq!(state.credentials.stored_credential_count(), Some(2)); + } + + #[tokio::test] + async fn separate_reauthorization_reactivates_a_revoked_user_credential() { + let state = test_server_state().await; + let user = test_user("alice"); + let grant = |refresh_token: &str, access_token: &str| DelegatedIdentityAuthorizationGrant { + issuer: "https://issuer.example.com".to_string(), + client_id: "openshell-cli".to_string(), + refresh_token: refresh_token.to_string(), + access_token: access_token.to_string(), + scopes: "offline_access".to_string(), + audience: TEST_AUDIENCE.to_string(), + }; + let original = super::upsert_credential( + &state, + &user, + grant("old-refresh", "old-access"), + current_time_ms() + 600_000, + ) + .await + .unwrap(); + super::handle_revoke_credential( + &state, + authed_request(RevokeDelegatedIdentityCredentialRequest { + id: original.object_id().to_string(), + expected_resource_version: original.get_resource_version(), + }), + ) + .await + .expect("credential revocation should succeed"); + + let replacement = super::upsert_credential( + &state, + &user, + grant("new-refresh", "new-access"), + current_time_ms() + 600_000, + ) + .await + .expect("a separate grant should replace a revoked credential"); + + assert_eq!(replacement.revoked_at_ms, 0); + let material = super::resolve_delegated_secret_material(&state, &replacement) + .await + .unwrap(); + assert_eq!(material.refresh_token, "new-refresh"); + assert_eq!(material.access_token, "new-access"); + } + + #[tokio::test] + async fn revoke_disables_resolution_while_failed_cleanup_remains_retryable() { + let state = test_server_state().await; + put_test_sandbox(&state, "delegated-revoke").await; + let credential = + put_test_credential_for_subject(&state, "delegated-identity-revoke", "dev-user").await; + put_test_delegation( + &state, + "sandbox-delegated-revoke", + credential.object_id(), + "dev-user", + ) + .await; + state.credentials.fail_next_delete(); + + let response = super::handle_revoke_credential( + &state, + authed_request(RevokeDelegatedIdentityCredentialRequest { + id: credential.object_id().to_string(), + expected_resource_version: credential.get_resource_version(), + }), + ) + .await + .expect("revocation should succeed even when cleanup is deferred") + .into_inner(); + + assert!(response.revoked); + let stored = state + .store + .get_message::(credential.object_id()) + .await + .unwrap() + .unwrap(); + assert!(stored.revoked_at_ms > 0); + assert!(stored.secret_material_handles.is_empty()); + assert_eq!(stored.pending_secret_deletions.len(), 2); + let sandbox = state + .store + .get_message::("sandbox-delegated-revoke") + .await + .unwrap() + .unwrap(); + let status = super::resolve_subject_access_token(&state, &sandbox) + .await + .expect_err("revoked credential must remain unusable"); + assert_eq!(status.code(), Code::FailedPrecondition); + assert!(status.message().contains("revoked")); + + super::run_credential_cleanup_tick(&state) + .await + .expect("cleanup retry should run"); + let stored = state + .store + .get_message::(credential.object_id()) + .await + .unwrap() + .unwrap(); + assert!(stored.pending_secret_deletions.is_empty()); + assert_eq!(state.credentials.stored_credential_count(), Some(0)); + } + + #[tokio::test] + async fn delete_unreferenced_credential_removes_generic_and_driver_records() { + let state = test_server_state().await; + let credential = put_test_credential(&state, "delegated-identity-delete").await; + assert_eq!(state.credentials.stored_credential_count(), Some(2)); + + let response = super::handle_delete_credential( + &state, + authed_request(DeleteDelegatedIdentityCredentialRequest { + id: credential.object_id().to_string(), + expected_resource_version: credential.get_resource_version(), + }), + ) + .await + .expect("unreferenced credential should delete") + .into_inner(); + + assert!(response.deleted); + assert!( + state + .store + .get_message::(credential.object_id()) + .await + .unwrap() + .is_none() + ); + assert_eq!(state.credentials.stored_credential_count(), Some(0)); + } + + #[tokio::test] + async fn delete_credential_waits_for_sandbox_sync_guard() { + let state = test_server_state().await; + let credential = put_test_credential(&state, "delegated-identity-delete-guard").await; + let guard = state + .compute + .mutation_guard(super::MutationScope::Global) + .await + .unwrap(); + let task_state = state.clone(); + let credential_id = credential.object_id().to_string(); + let expected_resource_version = credential.get_resource_version(); + let task = tokio::spawn(async move { + super::handle_delete_credential( + &task_state, + authed_request(DeleteDelegatedIdentityCredentialRequest { + id: credential_id, + expected_resource_version, + }), + ) + .await + }); + + tokio::time::sleep(std::time::Duration::from_millis(20)).await; + assert!( + !task.is_finished(), + "credential deletion should wait for sandbox sync guard" + ); + drop(guard); + + let response = tokio::time::timeout(std::time::Duration::from_secs(5), task) + .await + .expect("delete should finish after guard release") + .expect("join delete task") + .expect("delete should succeed") + .into_inner(); + assert!(response.deleted); + } + + #[tokio::test] + async fn delete_cleanup_failure_leaves_tombstone_for_retry() { + let state = test_server_state().await; + let credential = put_test_credential(&state, "delegated-identity-delete-retry").await; + state.credentials.fail_next_delete(); + + let status = super::handle_delete_credential( + &state, + authed_request(DeleteDelegatedIdentityCredentialRequest { + id: credential.object_id().to_string(), + expected_resource_version: credential.get_resource_version(), + }), + ) + .await + .expect_err("cleanup failure should defer final deletion"); + + assert!(status.message().contains("disabled")); + let tombstone = state + .store + .get_message::(credential.object_id()) + .await + .unwrap() + .unwrap(); + assert!(tombstone.revoked_at_ms > 0); + assert!( + tombstone + .metadata + .as_ref() + .is_some_and(|metadata| metadata.deletion_time.is_some()) + ); + assert!(tombstone.secret_material_handles.is_empty()); + assert_eq!(tombstone.pending_secret_deletions.len(), 2); + + let response = super::handle_delete_credential( + &state, + authed_request(DeleteDelegatedIdentityCredentialRequest { + id: credential.object_id().to_string(), + // A retry can use the pre-tombstone version because the record + // is already disabled and no longer admits state changes. + expected_resource_version: credential.get_resource_version(), + }), + ) + .await + .expect("deletion retry should clean material and remove the tombstone") + .into_inner(); + assert!(response.deleted); + assert_eq!(state.credentials.stored_credential_count(), Some(0)); + } + + #[tokio::test] + async fn delete_rejects_active_sandbox_delegation_reference() { + let state = test_server_state().await; + let credential = + put_test_credential_for_subject(&state, "delegated-identity-referenced", "dev-user") + .await; + put_test_delegation( + &state, + "sandbox-referenced", + credential.object_id(), + "dev-user", + ) + .await; + + let status = super::handle_delete_credential( + &state, + authed_request(DeleteDelegatedIdentityCredentialRequest { + id: credential.object_id().to_string(), + expected_resource_version: credential.get_resource_version(), + }), + ) + .await + .expect_err("active sandbox reference must prevent credential deletion"); + + assert_eq!(status.code(), Code::FailedPrecondition); + assert!(status.message().contains("active sandbox delegation")); + assert_eq!(state.credentials.stored_credential_count(), Some(2)); + } + + #[tokio::test] + async fn delegated_request_expiry_is_derived_from_validated_access_token() { + let server = wiremock::MockServer::start().await; + mount_test_oidc_issuer(&server).await; + let issuer = server.uri(); + let state = test_server_state_with_oidc(issuer.clone()).await; + let exp_secs = current_time_secs() + 1800; + let user = crate::auth::principal::UserPrincipal { + identity: crate::auth::identity::Identity { + subject: "alice".to_string(), + display_name: None, + roles: vec!["openshell-user".to_string()], + scopes: vec![], + provider: crate::auth::identity::IdentityProvider::Oidc, + }, + }; + let refresh_token = "recognizable-delegated-refresh-token".to_string(); + let access_token = mint_test_access_token(&issuer, "alice", exp_secs); + let request = DelegatedIdentityAuthorizationGrant { + issuer: issuer.clone(), + client_id: TEST_AUDIENCE.to_string(), + refresh_token: refresh_token.clone(), + access_token: access_token.clone(), + scopes: "openid profile".to_string(), + audience: TEST_AUDIENCE.to_string(), + }; + + let expires_at_ms = super::validate_authorization_grant(&state, &user, &request) + .await + .expect("delegation request should validate"); + let upserted = super::upsert_credential(&state, &user, request, expires_at_ms) + .await + .expect("credential should persist"); + + assert_eq!(expires_at_ms, exp_secs.saturating_mul(1000)); + assert_eq!( + upserted.access_token_expires_at_ms, + exp_secs.saturating_mul(1000) + ); + assert_eq!(upserted.secret_material_handles.len(), 2); + let record = state + .store + .get( + DelegatedIdentityCredential::object_type(), + upserted.object_id(), + ) + .await + .unwrap() + .unwrap(); + assert!( + !record + .payload + .windows(refresh_token.len()) + .any(|window| window == refresh_token.as_bytes()), + "generic delegated credential payload must not contain the refresh token" + ); + assert!( + !record + .payload + .windows(access_token.len()) + .any(|window| window == access_token.as_bytes()), + "generic delegated credential payload must not contain the access token" + ); + let resolved = super::resolve_delegated_secret_material(&state, &upserted) + .await + .expect("credential handles should resolve through the active driver"); + assert_eq!(resolved.refresh_token, refresh_token); + assert_eq!(resolved.access_token, access_token); + let debug = format!("{upserted:?}"); + assert!(!debug.contains(&resolved.refresh_token)); + assert!(!debug.contains(&resolved.access_token)); + } + + #[tokio::test] + async fn user_authorizes_and_reuses_a_separate_delegated_grant() { + let server = wiremock::MockServer::start().await; + mount_test_oidc_issuer(&server).await; + let issuer = server.uri(); + let state = test_server_state_with_oidc(issuer.clone()).await; + let access_token = mint_test_access_token(&issuer, "dev-user", current_time_secs() + 3600); + + let missing = super::handle_authorization_status( + &state, + authed_request(GetDelegatedIdentityAuthorizationStatusRequest {}), + ) + .await + .expect("missing grant status should succeed") + .into_inner(); + assert!(!missing.usable); + assert!(missing.reauthorization_required); + assert!(missing.reason.contains("missing")); + + let response = super::handle_authorize( + &state, + authed_request(AuthorizeDelegatedIdentityRequest { + grant: Some(DelegatedIdentityAuthorizationGrant { + issuer, + client_id: TEST_AUDIENCE.to_string(), + refresh_token: "delegated-refresh-token".to_string(), + access_token, + scopes: "offline_access".to_string(), + audience: TEST_AUDIENCE.to_string(), + }), + }), + ) + .await + .expect("separate delegated grant should be accepted") + .into_inner(); + + let credential = response.credential.expect("credential summary"); + assert_eq!(credential.principal_subject, "dev-user"); + assert_eq!(credential.scopes, "offline_access"); + let status = super::handle_authorization_status( + &state, + authed_request(GetDelegatedIdentityAuthorizationStatusRequest {}), + ) + .await + .expect("healthy grant status should succeed") + .into_inner(); + assert!(status.usable); + assert!(!status.reauthorization_required); + let summary = status.credential.expect("healthy credential summary"); + assert_eq!(summary.principal_subject, "dev-user"); + + let credential_id = summary.metadata.expect("credential metadata").id; + let current = state + .store + .get_message::(&credential_id) + .await + .unwrap() + .unwrap(); + state + .store + .update_message_cas::( + &credential_id, + current.get_resource_version(), + |credential| { + credential.revoked_at_ms = current_time_ms(); + super::move_active_handles_to_pending(credential); + }, + ) + .await + .unwrap(); + let revoked = super::handle_authorization_status( + &state, + authed_request(GetDelegatedIdentityAuthorizationStatusRequest {}), + ) + .await + .expect("revoked grant status should succeed") + .into_inner(); + assert!(!revoked.usable); + assert!(revoked.reauthorization_required); + assert!(revoked.reason.contains("revoked")); + } + + #[tokio::test] + async fn delegated_grant_subject_must_match_rpc_caller() { + let server = wiremock::MockServer::start().await; + mount_test_oidc_issuer(&server).await; + let issuer = server.uri(); + let state = test_server_state_with_oidc(issuer.clone()).await; + + let status = super::handle_authorize( + &state, + authed_request(AuthorizeDelegatedIdentityRequest { + grant: Some(DelegatedIdentityAuthorizationGrant { + issuer: issuer.clone(), + client_id: TEST_AUDIENCE.to_string(), + refresh_token: "delegated-refresh-token".to_string(), + access_token: mint_test_access_token( + &issuer, + "different-user", + current_time_secs() + 3600, + ), + scopes: "offline_access".to_string(), + audience: TEST_AUDIENCE.to_string(), + }), + }), + ) + .await + .expect_err("a user must not transfer another subject's grant"); + + assert_eq!(status.code(), Code::PermissionDenied); + assert!(status.message().contains("subject must match")); + } + + #[tokio::test] + async fn inactive_delegated_refresh_requests_separate_reauthorization() { + use wiremock::matchers::{method, path}; + use wiremock::{Mock, ResponseTemplate}; + + let server = wiremock::MockServer::start().await; + mount_test_oidc_issuer(&server).await; + let issuer = server.uri(); + let state = test_server_state_with_oidc(issuer.clone()).await; + Mock::given(method("POST")) + .and(path("/token")) + .respond_with(ResponseTemplate::new(400).set_body_json(serde_json::json!({ + "error": "invalid_grant", + "error_description": "Session not active" + }))) + .expect(1) + .mount(&server) + .await; + super::upsert_credential( + &state, + &test_user("dev-user"), + DelegatedIdentityAuthorizationGrant { + issuer, + client_id: TEST_AUDIENCE.to_string(), + refresh_token: "inactive-refresh-token".to_string(), + access_token: "expired-access-token".to_string(), + scopes: "openid offline_access".to_string(), + audience: TEST_AUDIENCE.to_string(), + }, + current_time_ms() - 1, + ) + .await + .expect("store an expired delegated grant"); + + let status = super::handle_authorization_status( + &state, + authed_request(GetDelegatedIdentityAuthorizationStatusRequest {}), + ) + .await + .expect("inactive refresh should become a status result") + .into_inner(); + + assert!(!status.usable); + assert!(status.reauthorization_required); + assert!(status.reason.contains("no longer active")); + assert!(!status.reason.contains("Session not active")); + server.verify().await; + } + + #[tokio::test] + async fn extend_failure_preserves_pre_authorized_credential() { + let server = wiremock::MockServer::start().await; + mount_test_oidc_issuer(&server).await; + let issuer = server.uri(); + let state = test_server_state_with_oidc(issuer.clone()).await; + put_test_sandbox(&state, "delegated").await; + let current_credential_id = delegated_credential_id(&issuer, "dev-user"); + let existing_credential = + put_test_credential_for_subject(&state, ¤t_credential_id, "dev-user").await; + let sandbox_id = "sandbox-delegated"; + let record_id = sandbox_delegated_identity_record_id(sandbox_id); + let malformed_record = SandboxDelegatedIdentityRecord { + metadata: Some(ObjectMeta { + id: String::new(), + name: record_id.clone(), + workspace: "default".to_string(), + labels: HashMap::new(), + annotations: HashMap::new(), + ..Default::default() + }), + sandbox_id: sandbox_id.to_string(), + delegated_identity: Some(SandboxDelegatedIdentity { + credential_id: existing_credential.object_id().to_string(), + principal_subject: "dev-user".to_string(), + delegation_expiration_time: timestamp_from_positive_millis( + current_time_ms() + 600_000, + ), + withdrawal_time: None, + }), + }; + state + .store + .put_scoped( + SandboxDelegatedIdentityRecord::object_type(), + &record_id, + &record_id, + "default", + sandbox_id, + &malformed_record.encode_to_vec(), + None, + ) + .await + .expect("store malformed record under valid lookup key"); + + let request = authed_request(ExtendSandboxDelegatedIdentityRequest { + name: "delegated".to_string(), + delegated_identity: Some(DelegatedIdentityRequest { + duration: openshell_core::time::duration_from_std(std::time::Duration::from_mins( + 10, + )) + .ok(), + }), + workspace_scope: Some(workspace_selector("default")), + }); + + let status = super::handle_extend(&state, request) + .await + .expect_err("record update failure should fail extend"); + + assert!( + status.message().contains("extend delegated identity"), + "unexpected error: {status:?}" + ); + assert!( + state + .store + .get_message::(existing_credential.object_id()) + .await + .unwrap() + .is_some(), + "pre-authorized credential should not be coupled to extend rollback" + ); + } + + #[tokio::test] + async fn extend_waits_for_sandbox_sync_guard() { + let state = test_server_state().await; + put_test_sandbox(&state, "delegated-extend-guard").await; + put_test_delegation( + &state, + "sandbox-delegated-extend-guard", + "delegated-identity-extend-guard", + "dev-user", + ) + .await; + let guard = state + .compute + .mutation_guard(super::MutationScope::sandbox( + "default", + "sandbox-delegated-extend-guard", + )) + .await + .unwrap(); + let task_state = state.clone(); + let task = tokio::spawn(async move { + super::handle_extend( + &task_state, + authed_request(ExtendSandboxDelegatedIdentityRequest { + name: "delegated-extend-guard".to_string(), + delegated_identity: Some(DelegatedIdentityRequest { + duration: openshell_core::time::duration_from_std( + std::time::Duration::from_mins(10), + ) + .ok(), + }), + workspace_scope: Some(workspace_selector("default")), + }), + ) + .await + }); + + tokio::time::sleep(std::time::Duration::from_millis(20)).await; + assert!( + !task.is_finished(), + "delegation extension should wait for sandbox sync guard" + ); + drop(guard); + + let status = tokio::time::timeout(std::time::Duration::from_secs(5), task) + .await + .expect("extend should finish after guard release") + .expect("join extend task") + .expect_err("extend without configured OIDC should fail after acquiring the guard"); + assert!(status.message().contains("OIDC")); + } + + #[tokio::test] + async fn sandbox_delegated_identity_status_reports_disabled_for_regular_sandbox() { + let state = test_server_state().await; + put_test_sandbox(&state, "regular").await; + + let response = super::handle_status( + &state, + authed_request(GetSandboxDelegatedIdentityStatusRequest { + name: "regular".to_string(), + workspace_scope: Some(workspace_selector("default")), + }), + ) + .await + .expect("regular sandbox status should report disabled") + .into_inner(); + + assert!(response.delegated_identity.is_none()); + assert!(response.credential_missing); + assert!(response.credential_revocation_time.is_none()); + assert!(timestamp_millis(response.current_time.as_ref()) > 0); + } + + #[tokio::test] + async fn sandbox_delegated_identity_status_requires_named_workspace_selector() { + let state = test_server_state().await; + + let missing = super::handle_status( + &state, + authed_request(GetSandboxDelegatedIdentityStatusRequest { + name: "regular".to_string(), + workspace_scope: None, + }), + ) + .await + .expect_err("workspace selector must be explicit"); + assert_eq!(missing.code(), Code::InvalidArgument); + + let all_workspaces = super::handle_status( + &state, + authed_request(GetSandboxDelegatedIdentityStatusRequest { + name: "regular".to_string(), + workspace_scope: Some(openshell_core::proto::all_workspaces_selector()), + }), + ) + .await + .expect_err("all-workspaces selector must be rejected"); + assert_eq!(all_workspaces.code(), Code::InvalidArgument); + } + + #[tokio::test] + async fn delegated_identity_credential_list_uses_continuation_tokens() { + let state = test_server_state().await; + put_test_credential(&state, "delegated-identity-list-a").await; + put_test_credential(&state, "delegated-identity-list-b").await; + + let first = super::handle_list_credentials( + &state, + authed_request(ListDelegatedIdentityCredentialsRequest { + page_size: 1, + page_token: String::new(), + }), + ) + .await + .expect("first credential page") + .into_inner(); + assert_eq!(first.credentials.len(), 1); + assert!(!first.next_page_token.is_empty()); + + let second = super::handle_list_credentials( + &state, + authed_request(ListDelegatedIdentityCredentialsRequest { + page_size: 100, + page_token: first.next_page_token, + }), + ) + .await + .expect("second credential page") + .into_inner(); + assert_eq!(second.credentials.len(), 1); + assert!(second.next_page_token.is_empty()); + assert_ne!( + first.credentials[0].metadata.as_ref().map(|meta| &meta.id), + second.credentials[0].metadata.as_ref().map(|meta| &meta.id) + ); + } + + #[tokio::test] + async fn sandbox_delegated_identity_status_rejects_non_delegating_user() { + let state = test_server_state().await; + put_test_sandbox(&state, "delegated").await; + let credential = put_test_credential(&state, "delegated-identity-status").await; + state + .store + .put_scoped_message( + &SandboxDelegatedIdentityRecord { + metadata: Some(ObjectMeta { + id: "sandbox-delegated-identity-sandbox-delegated".to_string(), + name: "sandbox-delegated-identity-sandbox-delegated".to_string(), + workspace: "default".to_string(), + labels: HashMap::new(), + annotations: HashMap::new(), + ..Default::default() + }), + sandbox_id: "sandbox-delegated".to_string(), + delegated_identity: Some(SandboxDelegatedIdentity { + credential_id: credential.object_id().to_string(), + principal_subject: "alice".to_string(), + delegation_expiration_time: timestamp_from_positive_millis(2_000_000), + withdrawal_time: None, + }), + }, + "sandbox-delegated", + ) + .await + .unwrap(); + + let mut request = Request::new(GetSandboxDelegatedIdentityStatusRequest { + name: "delegated".to_string(), + workspace_scope: Some(workspace_selector("default")), + }); + request + .extensions_mut() + .insert(crate::auth::principal::Principal::User( + crate::auth::principal::UserPrincipal { + identity: crate::auth::identity::Identity { + subject: "bob".to_string(), + display_name: None, + roles: vec!["openshell-user".to_string()], + scopes: vec![], + provider: crate::auth::identity::IdentityProvider::Oidc, + }, + }, + )); + + let status = super::handle_status(&state, request) + .await + .expect_err("non-delegating user should be rejected"); + + assert_eq!(status.code(), Code::PermissionDenied); + assert!(status.message().contains("delegating principal")); + } + + async fn put_test_credential( + state: &Arc, + id: &str, + ) -> DelegatedIdentityCredential { + put_test_credential_for_subject(state, id, "user-1").await + } + + async fn put_test_credential_for_subject( + state: &Arc, + id: &str, + subject: &str, + ) -> DelegatedIdentityCredential { + let credential = DelegatedIdentityCredential { + metadata: Some(ObjectMeta { + id: id.to_string(), + name: id.to_string(), + workspace: String::new(), + labels: HashMap::new(), + annotations: HashMap::new(), + ..Default::default() + }), + issuer: "https://issuer.example.com".to_string(), + client_id: "openshell-cli".to_string(), + principal_subject: subject.to_string(), + ..Default::default() + }; + let mut credential = credential; + credential.secret_material_handles = super::store_staged_secret_material( + state, + id, + &HashMap::from([ + ( + super::REFRESH_TOKEN_MATERIAL_KEY.to_string(), + "refresh".to_string(), + ), + ( + super::ACCESS_TOKEN_MATERIAL_KEY.to_string(), + "access".to_string(), + ), + ]), + ) + .await + .unwrap(); + state.store.put_message(&credential).await.unwrap(); + state + .store + .get_message::(id) + .await + .unwrap() + .unwrap() + } + + async fn put_test_sandbox(state: &Arc, name: &str) { + state + .store + .put_message(&Sandbox { + metadata: Some(ObjectMeta { + id: format!("sandbox-{name}"), + name: name.to_string(), + workspace: "default".to_string(), + labels: HashMap::new(), + annotations: HashMap::new(), + ..Default::default() + }), + spec: Some(SandboxSpec::default()), + status: Some(SandboxStatus::default()), + host_key_fingerprint: String::new(), + created_from_workload_template: None, + }) + .await + .unwrap(); + } + + async fn put_test_delegation( + state: &Arc, + sandbox_id: &str, + credential_id: &str, + subject: &str, + ) { + let record_id = sandbox_delegated_identity_record_id(sandbox_id); + state + .store + .put_scoped_message( + &SandboxDelegatedIdentityRecord { + metadata: Some(ObjectMeta { + id: record_id.clone(), + name: record_id, + workspace: "default".to_string(), + labels: HashMap::new(), + annotations: HashMap::new(), + ..Default::default() + }), + sandbox_id: sandbox_id.to_string(), + delegated_identity: Some(SandboxDelegatedIdentity { + credential_id: credential_id.to_string(), + principal_subject: subject.to_string(), + delegation_expiration_time: timestamp_from_positive_millis( + current_time_ms() + 600_000, + ), + withdrawal_time: None, + }), + }, + sandbox_id, + ) + .await + .unwrap(); + } + + fn test_user(subject: &str) -> crate::auth::principal::UserPrincipal { + crate::auth::principal::UserPrincipal { + identity: crate::auth::identity::Identity { + subject: subject.to_string(), + display_name: None, + roles: vec!["openshell-user".to_string()], + scopes: vec![], + provider: crate::auth::identity::IdentityProvider::Oidc, + }, + } + } + + async fn test_server_state_with_oidc(issuer: String) -> Arc { + let store = Arc::new( + Store::connect("sqlite::memory:?cache=shared") + .await + .unwrap(), + ); + crate::ensure_default_workspace(&store).await.unwrap(); + let compute = crate::compute::new_test_runtime(store.clone()).await; + let oidc = OidcConfig { + issuer, + dangerously_allow_insecure_http: true, + jwks_allowed_origins: Vec::new(), + audience: TEST_AUDIENCE.to_string(), + jwks_ttl_secs: 3600, + roles_claim: "realm_access.roles".to_string(), + admin_role: "openshell-admin".to_string(), + user_role: "openshell-user".to_string(), + scopes_claim: "scope".to_string(), + }; + let oidc_cache = Arc::new( + crate::auth::oidc::JwksCache::new(&oidc) + .await + .expect("OIDC cache should build from mock issuer"), + ); + Arc::new(crate::ServerState::new( + Config::new(None) + .with_database_url("sqlite::memory:?cache=shared") + .with_credential_drivers(["test-static"]) + .with_oidc(oidc), + store, + compute, + SandboxIndex::new(), + SandboxWatchBus::new(), + TracingLogBus::new(), + Arc::new(SupervisorSessionRegistry::new()), + Some(oidc_cache), + )) + } + + async fn mount_test_oidc_issuer(server: &wiremock::MockServer) { + use wiremock::matchers::{method, path}; + use wiremock::{Mock, ResponseTemplate}; + + let issuer = server.uri(); + Mock::given(method("GET")) + .and(path("/.well-known/openid-configuration")) + .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({ + "issuer": issuer, + "jwks_uri": format!("{issuer}/jwks"), + "token_endpoint": format!("{issuer}/token"), + }))) + .mount(server) + .await; + Mock::given(method("GET")) + .and(path("/jwks")) + .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({ + "keys": [{ + "kid": TEST_KID, + "kty": "RSA", + "n": TEST_RSA_KEY.modulus_b64, + "e": TEST_RSA_KEY.exponent_b64, + }], + }))) + .mount(server) + .await; + } + + async fn mount_refresh_token_response(server: &wiremock::MockServer, access_token: &str) { + use wiremock::matchers::{method, path}; + use wiremock::{Mock, ResponseTemplate}; + + Mock::given(method("POST")) + .and(path("/token")) + .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({ + "access_token": access_token, + "token_type": "Bearer", + "expires_in": 3600, + }))) + .mount(server) + .await; + } + + async fn mount_refresh_token_rotation_response( + server: &wiremock::MockServer, + access_token: &str, + refresh_token: &str, + ) { + use wiremock::matchers::{method, path}; + use wiremock::{Mock, ResponseTemplate}; + + Mock::given(method("POST")) + .and(path("/token")) + .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({ + "access_token": access_token, + "refresh_token": refresh_token, + "token_type": "Bearer", + "expires_in": 3600, + }))) + .mount(server) + .await; + } + + fn mint_test_access_token(issuer: &str, subject: &str, exp: i64) -> String { + crate::install_jsonwebtoken_crypto_provider(); + + let mut header = jsonwebtoken::Header::new(jsonwebtoken::Algorithm::RS256); + header.kid = Some(TEST_KID.to_string()); + let key = jsonwebtoken::EncodingKey::from_rsa_pem(TEST_RSA_KEY.private_pem.as_bytes()) + .expect("load RSA signing key"); + jsonwebtoken::encode( + &header, + &serde_json::json!({ + "sub": subject, + "preferred_username": subject, + "iss": issuer, + "aud": TEST_AUDIENCE, + "exp": exp, + "scope": "openid profile sandbox:write", + "realm_access": { "roles": ["openshell-user"] }, + }), + &key, + ) + .expect("sign RS256 token") + } + + fn current_time_secs() -> i64 { + i64::try_from( + std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .expect("system clock is after the unix epoch") + .as_secs(), + ) + .expect("current time fits in i64") + } +} diff --git a/crates/openshell-server/src/delegated_identity/secret_operations.rs b/crates/openshell-server/src/delegated_identity/secret_operations.rs new file mode 100644 index 0000000000..10685c1a0f --- /dev/null +++ b/crates/openshell-server/src/delegated_identity/secret_operations.rs @@ -0,0 +1,455 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +//! Non-secret write-ahead ownership for delegated credential material. +//! Publication and the operation receipt commit together. Cleanup first fences +//! publication, then deletes every prepared handle, including unknown outcomes. + +use super::delegated_secret_scope; +use crate::ServerState; +use crate::pagination::MAX_PAGE_SIZE; +use crate::persistence::{ + AtomicObjectWrite, ObjectListQuery, ObjectRecord, PersistenceError, current_time_ms, +}; +use openshell_core::proto::CredentialHandle; +use prost::Message; +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; +use std::sync::Arc; +use tonic::Status; + +const OBJECT_TYPE: &str = "delegated_secret_operation"; +const HANDLE_OPERATION: &str = "openshell.secret_operation"; +const STAGING_LIFETIME_MS: i64 = 300_000; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +enum Phase { + Writing, + Ready, + Committed, + Cleanup, + // A backend can complete a timed-out write after deletion succeeds. Keep + // these identities and repeat deletion instead of losing recovery coverage. + UncertainCleanup, +} + +#[derive(Serialize, Deserialize)] +struct Operation { + format_version: u32, + credential_id: String, + expires_at_ms: i64, + phase: Phase, + // Encoded opaque handles only, never token values. + handles: HashMap>, +} + +impl Operation { + fn decode(record: &ObjectRecord) -> Result { + let operation: Self = serde_json::from_slice(&record.payload) + .map_err(|_| Status::data_loss("invalid secret operation"))?; + if operation.format_version != 1 { + return Err(Status::failed_precondition( + "unknown secret operation format", + )); + } + Ok(operation) + } + + fn write(&self, id: &str, expected_version: Option) -> Result { + Ok(AtomicObjectWrite { + object_type: OBJECT_TYPE.into(), + id: id.into(), + name: id.into(), + workspace: String::new(), + scope: Some(self.credential_id.clone()), + payload: serde_json::to_vec(self) + .map_err(|_| Status::internal("encode secret operation"))?, + labels: None, + expected_version, + }) + } + + fn handles(&self) -> Result, Status> { + self.handles + .iter() + .map(|(key, bytes)| { + CredentialHandle::decode(bytes.as_slice()) + .map(|handle| (key.clone(), handle)) + .map_err(|_| Status::data_loss("invalid prepared secret handle")) + }) + .collect() + } +} + +fn persistence(error: PersistenceError) -> Status { + crate::grpc::persistence_error_to_status(error, "persist secret operation") +} + +async fn load(state: &Arc, id: &str) -> Result { + state + .store + .get(OBJECT_TYPE, id) + .await + .map_err(persistence)? + .ok_or_else(|| Status::aborted("secret operation no longer exists")) +} + +fn operation_id(handles: &HashMap) -> Result<&str, Status> { + let id = handles + .values() + .next() + .and_then(|handle| handle.metadata.get(HANDLE_OPERATION)) + .ok_or_else(|| Status::internal("secret handles lack an operation identity"))?; + if handles + .values() + .any(|handle| handle.metadata.get(HANDLE_OPERATION) != Some(id)) + { + return Err(Status::internal( + "secret handles belong to different operations", + )); + } + Ok(id) +} + +pub(super) async fn stage( + state: &Arc, + credential_id: &str, + material: &HashMap, +) -> Result, Status> { + let id = format!("delegated-secret-operation-{}", uuid::Uuid::new_v4()); + let mut handles = state + .credentials + .prepare_secret_material(delegated_secret_scope(credential_id), &id, material.keys()) + .await?; + for handle in handles.values_mut() { + handle.metadata.insert(HANDLE_OPERATION.into(), id.clone()); + } + let mut operation = Operation { + format_version: 1, + credential_id: credential_id.into(), + expires_at_ms: current_time_ms() + STAGING_LIFETIME_MS, + phase: Phase::Writing, + handles: handles + .iter() + .map(|(key, handle)| (key.clone(), handle.encode_to_vec())) + .collect(), + }; + state + .store + .put_objects_atomic(&[operation.write(&id, None)?]) + .await + .map_err(persistence)?; + let result = state + .credentials + .store_prepared_secret_material( + delegated_secret_scope(credential_id), + &id, + material, + &handles, + ) + .await; + if let Err(error) = result { + abort(state, &handles).await; + return Err(error); + } + operation.phase = Phase::Ready; + if let Err(error) = state + .store + .put_objects_atomic(&[operation.write(&id, Some(1))?]) + .await + { + // Recovery may have fenced an expired writer while storage was slow. + abort(state, &handles).await; + return Err(persistence(error)); + } + Ok(handles) +} + +/// Include this write in the same transaction as credential publication. +pub(super) async fn publication( + state: &Arc, + handles: &HashMap, +) -> Result { + let id = operation_id(handles)?; + let record = load(state, id).await?; + let mut operation = Operation::decode(&record)?; + if operation.phase != Phase::Ready || operation.expires_at_ms <= current_time_ms() { + return Err(Status::aborted( + "secret operation expired or entered cleanup", + )); + } + operation.phase = Phase::Committed; + operation.write(id, Some(record.resource_version)) +} + +pub(super) async fn abort(state: &Arc, handles: &HashMap) { + let result = async { + let id = operation_id(handles)?; + cleanup(state, load(state, id).await?, true).await + } + .await; + if let Err(error) = result { + tracing::warn!(error = %error, "staged secret cleanup deferred to durable recovery"); + } +} + +async fn cleanup( + state: &Arc, + record: ObjectRecord, + force: bool, +) -> Result<(), Status> { + let mut operation = Operation::decode(&record)?; + let mut version = record.resource_version; + match operation.phase { + Phase::Committed => { + // The credential owns these handles now. Never delete its material. + state + .store + .delete_if(OBJECT_TYPE, &record.id, version) + .await + .map_err(persistence)?; + return Ok(()); + } + Phase::Writing | Phase::Ready => { + if !force && operation.expires_at_ms > current_time_ms() { + return Ok(()); + } + operation.phase = if operation.phase == Phase::Writing { + Phase::UncertainCleanup + } else { + Phase::Cleanup + }; + state + .store + .put_objects_atomic(&[operation.write(&record.id, Some(version))?]) + .await + .map_err(persistence)?; + version += 1; + } + Phase::Cleanup | Phase::UncertainCleanup => {} + } + state + .credentials + .delete_secret_material_handles( + delegated_secret_scope(&operation.credential_id), + &operation.handles()?, + ) + .await?; + if operation.phase == Phase::Cleanup { + state + .store + .delete_if(OBJECT_TYPE, &record.id, version) + .await + .map_err(persistence)?; + } + Ok(()) +} + +pub(super) async fn tick(state: &Arc) -> Result<(), Status> { + let mut cursor = None; + loop { + let page = state + .store + .list_object_page( + OBJECT_TYPE, + ObjectListQuery::AllWorkspaces, + cursor.as_ref(), + MAX_PAGE_SIZE, + ) + .await + .map_err(persistence)?; + for record in page.records { + if let Err(error) = cleanup(state, record, false).await { + tracing::warn!(error = %error, "secret operation recovery will retry"); + } + } + let Some(next) = page.next_cursor else { + return Ok(()); + }; + cursor = Some(next); + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::grpc::test_support::test_server_state; + use crate::storage_proto::DelegatedIdentityCredential; + use openshell_core::proto::datamodel::v1::ObjectMeta; + + fn material() -> HashMap { + HashMap::from([ + ("access_token".into(), "access-secret".into()), + ("refresh_token".into(), "refresh-secret".into()), + ]) + } + + async fn operations(state: &Arc) -> Vec { + state.store.list_by_type(OBJECT_TYPE, 100, 0).await.unwrap() + } + + async fn expire(state: &Arc, record: &ObjectRecord) { + let mut operation = Operation::decode(record).unwrap(); + operation.expires_at_ms = 1; + state + .store + .put_objects_atomic(&[operation + .write(&record.id, Some(record.resource_version)) + .unwrap()]) + .await + .unwrap(); + } + + #[tokio::test] + async fn partial_first_grant_survives_failed_cleanup_without_parent() { + let state = test_server_state().await; + state.credentials.fail_next_store(); + state.credentials.fail_next_delete(); + stage(&state, "absent-parent", &material()) + .await + .unwrap_err(); + // The injected deletion failure can hit either the successful write + // or the handle whose write failed. Retain both prepared identities. + let records = operations(&state).await; + assert_eq!(records.len(), 1); + assert_eq!(Operation::decode(&records[0]).unwrap().handles.len(), 2); + assert!(!String::from_utf8_lossy(&records[0].payload).contains("refresh-secret")); + assert_eq!( + Operation::decode(&records[0]).unwrap().phase, + Phase::UncertainCleanup + ); + tick(&state).await.unwrap(); + assert_eq!(state.credentials.stored_credential_count(), Some(0)); + + // An unknown backend write can arrive even after a successful delete. + let operation = Operation::decode(&records[0]).unwrap(); + state + .credentials + .store_prepared_secret_material( + delegated_secret_scope("absent-parent"), + &records[0].id, + &material(), + &operation.handles().unwrap(), + ) + .await + .unwrap(); + tick(&state).await.unwrap(); + assert_eq!(state.credentials.stored_credential_count(), Some(0)); + } + + #[tokio::test] + async fn cancelled_partial_write_is_recovered_from_durable_handles() { + let state = test_server_state().await; + let (hit, release) = state.credentials.gate_next_store(); + let worker_state = state.clone(); + let task = + tokio::spawn(async move { stage(&worker_state, "absent-parent", &material()).await }); + hit.await.unwrap(); + assert_eq!(state.credentials.stored_credential_count(), Some(1)); + task.abort(); + assert!(task.await.unwrap_err().is_cancelled()); + drop(release); + let records = operations(&state).await; + expire(&state, &records[0]).await; + // This worker uses only storage, with no access to the cancelled task. + tick(&state).await.unwrap(); + assert_eq!(state.credentials.stored_credential_count(), Some(0)); + } + + #[tokio::test] + async fn cleanup_fences_a_prepared_publication_transaction() { + let state = test_server_state().await; + let handles = stage(&state, "parent", &material()).await.unwrap(); + let publication = publication(&state, &handles).await.unwrap(); + abort(&state, &handles).await; + let credential = DelegatedIdentityCredential { + metadata: Some(ObjectMeta { + id: "parent".into(), + name: "parent".into(), + ..Default::default() + }), + secret_material_handles: handles, + ..Default::default() + }; + let write = AtomicObjectWrite::message(&credential, None).unwrap(); + assert!( + state + .store + .put_objects_atomic(&[write, publication]) + .await + .is_err() + ); + assert!( + state + .store + .get_message::("parent") + .await + .unwrap() + .is_none() + ); + assert_eq!(state.credentials.stored_credential_count(), Some(0)); + } + + #[tokio::test] + async fn committed_material_survives_recovery_and_late_abort() { + let state = test_server_state().await; + let handles = stage(&state, "parent", &material()).await.unwrap(); + let credential = DelegatedIdentityCredential { + metadata: Some(ObjectMeta { + id: "parent".into(), + name: "parent".into(), + ..Default::default() + }), + secret_material_handles: handles.clone(), + ..Default::default() + }; + state + .store + .put_objects_atomic(&[ + AtomicObjectWrite::message(&credential, None).unwrap(), + publication(&state, &handles).await.unwrap(), + ]) + .await + .unwrap(); + abort(&state, &handles).await; + tick(&state).await.unwrap(); + assert_eq!(state.credentials.stored_credential_count(), Some(2)); + assert!(operations(&state).await.is_empty()); + } + + #[tokio::test] + async fn abandoned_material_is_recovered_after_database_reopen() { + let directory = tempfile::tempdir().unwrap(); + let url = format!("sqlite://{}", directory.path().join("gateway.db").display()); + let mut state = test_server_state().await; + Arc::get_mut(&mut state).unwrap().store = + Arc::new(crate::persistence::Store::connect(&url).await.unwrap()); + stage(&state, "absent-parent", &material()).await.unwrap(); + expire(&state, &operations(&state).await[0]).await; + // The fake external credential backend outlives the gateway, as Vault + // or Kubernetes Secrets would. The operation must come from the DB. + let credentials = state.credentials.clone(); + state.store.close_for_test().await; + drop(state); + let mut restarted = test_server_state().await; + let server = Arc::get_mut(&mut restarted).unwrap(); + server.store = Arc::new(crate::persistence::Store::connect(&url).await.unwrap()); + server.credentials = credentials; + tick(&restarted).await.unwrap(); + assert_eq!(restarted.credentials.stored_credential_count(), Some(0)); + assert!(operations(&restarted).await.is_empty()); + } + + #[tokio::test] + async fn successful_unpublished_write_is_recovered_after_expiry() { + let state = test_server_state().await; + stage(&state, "absent-parent", &material()).await.unwrap(); + let records = operations(&state).await; + tick(&state).await.unwrap(); + assert_eq!(state.credentials.stored_credential_count(), Some(2)); + expire(&state, &records[0]).await; + tick(&state).await.unwrap(); + assert_eq!(state.credentials.stored_credential_count(), Some(0)); + assert!(operations(&state).await.is_empty()); + } +} diff --git a/crates/openshell-server/src/grpc/mod.rs b/crates/openshell-server/src/grpc/mod.rs index b366dc4a58..2f65cd42a2 100644 --- a/crates/openshell-server/src/grpc/mod.rs +++ b/crates/openshell-server/src/grpc/mod.rs @@ -19,12 +19,14 @@ use openshell_core::extension_protocol::{ExtensionFamily, NegotiatedExtension}; use openshell_core::proto::{ AddWorkspaceMemberRequest, AddWorkspaceMemberResponse, ApproveAllDraftChunksRequest, ApproveAllDraftChunksResponse, ApproveDraftChunkRequest, ApproveDraftChunkResponse, - AttachSandboxProviderRequest, AttachSandboxProviderResponse, BeginRootfsTarStagingRequest, + AttachSandboxProviderRequest, AttachSandboxProviderResponse, AuthorizeDelegatedIdentityRequest, + AuthorizeDelegatedIdentityResponse, BeginRootfsTarStagingRequest, BeginRootfsTarStagingResponse, ClearDraftChunksRequest, ClearDraftChunksResponse, ComputeDriverCapabilities, ComputeDriverInfo, ConfigureProviderRefreshRequest, ConfigureProviderRefreshResponse, CpuResourceCapabilities, CreateProviderRequest, CreateSandboxRequest, CreateSandboxTemplateRequest, CreateSshSessionRequest, CreateSshSessionResponse, CreateWorkspaceRequest, CreateWorkspaceResponse, + DeleteDelegatedIdentityCredentialRequest, DeleteDelegatedIdentityCredentialResponse, DeleteProviderProfileRequest, DeleteProviderProfileResponse, DeleteProviderRefreshRequest, DeleteProviderRefreshResponse, DeleteProviderRequest, DeleteProviderResponse, DeleteSandboxRequest, DeleteSandboxResponse, DeleteSandboxTemplateRequest, @@ -32,20 +34,26 @@ use openshell_core::proto::{ DeleteWorkspaceRequest, DeleteWorkspaceResponse, DetachSandboxProviderRequest, DetachSandboxProviderResponse, EditDraftChunkRequest, EditDraftChunkResponse, ExchangeProviderSubjectTokenRequest, ExchangeProviderSubjectTokenResponse, ExecSandboxEvent, - ExecSandboxInput, ExecSandboxRequest, ExposeServiceRequest, ExtensionKind, + ExecSandboxInput, ExecSandboxRequest, ExposeServiceRequest, + ExtendSandboxDelegatedIdentityRequest, ExtendSandboxDelegatedIdentityResponse, ExtensionKind, FinalizeMainProcessExitRequest, FinalizeMainProcessExitResponse, GatewayMessage, - GetCurrentUserRequest, GetCurrentUserResponse, GetDraftHistoryRequest, GetDraftHistoryResponse, + GetCurrentUserRequest, GetCurrentUserResponse, GetDelegatedIdentityAuthorizationStatusRequest, + GetDelegatedIdentityAuthorizationStatusResponse, GetDelegatedIdentityCredentialStatusRequest, + GetDelegatedIdentityCredentialStatusResponse, GetDraftHistoryRequest, GetDraftHistoryResponse, GetDraftPolicyRequest, GetDraftPolicyResponse, GetGatewayConfigRequest, GetGatewayConfigResponse, GetGatewayInfoRequest, GetGatewayInfoResponse, GetProviderProfileRequest, GetProviderRefreshStatusRequest, GetProviderRefreshStatusResponse, - GetProviderRequest, GetSandboxConfigRequest, GetSandboxConfigResponse, GetSandboxLogsRequest, - GetSandboxLogsResponse, GetSandboxPolicyStatusRequest, GetSandboxPolicyStatusResponse, - GetSandboxProviderEnvironmentRequest, GetSandboxProviderEnvironmentResponse, - GetSandboxProviderStatusRequest, GetSandboxProviderStatusResponse, GetSandboxRequest, - GetSandboxTemplateRequest, GetServiceRequest, GetWorkspaceRequest, GetWorkspaceResponse, - GpuResourceCapabilities, HealthRequest, HealthResponse, ImportProviderProfilesRequest, - ImportProviderProfilesResponse, IssueSandboxTokenRequest, IssueSandboxTokenResponse, - LintProviderProfilesRequest, LintProviderProfilesResponse, ListProviderProfilesRequest, + GetProviderRequest, GetSandboxConfigRequest, GetSandboxConfigResponse, + GetSandboxDelegatedIdentityStatusRequest, GetSandboxDelegatedIdentityStatusResponse, + GetSandboxLogsRequest, GetSandboxLogsResponse, GetSandboxPolicyStatusRequest, + GetSandboxPolicyStatusResponse, GetSandboxProviderEnvironmentRequest, + GetSandboxProviderEnvironmentResponse, GetSandboxProviderStatusRequest, + GetSandboxProviderStatusResponse, GetSandboxRequest, GetSandboxTemplateRequest, + GetServiceRequest, GetWorkspaceRequest, GetWorkspaceResponse, GpuResourceCapabilities, + HealthRequest, HealthResponse, ImportProviderProfilesRequest, ImportProviderProfilesResponse, + IssueSandboxTokenRequest, IssueSandboxTokenResponse, LintProviderProfilesRequest, + LintProviderProfilesResponse, ListDelegatedIdentityCredentialsRequest, + ListDelegatedIdentityCredentialsResponse, ListProviderProfilesRequest, ListProviderProfilesResponse, ListProvidersRequest, ListProvidersResponse, ListSandboxPoliciesRequest, ListSandboxPoliciesResponse, ListSandboxProvidersRequest, ListSandboxProvidersResponse, ListSandboxTemplatesRequest, ListSandboxTemplatesResponse, @@ -58,13 +66,15 @@ use openshell_core::proto::{ RemoveWorkspaceMemberResponse, ReportEndpointStatusRequest, ReportEndpointStatusResponse, ReportMainProcessExitRequest, ReportMainProcessExitResponse, ReportPolicyStatusRequest, ReportPolicyStatusResponse, ReportProviderReadinessRequest, ReportProviderReadinessResponse, - ResourceCapabilities, RevokeSshSessionRequest, RevokeSshSessionResponse, + ResourceCapabilities, RevokeDelegatedIdentityCredentialRequest, + RevokeDelegatedIdentityCredentialResponse, RevokeSshSessionRequest, RevokeSshSessionResponse, RotateProviderCredentialRequest, RotateProviderCredentialResponse, SandboxResponse, SandboxTemplateResponse, ServiceEndpointResponse, ServiceStatus, StartSandboxRequest, StopSandboxRequest, SubmitPolicyAnalysisRequest, SubmitPolicyAnalysisResponse, SupervisorMessage, TcpForwardFrame, UndoDraftChunkRequest, UndoDraftChunkResponse, UpdateConfigRequest, UpdateConfigResponse, UpdateProviderProfilesRequest, UpdateProviderProfilesResponse, UpdateProviderRequest, WatchSandboxRequest, + WithdrawSandboxDelegatedIdentityRequest, WithdrawSandboxDelegatedIdentityResponse, open_shell_server::OpenShell, }; use serde::{Deserialize, Serialize}; @@ -327,6 +337,41 @@ impl OpenShell for OpenShellService { sandbox::handle_begin_rootfs_tar_staging(&self.state, request).await } + async fn get_delegated_identity_authorization_status( + &self, + request: Request, + ) -> Result, Status> { + crate::delegated_identity::handle_authorization_status(&self.state, request).await + } + + async fn authorize_delegated_identity( + &self, + request: Request, + ) -> Result, Status> { + crate::delegated_identity::handle_authorize(&self.state, request).await + } + + async fn get_sandbox_delegated_identity_status( + &self, + request: Request, + ) -> Result, Status> { + crate::delegated_identity::handle_status(&self.state, request).await + } + + async fn withdraw_sandbox_delegated_identity( + &self, + request: Request, + ) -> Result, Status> { + crate::delegated_identity::handle_withdraw(&self.state, request).await + } + + async fn extend_sandbox_delegated_identity( + &self, + request: Request, + ) -> Result, Status> { + crate::delegated_identity::handle_extend(&self.state, request).await + } + type WatchSandboxStream = sandbox::WatchSandboxStream; async fn watch_sandbox( @@ -649,7 +694,39 @@ impl OpenShell for OpenShellService { &self, request: Request, ) -> Result, Status> { - provider::handle_exchange_provider_subject_token(&self.state, request).await + Box::pin(provider::handle_exchange_provider_subject_token( + &self.state, + request, + )) + .await + } + + async fn list_delegated_identity_credentials( + &self, + request: Request, + ) -> Result, Status> { + crate::delegated_identity::handle_list_credentials(&self.state, request).await + } + + async fn get_delegated_identity_credential_status( + &self, + request: Request, + ) -> Result, Status> { + crate::delegated_identity::handle_get_credential_status(&self.state, request).await + } + + async fn revoke_delegated_identity_credential( + &self, + request: Request, + ) -> Result, Status> { + crate::delegated_identity::handle_revoke_credential(&self.state, request).await + } + + async fn delete_delegated_identity_credential( + &self, + request: Request, + ) -> Result, Status> { + crate::delegated_identity::handle_delete_credential(&self.state, request).await } async fn update_config( diff --git a/crates/openshell-server/src/grpc/mutation_tests.rs b/crates/openshell-server/src/grpc/mutation_tests.rs index 81f6fc8673..1e1ed12714 100644 --- a/crates/openshell-server/src/grpc/mutation_tests.rs +++ b/crates/openshell-server/src/grpc/mutation_tests.rs @@ -220,10 +220,19 @@ async fn allow_missing_does_not_hide_missing_parents_or_invalid_requests() { #[tokio::test] async fn repeated_revocation_completes_without_another_write() { let state = test_server_state().await; + state + .store + .put_message(&Sandbox { + metadata: Some(metadata("sandbox-session")), + ..Default::default() + }) + .await + .unwrap(); state .store .put_message(&SshSession { metadata: Some(metadata("session-token")), + sandbox_id: "sandbox-session".into(), token: "session-token".into(), ..Default::default() }) diff --git a/crates/openshell-server/src/grpc/policy.rs b/crates/openshell-server/src/grpc/policy.rs index e9c22bef6f..7801bb42df 100644 --- a/crates/openshell-server/src/grpc/policy.rs +++ b/crates/openshell-server/src/grpc/policy.rs @@ -4126,6 +4126,8 @@ async fn handle_update_config_inner( } let sandbox = sandbox.expect("non-global config update resolves a sandbox"); + crate::delegated_identity::ensure_delegated_identity_sandbox_user(state, principal, &sandbox) + .await?; let sandbox_id = sandbox.object_id().to_string(); replay_facts.resource(&sandbox)?; let mut response_annotations = sandbox_metadata_annotations(&sandbox); @@ -5061,6 +5063,8 @@ pub(super) async fn handle_get_sandbox_logs( MinWorkspaceRole::User, ) .await?; + crate::delegated_identity::ensure_delegated_identity_sandbox_user(state, &principal, &sandbox) + .await?; let sandbox_id = sandbox.object_id(); let lines = if req.lines == 0 { 2000 } else { req.lines }; @@ -5215,6 +5219,8 @@ pub(super) async fn handle_submit_policy_analysis( &req.name, ) .await?; + crate::delegated_identity::ensure_delegated_identity_sandbox_user(state, &principal, &sandbox) + .await?; let sandbox_id = sandbox.object_id().to_string(); for summary in &req.network_activity_summaries { state @@ -5732,6 +5738,8 @@ async fn handle_approve_draft_chunk_inner( require_no_global_policy(state).await?; + crate::delegated_identity::ensure_delegated_identity_sandbox_user(state, &principal, &sandbox) + .await?; let sandbox_id = sandbox.object_id().to_string(); replay_facts.resource(&sandbox)?; @@ -5877,6 +5885,8 @@ async fn handle_reject_draft_chunk_inner( return Err(Status::invalid_argument("chunk_id is required")); } + crate::delegated_identity::ensure_delegated_identity_sandbox_user(state, &principal, &sandbox) + .await?; let sandbox_id = sandbox.object_id().to_string(); replay_facts.resource(&sandbox)?; @@ -5981,6 +5991,8 @@ async fn handle_approve_all_draft_chunks_inner( require_no_global_policy(state).await?; + crate::delegated_identity::ensure_delegated_identity_sandbox_user(state, &principal, &sandbox) + .await?; let sandbox_id = sandbox.object_id().to_string(); replay_facts.resource(&sandbox)?; @@ -6285,6 +6297,8 @@ pub(super) async fn handle_edit_draft_chunk( .proposed_rule .ok_or_else(|| Status::invalid_argument("proposed_rule is required"))?; + crate::delegated_identity::ensure_delegated_identity_sandbox_user(state, &principal, &sandbox) + .await?; let sandbox_id = sandbox.object_id().to_string(); replay_facts.resource(&sandbox)?; @@ -6358,6 +6372,8 @@ async fn handle_undo_draft_chunk_inner( return Err(Status::invalid_argument("chunk_id is required")); } + crate::delegated_identity::ensure_delegated_identity_sandbox_user(state, &principal, &sandbox) + .await?; let sandbox_id = sandbox.object_id().to_string(); replay_facts.resource(&sandbox)?; @@ -6442,6 +6458,8 @@ pub(super) async fn handle_clear_draft_chunks( MinWorkspaceRole::Admin, ) .await?; + crate::delegated_identity::ensure_delegated_identity_sandbox_user(state, &principal, &sandbox) + .await?; let sandbox_id = sandbox.object_id().to_string(); replay_facts.resource(&sandbox)?; @@ -10184,6 +10202,63 @@ mod tests { ); } + #[tokio::test] + async fn get_sandbox_logs_rejects_non_delegating_workspace_user() { + use crate::storage_proto::SandboxDelegatedIdentityRecord; + use openshell_core::proto::SandboxDelegatedIdentity; + use openshell_core::proto::datamodel::v1::ObjectMeta; + + let state = test_server_state().await; + let sandbox = Sandbox { + metadata: Some(ObjectMeta { + id: "delegated-sandbox-id".to_string(), + name: "delegated-sandbox".to_string(), + workspace: "default".to_string(), + ..Default::default() + }), + ..Default::default() + }; + state.store.put_message(&sandbox).await.unwrap(); + let record_id = + crate::delegated_identity::sandbox_delegated_identity_record_id(sandbox.object_id()); + state + .store + .put_scoped_message( + &SandboxDelegatedIdentityRecord { + metadata: Some(ObjectMeta { + id: record_id.clone(), + name: record_id, + workspace: "default".to_string(), + ..Default::default() + }), + sandbox_id: sandbox.object_id().to_string(), + delegated_identity: Some(SandboxDelegatedIdentity { + credential_id: "delegated-credential".to_string(), + principal_subject: "delegating-user".to_string(), + delegation_expiration_time: None, + withdrawal_time: None, + }), + }, + sandbox.object_id(), + ) + .await + .unwrap(); + + let error = handle_get_sandbox_logs( + &state, + with_user(Request::new(GetSandboxLogsRequest { + sandbox: sandbox.object_name().to_string(), + workspace_scope: Some(openshell_core::proto::workspace_selector("default")), + ..Default::default() + })), + ) + .await + .expect_err("non-delegating workspace user should not read buffered logs"); + + assert_eq!(error.code(), Code::PermissionDenied); + assert!(error.message().contains("delegating principal")); + } + #[tokio::test] async fn get_sandbox_logs_rejects_invalid_since_time() { let state = test_server_state().await; diff --git a/crates/openshell-server/src/grpc/provider.rs b/crates/openshell-server/src/grpc/provider.rs index 883f897d15..b3bfd3dd4e 100644 --- a/crates/openshell-server/src/grpc/provider.rs +++ b/crates/openshell-server/src/grpc/provider.rs @@ -4098,17 +4098,21 @@ pub(super) async fn handle_exchange_provider_subject_token( Some(Principal::Sandbox(principal)) => Some(principal.sandbox_id.clone()), _ => None, }; - exchange_provider_subject_token_inner(state, request, request_id.as_deref()) - .await - .map_err(|status| { - let status = sanitize_token_grant_status(status); - log_token_grant_failure( - log_sandbox_id.as_deref(), - request_id.as_deref(), - TokenGrantFailure::from_status(&status), - ); - status - }) + Box::pin(exchange_provider_subject_token_inner( + state, + request, + request_id.as_deref(), + )) + .await + .map_err(|status| { + let status = sanitize_token_grant_status(status); + log_token_grant_failure( + log_sandbox_id.as_deref(), + request_id.as_deref(), + TokenGrantFailure::from_status(&status), + ); + status + }) } fn sanitize_token_grant_status(status: Status) -> Status { @@ -4308,30 +4312,54 @@ async fn exchange_provider_subject_token_inner( .subject_token .as_ref() .ok_or_else(|| token_grant_configuration_status(tonic::Code::FailedPrecondition))?; - if subject_token.source != "provider_credential" { - return Err(token_grant_configuration_status( - tonic::Code::FailedPrecondition, - )); - } - if !profile_proto - .credentials - .iter() - .any(|credential| credential.name == subject_token.credential) - { - return Err(token_grant_configuration_status( - tonic::Code::FailedPrecondition, - )); - } // Validate configuration before identity acquisition, cache lookup or any // token exchange; invalid configuration must never trigger an outbound grant. - let (cache_ttl, subject_token_expires_at_ms) = validate_intermediate_grant_configuration( + let (cache_ttl, _) = validate_intermediate_grant_configuration( token_grant, &provider, &subject_token.credential, )?; - let stored_subject_token = - resolve_subject_token_credential(&state.credentials, &provider, &subject_token.credential) - .await?; + let (stored_subject_token, subject_token_expires_at_ms, subject_cache_key) = + match subject_token.source.as_str() { + "provider_credential" => { + if !profile_proto + .credentials + .iter() + .any(|credential| credential.name == subject_token.credential) + { + return Err(token_grant_configuration_status( + tonic::Code::FailedPrecondition, + )); + } + ( + resolve_subject_token_credential( + &state.credentials, + &provider, + &subject_token.credential, + ) + .await?, + provider_credential_expiration_ms(&provider, &subject_token.credential)? + .unwrap_or_default(), + subject_token.credential.clone(), + ) + } + "sandbox_delegated_identity" => { + if !subject_token.credential.trim().is_empty() { + return Err(token_grant_configuration_status( + tonic::Code::FailedPrecondition, + )); + } + let (access_token, expires_at_ms, credential_id) = + crate::delegated_identity::resolve_subject_access_token(state, &sandbox) + .await?; + (access_token, expires_at_ms, credential_id) + } + _ => { + return Err(token_grant_configuration_status( + tonic::Code::FailedPrecondition, + )); + } + }; let jwt_svid_audience = effective_jwt_svid_audience(&token_grant.token_endpoint, &token_grant.jwt_svid_audience); @@ -4348,7 +4376,7 @@ async fn exchange_provider_subject_token_inner( let intermediate_cache_key = intermediate_token_cache_key(IntermediateTokenCacheKeyInput { provider: &provider, dynamic_credential: &req.credential_key, - subject_credential: &subject_token.credential, + subject_credential: &subject_cache_key, token_endpoint: &token_grant.token_endpoint, client_assertion_type: effective_client_assertion_type(&token_grant.client_assertion_type), subject_token_type: effective_token_type(&subject_token.subject_token_type), @@ -4381,11 +4409,29 @@ async fn exchange_provider_subject_token_inner( }, request_id, ) - .await?; + .await + .inspect_err(|status| { + warn!( + sandbox_id = %req.sandbox_id, + provider = %req.provider, + credential_key = %req.credential_key, + subject_credential = %subject_cache_key, + client_assertion_type = %effective_client_assertion_type(&token_grant.client_assertion_type), + gateway_svid_issuer = %gateway_claims.iss, + gateway_svid_subject = %gateway_claims.sub, + gateway_svid_audience = ?gateway_claims.aud, + supervisor_svid_issuer = %supervisor_claims.iss, + supervisor_svid_subject = %supervisor_claims.sub, + supervisor_svid_audience = ?supervisor_claims.aud, + status = ?status.code(), + error = %status.message(), + "intermediate provider token exchange failed" + ); + })?; let cache_expires_at_ms = intermediate_token_cache_expires_at_ms( &token_response, cache_ttl, - subject_token_expires_at_ms.unwrap_or_default(), + subject_token_expires_at_ms, supervisor_claims.exp, ); if cache_expires_at_ms > crate::persistence::current_time_ms() { @@ -5612,7 +5658,7 @@ mod tests { .with_writer(move || writer.clone()) .finish(); let capture = crate::otel_tracing::test_exporter::install_scoped(subscriber); - let error = handle_exchange_provider_subject_token(&state, request) + let error = Box::pin(handle_exchange_provider_subject_token(&state, request)) .await .unwrap_err(); drop(capture); diff --git a/crates/openshell-server/src/grpc/sandbox.rs b/crates/openshell-server/src/grpc/sandbox.rs index 463176fb65..21d5d1e783 100644 --- a/crates/openshell-server/src/grpc/sandbox.rs +++ b/crates/openshell-server/src/grpc/sandbox.rs @@ -11,10 +11,12 @@ use crate::ServerState; use crate::auth::workspace_authz::{ - AuthorizedWorkspaceScope, MinWorkspaceRole, authorize_list_workspace_selector, + AuthGrant, AuthorizedWorkspaceScope, MinWorkspaceRole, authorize_list_workspace_selector, authorize_sandbox_workspace, authorize_workspace, }; -use crate::compute::{MutationScope, SandboxDeletePreconditions}; +use crate::compute::MutationScope; +#[cfg(test)] +use crate::compute::SandboxDeletePreconditions; use crate::pagination::Pagination; use crate::persistence::{ ObjectLabels, ObjectListQuery, ObjectType, WriteCondition, generate_name, @@ -459,6 +461,7 @@ async fn handle_create_sandbox_inner( let request = request.into_inner(); let await_main_process_attachment = request.await_main_process_attachment; let workload_template_name = request.workload_template.trim().to_string(); + let delegated_identity_request = request.delegated_identity; validate_create_sandbox_request_pre_io(&request, &workload_template_name)?; @@ -691,6 +694,14 @@ async fn handle_create_sandbox_inner( status })?; + let delegated_identity = crate::delegated_identity::prepare_for_sandbox_create( + state, + &principal, + &sandbox, + delegated_identity_request, + ) + .await?; + let runtime_identity = crate::auth::sandbox_session::PersistedSandboxIdentity::new() .map_err(|error| Status::internal(error.to_string()))?; if let Some(metadata) = sandbox.metadata.as_mut() { @@ -721,6 +732,7 @@ async fn handle_create_sandbox_inner( sandbox_token, await_main_process_attachment, runtime_inputs, + delegated_identity.as_ref(), sandbox_lifecycle_guard, mutation_guard, )) @@ -1314,6 +1326,8 @@ pub(super) async fn handle_list_sandbox_providers( MinWorkspaceRole::User, ) .await?; + crate::delegated_identity::ensure_delegated_identity_sandbox_user(state, &principal, &sandbox) + .await?; let workspace = sandbox.object_workspace().to_string(); let pagination = Pagination::new( req.page_size, @@ -1414,6 +1428,8 @@ pub(super) async fn handle_attach_sandbox_provider( err } })?; + crate::delegated_identity::ensure_delegated_identity_sandbox_user(state, &principal, &sandbox) + .await?; let sandbox_name = sandbox.object_name().to_string(); let sandbox_id = sandbox .metadata @@ -1581,6 +1597,8 @@ pub(super) async fn handle_detach_sandbox_provider( )) .await .map_err(|err| super::persistence_error_to_status(err, "acquire sandbox mutation lock"))?; + crate::delegated_identity::ensure_delegated_identity_sandbox_user(state, &principal, &sandbox) + .await?; let sandbox_name = sandbox.object_name().to_string(); let sandbox_id = sandbox .metadata @@ -1714,18 +1732,34 @@ async fn handle_delete_sandbox_inner( let workspace = super::workspace::resolve_workspace(state.store.as_ref(), &authz.workspace) .await? .name; - // Main's canonical public delete request contains no caller-provided - // identity/version fields. The compute layer still binds the resolved - // object ID and lifecycle guards before committing deletion. - let preconditions = SandboxDeletePreconditions::default(); + let sandbox = state + .store + .get_message_by_name::(&workspace, &name) + .await + .map_err(|error| Status::internal(format!("fetch sandbox failed: {error}")))?; + let Some(sandbox) = sandbox else { + return if req.allow_missing { + Ok(Response::new(DeleteSandboxResponse { + outcome: openshell_core::proto::DeletionOutcome::AlreadyAbsent.into(), + sandbox_id: String::new(), + })) + } else { + Err(Status::not_found("sandbox not found")) + }; + }; + if !matches!( + authz.grant, + AuthGrant::PlatformAdmin | AuthGrant::Member(openshell_core::proto::WorkspaceRole::Admin) + ) { + crate::delegated_identity::ensure_delegated_identity_sandbox_user( + state, &principal, &sandbox, + ) + .await?; + } + let result = state .compute - .delete_sandbox_allow_missing_with_preconditions( - &workspace, - &name, - req.allow_missing, - preconditions, - ) + .delete_sandbox_by_id(sandbox.object_id(), sandbox.object_name()) .await?; if !result.sandbox_id.is_empty() { state.telemetry.end_sandbox_session(&result.sandbox_id); @@ -1770,7 +1804,30 @@ async fn handle_stop_sandbox_inner( .await?; let workspace = resolved.object_workspace(); let name = resolved.object_name(); - let sandbox = state.compute.stop_sandbox(workspace, name).await?; + let grant = authorize_sandbox_workspace( + &state.store, + &state.admin_role, + &principal, + workspace, + MinWorkspaceRole::User, + ) + .await?; + crate::delegated_identity::ensure_delegated_identity_sandbox_lifecycle_operation( + state, + &principal, + grant, + &resolved, + crate::delegated_identity::DelegatedSandboxLifecycleOperation::Stop, + ) + .await?; + let mut sandbox = state.compute.stop_resolved_sandbox(&resolved).await?; + let remote_authority = + crate::supervisor_session::remote_supervisor_owner(state, sandbox.object_id()) + .await? + .is_some(); + state + .supervisor_sessions + .project_endpoint_status(&mut sandbox, remote_authority); info!(sandbox_name = %name, "StopSandbox request completed successfully"); Ok(Response::new(SandboxResponse { sandbox: Some(sandbox), @@ -1811,13 +1868,25 @@ async fn handle_start_sandbox_inner( .await?; let workspace = resolved.object_workspace().to_string(); let name = resolved.object_name().to_string(); + let grant = authorize_sandbox_workspace( + &state.store, + &state.admin_role, + &principal, + &workspace, + MinWorkspaceRole::User, + ) + .await?; + crate::delegated_identity::ensure_delegated_identity_sandbox_lifecycle_operation( + state, + &principal, + grant, + &resolved, + crate::delegated_identity::DelegatedSandboxLifecycleOperation::Start, + ) + .await?; let mut sandbox = state .compute - .start_sandbox_authenticated( - &workspace, - &name, - state.sandbox_session_jwt_authority.as_deref(), - ) + .start_sandbox_authenticated(&resolved, state.sandbox_session_jwt_authority.as_deref()) .await?; let remote_authority = crate::supervisor_session::remote_supervisor_owner(state, sandbox.object_id()) @@ -1940,6 +2009,8 @@ pub(super) async fn handle_watch_sandbox( MinWorkspaceRole::User, ) .await?; + crate::delegated_identity::ensure_delegated_identity_sandbox_user(state, &principal, &sandbox) + .await?; let sandbox_id = sandbox.object_id().to_string(); let follow_status = req.follow_status; @@ -2532,6 +2603,8 @@ pub(super) async fn handle_exec_sandbox( MinWorkspaceRole::User, ) .await?; + crate::delegated_identity::ensure_delegated_identity_sandbox_user(state, &principal, &sandbox) + .await?; if let Some(completion) = &completion { completion.ensure_target(sandbox.object_id())?; @@ -3071,6 +3144,8 @@ pub(super) async fn handle_exec_sandbox_interactive_start( MinWorkspaceRole::User, ) .await?; + crate::delegated_identity::ensure_delegated_identity_sandbox_user(state, &principal, &sandbox) + .await?; if let Some(completion) = &completion { completion.ensure_target(sandbox.object_id())?; @@ -3165,6 +3240,8 @@ pub(super) async fn handle_create_ssh_session( MinWorkspaceRole::User, ) .await?; + crate::delegated_identity::ensure_delegated_identity_sandbox_user(state, &principal, &sandbox) + .await?; let sandbox_id = sandbox.object_id().to_string(); if !sandbox_relay_reachable(state, &sandbox) { @@ -3280,6 +3357,14 @@ pub(super) async fn handle_revoke_ssh_session( e } })?; + let sandbox = state + .store + .get_message::(&session.sandbox_id) + .await + .map_err(|e| Status::internal(format!("fetch sandbox failed: {e}")))? + .ok_or_else(|| Status::not_found("sandbox not found"))?; + crate::delegated_identity::ensure_delegated_identity_sandbox_user(state, &principal, &sandbox) + .await?; if session.revoked { return Ok(Response::new(RevokeSshSessionResponse { @@ -4040,6 +4125,8 @@ async fn run_exec_with_russh( #[cfg(test)] mod tests { use super::*; + use crate::auth::identity::{Identity, IdentityProvider}; + use crate::auth::principal::{Principal, UserPrincipal}; use crate::compute::NoopTestDriver; #[test] @@ -4052,9 +4139,12 @@ mod tests { authed_request, test_server_state, test_server_state_with_compute_driver, test_server_state_with_driver, }; + use crate::storage_proto::SandboxDelegatedIdentityRecord; use openshell_core::proto::datamodel::v1::ObjectMeta; use openshell_core::proto::{ - GpuResourceRequirements, SandboxServiceExposure, ServiceAuthorizationMode, ServiceEndpoint, + DelegatedIdentityRequest, GpuResourceRequirements, SandboxDelegatedIdentity, + SandboxServiceExposure, ServiceAuthorizationMode, ServiceEndpoint, WorkspaceMember, + WorkspaceRole, }; #[tokio::test] @@ -4783,6 +4873,112 @@ mod tests { } } + async fn put_delegated_test_sandbox( + state: &Arc, + name: &str, + principal_subject: &str, + ) { + let mut sandbox = test_sandbox(name, Vec::new()); + let runtime_identity = crate::auth::sandbox_session::PersistedSandboxIdentity::new() + .expect("test runtime identity"); + runtime_identity.write( + &mut sandbox + .metadata + .as_mut() + .expect("test sandbox metadata") + .annotations, + ); + let sandbox_id = sandbox.object_id().to_string(); + let record_id = + crate::delegated_identity::sandbox_delegated_identity_record_id(&sandbox_id); + let workspace = sandbox.object_workspace().to_string(); + state.store.put_message(&sandbox).await.unwrap(); + state + .store + .put_scoped_message( + &SandboxDelegatedIdentityRecord { + metadata: Some(ObjectMeta { + id: record_id.clone(), + name: record_id, + created_time: openshell_core::time::timestamp_from_millis(1_000_000).ok(), + labels: HashMap::new(), + resource_version: 0, + annotations: HashMap::new(), + workspace, + deletion_time: None, + }), + sandbox_id: sandbox_id.clone(), + delegated_identity: Some(SandboxDelegatedIdentity { + credential_id: "delegated-credential".to_string(), + principal_subject: principal_subject.to_string(), + delegation_expiration_time: openshell_core::time::timestamp_from_millis( + 2_000_000, + ) + .ok(), + withdrawal_time: None, + }), + }, + &sandbox_id, + ) + .await + .unwrap(); + } + + async fn put_workspace_member(state: &Arc, subject: &str, role: WorkspaceRole) { + state + .store + .put_message(&WorkspaceMember { + metadata: Some(ObjectMeta { + id: format!("workspace-member-{subject}"), + name: subject.to_string(), + created_time: openshell_core::time::timestamp_from_millis(1_000_000).ok(), + labels: HashMap::new(), + resource_version: 0, + annotations: HashMap::new(), + workspace: "default".to_string(), + deletion_time: None, + }), + principal_subject: subject.to_string(), + role: role.into(), + }) + .await + .unwrap(); + } + + fn user_request(mut request: Request, subject: &str) -> Request { + request + .extensions_mut() + .insert(Principal::User(UserPrincipal { + identity: Identity { + subject: subject.to_string(), + display_name: None, + roles: vec![], + scopes: vec![], + provider: IdentityProvider::Oidc, + }, + })); + request + } + + fn user_request_with_roles( + mut request: Request, + subject: &str, + roles: &[&str], + ) -> Request { + request + .extensions_mut() + .insert(Principal::User(UserPrincipal { + identity: Identity { + subject: subject.to_string(), + display_name: None, + roles: roles.iter().map(|role| (*role).to_string()).collect(), + scopes: vec![], + provider: IdentityProvider::Oidc, + }, + })); + request + } + #[tokio::test] async fn watch_producer_releases_request_span_when_client_disconnects() { use crate::otel_tracing::test_exporter; @@ -6611,6 +6807,7 @@ mod tests { await_main_process_attachment: false, workload_template: String::new(), service_exposures: Vec::new(), + delegated_identity: None, }), ) .await @@ -6686,6 +6883,7 @@ mod tests { await_main_process_attachment: false, workload_template: String::new(), service_exposures: Vec::new(), + delegated_identity: None, }), ) .await @@ -6724,6 +6922,7 @@ mod tests { await_main_process_attachment: false, workload_template: String::new(), service_exposures: Vec::new(), + delegated_identity: None, }), ) .await @@ -6806,6 +7005,7 @@ mod tests { await_main_process_attachment: false, workload_template: String::new(), service_exposures: Vec::new(), + delegated_identity: None, }), ) .await @@ -7186,6 +7386,7 @@ mod tests { await_main_process_attachment: false, workload_template: String::new(), service_exposures: Vec::new(), + delegated_identity: None, }), ) .await @@ -7536,6 +7737,7 @@ mod tests { await_main_process_attachment: false, workload_template: String::new(), service_exposures: Vec::new(), + delegated_identity: None, }), ) .await @@ -7607,6 +7809,7 @@ mod tests { await_main_process_attachment: false, workload_template: String::new(), service_exposures: Vec::new(), + delegated_identity: None, }), ) .await @@ -7643,6 +7846,7 @@ mod tests { await_main_process_attachment: false, workload_template: String::new(), service_exposures: Vec::new(), + delegated_identity: None, }), ) .await @@ -7687,6 +7891,7 @@ mod tests { await_main_process_attachment: false, workload_template: String::new(), service_exposures: Vec::new(), + delegated_identity: None, }), ) .await @@ -7711,6 +7916,54 @@ mod tests { ); } + #[tokio::test] + async fn create_sandbox_with_delegated_identity_waits_for_sandbox_sync_guard() { + let state = test_server_state().await; + let guard = state + .compute + .mutation_guard(MutationScope::Global) + .await + .unwrap(); + let task_state = state.clone(); + let task = tokio::spawn(async move { + handle_create_sandbox( + &task_state, + authed_request(CreateSandboxRequest { + name: "guarded-delegated".to_string(), + spec: Some(SandboxSpec::default()), + labels: HashMap::new(), + annotations: HashMap::new(), + workspace_scope: Some(openshell_core::proto::workspace_selector("default")), + await_main_process_attachment: false, + workload_template: String::new(), + request_id: String::new(), + service_exposures: Vec::new(), + delegated_identity: Some(DelegatedIdentityRequest { + duration: openshell_core::time::duration_from_std( + std::time::Duration::from_mins(10), + ) + .ok(), + }), + }), + ) + .await + }); + + tokio::time::sleep(std::time::Duration::from_millis(20)).await; + assert!( + !task.is_finished(), + "sandbox create with delegated identity should wait for sandbox sync guard" + ); + drop(guard); + + let status = tokio::time::timeout(std::time::Duration::from_secs(5), task) + .await + .expect("create should finish after guard release") + .expect("join create task") + .expect_err("create without configured OIDC should fail after acquiring the guard"); + assert!(status.message().contains("OIDC")); + } + #[tokio::test] async fn sandbox_template_handlers_create_get_list_and_delete_workspace_resource() { let state = test_server_state().await; @@ -8347,6 +8600,7 @@ mod tests { workload_template: "gpu-kata".to_string(), await_main_process_attachment: false, service_exposures: Vec::new(), + delegated_identity: None, }), ) .await @@ -8436,6 +8690,7 @@ mod tests { workload_template: "default-image".to_string(), await_main_process_attachment: false, service_exposures: Vec::new(), + delegated_identity: None, }), ) .await @@ -8490,6 +8745,7 @@ mod tests { workload_template: "default-gpu".to_string(), await_main_process_attachment: false, service_exposures: Vec::new(), + delegated_identity: None, }), ) .await @@ -8531,6 +8787,7 @@ mod tests { workload_template: "corrupt-template".to_string(), await_main_process_attachment: false, service_exposures: Vec::new(), + delegated_identity: None, }), ) .await @@ -8573,6 +8830,7 @@ mod tests { workload_template: "gpu-kata".to_string(), await_main_process_attachment: false, service_exposures: Vec::new(), + delegated_identity: None, }), ) .await @@ -8600,6 +8858,7 @@ mod tests { workload_template: "Invalid_Template_Name".to_string(), await_main_process_attachment: false, service_exposures: Vec::new(), + delegated_identity: None, }), ) .await @@ -8630,6 +8889,7 @@ mod tests { workload_template: "missing-template".to_string(), await_main_process_attachment: false, service_exposures: Vec::new(), + delegated_identity: None, }), ) .await @@ -8660,6 +8920,7 @@ mod tests { workload_template: String::new(), await_main_process_attachment: false, service_exposures: Vec::new(), + delegated_identity: None, }), ) .await @@ -9009,6 +9270,476 @@ mod tests { assert!(!sandbox_relay_reachable(&state, &sandbox)); } + #[tokio::test] + async fn delegated_identity_sandbox_allows_delegating_user_to_create_ssh_session() { + let state = test_server_state().await; + put_delegated_test_sandbox(&state, "work", "alice").await; + + let response = handle_create_ssh_session( + &state, + user_request( + Request::new(CreateSshSessionRequest { + sandbox: "work".to_string(), + workspace_scope: Some(openshell_core::proto::workspace_selector("default")), + }), + "alice", + ), + ) + .await + .expect("delegating user should be allowed") + .into_inner(); + + assert_eq!(response.sandbox_id, "sandbox-work"); + } + + #[tokio::test] + async fn delegated_identity_sandbox_rejects_other_user_create_ssh_session() { + let state = test_server_state().await; + put_delegated_test_sandbox(&state, "work", "alice").await; + + let err = handle_create_ssh_session( + &state, + user_request( + Request::new(CreateSshSessionRequest { + sandbox: "work".to_string(), + workspace_scope: Some(openshell_core::proto::workspace_selector("default")), + }), + "bob", + ), + ) + .await + .expect_err("non-delegating user should be denied"); + + assert_eq!(err.code(), tonic::Code::PermissionDenied); + assert!(err.message().contains("delegating principal")); + } + + #[tokio::test] + async fn delegated_identity_sandbox_rejects_other_user_exec() { + let state = test_server_state().await; + put_delegated_test_sandbox(&state, "work", "alice").await; + + let err = handle_exec_sandbox( + &state, + user_request( + Request::new(ExecSandboxRequest { + sandbox: "work".to_string(), + workspace_scope: Some(openshell_core::proto::workspace_selector("default")), + command: vec!["true".to_string()], + ..Default::default() + }), + "bob", + ), + ) + .await + .expect_err("non-delegating user should be denied"); + + assert_eq!(err.code(), tonic::Code::PermissionDenied); + assert!(err.message().contains("delegating principal")); + } + + #[tokio::test] + async fn delegated_identity_sandbox_remains_visible_to_workspace_user() { + let state = test_server_state().await; + put_delegated_test_sandbox(&state, "work", "alice").await; + + let response = handle_get_sandbox( + &state, + user_request( + Request::new(GetSandboxRequest { + name: "work".to_string(), + workspace_scope: Some(openshell_core::proto::workspace_selector("default")), + }), + "bob", + ), + ) + .await + .expect("workspace user should still see the sandbox") + .into_inner(); + assert_eq!( + response.sandbox.as_ref().unwrap().object_id(), + "sandbox-work" + ); + + let response = handle_list_sandboxes( + &state, + user_request( + Request::new(ListSandboxesRequest { + page_size: 100, + page_token: String::new(), + label_selector: String::new(), + workspace_scope: Some(openshell_core::proto::workspace_selector("default")), + }), + "bob", + ), + ) + .await + .expect("workspace user should still list the sandbox") + .into_inner(); + assert_eq!(response.sandboxes.len(), 1); + assert_eq!(response.sandboxes[0].object_id(), "sandbox-work"); + } + + #[tokio::test] + async fn delegated_identity_sandbox_allows_delegator_lifecycle_operations() { + let mut state = test_server_state().await; + Arc::get_mut(&mut state).unwrap().admin_role = "openshell-admin".to_string(); + put_workspace_member(&state, "alice", WorkspaceRole::User).await; + put_delegated_test_sandbox(&state, "work", "alice").await; + + let stopped = handle_stop_sandbox_inner( + &state, + user_request( + Request::new(StopSandboxRequest { + name: "work".to_string(), + workspace_scope: Some(openshell_core::proto::workspace_selector("default")), + request_id: String::new(), + }), + "alice", + ), + ) + .await + .expect("delegator should be allowed to stop delegated sandbox") + .into_inner() + .sandbox + .expect("stopped sandbox"); + assert_eq!(stopped.phase(), SandboxPhase::Stopped as i32); + + let started = handle_start_sandbox_inner( + &state, + user_request( + Request::new(StartSandboxRequest { + name: "work".to_string(), + workspace_scope: Some(openshell_core::proto::workspace_selector("default")), + request_id: String::new(), + }), + "alice", + ), + ) + .await + .expect("delegator should be allowed to start delegated sandbox") + .into_inner() + .sandbox + .expect("started sandbox"); + assert_eq!(started.phase(), SandboxPhase::Starting as i32); + + let deleted = handle_delete_sandbox_inner( + &state, + user_request( + Request::new(DeleteSandboxRequest { + name: "work".to_string(), + workspace_scope: Some(openshell_core::proto::workspace_selector("default")), + allow_missing: false, + request_id: String::new(), + }), + "alice", + ), + ) + .await + .expect("delegator should be allowed to delete delegated sandbox") + .into_inner(); + assert_eq!( + deleted.outcome, + openshell_core::proto::DeletionOutcome::Accepted as i32 + ); + } + + #[tokio::test] + async fn delegated_identity_sandbox_allows_workspace_admin_stop_but_rejects_start() { + let mut state = test_server_state().await; + Arc::get_mut(&mut state).unwrap().admin_role = "openshell-admin".to_string(); + put_workspace_member(&state, "bob", WorkspaceRole::Admin).await; + put_delegated_test_sandbox(&state, "work", "alice").await; + + handle_stop_sandbox_inner( + &state, + user_request( + Request::new(StopSandboxRequest { + name: "work".to_string(), + workspace_scope: Some(openshell_core::proto::workspace_selector("default")), + request_id: String::new(), + }), + "bob", + ), + ) + .await + .expect("workspace admin should be allowed to stop delegated sandbox"); + + let error = handle_start_sandbox_inner( + &state, + user_request( + Request::new(StartSandboxRequest { + name: "work".to_string(), + workspace_scope: Some(openshell_core::proto::workspace_selector("default")), + request_id: String::new(), + }), + "bob", + ), + ) + .await + .expect_err("workspace admin must not start delegated sandbox"); + assert_eq!(error.code(), tonic::Code::PermissionDenied); + assert!(error.message().contains("delegating principal")); + } + + #[tokio::test] + async fn delegated_identity_sandbox_allows_platform_admin_stop_but_rejects_start() { + let mut state = test_server_state().await; + Arc::get_mut(&mut state).unwrap().admin_role = "openshell-admin".to_string(); + put_delegated_test_sandbox(&state, "work", "alice").await; + + handle_stop_sandbox_inner( + &state, + user_request_with_roles( + Request::new(StopSandboxRequest { + name: "work".to_string(), + workspace_scope: Some(openshell_core::proto::workspace_selector("default")), + request_id: String::new(), + }), + "platform-admin", + &["openshell-admin"], + ), + ) + .await + .expect("platform admin should be allowed to stop delegated sandbox"); + + let error = handle_start_sandbox_inner( + &state, + user_request_with_roles( + Request::new(StartSandboxRequest { + name: "work".to_string(), + workspace_scope: Some(openshell_core::proto::workspace_selector("default")), + request_id: String::new(), + }), + "platform-admin", + &["openshell-admin"], + ), + ) + .await + .expect_err("platform admin must not start delegated sandbox"); + assert_eq!(error.code(), tonic::Code::PermissionDenied); + assert!(error.message().contains("delegating principal")); + } + + #[tokio::test] + async fn delegated_identity_sandbox_rejects_other_user_start_and_stop() { + let mut state = test_server_state().await; + Arc::get_mut(&mut state).unwrap().admin_role = "openshell-admin".to_string(); + put_workspace_member(&state, "bob", WorkspaceRole::User).await; + put_delegated_test_sandbox(&state, "work", "alice").await; + + let start_error = handle_start_sandbox_inner( + &state, + user_request( + Request::new(StartSandboxRequest { + name: "work".to_string(), + workspace_scope: Some(openshell_core::proto::workspace_selector("default")), + request_id: String::new(), + }), + "bob", + ), + ) + .await + .expect_err("other workspace user must not start delegated sandbox"); + assert_eq!(start_error.code(), tonic::Code::PermissionDenied); + + let stop_error = handle_stop_sandbox_inner( + &state, + user_request( + Request::new(StopSandboxRequest { + name: "work".to_string(), + workspace_scope: Some(openshell_core::proto::workspace_selector("default")), + request_id: String::new(), + }), + "bob", + ), + ) + .await + .expect_err("other workspace user must not stop delegated sandbox"); + assert_eq!(stop_error.code(), tonic::Code::PermissionDenied); + } + + #[tokio::test] + async fn ordinary_sandbox_retains_workspace_user_start_stop_access() { + let mut state = test_server_state().await; + Arc::get_mut(&mut state).unwrap().admin_role = "openshell-admin".to_string(); + put_workspace_member(&state, "bob", WorkspaceRole::User).await; + let mut sandbox = test_sandbox("work", Vec::new()); + crate::auth::sandbox_session::PersistedSandboxIdentity::new() + .expect("test runtime identity") + .write( + &mut sandbox + .metadata + .as_mut() + .expect("test sandbox metadata") + .annotations, + ); + state.store.put_message(&sandbox).await.unwrap(); + + handle_stop_sandbox_inner( + &state, + user_request( + Request::new(StopSandboxRequest { + name: "work".to_string(), + workspace_scope: Some(openshell_core::proto::workspace_selector("default")), + request_id: String::new(), + }), + "bob", + ), + ) + .await + .expect("workspace user should retain ordinary sandbox stop access"); + handle_start_sandbox_inner( + &state, + user_request( + Request::new(StartSandboxRequest { + name: "work".to_string(), + workspace_scope: Some(openshell_core::proto::workspace_selector("default")), + request_id: String::new(), + }), + "bob", + ), + ) + .await + .expect("workspace user should retain ordinary sandbox start access"); + } + + #[tokio::test] + async fn delegated_identity_sandbox_rejects_other_user_delete() { + let mut state = test_server_state().await; + Arc::get_mut(&mut state).unwrap().admin_role = "openshell-admin".to_string(); + put_workspace_member(&state, "bob", WorkspaceRole::User).await; + put_delegated_test_sandbox(&state, "work", "alice").await; + + let err = handle_delete_sandbox( + &state, + user_request( + Request::new(DeleteSandboxRequest { + name: "work".to_string(), + workspace_scope: Some(openshell_core::proto::workspace_selector("default")), + allow_missing: false, + request_id: String::new(), + }), + "bob", + ), + ) + .await + .expect_err("non-delegating user should be denied"); + + assert_eq!(err.code(), tonic::Code::PermissionDenied); + assert!(err.message().contains("delegating principal")); + assert!( + state + .store + .get_message::("sandbox-work") + .await + .unwrap() + .is_some(), + "denied delete must not remove the sandbox" + ); + } + + #[tokio::test] + async fn delegated_identity_sandbox_allows_workspace_admin_delete() { + let mut state = test_server_state().await; + Arc::get_mut(&mut state).unwrap().admin_role = "openshell-admin".to_string(); + put_workspace_member(&state, "bob", WorkspaceRole::Admin).await; + put_delegated_test_sandbox(&state, "work", "alice").await; + + let response = handle_delete_sandbox( + &state, + user_request( + Request::new(DeleteSandboxRequest { + name: "work".to_string(), + workspace_scope: Some(openshell_core::proto::workspace_selector("default")), + allow_missing: false, + request_id: String::new(), + }), + "bob", + ), + ) + .await + .expect("workspace admin should be allowed to delete delegated sandbox") + .into_inner(); + + assert_eq!( + response.outcome, + openshell_core::proto::DeletionOutcome::Accepted as i32 + ); + } + + #[tokio::test] + async fn delegated_identity_sandbox_allows_platform_admin_delete() { + let mut state = test_server_state().await; + Arc::get_mut(&mut state).unwrap().admin_role = "openshell-admin".to_string(); + put_delegated_test_sandbox(&state, "work", "alice").await; + + let response = handle_delete_sandbox( + &state, + user_request_with_roles( + Request::new(DeleteSandboxRequest { + name: "work".to_string(), + workspace_scope: Some(openshell_core::proto::workspace_selector("default")), + allow_missing: false, + request_id: String::new(), + }), + "bob", + &["openshell-admin"], + ), + ) + .await + .expect("platform admin should be allowed to delete delegated sandbox") + .into_inner(); + + assert_eq!( + response.outcome, + openshell_core::proto::DeletionOutcome::Accepted as i32 + ); + } + + #[tokio::test] + async fn delegated_identity_sandbox_rejects_other_user_revoke_ssh_session() { + let state = test_server_state().await; + put_delegated_test_sandbox(&state, "work", "alice").await; + let token = handle_create_ssh_session( + &state, + user_request( + Request::new(CreateSshSessionRequest { + sandbox: "work".to_string(), + workspace_scope: Some(openshell_core::proto::workspace_selector("default")), + }), + "alice", + ), + ) + .await + .unwrap() + .into_inner() + .token; + + let err = handle_revoke_ssh_session( + &state, + user_request( + Request::new(RevokeSshSessionRequest { + token: token.clone(), + allow_missing: false, + }), + "bob", + ), + ) + .await + .expect_err("non-delegating user should be denied"); + + assert_eq!(err.code(), tonic::Code::PermissionDenied); + assert!(err.message().contains("delegating principal")); + let session = state + .store + .get_message::(&token) + .await + .unwrap() + .expect("session should still exist after denied revocation"); + assert!(!session.revoked); + } + #[tokio::test] async fn concurrent_revoke_ssh_session_handles_cas_properly() { let state = test_server_state().await; diff --git a/crates/openshell-server/src/grpc/service.rs b/crates/openshell-server/src/grpc/service.rs index bb44f395b8..3711e45765 100644 --- a/crates/openshell-server/src/grpc/service.rs +++ b/crates/openshell-server/src/grpc/service.rs @@ -46,6 +46,8 @@ pub(super) async fn handle_expose_service( MinWorkspaceRole::User, ) .await?; + crate::delegated_identity::ensure_delegated_identity_sandbox_user(state, &principal, &sandbox) + .await?; let workspace = super::workspace::resolve_workspace(state.store.as_ref(), sandbox.object_workspace()) .await? @@ -321,6 +323,14 @@ pub(super) async fn handle_delete_service( outcome: super::deletion_outcome(false, req.allow_missing, "service endpoint")?, })); }; + let sandbox = state + .store + .get_message::(&endpoint.sandbox_id) + .await + .map_err(|e| Status::internal(format!("fetch sandbox failed: {e}")))? + .ok_or_else(|| Status::not_found("sandbox not found"))?; + crate::delegated_identity::ensure_delegated_identity_sandbox_user(state, &principal, &sandbox) + .await?; #[cfg(test)] if let Some(probe) = probe { diff --git a/crates/openshell-server/src/lib.rs b/crates/openshell-server/src/lib.rs index 8ca38f17b8..64d2225594 100644 --- a/crates/openshell-server/src/lib.rs +++ b/crates/openshell-server/src/lib.rs @@ -21,6 +21,7 @@ pub mod config_file; mod config_update_operation; mod credentials; mod defaults; +mod delegated_identity; mod gateway_listener; mod gateway_members; mod gateway_metrics; @@ -986,6 +987,7 @@ pub(crate) async fn run_server( ssh_sessions::spawn_session_reaper(store.clone(), Duration::from_hours(1)); supervisor_session::spawn_relay_reaper(state.clone(), Duration::from_secs(30)); provider_refresh::spawn_refresh_worker(state.clone(), Duration::from_mins(1)); + delegated_identity::spawn_credential_cleanup_worker(state.clone(), Duration::from_mins(1)); shutdown_signal().await; info!("Shutdown signal received; stopping gateway"); diff --git a/crates/openshell-server/src/persistence/atomic.rs b/crates/openshell-server/src/persistence/atomic.rs new file mode 100644 index 0000000000..d4966328c3 --- /dev/null +++ b/crates/openshell-server/src/persistence/atomic.rs @@ -0,0 +1,65 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +use super::{ + ObjectId, ObjectLabels, ObjectName, ObjectType, ObjectWorkspace, PersistenceError, + PersistenceResult, +}; +use prost::Message; + +/// An insert or version-fenced replacement in a multi-object transaction. +/// `None` creates; `Some(version)` replaces exactly that version. +#[derive(Debug, Clone)] +pub struct AtomicObjectWrite { + pub object_type: String, + pub id: String, + pub name: String, + pub workspace: String, + pub scope: Option, + pub payload: Vec, + pub labels: Option, + pub expected_version: Option, +} + +impl AtomicObjectWrite { + pub fn message< + T: Message + ObjectType + ObjectId + ObjectName + ObjectWorkspace + ObjectLabels, + >( + message: &T, + expected_version: Option, + ) -> PersistenceResult { + if T::requires_workspace() && message.object_workspace().is_empty() { + return Err(PersistenceError::Encode("missing object workspace".into())); + } + Ok(Self { + object_type: T::object_type().into(), + id: message.object_id().into(), + name: message.object_name().into(), + workspace: message.object_workspace().into(), + scope: None, + payload: message.encode_to_vec(), + labels: message + .object_labels() + .filter(|labels| !labels.is_empty()) + .map(|labels| serde_json::to_string(&labels)) + .transpose() + .map_err(|error| PersistenceError::Encode(error.to_string()))?, + expected_version, + }) + } +} + +pub(super) fn validate(writes: &[AtomicObjectWrite]) -> PersistenceResult<()> { + let mut ids = std::collections::HashSet::new(); + if writes.is_empty() + || writes.len() > 16 + || writes.iter().any(|write| { + write.id.is_empty() || write.expected_version == Some(0) || !ids.insert(&write.id) + }) + { + return Err(PersistenceError::Encode( + "invalid atomic object write batch".into(), + )); + } + Ok(()) +} diff --git a/crates/openshell-server/src/persistence/legacy_time_wire.rs b/crates/openshell-server/src/persistence/legacy_time_wire.rs index d5f12bab1b..b2ae168952 100644 --- a/crates/openshell-server/src/persistence/legacy_time_wire.rs +++ b/crates/openshell-server/src/persistence/legacy_time_wire.rs @@ -608,6 +608,8 @@ mod tests { "openshell.storage.v1.DraftChunkPayload", "openshell.storage.v1.StoredPolicyRevision", "openshell.storage.v1.StoredDraftChunk", + "openshell.storage.v1.DelegatedIdentityCredential", + "openshell.storage.v1.DelegatedIdentityRefreshLease", "openshell.internal.pagination.v1.ObjectCursor", ]; let mut violations = Vec::new(); diff --git a/crates/openshell-server/src/persistence/mod.rs b/crates/openshell-server/src/persistence/mod.rs index faa6fdb444..95ee68effa 100644 --- a/crates/openshell-server/src/persistence/mod.rs +++ b/crates/openshell-server/src/persistence/mod.rs @@ -3,9 +3,11 @@ //! Persistence layer for `OpenShell` Server. +mod atomic; mod legacy_time_wire; pub mod lock_order; pub mod mutation_lock; +pub use atomic::AtomicObjectWrite; mod postgres; #[cfg(test)] mod refresh_lock_tests; @@ -310,6 +312,12 @@ macro_rules! store_dispatch_traced { } impl Store { + /// Commit a bounded group of conditional object writes together. Every + /// condition must hold; cancellation before commit rolls back all writes. + pub async fn put_objects_atomic(&self, writes: &[AtomicObjectWrite]) -> PersistenceResult<()> { + atomic::validate(writes)?; + store_dispatch!(self.put_objects_atomic(writes)) + } /// Returns `true` for single-replica backends (`SQLite`) where no lease /// coordination is needed, `false` for multi-replica backends (`Postgres`). pub fn is_single_replica(&self) -> bool { diff --git a/crates/openshell-server/src/persistence/mutation_lock_pg_tests.rs b/crates/openshell-server/src/persistence/mutation_lock_pg_tests.rs index e0b00b6ed6..07c58449d2 100644 --- a/crates/openshell-server/src/persistence/mutation_lock_pg_tests.rs +++ b/crates/openshell-server/src/persistence/mutation_lock_pg_tests.rs @@ -130,6 +130,7 @@ async fn postgres_concurrent_sandbox_creates_with_ssh_identity_do_not_exhaust_lo None, Some(encoded), false, + None, lifecycle_guard, mutation_guard, )) diff --git a/crates/openshell-server/src/persistence/postgres.rs b/crates/openshell-server/src/persistence/postgres.rs index 64142e05b5..e42c0c1f2f 100644 --- a/crates/openshell-server/src/persistence/postgres.rs +++ b/crates/openshell-server/src/persistence/postgres.rs @@ -276,6 +276,42 @@ pub const DEFAULT_MAX_CONNECTIONS: u32 = 10; pub const MIN_MAX_CONNECTIONS: u32 = 2; impl PostgresStore { + pub async fn put_objects_atomic( + &self, + writes: &[super::AtomicObjectWrite], + ) -> PersistenceResult<()> { + let mut tx = self.pool.begin().await.map_err(|e| map_db_error(&e))?; + let now = current_time_ms(); + // Stable order avoids deadlocks between overlapping batches on Postgres. + let mut ordered: Vec<_> = writes.iter().collect(); + ordered.sort_by(|a, b| a.id.cmp(&b.id)); + for write in ordered { + if let Some(version) = write.expected_version { + let result = sqlx::query( + "UPDATE objects SET payload = $3, labels = $4, updated_at_ms = $5, resource_version = resource_version + 1 WHERE object_type = $1 AND id = $2 AND resource_version = $6" + ) + .bind(&write.object_type).bind(&write.id).bind(&write.payload) + .bind(write.labels.as_deref().map(serde_json::from_str::).transpose().map_err(|error| PersistenceError::Encode(error.to_string()))?).bind(now) + .bind(i64::try_from(version).map_err(|_| PersistenceError::Encode("resource version overflow".into()))?) + .execute(&mut *tx).await.map_err(|e| map_db_error(&e))?; + if result.rows_affected() != 1 { + return Err(PersistenceError::Conflict { + current_resource_version: None, + }); + } + } else { + sqlx::query( + "INSERT INTO objects (object_type, id, name, workspace, scope, payload, labels, created_at_ms, updated_at_ms, resource_version) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $8, 1)" + ) + .bind(&write.object_type).bind(&write.id).bind(&write.name) + .bind(&write.workspace).bind(&write.scope).bind(&write.payload) + .bind(write.labels.as_deref().map(serde_json::from_str::).transpose().map_err(|error| PersistenceError::Encode(error.to_string()))?).bind(now) + .execute(&mut *tx).await.map_err(|e| map_db_error(&e))?; + } + } + tx.commit().await.map_err(|e| map_db_error(&e)) + } + /// Connect and size the data and lock pools, falling back to /// [`DEFAULT_MAX_CONNECTIONS`] and `MUTATION_LOCK_POOL_MAX_CONNECTIONS`. pub async fn connect( diff --git a/crates/openshell-server/src/persistence/sqlite.rs b/crates/openshell-server/src/persistence/sqlite.rs index 77b13434e4..7d333a3a88 100644 --- a/crates/openshell-server/src/persistence/sqlite.rs +++ b/crates/openshell-server/src/persistence/sqlite.rs @@ -203,6 +203,45 @@ async fn pool_journal_settings(pool: &SqlitePool) -> PersistenceResult<(String, } impl SqliteStore { + pub async fn put_objects_atomic( + &self, + writes: &[super::AtomicObjectWrite], + ) -> PersistenceResult<()> { + let mut tx = self + .pool + .begin_with("BEGIN IMMEDIATE") + .await + .map_err(|e| map_db_error(&e))?; + let now = current_time_ms(); + // Stable order avoids deadlocks between overlapping batches on Postgres. + let mut ordered: Vec<_> = writes.iter().collect(); + ordered.sort_by(|a, b| a.id.cmp(&b.id)); + for write in ordered { + if let Some(version) = write.expected_version { + let result = sqlx::query( + "UPDATE objects SET payload = ?3, labels = ?4, updated_at_ms = ?5, resource_version = resource_version + 1 WHERE object_type = ?1 AND id = ?2 AND resource_version = ?6" + ) + .bind(&write.object_type).bind(&write.id).bind(&write.payload) + .bind(write.labels.as_deref()).bind(now) + .bind(i64::try_from(version).map_err(|_| PersistenceError::Encode("resource version overflow".into()))?) + .execute(&mut *tx).await.map_err(|e| map_db_error(&e))?; + if result.rows_affected() != 1 { + return Err(PersistenceError::Conflict { + current_resource_version: None, + }); + } + } else { + sqlx::query( + "INSERT INTO objects (object_type, id, name, workspace, scope, payload, labels, created_at_ms, updated_at_ms, resource_version) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?8, 1)" + ) + .bind(&write.object_type).bind(&write.id).bind(&write.name) + .bind(&write.workspace).bind(&write.scope).bind(&write.payload) + .bind(write.labels.as_deref()).bind(now) + .execute(&mut *tx).await.map_err(|e| map_db_error(&e))?; + } + } + tx.commit().await.map_err(|e| map_db_error(&e)) + } /// Closes the connection pool. #[cfg(test)] pub(crate) async fn close_for_test(&self) { diff --git a/crates/openshell-server/src/persistence/tests.rs b/crates/openshell-server/src/persistence/tests.rs index 93a1d94c8e..9611dbb37f 100644 --- a/crates/openshell-server/src/persistence/tests.rs +++ b/crates/openshell-server/src/persistence/tests.rs @@ -8,6 +8,75 @@ use openshell_core::proto::{ObjectForTest, Sandbox, SandboxPolicy, SandboxSpec}; use prost::Message; use std::collections::HashMap as StdHashMap; +async fn verify_atomic_object_transactions(store: &Store) { + use super::AtomicObjectWrite; + let prefix = uuid::Uuid::new_v4().to_string(); + let write = |suffix: &str| AtomicObjectWrite { + object_type: "atomic-test".into(), + id: format!("{prefix}-{suffix}"), + name: format!("{prefix}-{suffix}"), + workspace: "default".into(), + scope: Some(prefix.clone()), + payload: b"original".to_vec(), + labels: Some(r#"{"test":"atomic"}"#.into()), + expected_version: None, + }; + let first = write("a"); + let second = write("b"); + store + .put_objects_atomic(&[first.clone(), second.clone()]) + .await + .unwrap(); + let mut update_first = first.clone(); + update_first.expected_version = Some(1); + update_first.payload = b"replacement".to_vec(); + let mut stale_second = second.clone(); + stale_second.expected_version = Some(99); + assert!(matches!( + store + .put_objects_atomic(&[update_first.clone(), stale_second]) + .await, + Err(PersistenceError::Conflict { .. }) + )); + let retained = store.get("atomic-test", &first.id).await.unwrap().unwrap(); + assert_eq!(retained.payload, b"original"); + assert_eq!(retained.resource_version, 1); + let left = [update_first.clone()]; + let right = [update_first]; + let (left, right) = tokio::join!( + store.put_objects_atomic(&left), + store.put_objects_atomic(&right) + ); + assert_ne!( + left.is_ok(), + right.is_ok(), + "exactly one version-fenced writer wins" + ); + assert_eq!( + store + .get("atomic-test", &first.id) + .await + .unwrap() + .unwrap() + .resource_version, + 2 + ); + store.delete("atomic-test", &first.id).await.unwrap(); + store.delete("atomic-test", &second.id).await.unwrap(); +} + +#[tokio::test] +async fn atomic_objects_sqlite_roll_back_and_serialize_writers() { + verify_atomic_object_transactions(&test_store().await).await; +} + +#[tokio::test] +#[ignore = "requires a disposable database in OPENSHELL_TEST_POSTGRES_URL"] +async fn atomic_objects_postgres_roll_back_and_serialize_writers() { + let url = std::env::var("OPENSHELL_TEST_POSTGRES_URL").expect("Postgres test URL"); + verify_atomic_object_transactions(&Store::connect(&url).await.unwrap()).await; +} + /// A failed store call must be visible as a failure in the trace, not as a /// span that merely happened to return nothing. #[tokio::test] diff --git a/crates/openshell-server/src/storage_proto.rs b/crates/openshell-server/src/storage_proto.rs index 3ffff8945e..fc1e6e3932 100644 --- a/crates/openshell-server/src/storage_proto.rs +++ b/crates/openshell-server/src/storage_proto.rs @@ -107,6 +107,90 @@ impl ObjectWorkspace for StoredProviderCredentialRefreshStateV2 { } } +impl ObjectId for DelegatedIdentityCredential { + fn object_id(&self) -> &str { + self.metadata.as_ref().map_or("", |m| m.id.as_str()) + } +} + +impl ObjectName for DelegatedIdentityCredential { + fn object_name(&self) -> &str { + self.metadata.as_ref().map_or("", |m| m.name.as_str()) + } +} + +impl ObjectLabels for DelegatedIdentityCredential { + fn object_labels(&self) -> Option> { + self.metadata.as_ref().map(|m| m.labels.clone()) + } +} + +impl SetResourceVersion for DelegatedIdentityCredential { + fn set_resource_version(&mut self, version: u64) { + if let Some(meta) = self.metadata.as_mut() { + meta.resource_version = version; + } + } +} + +impl GetResourceVersion for DelegatedIdentityCredential { + fn get_resource_version(&self) -> u64 { + self.metadata.as_ref().map_or(0, |m| m.resource_version) + } +} + +impl ObjectWorkspace for DelegatedIdentityCredential { + fn object_workspace(&self) -> &str { + self.metadata.as_ref().map_or("", |m| m.workspace.as_str()) + } + + fn requires_workspace() -> bool { + false + } +} + +impl ObjectId for SandboxDelegatedIdentityRecord { + fn object_id(&self) -> &str { + self.metadata.as_ref().map_or("", |m| m.id.as_str()) + } +} + +impl ObjectName for SandboxDelegatedIdentityRecord { + fn object_name(&self) -> &str { + self.metadata.as_ref().map_or("", |m| m.name.as_str()) + } +} + +impl ObjectLabels for SandboxDelegatedIdentityRecord { + fn object_labels(&self) -> Option> { + self.metadata.as_ref().map(|m| m.labels.clone()) + } +} + +impl SetResourceVersion for SandboxDelegatedIdentityRecord { + fn set_resource_version(&mut self, version: u64) { + if let Some(meta) = self.metadata.as_mut() { + meta.resource_version = version; + } + } +} + +impl GetResourceVersion for SandboxDelegatedIdentityRecord { + fn get_resource_version(&self) -> u64 { + self.metadata.as_ref().map_or(0, |m| m.resource_version) + } +} + +impl ObjectWorkspace for SandboxDelegatedIdentityRecord { + fn object_workspace(&self) -> &str { + self.metadata.as_ref().map_or("", |m| m.workspace.as_str()) + } + + fn requires_workspace() -> bool { + true + } +} + #[cfg(test)] mod tests { use super::*; @@ -117,7 +201,7 @@ mod tests { use std::collections::{BTreeMap, BTreeSet, VecDeque}; const STORAGE_V1_SCHEMA_SHA256: &str = - "d68401809d8cea445c35233ef32412bbd041cb2ac5acaf368a0d0bf74d2ddf17"; + "e7ac6ea301c0a676152903b6de3cccebbca8126bb33eacd89fda817b29777281"; // Restart policy is stored in SandboxSpec, and the count and well-known // timestamps are stored in SandboxStatus. Legacy payloads decode with // Unspecified (treated as Never), zero count, and absent timestamps. @@ -125,7 +209,9 @@ mod tests { // additive declaration changes the durable and public/durable overlap // inventories; the provider-environment file map is public-only. The // request has no provider-file capability field: older supervisors ignore - // the additive file map while retaining the rest of the response. + // the additive file map while retaining the rest of the response. Delegated + // identity is stored alongside the existing durable sandbox and credential + // state while public lifecycle messages remain wire-compatible. // Preparation timing adds two optional timestamps to SandboxProvisioning. // Existing rows decode with both absent and retain their active deadline; // no stored attempt gains another phase or time budget on upgrade. @@ -155,11 +241,11 @@ mod tests { // closures and TokenGrantStatus to public reports. Older rows have no grant // record; absent report extensions retain compatibility without a rewrite. const PUBLIC_RPC_SCHEMA_SHA256: &str = - "cc06b38c9ecb4c6518e0d4963f40c4f16f992483ce111c7b23cbcb729db946ea"; + "cfb2613ea2e9ca04b267bd85f66621792583bf5deed7db662764e7f64663faea"; const DURABLE_SCHEMA_SHA256: &str = - "d4dc7abdb91b919db1f7be0d4652e30cbfda86f3b2ed9b59d5de1f76a2bf691d"; + "53a333ea5357449db393d47d7cab53a45772916c3193bd161d77fe80794c4947"; const PUBLIC_DURABLE_OVERLAP_SHA256: &str = - "ac1ffc4b5b81467de27270a4e33f6db1e31eeb2c52f79750dbaaf19515eadbb2"; + "c1c2a0aed64f3ce8544ca4a092856b19e1d3f6010036f3ad78acff7351be9bd1"; // Encoded with the schema that omitted stable_placeholder. Keep these // bytes fixed so the current encoder cannot hide a compatibility change. const LEGACY_STATIC_PROFILE: &str = "0a240a116c65676163792d70726f66696c652d6964120d6c65676163792d737461746963280712510a0d6c65676163792d73746174696312184c6567616379207374617469632063726564656e7469616c2a260a076170695f6b65791a1153594e5448455449435f4150495f4b455920012a06626561726572"; @@ -184,9 +270,12 @@ mod tests { // Field 1 is ignored while the remaining durable status fields retain their tags. const PRE_CANONICAL_SANDBOX_REFERENCE_STATUS: &str = "0a0b6c65676163792d6e616d6512056167656e7430023807"; - const STORAGE_MESSAGE_NAMES: [&str; 9] = [ + const STORAGE_MESSAGE_NAMES: [&str; 12] = [ + "DelegatedIdentityCredential", + "DelegatedIdentityRefreshLease", "DraftChunkPayload", "PolicyRevisionPayload", + "SandboxDelegatedIdentityRecord", "StoredConfigUpdateOperation", "StoredDraftChunk", "StoredPolicyRevision", @@ -213,11 +302,13 @@ mod tests { ".openshell.datamodel.v1.Workspace", ".openshell.sandbox.v1.SandboxPolicy", ".openshell.storage.v1.DraftChunkPayload", + ".openshell.storage.v1.DelegatedIdentityCredential", ".openshell.storage.v1.PolicyRevisionPayload", ".openshell.storage.v1.StoredConfigUpdateOperation", ".openshell.storage.v1.StoredProviderCredentialRefreshStateV2", ".openshell.storage.v1.StoredProviderProfile", ".openshell.v1.Sandbox", + ".openshell.storage.v1.SandboxDelegatedIdentityRecord", ".openshell.v1.SandboxWorkloadTemplate", ".openshell.v1.ServiceEndpoint", ".openshell.v1.SshSession", @@ -454,6 +545,53 @@ mod tests { ); } + #[test] + fn delegated_identity_storage_reserves_inline_token_fields() { + let descriptor = FileDescriptorSet::decode(STORAGE_FILE_DESCRIPTOR_SET) + .expect("storage descriptor set must decode"); + let message = descriptor + .file + .iter() + .find(|file| file.package.as_deref() == Some("openshell.storage.v1")) + .and_then(|file| { + file.message_type + .iter() + .find(|message| message.name.as_deref() == Some("DelegatedIdentityCredential")) + }) + .expect("delegated identity credential descriptor"); + + assert!( + message + .reserved_name + .iter() + .any(|name| name == "refresh_token") + ); + assert!( + message + .reserved_name + .iter() + .any(|name| name == "access_token") + ); + for field_number in [5, 6] { + assert!(message.reserved_range.iter().any(|range| { + range.start.is_some_and(|start| start <= field_number) + && range.end.is_some_and(|end| end > field_number) + })); + } + assert!( + message + .field + .iter() + .any(|field| field.name.as_deref() == Some("secret_material_handles")) + ); + assert!( + message + .field + .iter() + .any(|field| field.name.as_deref() == Some("pending_secret_deletions")) + ); + } + #[test] fn storage_types_are_absent_from_public_descriptor() { let public = FileDescriptorSet::decode(openshell_core::FILE_DESCRIPTOR_SET) @@ -570,12 +708,12 @@ mod tests { } assert_eq!( compiled_method_count, - 103 + PROVIDER_READINESS_RPC_SIGNATURES.len() + PEER_OWNER_RPC_SIGNATURES.len(), + 113 + PROVIDER_READINESS_RPC_SIGNATURES.len() + PEER_OWNER_RPC_SIGNATURES.len(), "classify every compiled RPC" ); assert_eq!( methods.len(), - 78 + PROVIDER_READINESS_RPC_SIGNATURES.len() + PEER_OWNER_RPC_SIGNATURES.len(), + 87 + PROVIDER_READINESS_RPC_SIGNATURES.len() + PEER_OWNER_RPC_SIGNATURES.len(), "inventory every public gateway RPC" ); assert_eq!( @@ -583,7 +721,7 @@ mod tests { .iter() .filter(|method| method.starts_with("openshell.v1.OpenShell/")) .count(), - 78 + PROVIDER_READINESS_RPC_SIGNATURES.len() + PEER_OWNER_RPC_SIGNATURES.len() + 87 + PROVIDER_READINESS_RPC_SIGNATURES.len() + PEER_OWNER_RPC_SIGNATURES.len() ); assert!(methods.iter().all(|method| !method.contains(".storage."))); @@ -627,9 +765,9 @@ mod tests { overlap_hash.as_str(), ), ( - (314, 28), - (95, 22), - (83, 22), + (336, 28), + (100, 22), + (84, 22), PUBLIC_RPC_SCHEMA_SHA256, DURABLE_SCHEMA_SHA256, PUBLIC_DURABLE_OVERLAP_SHA256 diff --git a/crates/openshell-server/tests/common/mod.rs b/crates/openshell-server/tests/common/mod.rs index 146169d7ba..1c43a6ce4c 100644 --- a/crates/openshell-server/tests/common/mod.rs +++ b/crates/openshell-server/tests/common/mod.rs @@ -135,6 +135,25 @@ impl OpenShell for TestOpenShell { Ok(Response::new(SandboxResponse::default())) } + async fn get_delegated_identity_authorization_status( + &self, + _request: tonic::Request< + openshell_core::proto::GetDelegatedIdentityAuthorizationStatusRequest, + >, + ) -> Result< + Response, + Status, + > { + Err(Status::unimplemented("unused")) + } + + async fn authorize_delegated_identity( + &self, + _request: tonic::Request, + ) -> Result, Status> { + Err(Status::unimplemented("unused")) + } + async fn create_sandbox_template( &self, _request: tonic::Request, @@ -177,6 +196,30 @@ impl OpenShell for TestOpenShell { Err(Status::unimplemented("unused")) } + async fn get_sandbox_delegated_identity_status( + &self, + _request: tonic::Request, + ) -> Result, Status> + { + Err(Status::unimplemented("unused")) + } + + async fn withdraw_sandbox_delegated_identity( + &self, + _request: tonic::Request, + ) -> Result, Status> + { + Err(Status::unimplemented("unused")) + } + + async fn extend_sandbox_delegated_identity( + &self, + _request: tonic::Request, + ) -> Result, Status> + { + Err(Status::unimplemented("unused")) + } + async fn get_sandbox( &self, _request: tonic::Request, @@ -200,6 +243,40 @@ impl OpenShell for TestOpenShell { )) } + async fn list_delegated_identity_credentials( + &self, + _request: tonic::Request, + ) -> Result, Status> + { + Err(Status::unimplemented("unused")) + } + + async fn get_delegated_identity_credential_status( + &self, + _request: tonic::Request< + openshell_core::proto::GetDelegatedIdentityCredentialStatusRequest, + >, + ) -> Result, Status> + { + Err(Status::unimplemented("unused")) + } + + async fn revoke_delegated_identity_credential( + &self, + _request: tonic::Request, + ) -> Result, Status> + { + Err(Status::unimplemented("unused")) + } + + async fn delete_delegated_identity_credential( + &self, + _request: tonic::Request, + ) -> Result, Status> + { + Err(Status::unimplemented("unused")) + } + async fn attach_sandbox_provider( &self, _request: tonic::Request, diff --git a/crates/openshell-server/tests/supervisor_relay_integration.rs b/crates/openshell-server/tests/supervisor_relay_integration.rs index dfb89f18c3..4e7e8462ad 100644 --- a/crates/openshell-server/tests/supervisor_relay_integration.rs +++ b/crates/openshell-server/tests/supervisor_relay_integration.rs @@ -108,6 +108,25 @@ impl OpenShell for RelayGateway { Err(Status::unimplemented("not used by this test server")) } + async fn get_delegated_identity_authorization_status( + &self, + _request: tonic::Request< + openshell_core::proto::GetDelegatedIdentityAuthorizationStatusRequest, + >, + ) -> Result< + Response, + Status, + > { + Err(Status::unimplemented("unused")) + } + + async fn authorize_delegated_identity( + &self, + _request: tonic::Request, + ) -> Result, Status> { + Err(Status::unimplemented("unused")) + } + type RelayStreamStream = std::pin::Pin< Box> + Send + 'static>, >; @@ -176,6 +195,62 @@ impl OpenShell for RelayGateway { Err(Status::unimplemented("unused")) } + async fn get_sandbox_delegated_identity_status( + &self, + _: tonic::Request, + ) -> Result, Status> + { + Err(Status::unimplemented("unused")) + } + + async fn withdraw_sandbox_delegated_identity( + &self, + _: tonic::Request, + ) -> Result, Status> + { + Err(Status::unimplemented("unused")) + } + + async fn extend_sandbox_delegated_identity( + &self, + _: tonic::Request, + ) -> Result, Status> + { + Err(Status::unimplemented("unused")) + } + + async fn list_delegated_identity_credentials( + &self, + _: tonic::Request, + ) -> Result, Status> + { + Err(Status::unimplemented("unused")) + } + + async fn get_delegated_identity_credential_status( + &self, + _: tonic::Request, + ) -> Result, Status> + { + Err(Status::unimplemented("unused")) + } + + async fn revoke_delegated_identity_credential( + &self, + _: tonic::Request, + ) -> Result, Status> + { + Err(Status::unimplemented("unused")) + } + + async fn delete_delegated_identity_credential( + &self, + _: tonic::Request, + ) -> Result, Status> + { + Err(Status::unimplemented("unused")) + } + type ExecSandboxStream = ReceiverStream>; async fn exec_sandbox( diff --git a/crates/openshell-supervisor-network/src/token_grant.rs b/crates/openshell-supervisor-network/src/token_grant.rs index e165754105..2a14c21d69 100644 --- a/crates/openshell-supervisor-network/src/token_grant.rs +++ b/crates/openshell-supervisor-network/src/token_grant.rs @@ -56,6 +56,7 @@ static TOKEN_GRANT_HTTP_CLIENT: LazyLock Option { - let now_ms = current_time_ms(); + /// Get a cached token if it exists and is not expired at `now_ms`. + fn get_at(&self, provider_name: &str, now_ms: i64) -> Option { let tokens = self.tokens.read().ok()?; let cached = tokens.get(provider_name)?; if cached.expires_at_ms > now_ms { @@ -251,6 +251,18 @@ async fn obtain_provider_token_with_grant( input: ObtainProviderTokenInput<'_>, grant: F, ) -> Result +where + F: FnOnce(String) -> Fut, + Fut: Future>, +{ + obtain_provider_token_with_grant_at(input, grant, current_time_ms()).await +} + +async fn obtain_provider_token_with_grant_at( + input: ObtainProviderTokenInput<'_>, + grant: F, + now_ms: i64, +) -> Result where F: FnOnce(String) -> Fut, Fut: Future>, @@ -268,7 +280,7 @@ where }); if input.cache_ttl_override != Some(Duration::ZERO) - && let Some(cached) = input.cache.get(&cache_key) + && let Some(cached) = input.cache.get_at(&cache_key, now_ms) { return Ok(cached); } @@ -283,10 +295,14 @@ where let token_response = grant(jwt_audience).await?; validate_access_token(&token_response.access_token)?; - let cache_ttl = token_cache_ttl(input.cache_ttl_override, token_response.expires_in); + let cache_ttl = token_cache_ttl( + input.cache_ttl_override, + token_response.expires_in, + input.grant_type, + ); if !cache_ttl.is_zero() { let ttl_ms = i64::try_from(cache_ttl.as_millis()).unwrap_or(i64::MAX); - let expires_at_ms = current_time_ms().saturating_add(ttl_ms); + let expires_at_ms = now_ms.saturating_add(ttl_ms); input.cache.set( cache_key, token_response.access_token.clone(), @@ -406,9 +422,22 @@ async fn perform_token_exchange( pub use oauth::validate_access_token; -fn token_cache_ttl(cache_ttl_override: Option, expires_in: i64) -> Duration { +fn token_cache_ttl( + cache_ttl_override: Option, + expires_in: i64, + grant_type: ProviderCredentialTokenGrantType, +) -> Duration { if let Some(cache_ttl_override) = cache_ttl_override { - return cache_ttl_override; + // Delegated-identity withdrawal and credential revocation rely on + // token-exchange cache entries expiring within this bounded window. + // Explicit overrides therefore remain subject to the grant-type cap. + return if grant_type == ProviderCredentialTokenGrantType::TokenExchange { + cache_ttl_override.min(Duration::from_secs( + u64::try_from(MAX_TOKEN_EXCHANGE_CACHE_TTL_SECONDS).unwrap_or(u64::MAX), + )) + } else { + cache_ttl_override + }; } let ttl = if expires_in > 0 { @@ -416,6 +445,7 @@ fn token_cache_ttl(cache_ttl_override: Option, expires_in: i64) -> Dur } else { DEFAULT_TOKEN_CACHE_TTL_SECONDS }; + let ttl = token_cache_ttl_cap_for_grant_type(ttl, grant_type); Duration::from_secs( u64::try_from(ttl.saturating_sub(TOKEN_CACHE_EXPIRY_SKEW_SECONDS).max(1)) @@ -423,6 +453,17 @@ fn token_cache_ttl(cache_ttl_override: Option, expires_in: i64) -> Dur ) } +fn token_cache_ttl_cap_for_grant_type( + ttl_seconds: i64, + grant_type: ProviderCredentialTokenGrantType, +) -> i64 { + if grant_type == ProviderCredentialTokenGrantType::TokenExchange { + ttl_seconds.min(MAX_TOKEN_EXCHANGE_CACHE_TTL_SECONDS) + } else { + ttl_seconds + } +} + /// Derive the issuer/realm URL from a token endpoint URL. /// /// For Keycloak token endpoints like: @@ -862,6 +903,7 @@ mod tests { scopes: &'a [String], cache_ttl_override: Option, expires_in: i64, + grant_type: ProviderCredentialTokenGrantType, grant_calls: Arc, } @@ -878,7 +920,7 @@ mod tests { audience: input.audience, scopes: input.scopes, cache_ttl_override: input.cache_ttl_override, - grant_type: ProviderCredentialTokenGrantType::ClientCredentials, + grant_type: input.grant_type, requested_token_type: "", }, move |_| { @@ -896,26 +938,31 @@ mod tests { .await } - async fn obtain_token_without_grant_call( - cache: &TokenCache, - provider_name: &str, - token_endpoint: &str, - jwt_svid_audience: &str, - audience: &str, - scopes: &[String], + struct CachedTokenLookupInput<'a> { + cache: &'a TokenCache, + provider_name: &'a str, + token_endpoint: &'a str, + jwt_svid_audience: &'a str, + audience: &'a str, + scopes: &'a [String], cache_ttl_override: Option, + grant_type: ProviderCredentialTokenGrantType, + } + + async fn obtain_token_without_grant_call( + input: CachedTokenLookupInput<'_>, ) -> Result { obtain_provider_token_with_grant( ObtainProviderTokenInput { - cache, - provider_name, - token_endpoint, - jwt_svid_audience, + cache: input.cache, + provider_name: input.provider_name, + token_endpoint: input.token_endpoint, + jwt_svid_audience: input.jwt_svid_audience, client_assertion_type: DEFAULT_CLIENT_ASSERTION_TYPE, - audience, - scopes, - cache_ttl_override, - grant_type: ProviderCredentialTokenGrantType::ClientCredentials, + audience: input.audience, + scopes: input.scopes, + cache_ttl_override: input.cache_ttl_override, + grant_type: input.grant_type, requested_token_type: "", }, |_| async { panic!("grant should not be called on cache hit") }, @@ -940,6 +987,7 @@ mod tests { scopes: &[], cache_ttl_override: ttl, expires_in: 60, + grant_type: ProviderCredentialTokenGrantType::ClientCredentials, grant_calls: grant_calls.clone(), }) .await @@ -1109,11 +1157,19 @@ mod tests { #[test] fn token_cache_ttl_uses_override_without_endpoint_skew() { assert_eq!( - token_cache_ttl(Some(Duration::from_mins(2)), 10), + token_cache_ttl( + Some(Duration::from_mins(2)), + 10, + ProviderCredentialTokenGrantType::ClientCredentials, + ), Duration::from_mins(2) ); assert_eq!( - token_cache_ttl(Some(Duration::from_mins(2)), i64::MAX), + token_cache_ttl( + Some(Duration::from_mins(2)), + i64::MAX, + ProviderCredentialTokenGrantType::ClientCredentials, + ), Duration::from_mins(2) ); } @@ -1121,29 +1177,86 @@ mod tests { #[test] fn token_cache_ttl_preserves_fractional_and_zero_overrides() { assert_eq!( - token_cache_ttl(Some(Duration::from_millis(500)), 60), + token_cache_ttl( + Some(Duration::from_millis(500)), + 60, + ProviderCredentialTokenGrantType::ClientCredentials, + ), Duration::from_millis(500) ); - assert_eq!(token_cache_ttl(Some(Duration::ZERO), 60), Duration::ZERO); + assert_eq!( + token_cache_ttl( + Some(Duration::ZERO), + 60, + ProviderCredentialTokenGrantType::ClientCredentials, + ), + Duration::ZERO + ); + } + + #[test] + fn token_exchange_cache_ttl_clamps_overrides_to_five_minutes() { + assert_eq!( + token_cache_ttl( + Some(Duration::from_mins(2)), + 10, + ProviderCredentialTokenGrantType::TokenExchange, + ), + Duration::from_mins(2) + ); + assert_eq!( + token_cache_ttl( + Some(Duration::from_mins(5)), + 10, + ProviderCredentialTokenGrantType::TokenExchange, + ), + Duration::from_mins(5) + ); + assert_eq!( + token_cache_ttl( + Some(Duration::from_mins(10)), + 10, + ProviderCredentialTokenGrantType::TokenExchange, + ), + Duration::from_secs(u64::try_from(MAX_TOKEN_EXCHANGE_CACHE_TTL_SECONDS).unwrap()) + ); } #[test] fn token_cache_ttl_skews_default_and_response_expires_in() { assert_eq!( - token_cache_ttl(None, 0), + token_cache_ttl(None, 0, ProviderCredentialTokenGrantType::ClientCredentials), Duration::from_secs( u64::try_from(DEFAULT_TOKEN_CACHE_TTL_SECONDS - TOKEN_CACHE_EXPIRY_SKEW_SECONDS) .unwrap() ) ); - assert_eq!(token_cache_ttl(None, 60), Duration::from_secs(30)); - assert_eq!(token_cache_ttl(None, 10), Duration::from_secs(1)); + assert_eq!( + token_cache_ttl( + None, + 60, + ProviderCredentialTokenGrantType::ClientCredentials + ), + Duration::from_secs(30) + ); + assert_eq!( + token_cache_ttl( + None, + 10, + ProviderCredentialTokenGrantType::ClientCredentials + ), + Duration::from_secs(1) + ); } #[test] fn token_cache_ttl_clamps_large_response_expires_in() { assert_eq!( - token_cache_ttl(None, i64::MAX), + token_cache_ttl( + None, + i64::MAX, + ProviderCredentialTokenGrantType::ClientCredentials, + ), Duration::from_secs( u64::try_from(MAX_TOKEN_EXPIRES_IN_SECONDS - TOKEN_CACHE_EXPIRY_SKEW_SECONDS) .unwrap() @@ -1168,6 +1281,7 @@ mod tests { scopes: &scopes, cache_ttl_override: None, expires_in: 120, + grant_type: ProviderCredentialTokenGrantType::ClientCredentials, grant_calls: calls.clone(), }) .await @@ -1185,15 +1299,16 @@ mod tests { requested_token_type: ACCESS_TOKEN_TYPE, }); cache.set(identity_cache_key, "expired".into(), current_time_ms() - 1); - let service_token = obtain_token_without_grant_call( - &cache, - service, - "https://auth.example.com/token", - "https://auth.example.com", - "api://resource", - &scopes, - None, - ) + let service_token = obtain_token_without_grant_call(CachedTokenLookupInput { + cache: &cache, + provider_name: service, + token_endpoint: "https://auth.example.com/token", + jwt_svid_audience: "https://auth.example.com", + audience: "api://resource", + scopes: &scopes, + cache_ttl_override: None, + grant_type: ProviderCredentialTokenGrantType::ClientCredentials, + }) .await .unwrap(); assert_eq!(service_token, "token-1"); @@ -1206,6 +1321,7 @@ mod tests { scopes: &scopes, cache_ttl_override: None, expires_in: 120, + grant_type: ProviderCredentialTokenGrantType::ClientCredentials, grant_calls: calls.clone(), }) .await @@ -1229,19 +1345,21 @@ mod tests { scopes: &scopes, cache_ttl_override: None, expires_in: 60, + grant_type: ProviderCredentialTokenGrantType::ClientCredentials, grant_calls: grant_calls.clone(), }) .await .expect("first call should grant token"); - let second = obtain_token_without_grant_call( - &cache, - "api.example.test\t443\t/v1/**\tprovider:access_token", - "https://auth.example.com/token", - "https://auth.example.com", - "api://resource", - &scopes, - None, - ) + let second = obtain_token_without_grant_call(CachedTokenLookupInput { + cache: &cache, + provider_name: "api.example.test\t443\t/v1/**\tprovider:access_token", + token_endpoint: "https://auth.example.com/token", + jwt_svid_audience: "https://auth.example.com", + audience: "api://resource", + scopes: &scopes, + cache_ttl_override: None, + grant_type: ProviderCredentialTokenGrantType::ClientCredentials, + }) .await .expect("second call should use cache"); @@ -1250,6 +1368,169 @@ mod tests { assert_eq!(grant_calls.load(Ordering::SeqCst), 1); } + #[tokio::test] + async fn obtain_provider_token_uses_short_cache_for_token_exchange() { + let cache = TokenCache::new(); + let grant_calls = Arc::new(AtomicUsize::new(0)); + let scopes = vec!["read".to_string()]; + + let first = obtain_counted_test_token(CountedTokenGrantInput { + cache: &cache, + provider_name: "api.example.test\t443\t/v1/**\tprovider:access_token", + token_endpoint: "https://auth.example.com/token", + jwt_svid_audience: "https://auth.example.com", + audience: "api://resource", + scopes: &scopes, + cache_ttl_override: None, + expires_in: 60, + grant_type: ProviderCredentialTokenGrantType::TokenExchange, + grant_calls: grant_calls.clone(), + }) + .await + .expect("first token exchange should grant token"); + let second = obtain_token_without_grant_call(CachedTokenLookupInput { + cache: &cache, + provider_name: "api.example.test\t443\t/v1/**\tprovider:access_token", + token_endpoint: "https://auth.example.com/token", + jwt_svid_audience: "https://auth.example.com", + audience: "api://resource", + scopes: &scopes, + cache_ttl_override: None, + grant_type: ProviderCredentialTokenGrantType::TokenExchange, + }) + .await + .expect("second token exchange should use supervisor cache"); + + assert_eq!(first, "token-1"); + assert_eq!(second, "token-1"); + assert_eq!(grant_calls.load(Ordering::SeqCst), 1); + assert_eq!( + token_cache_ttl( + None, + i64::MAX, + ProviderCredentialTokenGrantType::TokenExchange, + ), + Duration::from_secs( + u64::try_from( + MAX_TOKEN_EXCHANGE_CACHE_TTL_SECONDS - TOKEN_CACHE_EXPIRY_SKEW_SECONDS + ) + .unwrap() + ) + ); + assert_eq!( + token_cache_ttl( + Some(Duration::from_mins(2)), + i64::MAX, + ProviderCredentialTokenGrantType::TokenExchange, + ), + Duration::from_mins(2) + ); + assert_eq!( + token_cache_ttl( + Some(Duration::from_mins(10)), + i64::MAX, + ProviderCredentialTokenGrantType::TokenExchange, + ), + Duration::from_secs(u64::try_from(MAX_TOKEN_EXCHANGE_CACHE_TTL_SECONDS).unwrap()) + ); + } + + #[tokio::test] + async fn token_exchange_cache_rechecks_gateway_at_five_minute_bound() { + let cache = TokenCache::new(); + let grant_calls = Arc::new(AtomicUsize::new(0)); + let scopes = vec!["read".to_string()]; + let start_ms = 1_000_000; + + let first_calls = grant_calls.clone(); + let first = obtain_provider_token_with_grant_at( + ObtainProviderTokenInput { + cache: &cache, + provider_name: "api.example.test\t443\t/v1/**\tprovider:access_token", + token_endpoint: "https://auth.example.com/token", + jwt_svid_audience: "https://auth.example.com", + client_assertion_type: DEFAULT_CLIENT_ASSERTION_TYPE, + audience: "api://resource", + scopes: &scopes, + cache_ttl_override: Some(Duration::from_mins(10)), + grant_type: ProviderCredentialTokenGrantType::TokenExchange, + requested_token_type: ACCESS_TOKEN_TYPE, + }, + move |_| async move { + first_calls.fetch_add(1, Ordering::SeqCst); + Ok(OAuthTokenResponse { + access_token: "delegated-token".to_string(), + token_type: "Bearer".to_string(), + expires_in: 3600, + }) + }, + start_ms, + ) + .await + .expect("initial exchange should succeed"); + assert_eq!(first, "delegated-token"); + + let cached_calls = grant_calls.clone(); + let cached = obtain_provider_token_with_grant_at( + ObtainProviderTokenInput { + cache: &cache, + provider_name: "api.example.test\t443\t/v1/**\tprovider:access_token", + token_endpoint: "https://auth.example.com/token", + jwt_svid_audience: "https://auth.example.com", + client_assertion_type: DEFAULT_CLIENT_ASSERTION_TYPE, + audience: "api://resource", + scopes: &scopes, + cache_ttl_override: Some(Duration::from_mins(10)), + grant_type: ProviderCredentialTokenGrantType::TokenExchange, + requested_token_type: ACCESS_TOKEN_TYPE, + }, + move |_| async move { + cached_calls.fetch_add(1, Ordering::SeqCst); + Err(TokenGrantFailure::new( + TokenGrantFailureKind::Unclassified, + TokenGrantStage::GatewayRpc, + )) + }, + start_ms + 299_999, + ) + .await + .expect("cache should remain usable just before the bound"); + assert_eq!(cached, "delegated-token"); + assert_eq!(grant_calls.load(Ordering::SeqCst), 1); + + let denied_calls = grant_calls.clone(); + let error = obtain_provider_token_with_grant_at( + ObtainProviderTokenInput { + cache: &cache, + provider_name: "api.example.test\t443\t/v1/**\tprovider:access_token", + token_endpoint: "https://auth.example.com/token", + jwt_svid_audience: "https://auth.example.com", + client_assertion_type: DEFAULT_CLIENT_ASSERTION_TYPE, + audience: "api://resource", + scopes: &scopes, + cache_ttl_override: Some(Duration::from_mins(10)), + grant_type: ProviderCredentialTokenGrantType::TokenExchange, + requested_token_type: ACCESS_TOKEN_TYPE, + }, + move |_| async move { + denied_calls.fetch_add(1, Ordering::SeqCst); + Err(TokenGrantFailure::new( + TokenGrantFailureKind::ProviderAuthorizationRequired, + TokenGrantStage::GatewayRpc, + )) + }, + start_ms + 300_000, + ) + .await + .expect_err("expired cache entry must recheck the gateway"); + + assert_eq!( + error.kind(), + TokenGrantFailureKind::ProviderAuthorizationRequired + ); + assert_eq!(grant_calls.load(Ordering::SeqCst), 2); + } + #[tokio::test] async fn obtain_provider_token_separates_cache_by_audience_and_scopes() { let cache = TokenCache::new(); @@ -1266,6 +1547,7 @@ mod tests { scopes: &read_scope, cache_ttl_override: None, expires_in: 60, + grant_type: ProviderCredentialTokenGrantType::ClientCredentials, grant_calls: grant_calls.clone(), }) .await @@ -1279,6 +1561,7 @@ mod tests { scopes: &read_scope, cache_ttl_override: None, expires_in: 60, + grant_type: ProviderCredentialTokenGrantType::ClientCredentials, grant_calls: grant_calls.clone(), }) .await @@ -1292,6 +1575,7 @@ mod tests { scopes: &write_scope, cache_ttl_override: None, expires_in: 60, + grant_type: ProviderCredentialTokenGrantType::ClientCredentials, grant_calls: grant_calls.clone(), }) .await @@ -1338,6 +1622,7 @@ mod tests { scopes: &scopes, cache_ttl_override: None, expires_in: 60, + grant_type: ProviderCredentialTokenGrantType::ClientCredentials, grant_calls: grant_calls.clone(), }) .await @@ -1362,19 +1647,21 @@ mod tests { scopes: &scopes, cache_ttl_override: Some(Duration::from_mins(1)), expires_in: 0, + grant_type: ProviderCredentialTokenGrantType::ClientCredentials, grant_calls: grant_calls.clone(), }) .await .expect("first override call should grant token"); - let second = obtain_token_without_grant_call( - &cache, - "api.example.test\t443\t/v1/**\tprovider:access_token", - "https://auth.example.com/token", - "https://auth.example.com", - "api://resource", - &scopes, - Some(Duration::from_mins(1)), - ) + let second = obtain_token_without_grant_call(CachedTokenLookupInput { + cache: &cache, + provider_name: "api.example.test\t443\t/v1/**\tprovider:access_token", + token_endpoint: "https://auth.example.com/token", + jwt_svid_audience: "https://auth.example.com", + audience: "api://resource", + scopes: &scopes, + cache_ttl_override: Some(Duration::from_mins(1)), + grant_type: ProviderCredentialTokenGrantType::ClientCredentials, + }) .await .expect("override should keep token cached"); @@ -1397,6 +1684,7 @@ mod tests { scopes: &scopes, cache_ttl_override: Some(Duration::ZERO), expires_in: 60, + grant_type: ProviderCredentialTokenGrantType::ClientCredentials, grant_calls: grant_calls.clone(), }; diff --git a/crates/openshell-tui/src/lib.rs b/crates/openshell-tui/src/lib.rs index 86529c2e74..3fc5ce655a 100644 --- a/crates/openshell-tui/src/lib.rs +++ b/crates/openshell-tui/src/lib.rs @@ -1442,6 +1442,7 @@ fn spawn_create_sandbox(app: &mut App, tx: mpsc::UnboundedSender) { await_main_process_attachment: false, workload_template: String::new(), service_exposures: Vec::new(), + delegated_identity: None, }; let sandbox_name = diff --git a/deploy/helm/openshell/README.md b/deploy/helm/openshell/README.md index b43974dff1..1a90911d79 100644 --- a/deploy/helm/openshell/README.md +++ b/deploy/helm/openshell/README.md @@ -502,6 +502,7 @@ discovery endpoint or its TLS CA. | server.grpcRateLimit.windowSeconds | int | `0` | gRPC rate-limit window length in seconds. Must be positive (alongside requests) to enable rate limiting; 0 (default) disables it. | | server.hostGatewayIP | string | `""` | Host gateway IP for sandbox pod hostAliases. When set, sandbox pods get hostAliases entries mapping host.docker.internal and host.openshell.internal to this IP, allowing them to reach services running on the Docker host. Auto-detected by the cluster entrypoint script. | | server.logLevel | string | `"info"` | Gateway log level. | +| server.maxDelegatedIdentityDurationSecs | int | `86400` | Maximum delegated identity authorization window for one sandbox, in seconds. | | server.name | string | `""` | Operator-facing gateway name. Defaults to the chart fullname so all replicas in one installation share an identity. Set explicitly when one telemetry collector receives spans from multiple namespaces or clusters. | | server.ocsfLog.enabled | bool | `false` | Write gateway OCSF events as JSONL. | | server.ocsfLog.maxFiles | int | `7` | Rotated files retained when rotation is daily. | diff --git a/deploy/helm/openshell/templates/_toml.tpl b/deploy/helm/openshell/templates/_toml.tpl index 121c93159d..0f57725054 100644 --- a/deploy/helm/openshell/templates/_toml.tpl +++ b/deploy/helm/openshell/templates/_toml.tpl @@ -110,6 +110,12 @@ Top-level lists represent TOML arrays of tables and preserve their YAML order. * {{- range $legacyKey, $runtimeKey := dict "logLevel" "log_level" "enableLoopbackServiceHttp" "enable_loopback_service_http" "enableWebsocketTunnel" "enable_websocket_tunnel" "policyValidationFailureMode" "policy_validation_failure_mode" -}} {{- if not (hasKey $gateway $runtimeKey) -}}{{- $_ := set $gateway $runtimeKey (get $legacyServer $legacyKey) -}}{{- end -}} {{- end -}} +{{- if not (hasKey $gateway "max_delegated_identity_duration_secs") -}} +{{- $duration := 86400 -}} +{{- if hasKey $legacyServer "maxDelegatedIdentityDurationSecs" -}}{{- $duration = int (get $legacyServer "maxDelegatedIdentityDurationSecs") -}}{{- end -}} +{{- if lt $duration 0 -}}{{- fail "server.maxDelegatedIdentityDurationSecs must not be negative; it maps to an unsigned gateway setting" -}}{{- end -}} +{{- $_ := set $gateway "max_delegated_identity_duration_secs" $duration -}} +{{- end -}} {{- if not (hasKey $gateway "compute_driver") -}}{{- $_ := set $gateway "compute_driver" "kubernetes" -}}{{- end -}} {{- $serverDnsNames := .Values.pkiInitJob.serverDnsNames | default list -}} {{- if .Values.certManager.enabled -}}{{- $serverDnsNames = .Values.certManager.serverDnsNames | default list -}}{{- end -}} diff --git a/deploy/helm/openshell/tests/gateway_config_test.yaml b/deploy/helm/openshell/tests/gateway_config_test.yaml index c433bbed69..f778f453d6 100644 --- a/deploy/helm/openshell/tests/gateway_config_test.yaml +++ b/deploy/helm/openshell/tests/gateway_config_test.yaml @@ -847,6 +847,30 @@ tests: path: data["gateway.toml"] pattern: '(?ms)\[openshell\.gateway\].*?policy_validation_failure_mode\s*=\s*"retain_last_valid"' + - it: renders the default delegated identity duration limit + template: templates/gateway-config.yaml + asserts: + - matchRegex: + path: data["gateway.toml"] + pattern: '(?ms)\[openshell\.gateway\].*?max_delegated_identity_duration_secs\s*=\s*86400' + + - it: renders a configured delegated identity duration limit + template: templates/gateway-config.yaml + set: + server.maxDelegatedIdentityDurationSecs: 3600 + asserts: + - matchRegex: + path: data["gateway.toml"] + pattern: '(?ms)\[openshell\.gateway\].*?max_delegated_identity_duration_secs\s*=\s*3600' + + - it: rejects a negative delegated identity duration limit + template: templates/statefulset.yaml + set: + server.maxDelegatedIdentityDurationSecs: -1 + asserts: + - failedTemplate: + errorMessage: "server.maxDelegatedIdentityDurationSecs must not be negative; it maps to an unsigned gateway setting" + - it: renders the gRPC rate limit under [openshell.gateway] when both values are positive template: templates/gateway-config.yaml set: diff --git a/deploy/helm/openshell/values.yaml b/deploy/helm/openshell/values.yaml index 332605fa58..781f124813 100644 --- a/deploy/helm/openshell/values.yaml +++ b/deploy/helm/openshell/values.yaml @@ -446,6 +446,8 @@ server: # -- Posture when a candidate sandbox policy fails validation. `fail_closed` # deactivates the previous policy; `retain_last_valid` keeps it active. policyValidationFailureMode: fail_closed + # -- Maximum delegated identity authorization window for one sandbox, in seconds. + maxDelegatedIdentityDurationSecs: 86400 # Optional gateway-wide gRPC request rate limit. Applies only to gRPC API # traffic after protocol multiplexing; health, metrics, and loopback service # HTTP routes are not rate limited. Both values must be positive to enable the diff --git a/docs/how-it-works/gateways/authentication.mdx b/docs/how-it-works/gateways/authentication.mdx index b764d3fbfe..0be1ecae91 100644 --- a/docs/how-it-works/gateways/authentication.mdx +++ b/docs/how-it-works/gateways/authentication.mdx @@ -249,6 +249,29 @@ The connection flow: For a headless environment, set `OPENSHELL_NO_BROWSER=1` before registering or logging in to the gateway. When this variable is set and `OPENSHELL_OIDC_CLIENT_SECRET` is not configured, the CLI uses the Device Authorization Grant (RFC 8628) with S256 PKCE. This flow prompts the user to visit a verification URL on any device with a browser and enter a displayed code. The CLI polls the token endpoint until the user completes authorization. This requires the OIDC client to have the device authorization grant enabled on the identity provider. +Sandbox delegated identity uses another Authorization Code flow with PKCE when +the authenticated user has no usable gateway-owned delegated credential. The +CLI opens the browser and may reuse the existing identity-provider SSO session, +but obtains a token bundle separate from the normal CLI login. The delegated +request uses only the `openid` and `offline_access` scopes and sends the +configured gateway API audience. The CLI transfers the returned bundle to the +gateway and never writes it to `oidc_token.json`; the gateway then owns and +rotates that refresh-token chain. +The IdP must enable the authorization code flow and offline access for the +configured public client, allow the CLI's loopback callback URI, and issue a +distinct refresh-token chain for each authorization transaction. + +For token-exchange providers, ensure the issued and refreshed delegated access +tokens satisfy the token service's subject-token audience requirements. See +[Provider Profiles](/how-it-works/providers/profiles) for the exchange flow. + +OpenShell continues to use the normal CLI access token for status, +authorization, create, and extend RPCs. A long browser flow or image build may +outlive that token, so the CLI reloads or refreshes the normal login immediately +before constructing the final gRPC client. A healthy delegated grant is reused +without another prompt. A missing, revoked, or inactive delegated grant prompts +for a replacement without changing the local login. + 1. The CLI loads the stored OIDC token bundle. 2. If the access token is expired or near expiry, the CLI refreshes it with the OIDC scopes saved in the gateway metadata. If refresh fails, the command stops before sending a protected request and directs you to run `openshell gateway login `. 3. The CLI connects to the gateway and attaches `authorization: Bearer ` metadata to each gRPC request. diff --git a/docs/how-it-works/gateways/configuration.mdx b/docs/how-it-works/gateways/configuration.mdx index 1954b957a7..5b58fb1884 100644 --- a/docs/how-it-works/gateways/configuration.mdx +++ b/docs/how-it-works/gateways/configuration.mdx @@ -179,6 +179,8 @@ compute_driver = "kubernetes" credential_drivers = ["kubernetes-secrets"] ssh_session_ttl_secs = 3600 +# Maximum sandbox delegated identity window, in seconds. +max_delegated_identity_duration_secs = 86400 # Absolute image preparation and initial supervisor startup budget. # Applies to new attempts only; allowed values are 1 through 86400 seconds. @@ -322,6 +324,8 @@ certificate trust boundary. The client-certificate handshake policy has no `require_client_auth` TOML field. Client certificates are optional at the transport layer; gateway RPC authorization enforces the configured user or sandbox identity. +For Helm deployments, `server.maxDelegatedIdentityDurationSecs` renders `[openshell.gateway] max_delegated_identity_duration_secs`. The chart defaults it to `86400` seconds and rejects negative values. + `[openshell.gateway.tls]` supports optional SNI-based dual-certificate mode for deployments that need separate internal and external server certificates. Set `external_cert_path` and `external_key_path` to point at the external (e.g. ACME/publicly-trusted) certificate and key. List the hostnames that should be served with the external certificate in `external_server_names`. Connections whose TLS SNI hostname matches one of those names receive the external certificate; all other connections (including those with no SNI) receive the primary internal certificate from `cert_path`/`key_path`. Both fields must be set together — providing only one is a configuration error. On Kubernetes with the Helm chart, the external certificate is managed automatically when `certManager.serverIssuerRef.name` is set; the chart populates these fields from the cert-manager-issued external server certificate. `[openshell.gateway] policy_validation_failure_mode` controls what sandbox supervisors do when a complete candidate policy fails runtime validation. The default, `fail_closed`, deactivates the previous network policy, closes relays pinned to it, and denies new egress until a valid generation loads. `retain_last_valid` leaves the previous valid generation active. Both modes reject the candidate atomically; startup keeps the workload unstarted until the effective policy and matching provider configuration pass admission. A rejected startup exposes `ConfigurationInvalid` and remains available for policy/provider repair in either mode. Gateway mutation paths that can preflight a known effective scope reject invalid candidates before persistence and leave the active policy unchanged regardless of this setting. Changing the value requires restarting the gateway so it can reload `gateway.toml` and distribute the new posture to sandbox supervisors. @@ -604,7 +608,7 @@ The gateway validates snapshot structure and provider-profile semantics. It trea ## Credential Drivers -Set `credential_drivers` only when the gateway should store provider secrets in an external credential backend. Provider secrets include injectable credentials and gateway-only refresh material such as OAuth refresh tokens, client secrets, and service-account private keys. OpenShell supports at most one enabled credential driver at a time. When `credential_drivers` is omitted, the gateway uses its default encrypted database credential storage. `credential_drivers = []` is invalid in the TOML file; omit the field for the default encrypted store, or select a backend such as `kubernetes-secrets` or `vault`. +Set `credential_drivers` only when the gateway should store secrets in an external credential backend. Driver-owned secrets include injectable provider credentials, gateway-only provider refresh material such as OAuth refresh tokens, client secrets, and service-account private keys, and delegated identity access and refresh tokens. Generic provider, refresh-state, delegated-credential, and sandbox records contain opaque handles or non-secret metadata instead of those token values. OpenShell supports at most one enabled credential driver at a time. When `credential_drivers` is omitted, the gateway uses its default encrypted database credential storage. `credential_drivers = []` is invalid in the TOML file; omit the field for the default encrypted store, or select a backend such as `kubernetes-secrets` or `vault`. Credential driver tables are backend-owned and live under `[openshell.credential_drivers.]`. Built-in drivers default to in-tree transport, so they do not need a `transport` field. Use `transport = "uds"` with an absolute `socket_path` only for a remote gRPC driver over a Unix domain socket. @@ -639,7 +643,7 @@ role = "openshell-gateway" service_account_token_path = "/var/run/secrets/kubernetes.io/serviceaccount/token" ``` -For the default encrypted database store, OpenShell stores provider credentials as JSON envelopes encrypted with AES-256-GCM in the gateway database. Each credential gets a random data-encryption key; the gateway wraps that key with a local key-encryption key. By default, the key-encryption key is created at `$XDG_STATE_HOME/openshell/gateway/credentials/key-encryption-key.bin` with owner-only permissions. Use `[openshell.gateway.credential_storage] key_encryption_key_env` instead of `key_encryption_key_path` to load a base64-encoded 32-byte key-encryption key from an environment variable. Back up the database and key-encryption key together; losing either makes stored credentials unrecoverable. In Kubernetes, the Helm chart creates a retained Secret containing the shared key-encryption key, injects it as `OPENSHELL_GATEWAY_CREDENTIAL_KEY_ENCRYPTION_KEY`, and renders `key_encryption_key_env` for the gateway when no external credential driver is enabled. Multi-replica deployments need every replica to use the same database and key-encryption key; the chart default handles the key-encryption key side. +For the default encrypted database store, OpenShell stores driver-owned secrets as JSON envelopes encrypted with AES-256-GCM in the gateway database. Each secret gets a random data-encryption key; the gateway wraps that key with a local key-encryption key. By default, the key-encryption key is created at `$XDG_STATE_HOME/openshell/gateway/credentials/key-encryption-key.bin` with owner-only permissions. Use `[openshell.gateway.credential_storage] key_encryption_key_env` instead of `key_encryption_key_path` to load a base64-encoded 32-byte key-encryption key from an environment variable. Back up the database and key-encryption key together; losing either makes stored credentials unrecoverable. In Kubernetes, the Helm chart creates a retained Secret containing the shared key-encryption key, injects it as `OPENSHELL_GATEWAY_CREDENTIAL_KEY_ENCRYPTION_KEY`, and renders `key_encryption_key_env` for the gateway when no external credential driver is enabled. Multi-replica deployments need every replica to use the same database and key-encryption key; the chart default handles the key-encryption key side. For GitOps and `helm template` workflows where `lookup` returns empty, the chart-generated KEK Secret gets a random value on every render, making credentials unrecoverable. Set `server.credentialStorage.existingSecret` to the name of a pre-provisioned Secret containing the key-encryption key under the `key-encryption-key` data key. When set, the chart skips KEK Secret generation and references the provided Secret directly. diff --git a/docs/how-it-works/providers/profiles.mdx b/docs/how-it-works/providers/profiles.mdx index 1617e00afe..15976e1a57 100644 --- a/docs/how-it-works/providers/profiles.mdx +++ b/docs/how-it-works/providers/profiles.mdx @@ -624,7 +624,20 @@ openshell provider create \ --runtime-credentials ``` -For `token_exchange` profiles, the provider also stores the user subject token referenced by `token_grant.subject_token.credential`. That credential is gateway-only: the sandbox does not receive it as environment material, static credential binding metadata, or workload placeholder ownership. Create or update that provider credential from the current gateway OIDC login with `--from-oidc-token`. This requires an active named gateway that was registered for OIDC. The CLI copies the current OIDC access token and its expiry into the provider. If the stored gateway access token is expired and a refresh token is available, the CLI refreshes it first. OpenShell does not store the OIDC refresh token in the provider. When the stored subject-token credential expires, the gateway rejects intermediate token exchange until the provider is updated with a fresh token. +For `token_exchange` profiles, choose the subject token source explicitly. +OpenShell trims surrounding whitespace from `subject_token.source` and +`subject_token.credential` before using them. +Use `source: provider_credential` when the provider record should hold a subject +credential named by `token_grant.subject_token.credential`. That credential is +gateway-only: the sandbox does not receive it as environment material, static +credential binding metadata, or workload placeholder ownership. Create or update +that provider credential from the current gateway OIDC login with `--from-oidc-token`. +This requires an active named gateway that was registered for OIDC. The CLI +copies the current OIDC access token and its expiry into the provider. If the +stored gateway access token is expired and a refresh token is available, the CLI +refreshes it first. OpenShell does not store the OIDC refresh token in the +provider. When the stored subject-token credential expires, the gateway rejects +intermediate token exchange until the provider is updated with a fresh token. ```shell openshell provider create \ @@ -638,6 +651,139 @@ openshell provider update custom-api \ OpenShell infers the destination credential when the provider profile has exactly one `token_grant.subject_token.credential`. If a profile declares more than one token-exchange subject credential, pass `--credential ` to choose one. + +Delegate identity only through providers whose configuration administrators you +trust with your delegated access token. The gateway loads the current profile +for each exchange; creating a delegation does not pin its exchange configuration. +Workspace Admins can change workspace-scoped custom profiles, including +`token_grant.token_endpoint`, so they can redirect a subsequent exchange to an +endpoint they control. The gateway sends the delegator's raw access token as the +subject token to that endpoint, not the delegated refresh token or normal CLI +login credentials. The provider-credential subject-token path also trusts profile +administrators with its subject token. Delegated-sandbox ownership restricts +direct lifecycle and interactive access, but does not protect subject tokens from +administrators who control exchange configuration. + + +Use `source: sandbox_delegated_identity` when the subject token should be the +creating user's delegated OIDC identity. Create the provider with +`--runtime-credentials`, then create sandboxes with an explicit authorization +duration: + +```shell +openshell provider create \ + --name protected-services \ + --type protected-services-profile \ + --runtime-credentials + +openshell sandbox create \ + --provider protected-services \ + --use-my-identity-for=8h +``` + +Delegation must be enabled when the sandbox is created. A sandbox created +without `--use-my-identity-for` cannot add delegated identity later, although +the original delegator can extend or withdraw an existing delegation: + +```shell +openshell sandbox delegated-identity status my-sandbox +openshell sandbox delegated-identity extend my-sandbox --for=24h +openshell sandbox delegated-identity withdraw my-sandbox +``` + +The gateway measures the requested duration from its own clock when it handles +create or extend and enforces its configured maximum delegation duration. + +The status command reports `active`, `withdrawn`, `expired`, `revoked`, or +`credential-missing`. `revoked` means the sandbox delegation window still +exists, but the gateway-scoped delegated credential no longer authorizes token +exchange. + +The first create or extend that needs delegated identity checks whether the +authenticated user already has a usable gateway-owned authorization. If not, +the CLI starts another Authorization Code flow with PKCE, opens the browser, and +requests only `openid` and `offline_access`. The identity provider may reuse the +existing browser SSO session. This authorization is separate from +`openshell gateway login`: the CLI transfers its access and refresh tokens to +the gateway without writing them to the local `oidc_token.json`, and the normal +CLI login remains the bearer for every gateway RPC. Later sandboxes reuse the +gateway-owned authorization without another prompt while it remains usable. + +Configure the gateway OIDC client to allow the Authorization Code flow, the +CLI's loopback callback URI, and refresh tokens for `offline_access`. The IdP +must create a distinct grant and refresh-token chain for the second browser +authorization; rotating or single-use refresh tokens are supported. If the IdP +returns the normal CLI login's refresh token again, OpenShell rejects the +transfer to prevent two independent owners from rotating the same token. An IdP +may bind offline access to its login session, so ending that session can still +invalidate the delegated grant. + +Some token services require the delegated access token's `aud` claim to include +the client identity authenticated by the gateway JWT-SVID in the intermediate +exchange. This identity can differ from the gateway API audience. It is also +distinct from the supervisor identity requested as the intermediate token's +audience. Check the token service's rule and configure the IdP to include the +required audience in issued and refreshed delegated access tokens. + +If refresh fails because the delegated grant is revoked or no longer active, +retry `openshell sandbox create --use-my-identity-for=...` or +`openshell sandbox delegated-identity extend ... --for=...`. The status +preflight starts a new browser authorization and replaces the unusable +gateway-owned credential. Running `openshell gateway logout` or +`openshell gateway login` changes only the normal CLI login and does not repair +or revoke the delegated grant. + +Platform admins can inspect and revoke the gateway-scoped delegated credential +records: + +```shell +openshell delegated-credential list +openshell delegated-credential status +openshell delegated-credential revoke +openshell delegated-credential delete +``` + +The list command returns one continuation-token page. Use `--page-size` to +control the page size and pass a non-empty `next_page_token` back through +`--page-token`. JSON and YAML output include both the `credentials` collection +and `next_page_token`; table output reports the token on stderr. + +The gateway records OAuth storage handles before writing tokens, so interrupted +authorization and partial storage failures remain recoverable even when no +credential was created. Background recovery cleans up unpublished material after +its five-minute staging window and retries failed deletions. When a backend write +has an unknown outcome, the gateway retains a non-secret cleanup record and +repeats deletion to catch delayed writes. These records have no automatic +retention limit. External credential drivers must support handle preparation; +authorization fails before writing tokens if they do not. + +Sandbox creation records its provisioning state and delegation together. An +interrupted create can leave a provisioning sandbox for background reconciliation, +which also owns cleanup of its delegation. Check sandbox status before retrying a +create whose response was lost. + +Gateway replicas coordinate delegated credential refresh with a renewable lease. +A live owner renews through storage and commit, so a slow refresh does not lose +ownership just because its initial lease duration elapses. An expired lease lets +another replica recover after the owner fails. A crash or network partition after +the IdP rotates a refresh token but before the gateway commits it can still +require reauthorization. + +Withdrawal is not instantaneous containment. The gateway rejects requests when +their authorization checks observe delegation expiry, withdrawal, or credential +revocation, but already-authorized refreshes or exchanges may finish and populate +token caches. Each supervisor token-exchange cache entry lasts at most five +minutes from insertion; this is not a five-minute deadline measured from +withdrawal. Stop or delete the sandbox when continued execution is unacceptable. +You can also revoke the upstream IdP grant, but an already issued access token +remains usable until its resource server rejects it or it expires unless that +system enforces immediate revocation. + +These controls have different scopes: local gateway logout removes only the +CLI-owned login, sandbox withdrawal ends one sandbox's delegation window, +gateway credential revocation disables all sandboxes that reference that user's +gateway-owned grant, and IdP revocation invalidates the upstream OAuth grant. + Token grant fields: | Field | Required | Behavior | @@ -650,7 +796,7 @@ Token grant fields: | `scopes` | No | OAuth2 scopes sent as a space-separated `scope` parameter. | | `cache_ttl` | No | Protobuf-duration cache override, such as `300s` or `0.500s`. Omission uses the token response expiry; `0s` disables caching. The legacy `cache_ttl_seconds` input remains accepted. | | `requested_token_type` | No | RFC 8693 `requested_token_type` sent during token exchange. Defaults to `urn:ietf:params:oauth:token-type:access_token`. | -| `subject_token` | Required for `token_exchange` | Subject-token source used for the gateway-brokered intermediate exchange. Phase one supports `source: provider_credential`, where `credential` names another credential declared in the same profile. That referenced credential is broker-only and cannot declare workload injection metadata such as env vars, static placement, refresh, or token grant metadata. | +| `subject_token` | Required for `token_exchange` | Subject-token source used for the gateway-brokered intermediate exchange. Supports `source: provider_credential`, where `credential` names another credential declared in the same profile, and `source: sandbox_delegated_identity`, where the sandbox must have delegated OIDC identity enabled at create time. A provider-credential subject is broker-only and cannot declare workload injection metadata such as env vars, static placement, refresh, or token grant metadata. | | `subject_token.subject_token_type` | No | RFC 8693 `subject_token_type` for the stored subject token. Defaults to `urn:ietf:params:oauth:token-type:access_token`. | | `audience_overrides` | No | Endpoint-specific final-exchange `audience` and `scopes` overrides selected by host, port, and path. These overrides do not affect the gateway intermediate exchange. | diff --git a/docs/how-it-works/workspaces.mdx b/docs/how-it-works/workspaces.mdx index df5e2e1486..e04deebca4 100644 --- a/docs/how-it-works/workspaces.mdx +++ b/docs/how-it-works/workspaces.mdx @@ -60,6 +60,32 @@ The following table summarizes common operations. | Manage platform profiles or global configuration | Yes | No | No | | List resources across workspaces | Yes | No | No | +### Delegated sandbox exceptions + +A sandbox created with delegated identity acts on behalf of its delegating +user, so its lifecycle and interactive access are narrower than the ordinary +workspace role model. + +| Operation | Delegating user | Workspace Admin | Platform Admin | Other Workspace User | +|---|---|---|---|---| +| Start | Yes | No | No | No | +| Stop | Yes | Yes | Yes | No | +| Delete | Yes | Yes | Yes | No | +| Connect, exec, file transfer, forwarding, logs, services, providers, policy, or settings | Yes | No | No | No | + +Admins can stop a delegated sandbox for immediate containment or delete it to +release resources, but they cannot reactivate a workload that acts for another +user. Only the original delegator can restart it, including after delegation +expiry, withdrawal, or credential revocation. These exceptions do not change +access to sandboxes created without delegated identity. + +These restrictions govern direct sandbox access, not confidentiality against +administrators who control attached providers and profiles. Workspace Admins can +change workspace-scoped custom profiles to redirect token exchanges and receive +the delegator's subject access token. Delegate identity only when you trust those +administrators. Refer to [Profiles](/providers/profiles) for the token-exchange +trust requirements. + Local gateways without OIDC role configuration treat authenticated users as Platform Admins. Configure OIDC roles and workspace membership for shared diff --git a/e2e/rust/tests/provider_token_exchange.rs b/e2e/rust/tests/provider_token_exchange.rs index 7493a8fe89..004604732c 100644 --- a/e2e/rust/tests/provider_token_exchange.rs +++ b/e2e/rust/tests/provider_token_exchange.rs @@ -339,7 +339,23 @@ async fn start_spiffe_workload_api(path: &Path, subject: &str) -> FixtureHandle eprintln!("SPIFFE Workload API fixture failed: {err}"); } }); - return FixtureHandle { task }; + let fixture = FixtureHandle { task }; + // This lane uses a host-networked supervisor. Check its advertised + // fixture address before creating a sandbox, so a stale network alias + // fails here instead of timing out repeatedly during token injection. + let address = endpoint.strip_prefix("tcp:").expect("TCP fixture endpoint"); + let probe = tokio::time::timeout(Duration::from_secs(2), TcpStream::connect(address)) + .await + .unwrap_or_else(|_| { + panic!("SPIFFE fixture endpoint {endpoint} timed out; listener is {listen}") + }) + .unwrap_or_else(|err| { + panic!( + "SPIFFE fixture endpoint {endpoint} is unreachable (listener {listen}): {err}" + ) + }); + drop(probe); + return fixture; } let _ = fs::remove_file(path); let listener = UnixListener::bind(path).expect("bind SPIFFE Workload API socket"); diff --git a/e2e/with-podman-gateway.sh b/e2e/with-podman-gateway.sh index 57edfa0a4f..ff99288545 100755 --- a/e2e/with-podman-gateway.sh +++ b/e2e/with-podman-gateway.sh @@ -103,9 +103,9 @@ if [ "${OPENSHELL_E2E_SPIFFE_FIXTURE:-0}" = "1" ]; then export OPENSHELL_GATEWAY_SPIFFE_WORKLOAD_API_SOCKET="${OPENSHELL_E2E_GATEWAY_SPIFFE_SOCKET}" if [ -z "${OPENSHELL_E2E_PROVIDER_SPIFFE_SOCKET:-}" ]; then OPENSHELL_E2E_PROVIDER_SPIFFE_PORT="$(e2e_pick_port)" - export OPENSHELL_E2E_PROVIDER_SPIFFE_LISTEN="0.0.0.0:${OPENSHELL_E2E_PROVIDER_SPIFFE_PORT}" - # Podman supervisors run with host networking, so reach the host-side - # Workload API fixture over loopback rather than the workload bridge. + # The supervisor shares the host network, not the workload's network + # namespace. Its Workload API fixture is therefore on host loopback. + export OPENSHELL_E2E_PROVIDER_SPIFFE_LISTEN="127.0.0.1:${OPENSHELL_E2E_PROVIDER_SPIFFE_PORT}" export OPENSHELL_E2E_PROVIDER_SPIFFE_SOCKET="tcp:127.0.0.1:${OPENSHELL_E2E_PROVIDER_SPIFFE_PORT}" fi fi @@ -496,6 +496,16 @@ echo "Using Podman supervisor image: ${SUPERVISOR_IMAGE}" SANDBOX_BOUNDARY_IMAGE="$(resolve_podman_sandbox_runtime_image)" ensure_podman_sandbox_runtime_image "${SANDBOX_BOUNDARY_IMAGE}" +SANDBOX_BOUNDARY_DIGEST="$(podman_cmd image inspect --format '{{.Digest}}' "${SANDBOX_BOUNDARY_IMAGE}")" +if ! [[ "${SANDBOX_BOUNDARY_DIGEST}" =~ ^sha256:[0-9a-f]{64}$ ]]; then + echo "ERROR: could not resolve sandbox runtime image digest for ${SANDBOX_BOUNDARY_IMAGE}." >&2 + exit 2 +fi +SANDBOX_BOUNDARY_REPOSITORY="${SANDBOX_BOUNDARY_IMAGE%%@*}" +if [[ "${SANDBOX_BOUNDARY_REPOSITORY##*/}" == *:* ]]; then + SANDBOX_BOUNDARY_REPOSITORY="${SANDBOX_BOUNDARY_REPOSITORY%:*}" +fi +SANDBOX_BOUNDARY_IMAGE="${SANDBOX_BOUNDARY_REPOSITORY}@${SANDBOX_BOUNDARY_DIGEST}" echo "Using Podman sandbox runtime image: ${SANDBOX_BOUNDARY_IMAGE}" DEFAULT_SANDBOX_IMAGE="nvcr.io/nvidia/base/ubuntu:24.04" diff --git a/examples/spiffe-token-exchange-demo/podman/provider-profile.yaml b/examples/spiffe-token-exchange-demo/podman/provider-profile.yaml index b7d1cf30e5..92f130fb92 100644 --- a/examples/spiffe-token-exchange-demo/podman/provider-profile.yaml +++ b/examples/spiffe-token-exchange-demo/podman/provider-profile.yaml @@ -44,13 +44,11 @@ endpoints: - host: alpha-exchange port: 8080 protocol: rest - tls: none access: read-write enforcement: enforce - host: beta-exchange port: 8080 protocol: rest - tls: none access: read-write enforcement: enforce binaries: diff --git a/examples/spiffe-token-exchange-demo/provider-profile.yaml b/examples/spiffe-token-exchange-demo/provider-profile.yaml index 48e5dbe6c8..41b2d76406 100644 --- a/examples/spiffe-token-exchange-demo/provider-profile.yaml +++ b/examples/spiffe-token-exchange-demo/provider-profile.yaml @@ -39,13 +39,11 @@ endpoints: - host: alpha-exchange.default.svc.cluster.local port: 80 protocol: rest - tls: none access: read-write enforcement: enforce - host: beta-exchange.default.svc.cluster.local port: 80 protocol: rest - tls: none access: read-write enforcement: enforce binaries: diff --git a/examples/spiffe-token-grant-demo/provider-profile.yaml b/examples/spiffe-token-grant-demo/provider-profile.yaml index caadfe2b3a..8950d600aa 100644 --- a/examples/spiffe-token-grant-demo/provider-profile.yaml +++ b/examples/spiffe-token-grant-demo/provider-profile.yaml @@ -31,7 +31,6 @@ endpoints: - host: alpha.default.svc.cluster.local port: 80 protocol: rest - tls: none access: read-write enforcement: enforce allowed_ips: @@ -39,7 +38,6 @@ endpoints: - host: beta.default.svc.cluster.local port: 80 protocol: rest - tls: none access: read-write enforcement: enforce allowed_ips: diff --git a/proto/credential_driver.proto b/proto/credential_driver.proto index da0743a1f5..f003b59cd7 100644 --- a/proto/credential_driver.proto +++ b/proto/credential_driver.proto @@ -16,6 +16,10 @@ import "extension.proto"; // gateway-managed provider-secret handles. Secrets include injectable provider // credentials and gateway-only refresh material. service CredentialDriver { + // Allocate an opaque handle without writing secret material. The gateway + // persists it before StoreCredential uses it as existing_handle. Deletion + // must be idempotent even if the subsequent write never happened. + rpc PrepareCredential(PrepareCredentialRequest) returns (StoreCredentialResponse); // Report driver identity and feature support. rpc GetCapabilities(GetCredentialDriverCapabilitiesRequest) returns (GetCredentialDriverCapabilitiesResponse); @@ -66,7 +70,8 @@ message StoreCredentialRequest { string value = 3; // Existing handle to overwrite, if any. openshell.datamodel.v1.CredentialHandle existing_handle = 4; - // Workspace that owns this provider. Used to ensure cross-workspace uniqueness. + // Workspace that owns this secret. Empty denotes gateway-global secret + // material. Used to ensure cross-workspace uniqueness. string workspace = 5; // Provider UUID. Combined with workspace to ensure globally unique backend paths. string provider_id = 6; @@ -76,6 +81,14 @@ message StoreCredentialRequest { string object_id = 7; } +message PrepareCredentialRequest { + string provider = 1; + string credential_key = 2; + string workspace = 3; + string provider_id = 4; + string object_id = 5; +} + message StoreCredentialResponse { // Opaque handle for later resolution/deletion. openshell.datamodel.v1.CredentialHandle handle = 1; @@ -88,7 +101,8 @@ message DeleteCredentialRequest { string credential_key = 2; // Opaque handle to delete. openshell.datamodel.v1.CredentialHandle handle = 3; - // Workspace that owns this provider. Used to ensure cross-workspace uniqueness. + // Workspace that owns this secret. Empty denotes gateway-global secret + // material. Used to ensure cross-workspace uniqueness. string workspace = 4; // Provider UUID. Combined with workspace to ensure globally unique backend paths. string provider_id = 5; diff --git a/proto/openshell.proto b/proto/openshell.proto index f4186ea440..6209ed407c 100644 --- a/proto/openshell.proto +++ b/proto/openshell.proto @@ -70,6 +70,57 @@ service OpenShell { }; } + // Inspect whether the authenticated user has a usable gateway-owned + // delegated identity authorization. + rpc GetDelegatedIdentityAuthorizationStatus( + GetDelegatedIdentityAuthorizationStatusRequest) + returns (GetDelegatedIdentityAuthorizationStatusResponse) { + option (openshell.options.v1.authorization) = { + auth_mode: "bearer" + scope: "sandbox:write" + }; + } + + // Transfer a separate OAuth authorization grant to the gateway for use as + // the authenticated user's delegated identity. + rpc AuthorizeDelegatedIdentity(AuthorizeDelegatedIdentityRequest) + returns (AuthorizeDelegatedIdentityResponse) { + option (openshell.options.v1.authorization) = { + auth_mode: "bearer" + scope: "sandbox:write" + }; + } + + // Fetch delegated identity status for one sandbox. + rpc GetSandboxDelegatedIdentityStatus(GetSandboxDelegatedIdentityStatusRequest) + returns (GetSandboxDelegatedIdentityStatusResponse) { + option (openshell.options.v1.authorization) = { + auth_mode: "bearer" + scope: "sandbox:read" + workspace_role: "user" + }; + } + + // Withdraw delegated identity from one sandbox. + rpc WithdrawSandboxDelegatedIdentity(WithdrawSandboxDelegatedIdentityRequest) + returns (WithdrawSandboxDelegatedIdentityResponse) { + option (openshell.options.v1.authorization) = { + auth_mode: "bearer" + scope: "sandbox:write" + workspace_role: "user" + }; + } + + // Extend delegated identity for one sandbox. + rpc ExtendSandboxDelegatedIdentity(ExtendSandboxDelegatedIdentityRequest) + returns (ExtendSandboxDelegatedIdentityResponse) { + option (openshell.options.v1.authorization) = { + auth_mode: "bearer" + scope: "sandbox:write" + workspace_role: "user" + }; + } + // Fetch a sandbox by name. rpc GetSandbox(GetSandboxRequest) returns (SandboxResponse) { option (openshell.options.v1.authorization) = { @@ -401,6 +452,46 @@ service OpenShell { }; } + // List delegated identity credentials visible to the caller. + rpc ListDelegatedIdentityCredentials(ListDelegatedIdentityCredentialsRequest) + returns (ListDelegatedIdentityCredentialsResponse) { + option (openshell.options.v1.authorization) = { + auth_mode: "bearer" + scope: "provider:read" + global_role: "platform_admin" + }; + } + + // Fetch delegated identity credential status. + rpc GetDelegatedIdentityCredentialStatus(GetDelegatedIdentityCredentialStatusRequest) + returns (GetDelegatedIdentityCredentialStatusResponse) { + option (openshell.options.v1.authorization) = { + auth_mode: "bearer" + scope: "provider:read" + global_role: "platform_admin" + }; + } + + // Revoke a delegated identity credential. + rpc RevokeDelegatedIdentityCredential(RevokeDelegatedIdentityCredentialRequest) + returns (RevokeDelegatedIdentityCredentialResponse) { + option (openshell.options.v1.authorization) = { + auth_mode: "bearer" + scope: "provider:write" + global_role: "platform_admin" + }; + } + + // Delete a delegated identity credential. + rpc DeleteDelegatedIdentityCredential(DeleteDelegatedIdentityCredentialRequest) + returns (DeleteDelegatedIdentityCredentialResponse) { + option (openshell.options.v1.authorization) = { + auth_mode: "bearer" + scope: "provider:write" + global_role: "platform_admin" + }; + } + // Delete gateway-owned refresh configuration for one provider credential. rpc DeleteProviderRefresh(DeleteProviderRefreshRequest) returns (DeleteProviderRefreshResponse) { @@ -1019,9 +1110,15 @@ message Sandbox { string host_key_fingerprint = 6; // Read-only provenance for sandboxes created from a reusable workload template. SandboxWorkloadTemplateProvenance created_from_workload_template = 20; - reserved 4, 5; - reserved "phase", "current_policy_version"; + reserved "phase", "current_policy_version", "delegated_identity"; +} + +message SandboxDelegatedIdentity { + string credential_id = 1; + string principal_subject = 2; + google.protobuf.Timestamp delegation_expiration_time = 3; + google.protobuf.Timestamp withdrawal_time = 4; } // Desired sandbox configuration provided through the public API. @@ -1287,6 +1384,89 @@ message CreateSandboxRequest { // registered after the sandbox has been persisted and route only while the // sandbox is ready. repeated SandboxServiceExposure service_exposures = 9; + // Optional bounded use of the authenticated user's separately authorized, + // gateway-owned delegated identity credential. + DelegatedIdentityRequest delegated_identity = 10; +} + +message DelegatedIdentityRequest { + // Requested delegation duration. The gateway derives the absolute expiry + // from its own clock after validating this value against its configured maximum. + google.protobuf.Duration duration = 1; + reserved 2, 3, 4, 5, 6, 7, 8; + reserved "issuer", "client_id", "refresh_token", "access_token", + "access_token_expires_at_ms", "scopes", "audience"; +} + +// Transfer-only OAuth material obtained from a separate user authorization. +// The CLI must never install this bundle as its normal gateway login. +message DelegatedIdentityAuthorizationGrant { + string issuer = 1; + string client_id = 2; + string refresh_token = 3 [(openshell.options.v1.secret) = true]; + string access_token = 4 [(openshell.options.v1.secret) = true]; + string scopes = 5; + string audience = 6; +} + +message GetDelegatedIdentityAuthorizationStatusRequest {} + +message GetDelegatedIdentityAuthorizationStatusResponse { + bool usable = 1; + bool reauthorization_required = 2; + string reason = 3; + DelegatedIdentityCredentialSummary credential = 4; + google.protobuf.Timestamp current_time = 5; +} + +message AuthorizeDelegatedIdentityRequest { + DelegatedIdentityAuthorizationGrant grant = 1; +} + +message AuthorizeDelegatedIdentityResponse { + DelegatedIdentityCredentialSummary credential = 1; + google.protobuf.Timestamp current_time = 2; +} + +message GetSandboxDelegatedIdentityStatusRequest { + reserved 2; + reserved "workspace"; + string name = 1; + // Explicit workspace scope. The all-workspaces selection is invalid. + openshell.datamodel.v1.WorkspaceSelector workspace_scope = 3; +} + +message GetSandboxDelegatedIdentityStatusResponse { + SandboxDelegatedIdentity delegated_identity = 1; + google.protobuf.Timestamp current_time = 2; + google.protobuf.Timestamp credential_revocation_time = 3; + bool credential_missing = 4; +} + +message WithdrawSandboxDelegatedIdentityRequest { + reserved 2; + reserved "workspace"; + string name = 1; + // Explicit workspace scope. The all-workspaces selection is invalid. + openshell.datamodel.v1.WorkspaceSelector workspace_scope = 3; +} + +message WithdrawSandboxDelegatedIdentityResponse { + Sandbox sandbox = 1; + bool withdrawn = 2; +} + +message ExtendSandboxDelegatedIdentityRequest { + reserved 2; + reserved "workspace"; + string name = 1; + DelegatedIdentityRequest delegated_identity = 3; + // Explicit workspace scope. The all-workspaces selection is invalid. + openshell.datamodel.v1.WorkspaceSelector workspace_scope = 4; +} + +message ExtendSandboxDelegatedIdentityResponse { + Sandbox sandbox = 1; } message CreateSandboxTemplateRequest { @@ -2439,6 +2619,64 @@ message ProviderProfileDiscovery { repeated string credentials = 1; } +message DelegatedIdentityCredentialSummary { + openshell.datamodel.v1.ObjectMeta metadata = 1; + string issuer = 2; + string client_id = 3; + string principal_subject = 4; + bool refresh_token_present = 5; + bool access_token_present = 6; + google.protobuf.Timestamp access_token_expiration_time = 7; + string scopes = 8; + string audience = 9; + google.protobuf.Timestamp last_refresh_time = 10; + google.protobuf.Timestamp revocation_time = 11; +} + +message ListDelegatedIdentityCredentialsRequest { + // The maximum number of credentials to return. Zero uses 100. Values above + // 1000 are coerced to 1000; negative values are invalid. + int32 page_size = 1; + // Token from a previous ListDelegatedIdentityCredentials response. + string page_token = 2; +} + +message ListDelegatedIdentityCredentialsResponse { + repeated DelegatedIdentityCredentialSummary credentials = 1; + // Token for the next page. Empty when there are no subsequent pages. + string next_page_token = 2; +} + +message GetDelegatedIdentityCredentialStatusRequest { + string id = 1; +} + +message GetDelegatedIdentityCredentialStatusResponse { + DelegatedIdentityCredentialSummary credential = 1; + google.protobuf.Timestamp current_time = 2; +} + +message RevokeDelegatedIdentityCredentialRequest { + string id = 1; + uint64 expected_resource_version = 2; +} + +message RevokeDelegatedIdentityCredentialResponse { + reserved 1; + bool revoked = 2; + google.protobuf.Timestamp revocation_time = 3; + uint64 resource_version = 4; +} + +message DeleteDelegatedIdentityCredentialRequest { + string id = 1; + uint64 expected_resource_version = 2; +} + +message DeleteDelegatedIdentityCredentialResponse { + bool deleted = 1; +} + message GetProviderRefreshStatusRequest { // Workspace scope. Only a named workspace selection is accepted. openshell.datamodel.v1.WorkspaceSelector workspace_scope = 3; diff --git a/sdk/go/proto/openshellv1/openshell.pb.go b/sdk/go/proto/openshellv1/openshell.pb.go index 68a550bdec..9bad17a33e 100644 --- a/sdk/go/proto/openshellv1/openshell.pb.go +++ b/sdk/go/proto/openshellv1/openshell.pb.go @@ -2398,6 +2398,74 @@ func (x *Sandbox) GetCreatedFromWorkloadTemplate() *SandboxWorkloadTemplateProve return nil } +type SandboxDelegatedIdentity struct { + state protoimpl.MessageState `protogen:"open.v1"` + CredentialId string `protobuf:"bytes,1,opt,name=credential_id,json=credentialId,proto3" json:"credential_id,omitempty"` + PrincipalSubject string `protobuf:"bytes,2,opt,name=principal_subject,json=principalSubject,proto3" json:"principal_subject,omitempty"` + DelegationExpirationTime *timestamppb.Timestamp `protobuf:"bytes,3,opt,name=delegation_expiration_time,json=delegationExpirationTime,proto3" json:"delegation_expiration_time,omitempty"` + WithdrawalTime *timestamppb.Timestamp `protobuf:"bytes,4,opt,name=withdrawal_time,json=withdrawalTime,proto3" json:"withdrawal_time,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SandboxDelegatedIdentity) Reset() { + *x = SandboxDelegatedIdentity{} + mi := &file_openshell_proto_msgTypes[18] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SandboxDelegatedIdentity) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SandboxDelegatedIdentity) ProtoMessage() {} + +func (x *SandboxDelegatedIdentity) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[18] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SandboxDelegatedIdentity.ProtoReflect.Descriptor instead. +func (*SandboxDelegatedIdentity) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{18} +} + +func (x *SandboxDelegatedIdentity) GetCredentialId() string { + if x != nil { + return x.CredentialId + } + return "" +} + +func (x *SandboxDelegatedIdentity) GetPrincipalSubject() string { + if x != nil { + return x.PrincipalSubject + } + return "" +} + +func (x *SandboxDelegatedIdentity) GetDelegationExpirationTime() *timestamppb.Timestamp { + if x != nil { + return x.DelegationExpirationTime + } + return nil +} + +func (x *SandboxDelegatedIdentity) GetWithdrawalTime() *timestamppb.Timestamp { + if x != nil { + return x.WithdrawalTime + } + return nil +} + // Desired sandbox configuration provided through the public API. type SandboxSpec struct { state protoimpl.MessageState `protogen:"open.v1"` @@ -2432,7 +2500,7 @@ type SandboxSpec struct { func (x *SandboxSpec) Reset() { *x = SandboxSpec{} - mi := &file_openshell_proto_msgTypes[18] + mi := &file_openshell_proto_msgTypes[19] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2444,7 +2512,7 @@ func (x *SandboxSpec) String() string { func (*SandboxSpec) ProtoMessage() {} func (x *SandboxSpec) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[18] + mi := &file_openshell_proto_msgTypes[19] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2457,7 +2525,7 @@ func (x *SandboxSpec) ProtoReflect() protoreflect.Message { // Deprecated: Use SandboxSpec.ProtoReflect.Descriptor instead. func (*SandboxSpec) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{18} + return file_openshell_proto_rawDescGZIP(), []int{19} } func (x *SandboxSpec) GetLogLevel() string { @@ -2540,7 +2608,7 @@ type ResourceRequirements struct { func (x *ResourceRequirements) Reset() { *x = ResourceRequirements{} - mi := &file_openshell_proto_msgTypes[19] + mi := &file_openshell_proto_msgTypes[20] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2552,7 +2620,7 @@ func (x *ResourceRequirements) String() string { func (*ResourceRequirements) ProtoMessage() {} func (x *ResourceRequirements) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[19] + mi := &file_openshell_proto_msgTypes[20] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2565,7 +2633,7 @@ func (x *ResourceRequirements) ProtoReflect() protoreflect.Message { // Deprecated: Use ResourceRequirements.ProtoReflect.Descriptor instead. func (*ResourceRequirements) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{19} + return file_openshell_proto_rawDescGZIP(), []int{20} } func (x *ResourceRequirements) GetGpu() *GpuResourceRequirements { @@ -2587,7 +2655,7 @@ type GpuResourceRequirements struct { func (x *GpuResourceRequirements) Reset() { *x = GpuResourceRequirements{} - mi := &file_openshell_proto_msgTypes[20] + mi := &file_openshell_proto_msgTypes[21] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2599,7 +2667,7 @@ func (x *GpuResourceRequirements) String() string { func (*GpuResourceRequirements) ProtoMessage() {} func (x *GpuResourceRequirements) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[20] + mi := &file_openshell_proto_msgTypes[21] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2612,7 +2680,7 @@ func (x *GpuResourceRequirements) ProtoReflect() protoreflect.Message { // Deprecated: Use GpuResourceRequirements.ProtoReflect.Descriptor instead. func (*GpuResourceRequirements) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{20} + return file_openshell_proto_rawDescGZIP(), []int{21} } func (x *GpuResourceRequirements) GetCount() uint32 { @@ -2661,7 +2729,7 @@ type SandboxTemplate struct { func (x *SandboxTemplate) Reset() { *x = SandboxTemplate{} - mi := &file_openshell_proto_msgTypes[21] + mi := &file_openshell_proto_msgTypes[22] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2673,7 +2741,7 @@ func (x *SandboxTemplate) String() string { func (*SandboxTemplate) ProtoMessage() {} func (x *SandboxTemplate) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[21] + mi := &file_openshell_proto_msgTypes[22] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2686,7 +2754,7 @@ func (x *SandboxTemplate) ProtoReflect() protoreflect.Message { // Deprecated: Use SandboxTemplate.ProtoReflect.Descriptor instead. func (*SandboxTemplate) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{21} + return file_openshell_proto_rawDescGZIP(), []int{22} } func (x *SandboxTemplate) GetImage() string { @@ -2770,7 +2838,7 @@ type SandboxWorkloadTemplate struct { func (x *SandboxWorkloadTemplate) Reset() { *x = SandboxWorkloadTemplate{} - mi := &file_openshell_proto_msgTypes[22] + mi := &file_openshell_proto_msgTypes[23] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2782,7 +2850,7 @@ func (x *SandboxWorkloadTemplate) String() string { func (*SandboxWorkloadTemplate) ProtoMessage() {} func (x *SandboxWorkloadTemplate) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[22] + mi := &file_openshell_proto_msgTypes[23] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2795,7 +2863,7 @@ func (x *SandboxWorkloadTemplate) ProtoReflect() protoreflect.Message { // Deprecated: Use SandboxWorkloadTemplate.ProtoReflect.Descriptor instead. func (*SandboxWorkloadTemplate) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{22} + return file_openshell_proto_rawDescGZIP(), []int{23} } func (x *SandboxWorkloadTemplate) GetMetadata() *datamodelv1.ObjectMeta { @@ -2826,7 +2894,7 @@ type SandboxWorkloadTemplateSpec struct { func (x *SandboxWorkloadTemplateSpec) Reset() { *x = SandboxWorkloadTemplateSpec{} - mi := &file_openshell_proto_msgTypes[23] + mi := &file_openshell_proto_msgTypes[24] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2838,7 +2906,7 @@ func (x *SandboxWorkloadTemplateSpec) String() string { func (*SandboxWorkloadTemplateSpec) ProtoMessage() {} func (x *SandboxWorkloadTemplateSpec) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[23] + mi := &file_openshell_proto_msgTypes[24] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2851,7 +2919,7 @@ func (x *SandboxWorkloadTemplateSpec) ProtoReflect() protoreflect.Message { // Deprecated: Use SandboxWorkloadTemplateSpec.ProtoReflect.Descriptor instead. func (*SandboxWorkloadTemplateSpec) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{23} + return file_openshell_proto_rawDescGZIP(), []int{24} } func (x *SandboxWorkloadTemplateSpec) GetWorkload() *SandboxWorkloadConfig { @@ -2889,7 +2957,7 @@ type SandboxWorkloadConfig struct { func (x *SandboxWorkloadConfig) Reset() { *x = SandboxWorkloadConfig{} - mi := &file_openshell_proto_msgTypes[24] + mi := &file_openshell_proto_msgTypes[25] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2901,7 +2969,7 @@ func (x *SandboxWorkloadConfig) String() string { func (*SandboxWorkloadConfig) ProtoMessage() {} func (x *SandboxWorkloadConfig) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[24] + mi := &file_openshell_proto_msgTypes[25] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2914,7 +2982,7 @@ func (x *SandboxWorkloadConfig) ProtoReflect() protoreflect.Message { // Deprecated: Use SandboxWorkloadConfig.ProtoReflect.Descriptor instead. func (*SandboxWorkloadConfig) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{24} + return file_openshell_proto_rawDescGZIP(), []int{25} } func (x *SandboxWorkloadConfig) GetImage() string { @@ -2954,7 +3022,7 @@ type SandboxResources struct { func (x *SandboxResources) Reset() { *x = SandboxResources{} - mi := &file_openshell_proto_msgTypes[25] + mi := &file_openshell_proto_msgTypes[26] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2966,7 +3034,7 @@ func (x *SandboxResources) String() string { func (*SandboxResources) ProtoMessage() {} func (x *SandboxResources) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[25] + mi := &file_openshell_proto_msgTypes[26] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2979,7 +3047,7 @@ func (x *SandboxResources) ProtoReflect() protoreflect.Message { // Deprecated: Use SandboxResources.ProtoReflect.Descriptor instead. func (*SandboxResources) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{25} + return file_openshell_proto_rawDescGZIP(), []int{26} } func (x *SandboxResources) GetCpu() string { @@ -3012,7 +3080,7 @@ type SandboxServiceLevel struct { func (x *SandboxServiceLevel) Reset() { *x = SandboxServiceLevel{} - mi := &file_openshell_proto_msgTypes[26] + mi := &file_openshell_proto_msgTypes[27] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3024,7 +3092,7 @@ func (x *SandboxServiceLevel) String() string { func (*SandboxServiceLevel) ProtoMessage() {} func (x *SandboxServiceLevel) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[26] + mi := &file_openshell_proto_msgTypes[27] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3037,7 +3105,7 @@ func (x *SandboxServiceLevel) ProtoReflect() protoreflect.Message { // Deprecated: Use SandboxServiceLevel.ProtoReflect.Descriptor instead. func (*SandboxServiceLevel) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{26} + return file_openshell_proto_rawDescGZIP(), []int{27} } func (x *SandboxServiceLevel) GetStartup() *SandboxStartup { @@ -3057,7 +3125,7 @@ type SandboxStartup struct { func (x *SandboxStartup) Reset() { *x = SandboxStartup{} - mi := &file_openshell_proto_msgTypes[27] + mi := &file_openshell_proto_msgTypes[28] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3069,7 +3137,7 @@ func (x *SandboxStartup) String() string { func (*SandboxStartup) ProtoMessage() {} func (x *SandboxStartup) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[27] + mi := &file_openshell_proto_msgTypes[28] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3082,7 +3150,7 @@ func (x *SandboxStartup) ProtoReflect() protoreflect.Message { // Deprecated: Use SandboxStartup.ProtoReflect.Descriptor instead. func (*SandboxStartup) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{27} + return file_openshell_proto_rawDescGZIP(), []int{28} } func (x *SandboxStartup) GetReadyWithin() *durationpb.Duration { @@ -3109,7 +3177,7 @@ type SandboxWorkloadTemplateProvenance struct { func (x *SandboxWorkloadTemplateProvenance) Reset() { *x = SandboxWorkloadTemplateProvenance{} - mi := &file_openshell_proto_msgTypes[28] + mi := &file_openshell_proto_msgTypes[29] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3121,7 +3189,7 @@ func (x *SandboxWorkloadTemplateProvenance) String() string { func (*SandboxWorkloadTemplateProvenance) ProtoMessage() {} func (x *SandboxWorkloadTemplateProvenance) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[28] + mi := &file_openshell_proto_msgTypes[29] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3134,7 +3202,7 @@ func (x *SandboxWorkloadTemplateProvenance) ProtoReflect() protoreflect.Message // Deprecated: Use SandboxWorkloadTemplateProvenance.ProtoReflect.Descriptor instead. func (*SandboxWorkloadTemplateProvenance) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{28} + return file_openshell_proto_rawDescGZIP(), []int{29} } func (x *SandboxWorkloadTemplateProvenance) GetName() string { @@ -3200,7 +3268,7 @@ type SandboxStatus struct { func (x *SandboxStatus) Reset() { *x = SandboxStatus{} - mi := &file_openshell_proto_msgTypes[29] + mi := &file_openshell_proto_msgTypes[30] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3212,7 +3280,7 @@ func (x *SandboxStatus) String() string { func (*SandboxStatus) ProtoMessage() {} func (x *SandboxStatus) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[29] + mi := &file_openshell_proto_msgTypes[30] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3225,7 +3293,7 @@ func (x *SandboxStatus) ProtoReflect() protoreflect.Message { // Deprecated: Use SandboxStatus.ProtoReflect.Descriptor instead. func (*SandboxStatus) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{29} + return file_openshell_proto_rawDescGZIP(), []int{30} } func (x *SandboxStatus) GetAgentPod() string { @@ -3359,7 +3427,7 @@ type SandboxCondition struct { func (x *SandboxCondition) Reset() { *x = SandboxCondition{} - mi := &file_openshell_proto_msgTypes[30] + mi := &file_openshell_proto_msgTypes[31] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3371,7 +3439,7 @@ func (x *SandboxCondition) String() string { func (*SandboxCondition) ProtoMessage() {} func (x *SandboxCondition) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[30] + mi := &file_openshell_proto_msgTypes[31] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3384,7 +3452,7 @@ func (x *SandboxCondition) ProtoReflect() protoreflect.Message { // Deprecated: Use SandboxCondition.ProtoReflect.Descriptor instead. func (*SandboxCondition) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{30} + return file_openshell_proto_rawDescGZIP(), []int{31} } func (x *SandboxCondition) GetType() string { @@ -3443,7 +3511,7 @@ type PlatformEvent struct { func (x *PlatformEvent) Reset() { *x = PlatformEvent{} - mi := &file_openshell_proto_msgTypes[31] + mi := &file_openshell_proto_msgTypes[32] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3455,7 +3523,7 @@ func (x *PlatformEvent) String() string { func (*PlatformEvent) ProtoMessage() {} func (x *PlatformEvent) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[31] + mi := &file_openshell_proto_msgTypes[32] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3468,7 +3536,7 @@ func (x *PlatformEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use PlatformEvent.ProtoReflect.Descriptor instead. func (*PlatformEvent) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{31} + return file_openshell_proto_rawDescGZIP(), []int{32} } func (x *PlatformEvent) GetEventTime() *timestamppb.Timestamp { @@ -3538,13 +3606,16 @@ type CreateSandboxRequest struct { // registered after the sandbox has been persisted and route only while the // sandbox is ready. ServiceExposures []*SandboxServiceExposure `protobuf:"bytes,9,rep,name=service_exposures,json=serviceExposures,proto3" json:"service_exposures,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + // Optional bounded use of the authenticated user's separately authorized, + // gateway-owned delegated identity credential. + DelegatedIdentity *DelegatedIdentityRequest `protobuf:"bytes,10,opt,name=delegated_identity,json=delegatedIdentity,proto3" json:"delegated_identity,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *CreateSandboxRequest) Reset() { *x = CreateSandboxRequest{} - mi := &file_openshell_proto_msgTypes[32] + mi := &file_openshell_proto_msgTypes[33] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3556,7 +3627,7 @@ func (x *CreateSandboxRequest) String() string { func (*CreateSandboxRequest) ProtoMessage() {} func (x *CreateSandboxRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[32] + mi := &file_openshell_proto_msgTypes[33] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3569,7 +3640,7 @@ func (x *CreateSandboxRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use CreateSandboxRequest.ProtoReflect.Descriptor instead. func (*CreateSandboxRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{32} + return file_openshell_proto_rawDescGZIP(), []int{33} } func (x *CreateSandboxRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { @@ -3635,92 +3706,36 @@ func (x *CreateSandboxRequest) GetServiceExposures() []*SandboxServiceExposure { return nil } -type CreateSandboxTemplateRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - // Workspace scope. Only a named workspace selection is accepted. - WorkspaceScope *datamodelv1.WorkspaceSelector `protobuf:"bytes,2,opt,name=workspace_scope,json=workspaceScope,proto3" json:"workspace_scope,omitempty"` - Template *SandboxWorkloadTemplate `protobuf:"bytes,1,opt,name=template,proto3" json:"template,omitempty"` - // Optional nonzero UUID for durable at-most-once admission. Successful results - // can be replayed for 24 hours; see the API errors and retries reference. - RequestId string `protobuf:"bytes,3,opt,name=request_id,json=requestId,proto3" json:"request_id,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *CreateSandboxTemplateRequest) Reset() { - *x = CreateSandboxTemplateRequest{} - mi := &file_openshell_proto_msgTypes[33] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *CreateSandboxTemplateRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*CreateSandboxTemplateRequest) ProtoMessage() {} - -func (x *CreateSandboxTemplateRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[33] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use CreateSandboxTemplateRequest.ProtoReflect.Descriptor instead. -func (*CreateSandboxTemplateRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{33} -} - -func (x *CreateSandboxTemplateRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { - if x != nil { - return x.WorkspaceScope - } - return nil -} - -func (x *CreateSandboxTemplateRequest) GetTemplate() *SandboxWorkloadTemplate { +func (x *CreateSandboxRequest) GetDelegatedIdentity() *DelegatedIdentityRequest { if x != nil { - return x.Template + return x.DelegatedIdentity } return nil } -func (x *CreateSandboxTemplateRequest) GetRequestId() string { - if x != nil { - return x.RequestId - } - return "" -} - -type GetSandboxTemplateRequest struct { +type DelegatedIdentityRequest struct { state protoimpl.MessageState `protogen:"open.v1"` - // Workspace scope. Only a named workspace selection is accepted. - WorkspaceScope *datamodelv1.WorkspaceSelector `protobuf:"bytes,2,opt,name=workspace_scope,json=workspaceScope,proto3" json:"workspace_scope,omitempty"` - Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + // Requested delegation duration. The gateway derives the absolute expiry + // from its own clock after validating this value against its configured maximum. + Duration *durationpb.Duration `protobuf:"bytes,1,opt,name=duration,proto3" json:"duration,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } -func (x *GetSandboxTemplateRequest) Reset() { - *x = GetSandboxTemplateRequest{} +func (x *DelegatedIdentityRequest) Reset() { + *x = DelegatedIdentityRequest{} mi := &file_openshell_proto_msgTypes[34] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *GetSandboxTemplateRequest) String() string { +func (x *DelegatedIdentityRequest) String() string { return protoimpl.X.MessageStringOf(x) } -func (*GetSandboxTemplateRequest) ProtoMessage() {} +func (*DelegatedIdentityRequest) ProtoMessage() {} -func (x *GetSandboxTemplateRequest) ProtoReflect() protoreflect.Message { +func (x *DelegatedIdentityRequest) ProtoReflect() protoreflect.Message { mi := &file_openshell_proto_msgTypes[34] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -3732,55 +3747,46 @@ func (x *GetSandboxTemplateRequest) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use GetSandboxTemplateRequest.ProtoReflect.Descriptor instead. -func (*GetSandboxTemplateRequest) Descriptor() ([]byte, []int) { +// Deprecated: Use DelegatedIdentityRequest.ProtoReflect.Descriptor instead. +func (*DelegatedIdentityRequest) Descriptor() ([]byte, []int) { return file_openshell_proto_rawDescGZIP(), []int{34} } -func (x *GetSandboxTemplateRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { +func (x *DelegatedIdentityRequest) GetDuration() *durationpb.Duration { if x != nil { - return x.WorkspaceScope + return x.Duration } return nil } -func (x *GetSandboxTemplateRequest) GetName() string { - if x != nil { - return x.Name - } - return "" -} - -type ListSandboxTemplatesRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - // Workspace scope. Named and all-workspaces selections are accepted. - WorkspaceScope *datamodelv1.WorkspaceSelector `protobuf:"bytes,4,opt,name=workspace_scope,json=workspaceScope,proto3" json:"workspace_scope,omitempty"` - // The maximum number of templates to return. Zero uses 100. Values above - // 1000 are coerced to 1000; negative values are invalid. - PageSize int32 `protobuf:"varint,1,opt,name=page_size,json=pageSize,proto3" json:"page_size,omitempty"` - // Token from a previous ListSandboxTemplates response. All other request - // parameters except page_size must match the request that produced it. - PageToken string `protobuf:"bytes,2,opt,name=page_token,json=pageToken,proto3" json:"page_token,omitempty"` - // Optional label selector in key=value comma-separated form. - LabelSelector string `protobuf:"bytes,3,opt,name=label_selector,json=labelSelector,proto3" json:"label_selector,omitempty"` +// Transfer-only OAuth material obtained from a separate user authorization. +// The CLI must never install this bundle as its normal gateway login. +type DelegatedIdentityAuthorizationGrant struct { + state protoimpl.MessageState `protogen:"open.v1"` + Issuer string `protobuf:"bytes,1,opt,name=issuer,proto3" json:"issuer,omitempty"` + ClientId string `protobuf:"bytes,2,opt,name=client_id,json=clientId,proto3" json:"client_id,omitempty"` + RefreshToken string `protobuf:"bytes,3,opt,name=refresh_token,json=refreshToken,proto3" json:"refresh_token,omitempty"` + AccessToken string `protobuf:"bytes,4,opt,name=access_token,json=accessToken,proto3" json:"access_token,omitempty"` + Scopes string `protobuf:"bytes,5,opt,name=scopes,proto3" json:"scopes,omitempty"` + Audience string `protobuf:"bytes,6,opt,name=audience,proto3" json:"audience,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } -func (x *ListSandboxTemplatesRequest) Reset() { - *x = ListSandboxTemplatesRequest{} +func (x *DelegatedIdentityAuthorizationGrant) Reset() { + *x = DelegatedIdentityAuthorizationGrant{} mi := &file_openshell_proto_msgTypes[35] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *ListSandboxTemplatesRequest) String() string { +func (x *DelegatedIdentityAuthorizationGrant) String() string { return protoimpl.X.MessageStringOf(x) } -func (*ListSandboxTemplatesRequest) ProtoMessage() {} +func (*DelegatedIdentityAuthorizationGrant) ProtoMessage() {} -func (x *ListSandboxTemplatesRequest) ProtoReflect() protoreflect.Message { +func (x *DelegatedIdentityAuthorizationGrant) ProtoReflect() protoreflect.Message { mi := &file_openshell_proto_msgTypes[35] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -3792,67 +3798,73 @@ func (x *ListSandboxTemplatesRequest) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use ListSandboxTemplatesRequest.ProtoReflect.Descriptor instead. -func (*ListSandboxTemplatesRequest) Descriptor() ([]byte, []int) { +// Deprecated: Use DelegatedIdentityAuthorizationGrant.ProtoReflect.Descriptor instead. +func (*DelegatedIdentityAuthorizationGrant) Descriptor() ([]byte, []int) { return file_openshell_proto_rawDescGZIP(), []int{35} } -func (x *ListSandboxTemplatesRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { +func (x *DelegatedIdentityAuthorizationGrant) GetIssuer() string { if x != nil { - return x.WorkspaceScope + return x.Issuer } - return nil + return "" } -func (x *ListSandboxTemplatesRequest) GetPageSize() int32 { +func (x *DelegatedIdentityAuthorizationGrant) GetClientId() string { if x != nil { - return x.PageSize + return x.ClientId } - return 0 + return "" } -func (x *ListSandboxTemplatesRequest) GetPageToken() string { +func (x *DelegatedIdentityAuthorizationGrant) GetRefreshToken() string { if x != nil { - return x.PageToken + return x.RefreshToken } return "" } -func (x *ListSandboxTemplatesRequest) GetLabelSelector() string { +func (x *DelegatedIdentityAuthorizationGrant) GetAccessToken() string { if x != nil { - return x.LabelSelector + return x.AccessToken } return "" } -type DeleteSandboxTemplateRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - // Workspace scope. Only a named workspace selection is accepted. - WorkspaceScope *datamodelv1.WorkspaceSelector `protobuf:"bytes,2,opt,name=workspace_scope,json=workspaceScope,proto3" json:"workspace_scope,omitempty"` - Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` - // Succeed with ALREADY_ABSENT if the target is missing. Authorization and - // parent-workspace checks still apply. - AllowMissing bool `protobuf:"varint,3,opt,name=allow_missing,json=allowMissing,proto3" json:"allow_missing,omitempty"` - // Optional nonzero UUID. Same ID and payload replay success for 24 hours. - RequestId string `protobuf:"bytes,4,opt,name=request_id,json=requestId,proto3" json:"request_id,omitempty"` +func (x *DelegatedIdentityAuthorizationGrant) GetScopes() string { + if x != nil { + return x.Scopes + } + return "" +} + +func (x *DelegatedIdentityAuthorizationGrant) GetAudience() string { + if x != nil { + return x.Audience + } + return "" +} + +type GetDelegatedIdentityAuthorizationStatusRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } -func (x *DeleteSandboxTemplateRequest) Reset() { - *x = DeleteSandboxTemplateRequest{} +func (x *GetDelegatedIdentityAuthorizationStatusRequest) Reset() { + *x = GetDelegatedIdentityAuthorizationStatusRequest{} mi := &file_openshell_proto_msgTypes[36] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *DeleteSandboxTemplateRequest) String() string { +func (x *GetDelegatedIdentityAuthorizationStatusRequest) String() string { return protoimpl.X.MessageStringOf(x) } -func (*DeleteSandboxTemplateRequest) ProtoMessage() {} +func (*GetDelegatedIdentityAuthorizationStatusRequest) ProtoMessage() {} -func (x *DeleteSandboxTemplateRequest) ProtoReflect() protoreflect.Message { +func (x *GetDelegatedIdentityAuthorizationStatusRequest) ProtoReflect() protoreflect.Message { mi := &file_openshell_proto_msgTypes[36] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -3864,60 +3876,36 @@ func (x *DeleteSandboxTemplateRequest) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use DeleteSandboxTemplateRequest.ProtoReflect.Descriptor instead. -func (*DeleteSandboxTemplateRequest) Descriptor() ([]byte, []int) { +// Deprecated: Use GetDelegatedIdentityAuthorizationStatusRequest.ProtoReflect.Descriptor instead. +func (*GetDelegatedIdentityAuthorizationStatusRequest) Descriptor() ([]byte, []int) { return file_openshell_proto_rawDescGZIP(), []int{36} } -func (x *DeleteSandboxTemplateRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { - if x != nil { - return x.WorkspaceScope - } - return nil -} - -func (x *DeleteSandboxTemplateRequest) GetName() string { - if x != nil { - return x.Name - } - return "" -} - -func (x *DeleteSandboxTemplateRequest) GetAllowMissing() bool { - if x != nil { - return x.AllowMissing - } - return false -} - -func (x *DeleteSandboxTemplateRequest) GetRequestId() string { - if x != nil { - return x.RequestId - } - return "" -} - -type SandboxTemplateResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - Template *SandboxWorkloadTemplate `protobuf:"bytes,1,opt,name=template,proto3" json:"template,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache +type GetDelegatedIdentityAuthorizationStatusResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Usable bool `protobuf:"varint,1,opt,name=usable,proto3" json:"usable,omitempty"` + ReauthorizationRequired bool `protobuf:"varint,2,opt,name=reauthorization_required,json=reauthorizationRequired,proto3" json:"reauthorization_required,omitempty"` + Reason string `protobuf:"bytes,3,opt,name=reason,proto3" json:"reason,omitempty"` + Credential *DelegatedIdentityCredentialSummary `protobuf:"bytes,4,opt,name=credential,proto3" json:"credential,omitempty"` + CurrentTime *timestamppb.Timestamp `protobuf:"bytes,5,opt,name=current_time,json=currentTime,proto3" json:"current_time,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } -func (x *SandboxTemplateResponse) Reset() { - *x = SandboxTemplateResponse{} +func (x *GetDelegatedIdentityAuthorizationStatusResponse) Reset() { + *x = GetDelegatedIdentityAuthorizationStatusResponse{} mi := &file_openshell_proto_msgTypes[37] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *SandboxTemplateResponse) String() string { +func (x *GetDelegatedIdentityAuthorizationStatusResponse) String() string { return protoimpl.X.MessageStringOf(x) } -func (*SandboxTemplateResponse) ProtoMessage() {} +func (*GetDelegatedIdentityAuthorizationStatusResponse) ProtoMessage() {} -func (x *SandboxTemplateResponse) ProtoReflect() protoreflect.Message { +func (x *GetDelegatedIdentityAuthorizationStatusResponse) ProtoReflect() protoreflect.Message { mi := &file_openshell_proto_msgTypes[37] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -3929,41 +3917,67 @@ func (x *SandboxTemplateResponse) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use SandboxTemplateResponse.ProtoReflect.Descriptor instead. -func (*SandboxTemplateResponse) Descriptor() ([]byte, []int) { +// Deprecated: Use GetDelegatedIdentityAuthorizationStatusResponse.ProtoReflect.Descriptor instead. +func (*GetDelegatedIdentityAuthorizationStatusResponse) Descriptor() ([]byte, []int) { return file_openshell_proto_rawDescGZIP(), []int{37} } -func (x *SandboxTemplateResponse) GetTemplate() *SandboxWorkloadTemplate { +func (x *GetDelegatedIdentityAuthorizationStatusResponse) GetUsable() bool { if x != nil { - return x.Template + return x.Usable + } + return false +} + +func (x *GetDelegatedIdentityAuthorizationStatusResponse) GetReauthorizationRequired() bool { + if x != nil { + return x.ReauthorizationRequired + } + return false +} + +func (x *GetDelegatedIdentityAuthorizationStatusResponse) GetReason() string { + if x != nil { + return x.Reason + } + return "" +} + +func (x *GetDelegatedIdentityAuthorizationStatusResponse) GetCredential() *DelegatedIdentityCredentialSummary { + if x != nil { + return x.Credential } return nil } -type ListSandboxTemplatesResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - Templates []*SandboxWorkloadTemplate `protobuf:"bytes,1,rep,name=templates,proto3" json:"templates,omitempty"` - // Token for the next page. Empty when there are no subsequent pages. - NextPageToken string `protobuf:"bytes,2,opt,name=next_page_token,json=nextPageToken,proto3" json:"next_page_token,omitempty"` +func (x *GetDelegatedIdentityAuthorizationStatusResponse) GetCurrentTime() *timestamppb.Timestamp { + if x != nil { + return x.CurrentTime + } + return nil +} + +type AuthorizeDelegatedIdentityRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Grant *DelegatedIdentityAuthorizationGrant `protobuf:"bytes,1,opt,name=grant,proto3" json:"grant,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } -func (x *ListSandboxTemplatesResponse) Reset() { - *x = ListSandboxTemplatesResponse{} +func (x *AuthorizeDelegatedIdentityRequest) Reset() { + *x = AuthorizeDelegatedIdentityRequest{} mi := &file_openshell_proto_msgTypes[38] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *ListSandboxTemplatesResponse) String() string { +func (x *AuthorizeDelegatedIdentityRequest) String() string { return protoimpl.X.MessageStringOf(x) } -func (*ListSandboxTemplatesResponse) ProtoMessage() {} +func (*AuthorizeDelegatedIdentityRequest) ProtoMessage() {} -func (x *ListSandboxTemplatesResponse) ProtoReflect() protoreflect.Message { +func (x *AuthorizeDelegatedIdentityRequest) ProtoReflect() protoreflect.Message { mi := &file_openshell_proto_msgTypes[38] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -3975,46 +3989,40 @@ func (x *ListSandboxTemplatesResponse) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use ListSandboxTemplatesResponse.ProtoReflect.Descriptor instead. -func (*ListSandboxTemplatesResponse) Descriptor() ([]byte, []int) { +// Deprecated: Use AuthorizeDelegatedIdentityRequest.ProtoReflect.Descriptor instead. +func (*AuthorizeDelegatedIdentityRequest) Descriptor() ([]byte, []int) { return file_openshell_proto_rawDescGZIP(), []int{38} } -func (x *ListSandboxTemplatesResponse) GetTemplates() []*SandboxWorkloadTemplate { +func (x *AuthorizeDelegatedIdentityRequest) GetGrant() *DelegatedIdentityAuthorizationGrant { if x != nil { - return x.Templates + return x.Grant } return nil } -func (x *ListSandboxTemplatesResponse) GetNextPageToken() string { - if x != nil { - return x.NextPageToken - } - return "" -} - -type DeleteSandboxTemplateResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - Outcome DeletionOutcome `protobuf:"varint,2,opt,name=outcome,proto3,enum=openshell.v1.DeletionOutcome" json:"outcome,omitempty"` +type AuthorizeDelegatedIdentityResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Credential *DelegatedIdentityCredentialSummary `protobuf:"bytes,1,opt,name=credential,proto3" json:"credential,omitempty"` + CurrentTime *timestamppb.Timestamp `protobuf:"bytes,2,opt,name=current_time,json=currentTime,proto3" json:"current_time,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } -func (x *DeleteSandboxTemplateResponse) Reset() { - *x = DeleteSandboxTemplateResponse{} +func (x *AuthorizeDelegatedIdentityResponse) Reset() { + *x = AuthorizeDelegatedIdentityResponse{} mi := &file_openshell_proto_msgTypes[39] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *DeleteSandboxTemplateResponse) String() string { +func (x *AuthorizeDelegatedIdentityResponse) String() string { return protoimpl.X.MessageStringOf(x) } -func (*DeleteSandboxTemplateResponse) ProtoMessage() {} +func (*AuthorizeDelegatedIdentityResponse) ProtoMessage() {} -func (x *DeleteSandboxTemplateResponse) ProtoReflect() protoreflect.Message { +func (x *AuthorizeDelegatedIdentityResponse) ProtoReflect() protoreflect.Message { mi := &file_openshell_proto_msgTypes[39] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -4026,47 +4034,48 @@ func (x *DeleteSandboxTemplateResponse) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use DeleteSandboxTemplateResponse.ProtoReflect.Descriptor instead. -func (*DeleteSandboxTemplateResponse) Descriptor() ([]byte, []int) { +// Deprecated: Use AuthorizeDelegatedIdentityResponse.ProtoReflect.Descriptor instead. +func (*AuthorizeDelegatedIdentityResponse) Descriptor() ([]byte, []int) { return file_openshell_proto_rawDescGZIP(), []int{39} } -func (x *DeleteSandboxTemplateResponse) GetOutcome() DeletionOutcome { +func (x *AuthorizeDelegatedIdentityResponse) GetCredential() *DelegatedIdentityCredentialSummary { if x != nil { - return x.Outcome + return x.Credential } - return DeletionOutcome_DELETION_OUTCOME_UNSPECIFIED + return nil } -// Request a gateway-owned staging slot for a local rootfs tar archive. -type BeginRootfsTarStagingRequest struct { +func (x *AuthorizeDelegatedIdentityResponse) GetCurrentTime() *timestamppb.Timestamp { + if x != nil { + return x.CurrentTime + } + return nil +} + +type GetSandboxDelegatedIdentityStatusRequest struct { state protoimpl.MessageState `protogen:"open.v1"` - // Workspace scope. Only a named workspace selection is accepted. + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + // Explicit workspace scope. The all-workspaces selection is invalid. WorkspaceScope *datamodelv1.WorkspaceSelector `protobuf:"bytes,3,opt,name=workspace_scope,json=workspaceScope,proto3" json:"workspace_scope,omitempty"` - // Base file name of the local archive. The gateway uses it only to name the - // staged file; path separators and traversal components are rejected. - FileName string `protobuf:"bytes,1,opt,name=file_name,json=fileName,proto3" json:"file_name,omitempty"` - // Size of the local archive in bytes, checked against the driver limit - // before the gateway allocates a slot. - SizeBytes uint64 `protobuf:"varint,2,opt,name=size_bytes,json=sizeBytes,proto3" json:"size_bytes,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } -func (x *BeginRootfsTarStagingRequest) Reset() { - *x = BeginRootfsTarStagingRequest{} +func (x *GetSandboxDelegatedIdentityStatusRequest) Reset() { + *x = GetSandboxDelegatedIdentityStatusRequest{} mi := &file_openshell_proto_msgTypes[40] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *BeginRootfsTarStagingRequest) String() string { +func (x *GetSandboxDelegatedIdentityStatusRequest) String() string { return protoimpl.X.MessageStringOf(x) } -func (*BeginRootfsTarStagingRequest) ProtoMessage() {} +func (*GetSandboxDelegatedIdentityStatusRequest) ProtoMessage() {} -func (x *BeginRootfsTarStagingRequest) ProtoReflect() protoreflect.Message { +func (x *GetSandboxDelegatedIdentityStatusRequest) ProtoReflect() protoreflect.Message { mi := &file_openshell_proto_msgTypes[40] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -4078,63 +4087,49 @@ func (x *BeginRootfsTarStagingRequest) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use BeginRootfsTarStagingRequest.ProtoReflect.Descriptor instead. -func (*BeginRootfsTarStagingRequest) Descriptor() ([]byte, []int) { +// Deprecated: Use GetSandboxDelegatedIdentityStatusRequest.ProtoReflect.Descriptor instead. +func (*GetSandboxDelegatedIdentityStatusRequest) Descriptor() ([]byte, []int) { return file_openshell_proto_rawDescGZIP(), []int{40} } -func (x *BeginRootfsTarStagingRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { - if x != nil { - return x.WorkspaceScope - } - return nil -} - -func (x *BeginRootfsTarStagingRequest) GetFileName() string { +func (x *GetSandboxDelegatedIdentityStatusRequest) GetName() string { if x != nil { - return x.FileName + return x.Name } return "" } -func (x *BeginRootfsTarStagingRequest) GetSizeBytes() uint64 { +func (x *GetSandboxDelegatedIdentityStatusRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { if x != nil { - return x.SizeBytes + return x.WorkspaceScope } - return 0 + return nil } -// Gateway-issued staging slot. -type BeginRootfsTarStagingResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - // Opaque single-use token. Pass it as - // `template.driver_config..rootfs_tar_staging_token` on - // CreateSandbox. The first CreateSandbox presenting it consumes it. - StagingToken string `protobuf:"bytes,1,opt,name=staging_token,json=stagingToken,proto3" json:"staging_token,omitempty"` - // Absolute path on the gateway host the client must write the archive to. - UploadPath string `protobuf:"bytes,2,opt,name=upload_path,json=uploadPath,proto3" json:"upload_path,omitempty"` - // Maximum accepted archive size in bytes, enforced again by the driver. - MaxBytes uint64 `protobuf:"varint,3,opt,name=max_bytes,json=maxBytes,proto3" json:"max_bytes,omitempty"` - // Wall-clock deadline after which the gateway reclaims the slot. - ExpirationTime *timestamppb.Timestamp `protobuf:"bytes,104,opt,name=expiration_time,json=expirationTime,proto3" json:"expiration_time,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache +type GetSandboxDelegatedIdentityStatusResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + DelegatedIdentity *SandboxDelegatedIdentity `protobuf:"bytes,1,opt,name=delegated_identity,json=delegatedIdentity,proto3" json:"delegated_identity,omitempty"` + CurrentTime *timestamppb.Timestamp `protobuf:"bytes,2,opt,name=current_time,json=currentTime,proto3" json:"current_time,omitempty"` + CredentialRevocationTime *timestamppb.Timestamp `protobuf:"bytes,3,opt,name=credential_revocation_time,json=credentialRevocationTime,proto3" json:"credential_revocation_time,omitempty"` + CredentialMissing bool `protobuf:"varint,4,opt,name=credential_missing,json=credentialMissing,proto3" json:"credential_missing,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } -func (x *BeginRootfsTarStagingResponse) Reset() { - *x = BeginRootfsTarStagingResponse{} +func (x *GetSandboxDelegatedIdentityStatusResponse) Reset() { + *x = GetSandboxDelegatedIdentityStatusResponse{} mi := &file_openshell_proto_msgTypes[41] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *BeginRootfsTarStagingResponse) String() string { +func (x *GetSandboxDelegatedIdentityStatusResponse) String() string { return protoimpl.X.MessageStringOf(x) } -func (*BeginRootfsTarStagingResponse) ProtoMessage() {} +func (*GetSandboxDelegatedIdentityStatusResponse) ProtoMessage() {} -func (x *BeginRootfsTarStagingResponse) ProtoReflect() protoreflect.Message { +func (x *GetSandboxDelegatedIdentityStatusResponse) ProtoReflect() protoreflect.Message { mi := &file_openshell_proto_msgTypes[41] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -4146,63 +4141,62 @@ func (x *BeginRootfsTarStagingResponse) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use BeginRootfsTarStagingResponse.ProtoReflect.Descriptor instead. -func (*BeginRootfsTarStagingResponse) Descriptor() ([]byte, []int) { +// Deprecated: Use GetSandboxDelegatedIdentityStatusResponse.ProtoReflect.Descriptor instead. +func (*GetSandboxDelegatedIdentityStatusResponse) Descriptor() ([]byte, []int) { return file_openshell_proto_rawDescGZIP(), []int{41} } -func (x *BeginRootfsTarStagingResponse) GetStagingToken() string { +func (x *GetSandboxDelegatedIdentityStatusResponse) GetDelegatedIdentity() *SandboxDelegatedIdentity { if x != nil { - return x.StagingToken + return x.DelegatedIdentity } - return "" + return nil } -func (x *BeginRootfsTarStagingResponse) GetUploadPath() string { +func (x *GetSandboxDelegatedIdentityStatusResponse) GetCurrentTime() *timestamppb.Timestamp { if x != nil { - return x.UploadPath + return x.CurrentTime } - return "" + return nil } -func (x *BeginRootfsTarStagingResponse) GetMaxBytes() uint64 { +func (x *GetSandboxDelegatedIdentityStatusResponse) GetCredentialRevocationTime() *timestamppb.Timestamp { if x != nil { - return x.MaxBytes + return x.CredentialRevocationTime } - return 0 + return nil } -func (x *BeginRootfsTarStagingResponse) GetExpirationTime() *timestamppb.Timestamp { +func (x *GetSandboxDelegatedIdentityStatusResponse) GetCredentialMissing() bool { if x != nil { - return x.ExpirationTime + return x.CredentialMissing } - return nil + return false } -// Get sandbox request. -type GetSandboxRequest struct { +type WithdrawSandboxDelegatedIdentityRequest struct { state protoimpl.MessageState `protogen:"open.v1"` - // Workspace scope. Only a named workspace selection is accepted. - WorkspaceScope *datamodelv1.WorkspaceSelector `protobuf:"bytes,2,opt,name=workspace_scope,json=workspaceScope,proto3" json:"workspace_scope,omitempty"` - Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + // Explicit workspace scope. The all-workspaces selection is invalid. + WorkspaceScope *datamodelv1.WorkspaceSelector `protobuf:"bytes,3,opt,name=workspace_scope,json=workspaceScope,proto3" json:"workspace_scope,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } -func (x *GetSandboxRequest) Reset() { - *x = GetSandboxRequest{} +func (x *WithdrawSandboxDelegatedIdentityRequest) Reset() { + *x = WithdrawSandboxDelegatedIdentityRequest{} mi := &file_openshell_proto_msgTypes[42] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *GetSandboxRequest) String() string { +func (x *WithdrawSandboxDelegatedIdentityRequest) String() string { return protoimpl.X.MessageStringOf(x) } -func (*GetSandboxRequest) ProtoMessage() {} +func (*WithdrawSandboxDelegatedIdentityRequest) ProtoMessage() {} -func (x *GetSandboxRequest) ProtoReflect() protoreflect.Message { +func (x *WithdrawSandboxDelegatedIdentityRequest) ProtoReflect() protoreflect.Message { mi := &file_openshell_proto_msgTypes[42] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -4214,56 +4208,47 @@ func (x *GetSandboxRequest) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use GetSandboxRequest.ProtoReflect.Descriptor instead. -func (*GetSandboxRequest) Descriptor() ([]byte, []int) { +// Deprecated: Use WithdrawSandboxDelegatedIdentityRequest.ProtoReflect.Descriptor instead. +func (*WithdrawSandboxDelegatedIdentityRequest) Descriptor() ([]byte, []int) { return file_openshell_proto_rawDescGZIP(), []int{42} } -func (x *GetSandboxRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { +func (x *WithdrawSandboxDelegatedIdentityRequest) GetName() string { if x != nil { - return x.WorkspaceScope + return x.Name } - return nil + return "" } -func (x *GetSandboxRequest) GetName() string { +func (x *WithdrawSandboxDelegatedIdentityRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { if x != nil { - return x.Name + return x.WorkspaceScope } - return "" + return nil } -// List sandboxes request. -type ListSandboxesRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - // Workspace scope. Named and all-workspaces selections are accepted. - WorkspaceScope *datamodelv1.WorkspaceSelector `protobuf:"bytes,4,opt,name=workspace_scope,json=workspaceScope,proto3" json:"workspace_scope,omitempty"` - // The maximum number of sandboxes to return. Zero uses 100. Values above - // 1000 are coerced to 1000; negative values are invalid. - PageSize int32 `protobuf:"varint,1,opt,name=page_size,json=pageSize,proto3" json:"page_size,omitempty"` - // Token from a previous ListSandboxes response. All other request parameters - // except page_size must match the request that produced it. - PageToken string `protobuf:"bytes,2,opt,name=page_token,json=pageToken,proto3" json:"page_token,omitempty"` - // Optional label selector for filtering (format: "key1=value1,key2=value2"). - LabelSelector string `protobuf:"bytes,3,opt,name=label_selector,json=labelSelector,proto3" json:"label_selector,omitempty"` +type WithdrawSandboxDelegatedIdentityResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Sandbox *Sandbox `protobuf:"bytes,1,opt,name=sandbox,proto3" json:"sandbox,omitempty"` + Withdrawn bool `protobuf:"varint,2,opt,name=withdrawn,proto3" json:"withdrawn,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } -func (x *ListSandboxesRequest) Reset() { - *x = ListSandboxesRequest{} +func (x *WithdrawSandboxDelegatedIdentityResponse) Reset() { + *x = WithdrawSandboxDelegatedIdentityResponse{} mi := &file_openshell_proto_msgTypes[43] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *ListSandboxesRequest) String() string { +func (x *WithdrawSandboxDelegatedIdentityResponse) String() string { return protoimpl.X.MessageStringOf(x) } -func (*ListSandboxesRequest) ProtoMessage() {} +func (*WithdrawSandboxDelegatedIdentityResponse) ProtoMessage() {} -func (x *ListSandboxesRequest) ProtoReflect() protoreflect.Message { +func (x *WithdrawSandboxDelegatedIdentityResponse) ProtoReflect() protoreflect.Message { mi := &file_openshell_proto_msgTypes[43] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -4275,69 +4260,49 @@ func (x *ListSandboxesRequest) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use ListSandboxesRequest.ProtoReflect.Descriptor instead. -func (*ListSandboxesRequest) Descriptor() ([]byte, []int) { +// Deprecated: Use WithdrawSandboxDelegatedIdentityResponse.ProtoReflect.Descriptor instead. +func (*WithdrawSandboxDelegatedIdentityResponse) Descriptor() ([]byte, []int) { return file_openshell_proto_rawDescGZIP(), []int{43} } -func (x *ListSandboxesRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { +func (x *WithdrawSandboxDelegatedIdentityResponse) GetSandbox() *Sandbox { if x != nil { - return x.WorkspaceScope + return x.Sandbox } return nil } -func (x *ListSandboxesRequest) GetPageSize() int32 { - if x != nil { - return x.PageSize - } - return 0 -} - -func (x *ListSandboxesRequest) GetPageToken() string { - if x != nil { - return x.PageToken - } - return "" -} - -func (x *ListSandboxesRequest) GetLabelSelector() string { +func (x *WithdrawSandboxDelegatedIdentityResponse) GetWithdrawn() bool { if x != nil { - return x.LabelSelector + return x.Withdrawn } - return "" + return false } -// List providers attached to a sandbox request. -type ListSandboxProvidersRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - // Workspace scope. Only a named workspace selection is accepted. - WorkspaceScope *datamodelv1.WorkspaceSelector `protobuf:"bytes,2,opt,name=workspace_scope,json=workspaceScope,proto3" json:"workspace_scope,omitempty"` - Sandbox string `protobuf:"bytes,1,opt,name=sandbox,proto3" json:"sandbox,omitempty"` - // The maximum number of providers to return. Zero uses 100. Values above - // 1000 are coerced to 1000; negative values are invalid. - PageSize int32 `protobuf:"varint,3,opt,name=page_size,json=pageSize,proto3" json:"page_size,omitempty"` - // Token from a previous ListSandboxProviders response. All other request - // parameters except page_size must match the request that produced it. - PageToken string `protobuf:"bytes,4,opt,name=page_token,json=pageToken,proto3" json:"page_token,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache +type ExtendSandboxDelegatedIdentityRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + DelegatedIdentity *DelegatedIdentityRequest `protobuf:"bytes,3,opt,name=delegated_identity,json=delegatedIdentity,proto3" json:"delegated_identity,omitempty"` + // Explicit workspace scope. The all-workspaces selection is invalid. + WorkspaceScope *datamodelv1.WorkspaceSelector `protobuf:"bytes,4,opt,name=workspace_scope,json=workspaceScope,proto3" json:"workspace_scope,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } -func (x *ListSandboxProvidersRequest) Reset() { - *x = ListSandboxProvidersRequest{} +func (x *ExtendSandboxDelegatedIdentityRequest) Reset() { + *x = ExtendSandboxDelegatedIdentityRequest{} mi := &file_openshell_proto_msgTypes[44] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *ListSandboxProvidersRequest) String() string { +func (x *ExtendSandboxDelegatedIdentityRequest) String() string { return protoimpl.X.MessageStringOf(x) } -func (*ListSandboxProvidersRequest) ProtoMessage() {} +func (*ExtendSandboxDelegatedIdentityRequest) ProtoMessage() {} -func (x *ListSandboxProvidersRequest) ProtoReflect() protoreflect.Message { +func (x *ExtendSandboxDelegatedIdentityRequest) ProtoReflect() protoreflect.Message { mi := &file_openshell_proto_msgTypes[44] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -4349,73 +4314,53 @@ func (x *ListSandboxProvidersRequest) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use ListSandboxProvidersRequest.ProtoReflect.Descriptor instead. -func (*ListSandboxProvidersRequest) Descriptor() ([]byte, []int) { +// Deprecated: Use ExtendSandboxDelegatedIdentityRequest.ProtoReflect.Descriptor instead. +func (*ExtendSandboxDelegatedIdentityRequest) Descriptor() ([]byte, []int) { return file_openshell_proto_rawDescGZIP(), []int{44} } -func (x *ListSandboxProvidersRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { - if x != nil { - return x.WorkspaceScope - } - return nil -} - -func (x *ListSandboxProvidersRequest) GetSandbox() string { +func (x *ExtendSandboxDelegatedIdentityRequest) GetName() string { if x != nil { - return x.Sandbox + return x.Name } return "" } -func (x *ListSandboxProvidersRequest) GetPageSize() int32 { +func (x *ExtendSandboxDelegatedIdentityRequest) GetDelegatedIdentity() *DelegatedIdentityRequest { if x != nil { - return x.PageSize + return x.DelegatedIdentity } - return 0 + return nil } -func (x *ListSandboxProvidersRequest) GetPageToken() string { +func (x *ExtendSandboxDelegatedIdentityRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { if x != nil { - return x.PageToken + return x.WorkspaceScope } - return "" + return nil } -// Attach provider to sandbox request. -type AttachSandboxProviderRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - // Workspace scope. Only a named workspace selection is accepted. - WorkspaceScope *datamodelv1.WorkspaceSelector `protobuf:"bytes,4,opt,name=workspace_scope,json=workspaceScope,proto3" json:"workspace_scope,omitempty"` - Sandbox string `protobuf:"bytes,1,opt,name=sandbox,proto3" json:"sandbox,omitempty"` - // Provider name to attach. - Provider string `protobuf:"bytes,2,opt,name=provider,proto3" json:"provider,omitempty"` - // Expected resource version for optimistic concurrency control. - // If 0, the server uses the current version (backward compatibility). - // If non-zero, the server validates that the sandbox's current resource_version - // matches this value before applying the mutation, returning ABORTED on mismatch. - ExpectedResourceVersion uint64 `protobuf:"varint,3,opt,name=expected_resource_version,json=expectedResourceVersion,proto3" json:"expected_resource_version,omitempty"` - // Optional nonzero UUID for durable at-most-once admission. Successful results - // can be replayed for 24 hours; see the API errors and retries reference. - RequestId string `protobuf:"bytes,5,opt,name=request_id,json=requestId,proto3" json:"request_id,omitempty"` +type ExtendSandboxDelegatedIdentityResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Sandbox *Sandbox `protobuf:"bytes,1,opt,name=sandbox,proto3" json:"sandbox,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } -func (x *AttachSandboxProviderRequest) Reset() { - *x = AttachSandboxProviderRequest{} +func (x *ExtendSandboxDelegatedIdentityResponse) Reset() { + *x = ExtendSandboxDelegatedIdentityResponse{} mi := &file_openshell_proto_msgTypes[45] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *AttachSandboxProviderRequest) String() string { +func (x *ExtendSandboxDelegatedIdentityResponse) String() string { return protoimpl.X.MessageStringOf(x) } -func (*AttachSandboxProviderRequest) ProtoMessage() {} +func (*ExtendSandboxDelegatedIdentityResponse) ProtoMessage() {} -func (x *AttachSandboxProviderRequest) ProtoReflect() protoreflect.Message { +func (x *ExtendSandboxDelegatedIdentityResponse) ProtoReflect() protoreflect.Message { mi := &file_openshell_proto_msgTypes[45] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -4427,80 +4372,44 @@ func (x *AttachSandboxProviderRequest) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use AttachSandboxProviderRequest.ProtoReflect.Descriptor instead. -func (*AttachSandboxProviderRequest) Descriptor() ([]byte, []int) { +// Deprecated: Use ExtendSandboxDelegatedIdentityResponse.ProtoReflect.Descriptor instead. +func (*ExtendSandboxDelegatedIdentityResponse) Descriptor() ([]byte, []int) { return file_openshell_proto_rawDescGZIP(), []int{45} } -func (x *AttachSandboxProviderRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { - if x != nil { - return x.WorkspaceScope - } - return nil -} - -func (x *AttachSandboxProviderRequest) GetSandbox() string { +func (x *ExtendSandboxDelegatedIdentityResponse) GetSandbox() *Sandbox { if x != nil { return x.Sandbox } - return "" -} - -func (x *AttachSandboxProviderRequest) GetProvider() string { - if x != nil { - return x.Provider - } - return "" -} - -func (x *AttachSandboxProviderRequest) GetExpectedResourceVersion() uint64 { - if x != nil { - return x.ExpectedResourceVersion - } - return 0 -} - -func (x *AttachSandboxProviderRequest) GetRequestId() string { - if x != nil { - return x.RequestId - } - return "" + return nil } -// Detach provider from sandbox request. -type DetachSandboxProviderRequest struct { +type CreateSandboxTemplateRequest struct { state protoimpl.MessageState `protogen:"open.v1"` // Workspace scope. Only a named workspace selection is accepted. - WorkspaceScope *datamodelv1.WorkspaceSelector `protobuf:"bytes,4,opt,name=workspace_scope,json=workspaceScope,proto3" json:"workspace_scope,omitempty"` - Sandbox string `protobuf:"bytes,1,opt,name=sandbox,proto3" json:"sandbox,omitempty"` - // Provider name to detach. - Provider string `protobuf:"bytes,2,opt,name=provider,proto3" json:"provider,omitempty"` - // Expected resource version for optimistic concurrency control. - // If 0, the server uses the current version (backward compatibility). - // If non-zero, the server validates that the sandbox's current resource_version - // matches this value before applying the mutation, returning ABORTED on mismatch. - ExpectedResourceVersion uint64 `protobuf:"varint,3,opt,name=expected_resource_version,json=expectedResourceVersion,proto3" json:"expected_resource_version,omitempty"` + WorkspaceScope *datamodelv1.WorkspaceSelector `protobuf:"bytes,2,opt,name=workspace_scope,json=workspaceScope,proto3" json:"workspace_scope,omitempty"` + Template *SandboxWorkloadTemplate `protobuf:"bytes,1,opt,name=template,proto3" json:"template,omitempty"` // Optional nonzero UUID for durable at-most-once admission. Successful results // can be replayed for 24 hours; see the API errors and retries reference. - RequestId string `protobuf:"bytes,5,opt,name=request_id,json=requestId,proto3" json:"request_id,omitempty"` + RequestId string `protobuf:"bytes,3,opt,name=request_id,json=requestId,proto3" json:"request_id,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } -func (x *DetachSandboxProviderRequest) Reset() { - *x = DetachSandboxProviderRequest{} +func (x *CreateSandboxTemplateRequest) Reset() { + *x = CreateSandboxTemplateRequest{} mi := &file_openshell_proto_msgTypes[46] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *DetachSandboxProviderRequest) String() string { +func (x *CreateSandboxTemplateRequest) String() string { return protoimpl.X.MessageStringOf(x) } -func (*DetachSandboxProviderRequest) ProtoMessage() {} +func (*CreateSandboxTemplateRequest) ProtoMessage() {} -func (x *DetachSandboxProviderRequest) ProtoReflect() protoreflect.Message { +func (x *CreateSandboxTemplateRequest) ProtoReflect() protoreflect.Message { mi := &file_openshell_proto_msgTypes[46] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -4512,77 +4421,55 @@ func (x *DetachSandboxProviderRequest) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use DetachSandboxProviderRequest.ProtoReflect.Descriptor instead. -func (*DetachSandboxProviderRequest) Descriptor() ([]byte, []int) { +// Deprecated: Use CreateSandboxTemplateRequest.ProtoReflect.Descriptor instead. +func (*CreateSandboxTemplateRequest) Descriptor() ([]byte, []int) { return file_openshell_proto_rawDescGZIP(), []int{46} } -func (x *DetachSandboxProviderRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { +func (x *CreateSandboxTemplateRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { if x != nil { return x.WorkspaceScope } return nil } -func (x *DetachSandboxProviderRequest) GetSandbox() string { - if x != nil { - return x.Sandbox - } - return "" -} - -func (x *DetachSandboxProviderRequest) GetProvider() string { - if x != nil { - return x.Provider - } - return "" -} - -func (x *DetachSandboxProviderRequest) GetExpectedResourceVersion() uint64 { +func (x *CreateSandboxTemplateRequest) GetTemplate() *SandboxWorkloadTemplate { if x != nil { - return x.ExpectedResourceVersion + return x.Template } - return 0 + return nil } -func (x *DetachSandboxProviderRequest) GetRequestId() string { +func (x *CreateSandboxTemplateRequest) GetRequestId() string { if x != nil { return x.RequestId } return "" } -// Delete sandbox request. -type DeleteSandboxRequest struct { +type GetSandboxTemplateRequest struct { state protoimpl.MessageState `protogen:"open.v1"` // Workspace scope. Only a named workspace selection is accepted. WorkspaceScope *datamodelv1.WorkspaceSelector `protobuf:"bytes,2,opt,name=workspace_scope,json=workspaceScope,proto3" json:"workspace_scope,omitempty"` - // Canonical sandbox name. - Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` - // Succeed with ALREADY_ABSENT if the target is missing. Does not wait for - // asynchronous cleanup and does not suppress authorization or parent errors. - AllowMissing bool `protobuf:"varint,3,opt,name=allow_missing,json=allowMissing,proto3" json:"allow_missing,omitempty"` - // Optional nonzero UUID for durable at-most-once admission. Successful results - // can be replayed for 24 hours; see the API errors and retries reference. - RequestId string `protobuf:"bytes,4,opt,name=request_id,json=requestId,proto3" json:"request_id,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } -func (x *DeleteSandboxRequest) Reset() { - *x = DeleteSandboxRequest{} +func (x *GetSandboxTemplateRequest) Reset() { + *x = GetSandboxTemplateRequest{} mi := &file_openshell_proto_msgTypes[47] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *DeleteSandboxRequest) String() string { +func (x *GetSandboxTemplateRequest) String() string { return protoimpl.X.MessageStringOf(x) } -func (*DeleteSandboxRequest) ProtoMessage() {} +func (*GetSandboxTemplateRequest) ProtoMessage() {} -func (x *DeleteSandboxRequest) ProtoReflect() protoreflect.Message { +func (x *GetSandboxTemplateRequest) ProtoReflect() protoreflect.Message { mi := &file_openshell_proto_msgTypes[47] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -4594,66 +4481,55 @@ func (x *DeleteSandboxRequest) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use DeleteSandboxRequest.ProtoReflect.Descriptor instead. -func (*DeleteSandboxRequest) Descriptor() ([]byte, []int) { +// Deprecated: Use GetSandboxTemplateRequest.ProtoReflect.Descriptor instead. +func (*GetSandboxTemplateRequest) Descriptor() ([]byte, []int) { return file_openshell_proto_rawDescGZIP(), []int{47} } -func (x *DeleteSandboxRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { +func (x *GetSandboxTemplateRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { if x != nil { return x.WorkspaceScope } return nil } -func (x *DeleteSandboxRequest) GetName() string { +func (x *GetSandboxTemplateRequest) GetName() string { if x != nil { return x.Name } return "" } -func (x *DeleteSandboxRequest) GetAllowMissing() bool { - if x != nil { - return x.AllowMissing - } - return false -} - -func (x *DeleteSandboxRequest) GetRequestId() string { - if x != nil { - return x.RequestId - } - return "" -} - -// Stop sandbox request. -type StopSandboxRequest struct { +type ListSandboxTemplatesRequest struct { state protoimpl.MessageState `protogen:"open.v1"` - // Workspace scope. Only a named workspace selection is accepted. - WorkspaceScope *datamodelv1.WorkspaceSelector `protobuf:"bytes,2,opt,name=workspace_scope,json=workspaceScope,proto3" json:"workspace_scope,omitempty"` - Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` - // Optional nonzero UUID for durable at-most-once admission. Successful results - // can be replayed for 24 hours; see the API errors and retries reference. - RequestId string `protobuf:"bytes,3,opt,name=request_id,json=requestId,proto3" json:"request_id,omitempty"` + // Workspace scope. Named and all-workspaces selections are accepted. + WorkspaceScope *datamodelv1.WorkspaceSelector `protobuf:"bytes,4,opt,name=workspace_scope,json=workspaceScope,proto3" json:"workspace_scope,omitempty"` + // The maximum number of templates to return. Zero uses 100. Values above + // 1000 are coerced to 1000; negative values are invalid. + PageSize int32 `protobuf:"varint,1,opt,name=page_size,json=pageSize,proto3" json:"page_size,omitempty"` + // Token from a previous ListSandboxTemplates response. All other request + // parameters except page_size must match the request that produced it. + PageToken string `protobuf:"bytes,2,opt,name=page_token,json=pageToken,proto3" json:"page_token,omitempty"` + // Optional label selector in key=value comma-separated form. + LabelSelector string `protobuf:"bytes,3,opt,name=label_selector,json=labelSelector,proto3" json:"label_selector,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } -func (x *StopSandboxRequest) Reset() { - *x = StopSandboxRequest{} +func (x *ListSandboxTemplatesRequest) Reset() { + *x = ListSandboxTemplatesRequest{} mi := &file_openshell_proto_msgTypes[48] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *StopSandboxRequest) String() string { +func (x *ListSandboxTemplatesRequest) String() string { return protoimpl.X.MessageStringOf(x) } -func (*StopSandboxRequest) ProtoMessage() {} +func (*ListSandboxTemplatesRequest) ProtoMessage() {} -func (x *StopSandboxRequest) ProtoReflect() protoreflect.Message { +func (x *ListSandboxTemplatesRequest) ProtoReflect() protoreflect.Message { mi := &file_openshell_proto_msgTypes[48] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -4665,59 +4541,67 @@ func (x *StopSandboxRequest) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use StopSandboxRequest.ProtoReflect.Descriptor instead. -func (*StopSandboxRequest) Descriptor() ([]byte, []int) { +// Deprecated: Use ListSandboxTemplatesRequest.ProtoReflect.Descriptor instead. +func (*ListSandboxTemplatesRequest) Descriptor() ([]byte, []int) { return file_openshell_proto_rawDescGZIP(), []int{48} } -func (x *StopSandboxRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { +func (x *ListSandboxTemplatesRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { if x != nil { return x.WorkspaceScope } return nil } -func (x *StopSandboxRequest) GetName() string { +func (x *ListSandboxTemplatesRequest) GetPageSize() int32 { if x != nil { - return x.Name + return x.PageSize + } + return 0 +} + +func (x *ListSandboxTemplatesRequest) GetPageToken() string { + if x != nil { + return x.PageToken } return "" } -func (x *StopSandboxRequest) GetRequestId() string { +func (x *ListSandboxTemplatesRequest) GetLabelSelector() string { if x != nil { - return x.RequestId + return x.LabelSelector } return "" } -// Start sandbox request. -type StartSandboxRequest struct { +type DeleteSandboxTemplateRequest struct { state protoimpl.MessageState `protogen:"open.v1"` // Workspace scope. Only a named workspace selection is accepted. WorkspaceScope *datamodelv1.WorkspaceSelector `protobuf:"bytes,2,opt,name=workspace_scope,json=workspaceScope,proto3" json:"workspace_scope,omitempty"` Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` - // Optional nonzero UUID for durable at-most-once admission. Successful results - // can be replayed for 24 hours; see the API errors and retries reference. - RequestId string `protobuf:"bytes,3,opt,name=request_id,json=requestId,proto3" json:"request_id,omitempty"` + // Succeed with ALREADY_ABSENT if the target is missing. Authorization and + // parent-workspace checks still apply. + AllowMissing bool `protobuf:"varint,3,opt,name=allow_missing,json=allowMissing,proto3" json:"allow_missing,omitempty"` + // Optional nonzero UUID. Same ID and payload replay success for 24 hours. + RequestId string `protobuf:"bytes,4,opt,name=request_id,json=requestId,proto3" json:"request_id,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } -func (x *StartSandboxRequest) Reset() { - *x = StartSandboxRequest{} +func (x *DeleteSandboxTemplateRequest) Reset() { + *x = DeleteSandboxTemplateRequest{} mi := &file_openshell_proto_msgTypes[49] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *StartSandboxRequest) String() string { +func (x *DeleteSandboxTemplateRequest) String() string { return protoimpl.X.MessageStringOf(x) } -func (*StartSandboxRequest) ProtoMessage() {} +func (*DeleteSandboxTemplateRequest) ProtoMessage() {} -func (x *StartSandboxRequest) ProtoReflect() protoreflect.Message { +func (x *DeleteSandboxTemplateRequest) ProtoReflect() protoreflect.Message { mi := &file_openshell_proto_msgTypes[49] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -4729,57 +4613,60 @@ func (x *StartSandboxRequest) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use StartSandboxRequest.ProtoReflect.Descriptor instead. -func (*StartSandboxRequest) Descriptor() ([]byte, []int) { +// Deprecated: Use DeleteSandboxTemplateRequest.ProtoReflect.Descriptor instead. +func (*DeleteSandboxTemplateRequest) Descriptor() ([]byte, []int) { return file_openshell_proto_rawDescGZIP(), []int{49} } -func (x *StartSandboxRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { +func (x *DeleteSandboxTemplateRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { if x != nil { return x.WorkspaceScope } return nil } -func (x *StartSandboxRequest) GetName() string { +func (x *DeleteSandboxTemplateRequest) GetName() string { if x != nil { return x.Name } return "" } -func (x *StartSandboxRequest) GetRequestId() string { +func (x *DeleteSandboxTemplateRequest) GetAllowMissing() bool { + if x != nil { + return x.AllowMissing + } + return false +} + +func (x *DeleteSandboxTemplateRequest) GetRequestId() string { if x != nil { return x.RequestId } return "" } -// Sandbox response. -type SandboxResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - Sandbox *Sandbox `protobuf:"bytes,1,opt,name=sandbox,proto3" json:"sandbox,omitempty"` - // Service URLs created by CreateSandbox, keyed by service name. The empty - // key identifies the unnamed service. Other sandbox RPCs return an empty map. - ServiceUrls map[string]string `protobuf:"bytes,2,rep,name=service_urls,json=serviceUrls,proto3" json:"service_urls,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` +type SandboxTemplateResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Template *SandboxWorkloadTemplate `protobuf:"bytes,1,opt,name=template,proto3" json:"template,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } -func (x *SandboxResponse) Reset() { - *x = SandboxResponse{} +func (x *SandboxTemplateResponse) Reset() { + *x = SandboxTemplateResponse{} mi := &file_openshell_proto_msgTypes[50] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *SandboxResponse) String() string { +func (x *SandboxTemplateResponse) String() string { return protoimpl.X.MessageStringOf(x) } -func (*SandboxResponse) ProtoMessage() {} +func (*SandboxTemplateResponse) ProtoMessage() {} -func (x *SandboxResponse) ProtoReflect() protoreflect.Message { +func (x *SandboxTemplateResponse) ProtoReflect() protoreflect.Message { mi := &file_openshell_proto_msgTypes[50] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -4791,49 +4678,41 @@ func (x *SandboxResponse) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use SandboxResponse.ProtoReflect.Descriptor instead. -func (*SandboxResponse) Descriptor() ([]byte, []int) { +// Deprecated: Use SandboxTemplateResponse.ProtoReflect.Descriptor instead. +func (*SandboxTemplateResponse) Descriptor() ([]byte, []int) { return file_openshell_proto_rawDescGZIP(), []int{50} } -func (x *SandboxResponse) GetSandbox() *Sandbox { - if x != nil { - return x.Sandbox - } - return nil -} - -func (x *SandboxResponse) GetServiceUrls() map[string]string { +func (x *SandboxTemplateResponse) GetTemplate() *SandboxWorkloadTemplate { if x != nil { - return x.ServiceUrls + return x.Template } return nil } -// List sandboxes response. -type ListSandboxesResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - Sandboxes []*Sandbox `protobuf:"bytes,1,rep,name=sandboxes,proto3" json:"sandboxes,omitempty"` +type ListSandboxTemplatesResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Templates []*SandboxWorkloadTemplate `protobuf:"bytes,1,rep,name=templates,proto3" json:"templates,omitempty"` // Token for the next page. Empty when there are no subsequent pages. NextPageToken string `protobuf:"bytes,2,opt,name=next_page_token,json=nextPageToken,proto3" json:"next_page_token,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } -func (x *ListSandboxesResponse) Reset() { - *x = ListSandboxesResponse{} +func (x *ListSandboxTemplatesResponse) Reset() { + *x = ListSandboxTemplatesResponse{} mi := &file_openshell_proto_msgTypes[51] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *ListSandboxesResponse) String() string { +func (x *ListSandboxTemplatesResponse) String() string { return protoimpl.X.MessageStringOf(x) } -func (*ListSandboxesResponse) ProtoMessage() {} +func (*ListSandboxTemplatesResponse) ProtoMessage() {} -func (x *ListSandboxesResponse) ProtoReflect() protoreflect.Message { +func (x *ListSandboxTemplatesResponse) ProtoReflect() protoreflect.Message { mi := &file_openshell_proto_msgTypes[51] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -4845,49 +4724,46 @@ func (x *ListSandboxesResponse) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use ListSandboxesResponse.ProtoReflect.Descriptor instead. -func (*ListSandboxesResponse) Descriptor() ([]byte, []int) { +// Deprecated: Use ListSandboxTemplatesResponse.ProtoReflect.Descriptor instead. +func (*ListSandboxTemplatesResponse) Descriptor() ([]byte, []int) { return file_openshell_proto_rawDescGZIP(), []int{51} } -func (x *ListSandboxesResponse) GetSandboxes() []*Sandbox { +func (x *ListSandboxTemplatesResponse) GetTemplates() []*SandboxWorkloadTemplate { if x != nil { - return x.Sandboxes + return x.Templates } return nil } -func (x *ListSandboxesResponse) GetNextPageToken() string { +func (x *ListSandboxTemplatesResponse) GetNextPageToken() string { if x != nil { return x.NextPageToken } return "" } -// List providers attached to a sandbox response. -type ListSandboxProvidersResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - Providers []*datamodelv1.Provider `protobuf:"bytes,1,rep,name=providers,proto3" json:"providers,omitempty"` - // Token for the next page. Empty when there are no subsequent pages. - NextPageToken string `protobuf:"bytes,2,opt,name=next_page_token,json=nextPageToken,proto3" json:"next_page_token,omitempty"` +type DeleteSandboxTemplateResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Outcome DeletionOutcome `protobuf:"varint,2,opt,name=outcome,proto3,enum=openshell.v1.DeletionOutcome" json:"outcome,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } -func (x *ListSandboxProvidersResponse) Reset() { - *x = ListSandboxProvidersResponse{} +func (x *DeleteSandboxTemplateResponse) Reset() { + *x = DeleteSandboxTemplateResponse{} mi := &file_openshell_proto_msgTypes[52] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *ListSandboxProvidersResponse) String() string { +func (x *DeleteSandboxTemplateResponse) String() string { return protoimpl.X.MessageStringOf(x) } -func (*ListSandboxProvidersResponse) ProtoMessage() {} +func (*DeleteSandboxTemplateResponse) ProtoMessage() {} -func (x *ListSandboxProvidersResponse) ProtoReflect() protoreflect.Message { +func (x *DeleteSandboxTemplateResponse) ProtoReflect() protoreflect.Message { mi := &file_openshell_proto_msgTypes[52] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -4899,51 +4775,47 @@ func (x *ListSandboxProvidersResponse) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use ListSandboxProvidersResponse.ProtoReflect.Descriptor instead. -func (*ListSandboxProvidersResponse) Descriptor() ([]byte, []int) { +// Deprecated: Use DeleteSandboxTemplateResponse.ProtoReflect.Descriptor instead. +func (*DeleteSandboxTemplateResponse) Descriptor() ([]byte, []int) { return file_openshell_proto_rawDescGZIP(), []int{52} } -func (x *ListSandboxProvidersResponse) GetProviders() []*datamodelv1.Provider { - if x != nil { - return x.Providers - } - return nil -} - -func (x *ListSandboxProvidersResponse) GetNextPageToken() string { +func (x *DeleteSandboxTemplateResponse) GetOutcome() DeletionOutcome { if x != nil { - return x.NextPageToken + return x.Outcome } - return "" + return DeletionOutcome_DELETION_OUTCOME_UNSPECIFIED } -// Attach provider to sandbox response. -type AttachSandboxProviderResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - Sandbox *Sandbox `protobuf:"bytes,1,opt,name=sandbox,proto3" json:"sandbox,omitempty"` - // True when the provider was newly attached. False means it was already attached. - Attached bool `protobuf:"varint,2,opt,name=attached,proto3" json:"attached,omitempty"` - // Persisted intent; readiness requires current supervisor observations. - Receipt *ProviderMutationReceipt `protobuf:"bytes,3,opt,name=receipt,proto3" json:"receipt,omitempty"` +// Request a gateway-owned staging slot for a local rootfs tar archive. +type BeginRootfsTarStagingRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Workspace scope. Only a named workspace selection is accepted. + WorkspaceScope *datamodelv1.WorkspaceSelector `protobuf:"bytes,3,opt,name=workspace_scope,json=workspaceScope,proto3" json:"workspace_scope,omitempty"` + // Base file name of the local archive. The gateway uses it only to name the + // staged file; path separators and traversal components are rejected. + FileName string `protobuf:"bytes,1,opt,name=file_name,json=fileName,proto3" json:"file_name,omitempty"` + // Size of the local archive in bytes, checked against the driver limit + // before the gateway allocates a slot. + SizeBytes uint64 `protobuf:"varint,2,opt,name=size_bytes,json=sizeBytes,proto3" json:"size_bytes,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } -func (x *AttachSandboxProviderResponse) Reset() { - *x = AttachSandboxProviderResponse{} +func (x *BeginRootfsTarStagingRequest) Reset() { + *x = BeginRootfsTarStagingRequest{} mi := &file_openshell_proto_msgTypes[53] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *AttachSandboxProviderResponse) String() string { +func (x *BeginRootfsTarStagingRequest) String() string { return protoimpl.X.MessageStringOf(x) } -func (*AttachSandboxProviderResponse) ProtoMessage() {} +func (*BeginRootfsTarStagingRequest) ProtoMessage() {} -func (x *AttachSandboxProviderResponse) ProtoReflect() protoreflect.Message { +func (x *BeginRootfsTarStagingRequest) ProtoReflect() protoreflect.Message { mi := &file_openshell_proto_msgTypes[53] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -4955,58 +4827,63 @@ func (x *AttachSandboxProviderResponse) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use AttachSandboxProviderResponse.ProtoReflect.Descriptor instead. -func (*AttachSandboxProviderResponse) Descriptor() ([]byte, []int) { +// Deprecated: Use BeginRootfsTarStagingRequest.ProtoReflect.Descriptor instead. +func (*BeginRootfsTarStagingRequest) Descriptor() ([]byte, []int) { return file_openshell_proto_rawDescGZIP(), []int{53} } -func (x *AttachSandboxProviderResponse) GetSandbox() *Sandbox { +func (x *BeginRootfsTarStagingRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { if x != nil { - return x.Sandbox + return x.WorkspaceScope } return nil } -func (x *AttachSandboxProviderResponse) GetAttached() bool { +func (x *BeginRootfsTarStagingRequest) GetFileName() string { if x != nil { - return x.Attached + return x.FileName } - return false + return "" } -func (x *AttachSandboxProviderResponse) GetReceipt() *ProviderMutationReceipt { +func (x *BeginRootfsTarStagingRequest) GetSizeBytes() uint64 { if x != nil { - return x.Receipt + return x.SizeBytes } - return nil + return 0 } -// Detach provider from sandbox response. -type DetachSandboxProviderResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - Sandbox *Sandbox `protobuf:"bytes,1,opt,name=sandbox,proto3" json:"sandbox,omitempty"` - // True when the provider was removed. False means it was not attached. - Detached bool `protobuf:"varint,2,opt,name=detached,proto3" json:"detached,omitempty"` - // Revocation is complete only when this receipt reports REVOKED. - Receipt *ProviderMutationReceipt `protobuf:"bytes,3,opt,name=receipt,proto3" json:"receipt,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache +// Gateway-issued staging slot. +type BeginRootfsTarStagingResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Opaque single-use token. Pass it as + // `template.driver_config..rootfs_tar_staging_token` on + // CreateSandbox. The first CreateSandbox presenting it consumes it. + StagingToken string `protobuf:"bytes,1,opt,name=staging_token,json=stagingToken,proto3" json:"staging_token,omitempty"` + // Absolute path on the gateway host the client must write the archive to. + UploadPath string `protobuf:"bytes,2,opt,name=upload_path,json=uploadPath,proto3" json:"upload_path,omitempty"` + // Maximum accepted archive size in bytes, enforced again by the driver. + MaxBytes uint64 `protobuf:"varint,3,opt,name=max_bytes,json=maxBytes,proto3" json:"max_bytes,omitempty"` + // Wall-clock deadline after which the gateway reclaims the slot. + ExpirationTime *timestamppb.Timestamp `protobuf:"bytes,104,opt,name=expiration_time,json=expirationTime,proto3" json:"expiration_time,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } -func (x *DetachSandboxProviderResponse) Reset() { - *x = DetachSandboxProviderResponse{} +func (x *BeginRootfsTarStagingResponse) Reset() { + *x = BeginRootfsTarStagingResponse{} mi := &file_openshell_proto_msgTypes[54] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *DetachSandboxProviderResponse) String() string { +func (x *BeginRootfsTarStagingResponse) String() string { return protoimpl.X.MessageStringOf(x) } -func (*DetachSandboxProviderResponse) ProtoMessage() {} +func (*BeginRootfsTarStagingResponse) ProtoMessage() {} -func (x *DetachSandboxProviderResponse) ProtoReflect() protoreflect.Message { +func (x *BeginRootfsTarStagingResponse) ProtoReflect() protoreflect.Message { mi := &file_openshell_proto_msgTypes[54] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -5018,62 +4895,63 @@ func (x *DetachSandboxProviderResponse) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use DetachSandboxProviderResponse.ProtoReflect.Descriptor instead. -func (*DetachSandboxProviderResponse) Descriptor() ([]byte, []int) { +// Deprecated: Use BeginRootfsTarStagingResponse.ProtoReflect.Descriptor instead. +func (*BeginRootfsTarStagingResponse) Descriptor() ([]byte, []int) { return file_openshell_proto_rawDescGZIP(), []int{54} } -func (x *DetachSandboxProviderResponse) GetSandbox() *Sandbox { +func (x *BeginRootfsTarStagingResponse) GetStagingToken() string { if x != nil { - return x.Sandbox + return x.StagingToken } - return nil + return "" } -func (x *DetachSandboxProviderResponse) GetDetached() bool { +func (x *BeginRootfsTarStagingResponse) GetUploadPath() string { if x != nil { - return x.Detached + return x.UploadPath } - return false + return "" } -func (x *DetachSandboxProviderResponse) GetReceipt() *ProviderMutationReceipt { +func (x *BeginRootfsTarStagingResponse) GetMaxBytes() uint64 { if x != nil { - return x.Receipt + return x.MaxBytes + } + return 0 +} + +func (x *BeginRootfsTarStagingResponse) GetExpirationTime() *timestamppb.Timestamp { + if x != nil { + return x.ExpirationTime } return nil } -// Exact desired authority. Revisions are opaque identities, never ordered. -type ProviderDesiredIdentity struct { - state protoimpl.MessageState `protogen:"open.v1"` - SandboxId string `protobuf:"bytes,1,opt,name=sandbox_id,json=sandboxId,proto3" json:"sandbox_id,omitempty"` - Sandbox string `protobuf:"bytes,2,opt,name=sandbox,proto3" json:"sandbox,omitempty"` - AttachmentEpoch string `protobuf:"bytes,3,opt,name=attachment_epoch,json=attachmentEpoch,proto3" json:"attachment_epoch,omitempty"` - // Empty for a detached provider. - ProviderId string `protobuf:"bytes,4,opt,name=provider_id,json=providerId,proto3" json:"provider_id,omitempty"` - ProviderResourceVersion uint64 `protobuf:"varint,5,opt,name=provider_resource_version,json=providerResourceVersion,proto3" json:"provider_resource_version,omitempty"` - ProviderEnvRevision uint64 `protobuf:"varint,6,opt,name=provider_env_revision,json=providerEnvRevision,proto3" json:"provider_env_revision,omitempty"` - ConfigRevision uint64 `protobuf:"varint,7,opt,name=config_revision,json=configRevision,proto3" json:"config_revision,omitempty"` - PolicyHash string `protobuf:"bytes,8,opt,name=policy_hash,json=policyHash,proto3" json:"policy_hash,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache +// Get sandbox request. +type GetSandboxRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Workspace scope. Only a named workspace selection is accepted. + WorkspaceScope *datamodelv1.WorkspaceSelector `protobuf:"bytes,2,opt,name=workspace_scope,json=workspaceScope,proto3" json:"workspace_scope,omitempty"` + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } -func (x *ProviderDesiredIdentity) Reset() { - *x = ProviderDesiredIdentity{} +func (x *GetSandboxRequest) Reset() { + *x = GetSandboxRequest{} mi := &file_openshell_proto_msgTypes[55] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *ProviderDesiredIdentity) String() string { +func (x *GetSandboxRequest) String() string { return protoimpl.X.MessageStringOf(x) } -func (*ProviderDesiredIdentity) ProtoMessage() {} +func (*GetSandboxRequest) ProtoMessage() {} -func (x *ProviderDesiredIdentity) ProtoReflect() protoreflect.Message { +func (x *GetSandboxRequest) ProtoReflect() protoreflect.Message { mi := &file_openshell_proto_msgTypes[55] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -5085,95 +4963,56 @@ func (x *ProviderDesiredIdentity) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use ProviderDesiredIdentity.ProtoReflect.Descriptor instead. -func (*ProviderDesiredIdentity) Descriptor() ([]byte, []int) { +// Deprecated: Use GetSandboxRequest.ProtoReflect.Descriptor instead. +func (*GetSandboxRequest) Descriptor() ([]byte, []int) { return file_openshell_proto_rawDescGZIP(), []int{55} } -func (x *ProviderDesiredIdentity) GetSandboxId() string { - if x != nil { - return x.SandboxId - } - return "" -} - -func (x *ProviderDesiredIdentity) GetSandbox() string { - if x != nil { - return x.Sandbox - } - return "" -} - -func (x *ProviderDesiredIdentity) GetAttachmentEpoch() string { - if x != nil { - return x.AttachmentEpoch - } - return "" -} - -func (x *ProviderDesiredIdentity) GetProviderId() string { - if x != nil { - return x.ProviderId - } - return "" -} - -func (x *ProviderDesiredIdentity) GetProviderResourceVersion() uint64 { - if x != nil { - return x.ProviderResourceVersion - } - return 0 -} - -func (x *ProviderDesiredIdentity) GetProviderEnvRevision() uint64 { - if x != nil { - return x.ProviderEnvRevision - } - return 0 -} - -func (x *ProviderDesiredIdentity) GetConfigRevision() uint64 { +func (x *GetSandboxRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { if x != nil { - return x.ConfigRevision + return x.WorkspaceScope } - return 0 + return nil } -func (x *ProviderDesiredIdentity) GetPolicyHash() string { +func (x *GetSandboxRequest) GetName() string { if x != nil { - return x.PolicyHash + return x.Name } return "" } -// Identifies one component snapshot revision. Revisions are equality tokens, -// not members of one shared ordering domain. -type ConfigSnapshotRevision struct { +// List sandboxes request. +type ListSandboxesRequest struct { state protoimpl.MessageState `protogen:"open.v1"` - // Types that are valid to be assigned to Component: - // - // *ConfigSnapshotRevision_SandboxConfig - // *ConfigSnapshotRevision_ProviderEnvironment - // *ConfigSnapshotRevision_ProviderTarget - Component isConfigSnapshotRevision_Component `protobuf_oneof:"component"` + // Workspace scope. Named and all-workspaces selections are accepted. + WorkspaceScope *datamodelv1.WorkspaceSelector `protobuf:"bytes,4,opt,name=workspace_scope,json=workspaceScope,proto3" json:"workspace_scope,omitempty"` + // The maximum number of sandboxes to return. Zero uses 100. Values above + // 1000 are coerced to 1000; negative values are invalid. + PageSize int32 `protobuf:"varint,1,opt,name=page_size,json=pageSize,proto3" json:"page_size,omitempty"` + // Token from a previous ListSandboxes response. All other request parameters + // except page_size must match the request that produced it. + PageToken string `protobuf:"bytes,2,opt,name=page_token,json=pageToken,proto3" json:"page_token,omitempty"` + // Optional label selector for filtering (format: "key1=value1,key2=value2"). + LabelSelector string `protobuf:"bytes,3,opt,name=label_selector,json=labelSelector,proto3" json:"label_selector,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } -func (x *ConfigSnapshotRevision) Reset() { - *x = ConfigSnapshotRevision{} +func (x *ListSandboxesRequest) Reset() { + *x = ListSandboxesRequest{} mi := &file_openshell_proto_msgTypes[56] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *ConfigSnapshotRevision) String() string { +func (x *ListSandboxesRequest) String() string { return protoimpl.X.MessageStringOf(x) } -func (*ConfigSnapshotRevision) ProtoMessage() {} +func (*ListSandboxesRequest) ProtoMessage() {} -func (x *ConfigSnapshotRevision) ProtoReflect() protoreflect.Message { +func (x *ListSandboxesRequest) ProtoReflect() protoreflect.Message { mi := &file_openshell_proto_msgTypes[56] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -5185,97 +5024,69 @@ func (x *ConfigSnapshotRevision) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use ConfigSnapshotRevision.ProtoReflect.Descriptor instead. -func (*ConfigSnapshotRevision) Descriptor() ([]byte, []int) { +// Deprecated: Use ListSandboxesRequest.ProtoReflect.Descriptor instead. +func (*ListSandboxesRequest) Descriptor() ([]byte, []int) { return file_openshell_proto_rawDescGZIP(), []int{56} } -func (x *ConfigSnapshotRevision) GetComponent() isConfigSnapshotRevision_Component { +func (x *ListSandboxesRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { if x != nil { - return x.Component + return x.WorkspaceScope } return nil } -func (x *ConfigSnapshotRevision) GetSandboxConfig() *SandboxConfigRevision { +func (x *ListSandboxesRequest) GetPageSize() int32 { if x != nil { - if x, ok := x.Component.(*ConfigSnapshotRevision_SandboxConfig); ok { - return x.SandboxConfig - } + return x.PageSize } - return nil + return 0 } -func (x *ConfigSnapshotRevision) GetProviderEnvironment() uint64 { +func (x *ListSandboxesRequest) GetPageToken() string { if x != nil { - if x, ok := x.Component.(*ConfigSnapshotRevision_ProviderEnvironment); ok { - return x.ProviderEnvironment - } + return x.PageToken } - return 0 + return "" } -func (x *ConfigSnapshotRevision) GetProviderTarget() *ProviderDesiredIdentity { +func (x *ListSandboxesRequest) GetLabelSelector() string { if x != nil { - if x, ok := x.Component.(*ConfigSnapshotRevision_ProviderTarget); ok { - return x.ProviderTarget - } + return x.LabelSelector } - return nil -} - -type isConfigSnapshotRevision_Component interface { - isConfigSnapshotRevision_Component() -} - -type ConfigSnapshotRevision_SandboxConfig struct { - SandboxConfig *SandboxConfigRevision `protobuf:"bytes,1,opt,name=sandbox_config,json=sandboxConfig,proto3,oneof"` -} - -type ConfigSnapshotRevision_ProviderEnvironment struct { - ProviderEnvironment uint64 `protobuf:"varint,2,opt,name=provider_environment,json=providerEnvironment,proto3,oneof"` -} - -type ConfigSnapshotRevision_ProviderTarget struct { - // Complete desired authority for one sandbox-scoped provider mutation. - ProviderTarget *ProviderDesiredIdentity `protobuf:"bytes,3,opt,name=provider_target,json=providerTarget,proto3,oneof"` + return "" } -func (*ConfigSnapshotRevision_SandboxConfig) isConfigSnapshotRevision_Component() {} - -func (*ConfigSnapshotRevision_ProviderEnvironment) isConfigSnapshotRevision_Component() {} - -func (*ConfigSnapshotRevision_ProviderTarget) isConfigSnapshotRevision_Component() {} - -// Identity needed to correlate effective sandbox configuration with the -// policy-history row whose apply status the gateway records. -type SandboxConfigRevision struct { - state protoimpl.MessageState `protogen:"open.v1"` - ConfigRevision uint64 `protobuf:"varint,1,opt,name=config_revision,json=configRevision,proto3" json:"config_revision,omitempty"` - PolicyVersion uint32 `protobuf:"varint,2,opt,name=policy_version,json=policyVersion,proto3" json:"policy_version,omitempty"` - PolicySource sandboxv1.PolicySource `protobuf:"varint,3,opt,name=policy_source,json=policySource,proto3,enum=openshell.sandbox.v1.PolicySource" json:"policy_source,omitempty"` - GlobalPolicyVersion uint32 `protobuf:"varint,4,opt,name=global_policy_version,json=globalPolicyVersion,proto3" json:"global_policy_version,omitempty"` - // Monotonic revision of the sandbox-scoped settings row. This disambiguates - // setting operations whose effective config fingerprint is equality-only. - SettingsRevision uint64 `protobuf:"varint,5,opt,name=settings_revision,json=settingsRevision,proto3" json:"settings_revision,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache +// List providers attached to a sandbox request. +type ListSandboxProvidersRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Workspace scope. Only a named workspace selection is accepted. + WorkspaceScope *datamodelv1.WorkspaceSelector `protobuf:"bytes,2,opt,name=workspace_scope,json=workspaceScope,proto3" json:"workspace_scope,omitempty"` + Sandbox string `protobuf:"bytes,1,opt,name=sandbox,proto3" json:"sandbox,omitempty"` + // The maximum number of providers to return. Zero uses 100. Values above + // 1000 are coerced to 1000; negative values are invalid. + PageSize int32 `protobuf:"varint,3,opt,name=page_size,json=pageSize,proto3" json:"page_size,omitempty"` + // Token from a previous ListSandboxProviders response. All other request + // parameters except page_size must match the request that produced it. + PageToken string `protobuf:"bytes,4,opt,name=page_token,json=pageToken,proto3" json:"page_token,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } -func (x *SandboxConfigRevision) Reset() { - *x = SandboxConfigRevision{} +func (x *ListSandboxProvidersRequest) Reset() { + *x = ListSandboxProvidersRequest{} mi := &file_openshell_proto_msgTypes[57] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *SandboxConfigRevision) String() string { +func (x *ListSandboxProvidersRequest) String() string { return protoimpl.X.MessageStringOf(x) } -func (*SandboxConfigRevision) ProtoMessage() {} +func (*ListSandboxProvidersRequest) ProtoMessage() {} -func (x *SandboxConfigRevision) ProtoReflect() protoreflect.Message { +func (x *ListSandboxProvidersRequest) ProtoReflect() protoreflect.Message { mi := &file_openshell_proto_msgTypes[57] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -5287,79 +5098,73 @@ func (x *SandboxConfigRevision) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use SandboxConfigRevision.ProtoReflect.Descriptor instead. -func (*SandboxConfigRevision) Descriptor() ([]byte, []int) { +// Deprecated: Use ListSandboxProvidersRequest.ProtoReflect.Descriptor instead. +func (*ListSandboxProvidersRequest) Descriptor() ([]byte, []int) { return file_openshell_proto_rawDescGZIP(), []int{57} } -func (x *SandboxConfigRevision) GetConfigRevision() uint64 { - if x != nil { - return x.ConfigRevision - } - return 0 -} - -func (x *SandboxConfigRevision) GetPolicyVersion() uint32 { +func (x *ListSandboxProvidersRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { if x != nil { - return x.PolicyVersion + return x.WorkspaceScope } - return 0 + return nil } -func (x *SandboxConfigRevision) GetPolicySource() sandboxv1.PolicySource { +func (x *ListSandboxProvidersRequest) GetSandbox() string { if x != nil { - return x.PolicySource + return x.Sandbox } - return sandboxv1.PolicySource(0) + return "" } -func (x *SandboxConfigRevision) GetGlobalPolicyVersion() uint32 { +func (x *ListSandboxProvidersRequest) GetPageSize() int32 { if x != nil { - return x.GlobalPolicyVersion + return x.PageSize } return 0 } -func (x *SandboxConfigRevision) GetSettingsRevision() uint64 { +func (x *ListSandboxProvidersRequest) GetPageToken() string { if x != nil { - return x.SettingsRevision + return x.PageToken } - return 0 + return "" } -// Durable progress for one sandbox-scoped desired-state mutation. Snapshot -// contents and credentials are never stored in this resource. -type ConfigUpdateOperation struct { - state protoimpl.MessageState `protogen:"open.v1"` - OperationId string `protobuf:"bytes,1,opt,name=operation_id,json=operationId,proto3" json:"operation_id,omitempty"` - SandboxId string `protobuf:"bytes,2,opt,name=sandbox_id,json=sandboxId,proto3" json:"sandbox_id,omitempty"` - Component ConfigComponent `protobuf:"varint,3,opt,name=component,proto3,enum=openshell.v1.ConfigComponent" json:"component,omitempty"` - TargetRevision *ConfigSnapshotRevision `protobuf:"bytes,4,opt,name=target_revision,json=targetRevision,proto3" json:"target_revision,omitempty"` - State ConfigUpdateOperationState `protobuf:"varint,5,opt,name=state,proto3,enum=openshell.v1.ConfigUpdateOperationState" json:"state,omitempty"` - Outcome ConfigApplyOutcome `protobuf:"varint,6,opt,name=outcome,proto3,enum=openshell.v1.ConfigApplyOutcome" json:"outcome,omitempty"` - SanitizedError string `protobuf:"bytes,7,opt,name=sanitized_error,json=sanitizedError,proto3" json:"sanitized_error,omitempty"` - CreatedTime *timestamppb.Timestamp `protobuf:"bytes,108,opt,name=created_time,json=createdTime,proto3" json:"created_time,omitempty"` - UpdatedTime *timestamppb.Timestamp `protobuf:"bytes,109,opt,name=updated_time,json=updatedTime,proto3" json:"updated_time,omitempty"` - // Absent until the operation reaches a terminal state. - CompletedTime *timestamppb.Timestamp `protobuf:"bytes,110,opt,name=completed_time,json=completedTime,proto3" json:"completed_time,omitempty"` +// Attach provider to sandbox request. +type AttachSandboxProviderRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Workspace scope. Only a named workspace selection is accepted. + WorkspaceScope *datamodelv1.WorkspaceSelector `protobuf:"bytes,4,opt,name=workspace_scope,json=workspaceScope,proto3" json:"workspace_scope,omitempty"` + Sandbox string `protobuf:"bytes,1,opt,name=sandbox,proto3" json:"sandbox,omitempty"` + // Provider name to attach. + Provider string `protobuf:"bytes,2,opt,name=provider,proto3" json:"provider,omitempty"` + // Expected resource version for optimistic concurrency control. + // If 0, the server uses the current version (backward compatibility). + // If non-zero, the server validates that the sandbox's current resource_version + // matches this value before applying the mutation, returning ABORTED on mismatch. + ExpectedResourceVersion uint64 `protobuf:"varint,3,opt,name=expected_resource_version,json=expectedResourceVersion,proto3" json:"expected_resource_version,omitempty"` + // Optional nonzero UUID for durable at-most-once admission. Successful results + // can be replayed for 24 hours; see the API errors and retries reference. + RequestId string `protobuf:"bytes,5,opt,name=request_id,json=requestId,proto3" json:"request_id,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } -func (x *ConfigUpdateOperation) Reset() { - *x = ConfigUpdateOperation{} +func (x *AttachSandboxProviderRequest) Reset() { + *x = AttachSandboxProviderRequest{} mi := &file_openshell_proto_msgTypes[58] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *ConfigUpdateOperation) String() string { +func (x *AttachSandboxProviderRequest) String() string { return protoimpl.X.MessageStringOf(x) } -func (*ConfigUpdateOperation) ProtoMessage() {} +func (*AttachSandboxProviderRequest) ProtoMessage() {} -func (x *ConfigUpdateOperation) ProtoReflect() protoreflect.Message { +func (x *AttachSandboxProviderRequest) ProtoReflect() protoreflect.Message { mi := &file_openshell_proto_msgTypes[58] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -5371,110 +5176,80 @@ func (x *ConfigUpdateOperation) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use ConfigUpdateOperation.ProtoReflect.Descriptor instead. -func (*ConfigUpdateOperation) Descriptor() ([]byte, []int) { +// Deprecated: Use AttachSandboxProviderRequest.ProtoReflect.Descriptor instead. +func (*AttachSandboxProviderRequest) Descriptor() ([]byte, []int) { return file_openshell_proto_rawDescGZIP(), []int{58} } -func (x *ConfigUpdateOperation) GetOperationId() string { - if x != nil { - return x.OperationId - } - return "" -} - -func (x *ConfigUpdateOperation) GetSandboxId() string { - if x != nil { - return x.SandboxId - } - return "" -} - -func (x *ConfigUpdateOperation) GetComponent() ConfigComponent { - if x != nil { - return x.Component - } - return ConfigComponent_CONFIG_COMPONENT_UNSPECIFIED -} - -func (x *ConfigUpdateOperation) GetTargetRevision() *ConfigSnapshotRevision { +func (x *AttachSandboxProviderRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { if x != nil { - return x.TargetRevision + return x.WorkspaceScope } return nil } -func (x *ConfigUpdateOperation) GetState() ConfigUpdateOperationState { - if x != nil { - return x.State - } - return ConfigUpdateOperationState_CONFIG_UPDATE_OPERATION_STATE_UNSPECIFIED -} - -func (x *ConfigUpdateOperation) GetOutcome() ConfigApplyOutcome { - if x != nil { - return x.Outcome - } - return ConfigApplyOutcome_CONFIG_APPLY_OUTCOME_UNSPECIFIED -} - -func (x *ConfigUpdateOperation) GetSanitizedError() string { +func (x *AttachSandboxProviderRequest) GetSandbox() string { if x != nil { - return x.SanitizedError + return x.Sandbox } return "" } -func (x *ConfigUpdateOperation) GetCreatedTime() *timestamppb.Timestamp { +func (x *AttachSandboxProviderRequest) GetProvider() string { if x != nil { - return x.CreatedTime + return x.Provider } - return nil + return "" } -func (x *ConfigUpdateOperation) GetUpdatedTime() *timestamppb.Timestamp { +func (x *AttachSandboxProviderRequest) GetExpectedResourceVersion() uint64 { if x != nil { - return x.UpdatedTime + return x.ExpectedResourceVersion } - return nil + return 0 } -func (x *ConfigUpdateOperation) GetCompletedTime() *timestamppb.Timestamp { +func (x *AttachSandboxProviderRequest) GetRequestId() string { if x != nil { - return x.CompletedTime + return x.RequestId } - return nil + return "" } -// Immutable, secret-free record of one sandbox's intended provider mutation. -type ProviderMutationReceipt struct { - state protoimpl.MessageState `protogen:"open.v1"` - ReceiptId string `protobuf:"bytes,1,opt,name=receipt_id,json=receiptId,proto3" json:"receipt_id,omitempty"` - // Shared by all sandbox receipts from one provider update. - MutationId string `protobuf:"bytes,2,opt,name=mutation_id,json=mutationId,proto3" json:"mutation_id,omitempty"` - Provider string `protobuf:"bytes,3,opt,name=provider,proto3" json:"provider,omitempty"` - Workspace string `protobuf:"bytes,4,opt,name=workspace,proto3" json:"workspace,omitempty"` - Kind ProviderMutationKind `protobuf:"varint,5,opt,name=kind,proto3,enum=openshell.v1.ProviderMutationKind" json:"kind,omitempty"` - Desired *ProviderDesiredIdentity `protobuf:"bytes,6,opt,name=desired,proto3" json:"desired,omitempty"` - PersistedTime *timestamppb.Timestamp `protobuf:"bytes,107,opt,name=persisted_time,json=persistedTime,proto3" json:"persisted_time,omitempty"` +// Detach provider from sandbox request. +type DetachSandboxProviderRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Workspace scope. Only a named workspace selection is accepted. + WorkspaceScope *datamodelv1.WorkspaceSelector `protobuf:"bytes,4,opt,name=workspace_scope,json=workspaceScope,proto3" json:"workspace_scope,omitempty"` + Sandbox string `protobuf:"bytes,1,opt,name=sandbox,proto3" json:"sandbox,omitempty"` + // Provider name to detach. + Provider string `protobuf:"bytes,2,opt,name=provider,proto3" json:"provider,omitempty"` + // Expected resource version for optimistic concurrency control. + // If 0, the server uses the current version (backward compatibility). + // If non-zero, the server validates that the sandbox's current resource_version + // matches this value before applying the mutation, returning ABORTED on mismatch. + ExpectedResourceVersion uint64 `protobuf:"varint,3,opt,name=expected_resource_version,json=expectedResourceVersion,proto3" json:"expected_resource_version,omitempty"` + // Optional nonzero UUID for durable at-most-once admission. Successful results + // can be replayed for 24 hours; see the API errors and retries reference. + RequestId string `protobuf:"bytes,5,opt,name=request_id,json=requestId,proto3" json:"request_id,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } -func (x *ProviderMutationReceipt) Reset() { - *x = ProviderMutationReceipt{} +func (x *DetachSandboxProviderRequest) Reset() { + *x = DetachSandboxProviderRequest{} mi := &file_openshell_proto_msgTypes[59] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *ProviderMutationReceipt) String() string { +func (x *DetachSandboxProviderRequest) String() string { return protoimpl.X.MessageStringOf(x) } -func (*ProviderMutationReceipt) ProtoMessage() {} +func (*DetachSandboxProviderRequest) ProtoMessage() {} -func (x *ProviderMutationReceipt) ProtoReflect() protoreflect.Message { +func (x *DetachSandboxProviderRequest) ProtoReflect() protoreflect.Message { mi := &file_openshell_proto_msgTypes[59] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -5486,96 +5261,77 @@ func (x *ProviderMutationReceipt) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use ProviderMutationReceipt.ProtoReflect.Descriptor instead. -func (*ProviderMutationReceipt) Descriptor() ([]byte, []int) { +// Deprecated: Use DetachSandboxProviderRequest.ProtoReflect.Descriptor instead. +func (*DetachSandboxProviderRequest) Descriptor() ([]byte, []int) { return file_openshell_proto_rawDescGZIP(), []int{59} } -func (x *ProviderMutationReceipt) GetReceiptId() string { +func (x *DetachSandboxProviderRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { if x != nil { - return x.ReceiptId + return x.WorkspaceScope } - return "" + return nil } -func (x *ProviderMutationReceipt) GetMutationId() string { +func (x *DetachSandboxProviderRequest) GetSandbox() string { if x != nil { - return x.MutationId + return x.Sandbox } return "" } -func (x *ProviderMutationReceipt) GetProvider() string { +func (x *DetachSandboxProviderRequest) GetProvider() string { if x != nil { return x.Provider } return "" } -func (x *ProviderMutationReceipt) GetWorkspace() string { - if x != nil { - return x.Workspace - } - return "" -} - -func (x *ProviderMutationReceipt) GetKind() ProviderMutationKind { - if x != nil { - return x.Kind - } - return ProviderMutationKind_PROVIDER_MUTATION_KIND_UNSPECIFIED -} - -func (x *ProviderMutationReceipt) GetDesired() *ProviderDesiredIdentity { +func (x *DetachSandboxProviderRequest) GetExpectedResourceVersion() uint64 { if x != nil { - return x.Desired + return x.ExpectedResourceVersion } - return nil + return 0 } -func (x *ProviderMutationReceipt) GetPersistedTime() *timestamppb.Timestamp { +func (x *DetachSandboxProviderRequest) GetRequestId() string { if x != nil { - return x.PersistedTime + return x.RequestId } - return nil + return "" } -// Installed state reported by the current supervisor. Process installation is -// acknowledged by the authenticated sandbox boundary after replacing its -// environment for future process launches. -type ProviderReadinessObservation struct { +// Delete sandbox request. +type DeleteSandboxRequest struct { state protoimpl.MessageState `protogen:"open.v1"` - // Gateway-issued identifier from the current ConnectSupervisor response. - SessionId string `protobuf:"bytes,1,opt,name=session_id,json=sessionId,proto3" json:"session_id,omitempty"` - // Monotonic only within this connection; unrelated to revision fingerprints. - Sequence uint64 `protobuf:"varint,2,opt,name=sequence,proto3" json:"sequence,omitempty"` - AttachmentEpoch string `protobuf:"bytes,3,opt,name=attachment_epoch,json=attachmentEpoch,proto3" json:"attachment_epoch,omitempty"` - ProviderEnvRevision uint64 `protobuf:"varint,4,opt,name=provider_env_revision,json=providerEnvRevision,proto3" json:"provider_env_revision,omitempty"` - ConfigRevision uint64 `protobuf:"varint,5,opt,name=config_revision,json=configRevision,proto3" json:"config_revision,omitempty"` - PolicyHash string `protobuf:"bytes,6,opt,name=policy_hash,json=policyHash,proto3" json:"policy_hash,omitempty"` - CredentialsInstalled bool `protobuf:"varint,7,opt,name=credentials_installed,json=credentialsInstalled,proto3" json:"credentials_installed,omitempty"` - PolicyActive bool `protobuf:"varint,8,opt,name=policy_active,json=policyActive,proto3" json:"policy_active,omitempty"` - LaunchEnvironmentInstalled bool `protobuf:"varint,9,opt,name=launch_environment_installed,json=launchEnvironmentInstalled,proto3" json:"launch_environment_installed,omitempty"` - ProcessInstanceId string `protobuf:"bytes,10,opt,name=process_instance_id,json=processInstanceId,proto3" json:"process_instance_id,omitempty"` - Reason ProviderReadinessReason `protobuf:"varint,11,opt,name=reason,proto3,enum=openshell.v1.ProviderReadinessReason" json:"reason,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + // Workspace scope. Only a named workspace selection is accepted. + WorkspaceScope *datamodelv1.WorkspaceSelector `protobuf:"bytes,2,opt,name=workspace_scope,json=workspaceScope,proto3" json:"workspace_scope,omitempty"` + // Canonical sandbox name. + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + // Succeed with ALREADY_ABSENT if the target is missing. Does not wait for + // asynchronous cleanup and does not suppress authorization or parent errors. + AllowMissing bool `protobuf:"varint,3,opt,name=allow_missing,json=allowMissing,proto3" json:"allow_missing,omitempty"` + // Optional nonzero UUID for durable at-most-once admission. Successful results + // can be replayed for 24 hours; see the API errors and retries reference. + RequestId string `protobuf:"bytes,4,opt,name=request_id,json=requestId,proto3" json:"request_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } -func (x *ProviderReadinessObservation) Reset() { - *x = ProviderReadinessObservation{} +func (x *DeleteSandboxRequest) Reset() { + *x = DeleteSandboxRequest{} mi := &file_openshell_proto_msgTypes[60] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *ProviderReadinessObservation) String() string { +func (x *DeleteSandboxRequest) String() string { return protoimpl.X.MessageStringOf(x) } -func (*ProviderReadinessObservation) ProtoMessage() {} +func (*DeleteSandboxRequest) ProtoMessage() {} -func (x *ProviderReadinessObservation) ProtoReflect() protoreflect.Message { +func (x *DeleteSandboxRequest) ProtoReflect() protoreflect.Message { mi := &file_openshell_proto_msgTypes[60] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -5587,119 +5343,66 @@ func (x *ProviderReadinessObservation) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use ProviderReadinessObservation.ProtoReflect.Descriptor instead. -func (*ProviderReadinessObservation) Descriptor() ([]byte, []int) { +// Deprecated: Use DeleteSandboxRequest.ProtoReflect.Descriptor instead. +func (*DeleteSandboxRequest) Descriptor() ([]byte, []int) { return file_openshell_proto_rawDescGZIP(), []int{60} } -func (x *ProviderReadinessObservation) GetSessionId() string { - if x != nil { - return x.SessionId - } - return "" -} - -func (x *ProviderReadinessObservation) GetSequence() uint64 { +func (x *DeleteSandboxRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { if x != nil { - return x.Sequence + return x.WorkspaceScope } - return 0 + return nil } -func (x *ProviderReadinessObservation) GetAttachmentEpoch() string { +func (x *DeleteSandboxRequest) GetName() string { if x != nil { - return x.AttachmentEpoch + return x.Name } return "" } -func (x *ProviderReadinessObservation) GetProviderEnvRevision() uint64 { +func (x *DeleteSandboxRequest) GetAllowMissing() bool { if x != nil { - return x.ProviderEnvRevision + return x.AllowMissing } - return 0 + return false } -func (x *ProviderReadinessObservation) GetConfigRevision() uint64 { +func (x *DeleteSandboxRequest) GetRequestId() string { if x != nil { - return x.ConfigRevision + return x.RequestId } - return 0 + return "" } -func (x *ProviderReadinessObservation) GetPolicyHash() string { - if x != nil { - return x.PolicyHash - } - return "" -} - -func (x *ProviderReadinessObservation) GetCredentialsInstalled() bool { - if x != nil { - return x.CredentialsInstalled - } - return false -} - -func (x *ProviderReadinessObservation) GetPolicyActive() bool { - if x != nil { - return x.PolicyActive - } - return false -} - -func (x *ProviderReadinessObservation) GetLaunchEnvironmentInstalled() bool { - if x != nil { - return x.LaunchEnvironmentInstalled - } - return false -} - -func (x *ProviderReadinessObservation) GetProcessInstanceId() string { - if x != nil { - return x.ProcessInstanceId - } - return "" -} - -func (x *ProviderReadinessObservation) GetReason() ProviderReadinessReason { - if x != nil { - return x.Reason - } - return ProviderReadinessReason_PROVIDER_READINESS_REASON_UNSPECIFIED -} - -// Operator view of desired and observed state; contains no credential material. -type ProviderReadinessStatus struct { - state protoimpl.MessageState `protogen:"open.v1"` - Receipt *ProviderMutationReceipt `protobuf:"bytes,1,opt,name=receipt,proto3" json:"receipt,omitempty"` - State ProviderReadinessState `protobuf:"varint,2,opt,name=state,proto3,enum=openshell.v1.ProviderReadinessState" json:"state,omitempty"` - Reason ProviderReadinessReason `protobuf:"varint,3,opt,name=reason,proto3,enum=openshell.v1.ProviderReadinessReason" json:"reason,omitempty"` - Observed *ProviderReadinessObservation `protobuf:"bytes,4,opt,name=observed,proto3" json:"observed,omitempty"` - NetworkInstanceId string `protobuf:"bytes,5,opt,name=network_instance_id,json=networkInstanceId,proto3" json:"network_instance_id,omitempty"` - // Absent until the current supervisor session supplies accepted evidence. - ObservedTime *timestamppb.Timestamp `protobuf:"bytes,106,opt,name=observed_time,json=observedTime,proto3" json:"observed_time,omitempty"` - EvaluatedTime *timestamppb.Timestamp `protobuf:"bytes,107,opt,name=evaluated_time,json=evaluatedTime,proto3" json:"evaluated_time,omitempty"` - // Durable operation for the receipt, including its terminal apply outcome. - Operation *ConfigUpdateOperation `protobuf:"bytes,8,opt,name=operation,proto3" json:"operation,omitempty"` +// Stop sandbox request. +type StopSandboxRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Workspace scope. Only a named workspace selection is accepted. + WorkspaceScope *datamodelv1.WorkspaceSelector `protobuf:"bytes,2,opt,name=workspace_scope,json=workspaceScope,proto3" json:"workspace_scope,omitempty"` + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + // Optional nonzero UUID for durable at-most-once admission. Successful results + // can be replayed for 24 hours; see the API errors and retries reference. + RequestId string `protobuf:"bytes,3,opt,name=request_id,json=requestId,proto3" json:"request_id,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } -func (x *ProviderReadinessStatus) Reset() { - *x = ProviderReadinessStatus{} +func (x *StopSandboxRequest) Reset() { + *x = StopSandboxRequest{} mi := &file_openshell_proto_msgTypes[61] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *ProviderReadinessStatus) String() string { +func (x *StopSandboxRequest) String() string { return protoimpl.X.MessageStringOf(x) } -func (*ProviderReadinessStatus) ProtoMessage() {} +func (*StopSandboxRequest) ProtoMessage() {} -func (x *ProviderReadinessStatus) ProtoReflect() protoreflect.Message { +func (x *StopSandboxRequest) ProtoReflect() protoreflect.Message { mi := &file_openshell_proto_msgTypes[61] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -5711,94 +5414,59 @@ func (x *ProviderReadinessStatus) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use ProviderReadinessStatus.ProtoReflect.Descriptor instead. -func (*ProviderReadinessStatus) Descriptor() ([]byte, []int) { +// Deprecated: Use StopSandboxRequest.ProtoReflect.Descriptor instead. +func (*StopSandboxRequest) Descriptor() ([]byte, []int) { return file_openshell_proto_rawDescGZIP(), []int{61} } -func (x *ProviderReadinessStatus) GetReceipt() *ProviderMutationReceipt { - if x != nil { - return x.Receipt - } - return nil -} - -func (x *ProviderReadinessStatus) GetState() ProviderReadinessState { - if x != nil { - return x.State - } - return ProviderReadinessState_PROVIDER_READINESS_STATE_UNSPECIFIED -} - -func (x *ProviderReadinessStatus) GetReason() ProviderReadinessReason { - if x != nil { - return x.Reason - } - return ProviderReadinessReason_PROVIDER_READINESS_REASON_UNSPECIFIED -} - -func (x *ProviderReadinessStatus) GetObserved() *ProviderReadinessObservation { +func (x *StopSandboxRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { if x != nil { - return x.Observed + return x.WorkspaceScope } return nil } -func (x *ProviderReadinessStatus) GetNetworkInstanceId() string { +func (x *StopSandboxRequest) GetName() string { if x != nil { - return x.NetworkInstanceId + return x.Name } return "" } -func (x *ProviderReadinessStatus) GetObservedTime() *timestamppb.Timestamp { - if x != nil { - return x.ObservedTime - } - return nil -} - -func (x *ProviderReadinessStatus) GetEvaluatedTime() *timestamppb.Timestamp { - if x != nil { - return x.EvaluatedTime - } - return nil -} - -func (x *ProviderReadinessStatus) GetOperation() *ConfigUpdateOperation { +func (x *StopSandboxRequest) GetRequestId() string { if x != nil { - return x.Operation + return x.RequestId } - return nil + return "" } -// Query an immutable receipt, or reconstruct the current desired state when -// receipt_id is empty. The sandbox identity must match the receipt. -type GetSandboxProviderStatusRequest struct { +// Start sandbox request. +type StartSandboxRequest struct { state protoimpl.MessageState `protogen:"open.v1"` // Workspace scope. Only a named workspace selection is accepted. - WorkspaceScope *datamodelv1.WorkspaceSelector `protobuf:"bytes,4,opt,name=workspace_scope,json=workspaceScope,proto3" json:"workspace_scope,omitempty"` - Sandbox string `protobuf:"bytes,1,opt,name=sandbox,proto3" json:"sandbox,omitempty"` - Provider string `protobuf:"bytes,2,opt,name=provider,proto3" json:"provider,omitempty"` - ReceiptId string `protobuf:"bytes,3,opt,name=receipt_id,json=receiptId,proto3" json:"receipt_id,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + WorkspaceScope *datamodelv1.WorkspaceSelector `protobuf:"bytes,2,opt,name=workspace_scope,json=workspaceScope,proto3" json:"workspace_scope,omitempty"` + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + // Optional nonzero UUID for durable at-most-once admission. Successful results + // can be replayed for 24 hours; see the API errors and retries reference. + RequestId string `protobuf:"bytes,3,opt,name=request_id,json=requestId,proto3" json:"request_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } -func (x *GetSandboxProviderStatusRequest) Reset() { - *x = GetSandboxProviderStatusRequest{} +func (x *StartSandboxRequest) Reset() { + *x = StartSandboxRequest{} mi := &file_openshell_proto_msgTypes[62] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *GetSandboxProviderStatusRequest) String() string { +func (x *StartSandboxRequest) String() string { return protoimpl.X.MessageStringOf(x) } -func (*GetSandboxProviderStatusRequest) ProtoMessage() {} +func (*StartSandboxRequest) ProtoMessage() {} -func (x *GetSandboxProviderStatusRequest) ProtoReflect() protoreflect.Message { +func (x *StartSandboxRequest) ProtoReflect() protoreflect.Message { mi := &file_openshell_proto_msgTypes[62] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -5810,60 +5478,57 @@ func (x *GetSandboxProviderStatusRequest) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use GetSandboxProviderStatusRequest.ProtoReflect.Descriptor instead. -func (*GetSandboxProviderStatusRequest) Descriptor() ([]byte, []int) { +// Deprecated: Use StartSandboxRequest.ProtoReflect.Descriptor instead. +func (*StartSandboxRequest) Descriptor() ([]byte, []int) { return file_openshell_proto_rawDescGZIP(), []int{62} } -func (x *GetSandboxProviderStatusRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { +func (x *StartSandboxRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { if x != nil { return x.WorkspaceScope } return nil } -func (x *GetSandboxProviderStatusRequest) GetSandbox() string { - if x != nil { - return x.Sandbox - } - return "" -} - -func (x *GetSandboxProviderStatusRequest) GetProvider() string { +func (x *StartSandboxRequest) GetName() string { if x != nil { - return x.Provider + return x.Name } return "" } -func (x *GetSandboxProviderStatusRequest) GetReceiptId() string { +func (x *StartSandboxRequest) GetRequestId() string { if x != nil { - return x.ReceiptId + return x.RequestId } return "" } -type GetSandboxProviderStatusResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - Status *ProviderReadinessStatus `protobuf:"bytes,1,opt,name=status,proto3" json:"status,omitempty"` +// Sandbox response. +type SandboxResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Sandbox *Sandbox `protobuf:"bytes,1,opt,name=sandbox,proto3" json:"sandbox,omitempty"` + // Service URLs created by CreateSandbox, keyed by service name. The empty + // key identifies the unnamed service. Other sandbox RPCs return an empty map. + ServiceUrls map[string]string `protobuf:"bytes,2,rep,name=service_urls,json=serviceUrls,proto3" json:"service_urls,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } -func (x *GetSandboxProviderStatusResponse) Reset() { - *x = GetSandboxProviderStatusResponse{} +func (x *SandboxResponse) Reset() { + *x = SandboxResponse{} mi := &file_openshell_proto_msgTypes[63] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *GetSandboxProviderStatusResponse) String() string { +func (x *SandboxResponse) String() string { return protoimpl.X.MessageStringOf(x) } -func (*GetSandboxProviderStatusResponse) ProtoMessage() {} +func (*SandboxResponse) ProtoMessage() {} -func (x *GetSandboxProviderStatusResponse) ProtoReflect() protoreflect.Message { +func (x *SandboxResponse) ProtoReflect() protoreflect.Message { mi := &file_openshell_proto_msgTypes[63] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -5875,42 +5540,49 @@ func (x *GetSandboxProviderStatusResponse) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use GetSandboxProviderStatusResponse.ProtoReflect.Descriptor instead. -func (*GetSandboxProviderStatusResponse) Descriptor() ([]byte, []int) { +// Deprecated: Use SandboxResponse.ProtoReflect.Descriptor instead. +func (*SandboxResponse) Descriptor() ([]byte, []int) { return file_openshell_proto_rawDescGZIP(), []int{63} } -func (x *GetSandboxProviderStatusResponse) GetStatus() *ProviderReadinessStatus { +func (x *SandboxResponse) GetSandbox() *Sandbox { if x != nil { - return x.Status + return x.Sandbox } return nil } -// Installation evidence from the authenticated supervisor for this sandbox. -// A caller-provided instance identifier alone never establishes authority. -type ReportProviderReadinessRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - SandboxId string `protobuf:"bytes,1,opt,name=sandbox_id,json=sandboxId,proto3" json:"sandbox_id,omitempty"` - Observation *ProviderReadinessObservation `protobuf:"bytes,2,opt,name=observation,proto3" json:"observation,omitempty"` +func (x *SandboxResponse) GetServiceUrls() map[string]string { + if x != nil { + return x.ServiceUrls + } + return nil +} + +// List sandboxes response. +type ListSandboxesResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Sandboxes []*Sandbox `protobuf:"bytes,1,rep,name=sandboxes,proto3" json:"sandboxes,omitempty"` + // Token for the next page. Empty when there are no subsequent pages. + NextPageToken string `protobuf:"bytes,2,opt,name=next_page_token,json=nextPageToken,proto3" json:"next_page_token,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } -func (x *ReportProviderReadinessRequest) Reset() { - *x = ReportProviderReadinessRequest{} +func (x *ListSandboxesResponse) Reset() { + *x = ListSandboxesResponse{} mi := &file_openshell_proto_msgTypes[64] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *ReportProviderReadinessRequest) String() string { +func (x *ListSandboxesResponse) String() string { return protoimpl.X.MessageStringOf(x) } -func (*ReportProviderReadinessRequest) ProtoMessage() {} +func (*ListSandboxesResponse) ProtoMessage() {} -func (x *ReportProviderReadinessRequest) ProtoReflect() protoreflect.Message { +func (x *ListSandboxesResponse) ProtoReflect() protoreflect.Message { mi := &file_openshell_proto_msgTypes[64] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -5922,50 +5594,49 @@ func (x *ReportProviderReadinessRequest) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use ReportProviderReadinessRequest.ProtoReflect.Descriptor instead. -func (*ReportProviderReadinessRequest) Descriptor() ([]byte, []int) { +// Deprecated: Use ListSandboxesResponse.ProtoReflect.Descriptor instead. +func (*ListSandboxesResponse) Descriptor() ([]byte, []int) { return file_openshell_proto_rawDescGZIP(), []int{64} } -func (x *ReportProviderReadinessRequest) GetSandboxId() string { +func (x *ListSandboxesResponse) GetSandboxes() []*Sandbox { if x != nil { - return x.SandboxId + return x.Sandboxes } - return "" + return nil } -func (x *ReportProviderReadinessRequest) GetObservation() *ProviderReadinessObservation { +func (x *ListSandboxesResponse) GetNextPageToken() string { if x != nil { - return x.Observation + return x.NextPageToken } - return nil + return "" } -// Acknowledges accepted evidence without granting a separate session authority. -// Identical retries do not extend the evidence's original acceptance time. -type ReportProviderReadinessResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - AcceptedSequence uint64 `protobuf:"varint,1,opt,name=accepted_sequence,json=acceptedSequence,proto3" json:"accepted_sequence,omitempty"` - ReportInterval *durationpb.Duration `protobuf:"bytes,102,opt,name=report_interval,json=reportInterval,proto3" json:"report_interval,omitempty"` - ObservationTtl *durationpb.Duration `protobuf:"bytes,103,opt,name=observation_ttl,json=observationTtl,proto3" json:"observation_ttl,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache +// List providers attached to a sandbox response. +type ListSandboxProvidersResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Providers []*datamodelv1.Provider `protobuf:"bytes,1,rep,name=providers,proto3" json:"providers,omitempty"` + // Token for the next page. Empty when there are no subsequent pages. + NextPageToken string `protobuf:"bytes,2,opt,name=next_page_token,json=nextPageToken,proto3" json:"next_page_token,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } -func (x *ReportProviderReadinessResponse) Reset() { - *x = ReportProviderReadinessResponse{} +func (x *ListSandboxProvidersResponse) Reset() { + *x = ListSandboxProvidersResponse{} mi := &file_openshell_proto_msgTypes[65] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *ReportProviderReadinessResponse) String() string { +func (x *ListSandboxProvidersResponse) String() string { return protoimpl.X.MessageStringOf(x) } -func (*ReportProviderReadinessResponse) ProtoMessage() {} +func (*ListSandboxProvidersResponse) ProtoMessage() {} -func (x *ReportProviderReadinessResponse) ProtoReflect() protoreflect.Message { +func (x *ListSandboxProvidersResponse) ProtoReflect() protoreflect.Message { mi := &file_openshell_proto_msgTypes[65] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -5977,57 +5648,51 @@ func (x *ReportProviderReadinessResponse) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use ReportProviderReadinessResponse.ProtoReflect.Descriptor instead. -func (*ReportProviderReadinessResponse) Descriptor() ([]byte, []int) { +// Deprecated: Use ListSandboxProvidersResponse.ProtoReflect.Descriptor instead. +func (*ListSandboxProvidersResponse) Descriptor() ([]byte, []int) { return file_openshell_proto_rawDescGZIP(), []int{65} } -func (x *ReportProviderReadinessResponse) GetAcceptedSequence() uint64 { - if x != nil { - return x.AcceptedSequence - } - return 0 -} - -func (x *ReportProviderReadinessResponse) GetReportInterval() *durationpb.Duration { +func (x *ListSandboxProvidersResponse) GetProviders() []*datamodelv1.Provider { if x != nil { - return x.ReportInterval + return x.Providers } return nil } -func (x *ReportProviderReadinessResponse) GetObservationTtl() *durationpb.Duration { +func (x *ListSandboxProvidersResponse) GetNextPageToken() string { if x != nil { - return x.ObservationTtl + return x.NextPageToken } - return nil + return "" } -// Delete sandbox response. -type DeleteSandboxResponse struct { +// Attach provider to sandbox response. +type AttachSandboxProviderResponse struct { state protoimpl.MessageState `protogen:"open.v1"` - Outcome DeletionOutcome `protobuf:"varint,2,opt,name=outcome,proto3,enum=openshell.v1.DeletionOutcome" json:"outcome,omitempty"` - // Immutable identity of the targeted sandbox, empty for ALREADY_ABSENT. - // A same-name replacement is not part of this deletion. - SandboxId string `protobuf:"bytes,3,opt,name=sandbox_id,json=sandboxId,proto3" json:"sandbox_id,omitempty"` + Sandbox *Sandbox `protobuf:"bytes,1,opt,name=sandbox,proto3" json:"sandbox,omitempty"` + // True when the provider was newly attached. False means it was already attached. + Attached bool `protobuf:"varint,2,opt,name=attached,proto3" json:"attached,omitempty"` + // Persisted intent; readiness requires current supervisor observations. + Receipt *ProviderMutationReceipt `protobuf:"bytes,3,opt,name=receipt,proto3" json:"receipt,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } -func (x *DeleteSandboxResponse) Reset() { - *x = DeleteSandboxResponse{} +func (x *AttachSandboxProviderResponse) Reset() { + *x = AttachSandboxProviderResponse{} mi := &file_openshell_proto_msgTypes[66] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *DeleteSandboxResponse) String() string { +func (x *AttachSandboxProviderResponse) String() string { return protoimpl.X.MessageStringOf(x) } -func (*DeleteSandboxResponse) ProtoMessage() {} +func (*AttachSandboxProviderResponse) ProtoMessage() {} -func (x *DeleteSandboxResponse) ProtoReflect() protoreflect.Message { +func (x *AttachSandboxProviderResponse) ProtoReflect() protoreflect.Message { mi := &file_openshell_proto_msgTypes[66] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -6039,49 +5704,58 @@ func (x *DeleteSandboxResponse) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use DeleteSandboxResponse.ProtoReflect.Descriptor instead. -func (*DeleteSandboxResponse) Descriptor() ([]byte, []int) { +// Deprecated: Use AttachSandboxProviderResponse.ProtoReflect.Descriptor instead. +func (*AttachSandboxProviderResponse) Descriptor() ([]byte, []int) { return file_openshell_proto_rawDescGZIP(), []int{66} } -func (x *DeleteSandboxResponse) GetOutcome() DeletionOutcome { +func (x *AttachSandboxProviderResponse) GetSandbox() *Sandbox { if x != nil { - return x.Outcome + return x.Sandbox } - return DeletionOutcome_DELETION_OUTCOME_UNSPECIFIED + return nil } -func (x *DeleteSandboxResponse) GetSandboxId() string { +func (x *AttachSandboxProviderResponse) GetAttached() bool { if x != nil { - return x.SandboxId + return x.Attached } - return "" + return false } -// Create SSH session request. -type CreateSshSessionRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - // Workspace scope. Only a named workspace selection is accepted. - WorkspaceScope *datamodelv1.WorkspaceSelector `protobuf:"bytes,3,opt,name=workspace_scope,json=workspaceScope,proto3" json:"workspace_scope,omitempty"` - Sandbox string `protobuf:"bytes,1,opt,name=sandbox,proto3" json:"sandbox,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache +func (x *AttachSandboxProviderResponse) GetReceipt() *ProviderMutationReceipt { + if x != nil { + return x.Receipt + } + return nil } -func (x *CreateSshSessionRequest) Reset() { - *x = CreateSshSessionRequest{} +// Detach provider from sandbox response. +type DetachSandboxProviderResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Sandbox *Sandbox `protobuf:"bytes,1,opt,name=sandbox,proto3" json:"sandbox,omitempty"` + // True when the provider was removed. False means it was not attached. + Detached bool `protobuf:"varint,2,opt,name=detached,proto3" json:"detached,omitempty"` + // Revocation is complete only when this receipt reports REVOKED. + Receipt *ProviderMutationReceipt `protobuf:"bytes,3,opt,name=receipt,proto3" json:"receipt,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DetachSandboxProviderResponse) Reset() { + *x = DetachSandboxProviderResponse{} mi := &file_openshell_proto_msgTypes[67] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *CreateSshSessionRequest) String() string { +func (x *DetachSandboxProviderResponse) String() string { return protoimpl.X.MessageStringOf(x) } -func (*CreateSshSessionRequest) ProtoMessage() {} +func (*DetachSandboxProviderResponse) ProtoMessage() {} -func (x *CreateSshSessionRequest) ProtoReflect() protoreflect.Message { +func (x *DetachSandboxProviderResponse) ProtoReflect() protoreflect.Message { mi := &file_openshell_proto_msgTypes[67] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -6093,71 +5767,62 @@ func (x *CreateSshSessionRequest) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use CreateSshSessionRequest.ProtoReflect.Descriptor instead. -func (*CreateSshSessionRequest) Descriptor() ([]byte, []int) { +// Deprecated: Use DetachSandboxProviderResponse.ProtoReflect.Descriptor instead. +func (*DetachSandboxProviderResponse) Descriptor() ([]byte, []int) { return file_openshell_proto_rawDescGZIP(), []int{67} } -func (x *CreateSshSessionRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { +func (x *DetachSandboxProviderResponse) GetSandbox() *Sandbox { if x != nil { - return x.WorkspaceScope + return x.Sandbox } return nil } -func (x *CreateSshSessionRequest) GetSandbox() string { +func (x *DetachSandboxProviderResponse) GetDetached() bool { if x != nil { - return x.Sandbox + return x.Detached } - return "" + return false } -// Create SSH session response. -// -// Fields are interpolated into an SSH `ProxyCommand` string that OpenSSH -// executes through `/bin/sh -c` on the caller's workstation. Servers MUST -// uphold the charset contract below; clients MUST reject responses that -// violate it. The client's own escaping provides defense-in-depth, but -// narrow charsets close injection vectors at the trust boundary. -type CreateSshSessionResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - // Sandbox id. [A-Za-z0-9._-]{1,128}. - SandboxId string `protobuf:"bytes,1,opt,name=sandbox_id,json=sandboxId,proto3" json:"sandbox_id,omitempty"` - // Session token for the gateway tunnel. URL-safe ASCII - // ([A-Za-z0-9._~+/=-]) up to 4096 bytes. No shell metacharacters or - // whitespace. - Token string `protobuf:"bytes,2,opt,name=token,proto3" json:"token,omitempty"` - // Gateway host for SSH proxy connection. IPv4 address, bracketed IPv6 - // address, or DNS hostname (Punycode-encoded for IDN). Alphanumeric plus - // `.-:[]` only, up to 253 bytes. - GatewayHost string `protobuf:"bytes,3,opt,name=gateway_host,json=gatewayHost,proto3" json:"gateway_host,omitempty"` - // Gateway port for SSH proxy connection. Must be in range 1..=65535. - GatewayPort uint32 `protobuf:"varint,4,opt,name=gateway_port,json=gatewayPort,proto3" json:"gateway_port,omitempty"` - // Gateway scheme. Must be exactly "http" or "https". - GatewayScheme string `protobuf:"bytes,5,opt,name=gateway_scheme,json=gatewayScheme,proto3" json:"gateway_scheme,omitempty"` - // Expected sandbox SSH host identity in OpenSSH SHA256: form. - // Stable for the sandbox's lifetime. Empty only for legacy runtimes. - HostKeyFingerprint string `protobuf:"bytes,7,opt,name=host_key_fingerprint,json=hostKeyFingerprint,proto3" json:"host_key_fingerprint,omitempty"` - // Absolute expiry. Absence means no expiry. - ExpirationTime *timestamppb.Timestamp `protobuf:"bytes,108,opt,name=expiration_time,json=expirationTime,proto3" json:"expiration_time,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache +func (x *DetachSandboxProviderResponse) GetReceipt() *ProviderMutationReceipt { + if x != nil { + return x.Receipt + } + return nil } -func (x *CreateSshSessionResponse) Reset() { - *x = CreateSshSessionResponse{} +// Exact desired authority. Revisions are opaque identities, never ordered. +type ProviderDesiredIdentity struct { + state protoimpl.MessageState `protogen:"open.v1"` + SandboxId string `protobuf:"bytes,1,opt,name=sandbox_id,json=sandboxId,proto3" json:"sandbox_id,omitempty"` + Sandbox string `protobuf:"bytes,2,opt,name=sandbox,proto3" json:"sandbox,omitempty"` + AttachmentEpoch string `protobuf:"bytes,3,opt,name=attachment_epoch,json=attachmentEpoch,proto3" json:"attachment_epoch,omitempty"` + // Empty for a detached provider. + ProviderId string `protobuf:"bytes,4,opt,name=provider_id,json=providerId,proto3" json:"provider_id,omitempty"` + ProviderResourceVersion uint64 `protobuf:"varint,5,opt,name=provider_resource_version,json=providerResourceVersion,proto3" json:"provider_resource_version,omitempty"` + ProviderEnvRevision uint64 `protobuf:"varint,6,opt,name=provider_env_revision,json=providerEnvRevision,proto3" json:"provider_env_revision,omitempty"` + ConfigRevision uint64 `protobuf:"varint,7,opt,name=config_revision,json=configRevision,proto3" json:"config_revision,omitempty"` + PolicyHash string `protobuf:"bytes,8,opt,name=policy_hash,json=policyHash,proto3" json:"policy_hash,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ProviderDesiredIdentity) Reset() { + *x = ProviderDesiredIdentity{} mi := &file_openshell_proto_msgTypes[68] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *CreateSshSessionResponse) String() string { +func (x *ProviderDesiredIdentity) String() string { return protoimpl.X.MessageStringOf(x) } -func (*CreateSshSessionResponse) ProtoMessage() {} +func (*ProviderDesiredIdentity) ProtoMessage() {} -func (x *CreateSshSessionResponse) ProtoReflect() protoreflect.Message { +func (x *ProviderDesiredIdentity) ProtoReflect() protoreflect.Message { mi := &file_openshell_proto_msgTypes[68] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -6169,95 +5834,95 @@ func (x *CreateSshSessionResponse) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use CreateSshSessionResponse.ProtoReflect.Descriptor instead. -func (*CreateSshSessionResponse) Descriptor() ([]byte, []int) { +// Deprecated: Use ProviderDesiredIdentity.ProtoReflect.Descriptor instead. +func (*ProviderDesiredIdentity) Descriptor() ([]byte, []int) { return file_openshell_proto_rawDescGZIP(), []int{68} } -func (x *CreateSshSessionResponse) GetSandboxId() string { +func (x *ProviderDesiredIdentity) GetSandboxId() string { if x != nil { return x.SandboxId } return "" } -func (x *CreateSshSessionResponse) GetToken() string { +func (x *ProviderDesiredIdentity) GetSandbox() string { if x != nil { - return x.Token + return x.Sandbox } return "" } -func (x *CreateSshSessionResponse) GetGatewayHost() string { +func (x *ProviderDesiredIdentity) GetAttachmentEpoch() string { if x != nil { - return x.GatewayHost + return x.AttachmentEpoch } return "" } -func (x *CreateSshSessionResponse) GetGatewayPort() uint32 { +func (x *ProviderDesiredIdentity) GetProviderId() string { if x != nil { - return x.GatewayPort + return x.ProviderId } - return 0 + return "" } -func (x *CreateSshSessionResponse) GetGatewayScheme() string { +func (x *ProviderDesiredIdentity) GetProviderResourceVersion() uint64 { if x != nil { - return x.GatewayScheme + return x.ProviderResourceVersion } - return "" + return 0 } -func (x *CreateSshSessionResponse) GetHostKeyFingerprint() string { +func (x *ProviderDesiredIdentity) GetProviderEnvRevision() uint64 { if x != nil { - return x.HostKeyFingerprint + return x.ProviderEnvRevision } - return "" + return 0 } -func (x *CreateSshSessionResponse) GetExpirationTime() *timestamppb.Timestamp { +func (x *ProviderDesiredIdentity) GetConfigRevision() uint64 { if x != nil { - return x.ExpirationTime + return x.ConfigRevision } - return nil + return 0 } -// Request to expose an HTTP service running inside a sandbox. -type ExposeServiceRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - // Workspace scope. Only a named workspace selection is accepted. - WorkspaceScope *datamodelv1.WorkspaceSelector `protobuf:"bytes,5,opt,name=workspace_scope,json=workspaceScope,proto3" json:"workspace_scope,omitempty"` - // Service name within the sandbox. - Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` - // Loopback TCP port inside the sandbox. - TargetPort uint32 `protobuf:"varint,3,opt,name=target_port,json=targetPort,proto3" json:"target_port,omitempty"` - // Whether to print/use the browser-facing service URL. - Domain bool `protobuf:"varint,4,opt,name=domain,proto3" json:"domain,omitempty"` - Sandbox string `protobuf:"bytes,1,opt,name=sandbox,proto3" json:"sandbox,omitempty"` - // Optional nonzero UUID for durable at-most-once admission. Successful results - // can be replayed for 24 hours; see the API errors and retries reference. - RequestId string `protobuf:"bytes,6,opt,name=request_id,json=requestId,proto3" json:"request_id,omitempty"` - // Application authorization behavior. Omission resolves to STRIP. - AuthorizationMode ServiceAuthorizationMode `protobuf:"varint,7,opt,name=authorization_mode,json=authorizationMode,proto3,enum=openshell.v1.ServiceAuthorizationMode" json:"authorization_mode,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache +func (x *ProviderDesiredIdentity) GetPolicyHash() string { + if x != nil { + return x.PolicyHash + } + return "" } -func (x *ExposeServiceRequest) Reset() { - *x = ExposeServiceRequest{} - mi := &file_openshell_proto_msgTypes[69] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) +// Identifies one component snapshot revision. Revisions are equality tokens, +// not members of one shared ordering domain. +type ConfigSnapshotRevision struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Types that are valid to be assigned to Component: + // + // *ConfigSnapshotRevision_SandboxConfig + // *ConfigSnapshotRevision_ProviderEnvironment + // *ConfigSnapshotRevision_ProviderTarget + Component isConfigSnapshotRevision_Component `protobuf_oneof:"component"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ConfigSnapshotRevision) Reset() { + *x = ConfigSnapshotRevision{} + mi := &file_openshell_proto_msgTypes[69] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *ExposeServiceRequest) String() string { +func (x *ConfigSnapshotRevision) String() string { return protoimpl.X.MessageStringOf(x) } -func (*ExposeServiceRequest) ProtoMessage() {} +func (*ConfigSnapshotRevision) ProtoMessage() {} -func (x *ExposeServiceRequest) ProtoReflect() protoreflect.Message { +func (x *ConfigSnapshotRevision) ProtoReflect() protoreflect.Message { mi := &file_openshell_proto_msgTypes[69] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -6269,86 +5934,97 @@ func (x *ExposeServiceRequest) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use ExposeServiceRequest.ProtoReflect.Descriptor instead. -func (*ExposeServiceRequest) Descriptor() ([]byte, []int) { +// Deprecated: Use ConfigSnapshotRevision.ProtoReflect.Descriptor instead. +func (*ConfigSnapshotRevision) Descriptor() ([]byte, []int) { return file_openshell_proto_rawDescGZIP(), []int{69} } -func (x *ExposeServiceRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { +func (x *ConfigSnapshotRevision) GetComponent() isConfigSnapshotRevision_Component { if x != nil { - return x.WorkspaceScope + return x.Component } return nil } -func (x *ExposeServiceRequest) GetName() string { +func (x *ConfigSnapshotRevision) GetSandboxConfig() *SandboxConfigRevision { if x != nil { - return x.Name + if x, ok := x.Component.(*ConfigSnapshotRevision_SandboxConfig); ok { + return x.SandboxConfig + } } - return "" + return nil } -func (x *ExposeServiceRequest) GetTargetPort() uint32 { +func (x *ConfigSnapshotRevision) GetProviderEnvironment() uint64 { if x != nil { - return x.TargetPort + if x, ok := x.Component.(*ConfigSnapshotRevision_ProviderEnvironment); ok { + return x.ProviderEnvironment + } } return 0 } -func (x *ExposeServiceRequest) GetDomain() bool { +func (x *ConfigSnapshotRevision) GetProviderTarget() *ProviderDesiredIdentity { if x != nil { - return x.Domain + if x, ok := x.Component.(*ConfigSnapshotRevision_ProviderTarget); ok { + return x.ProviderTarget + } } - return false + return nil } -func (x *ExposeServiceRequest) GetSandbox() string { - if x != nil { - return x.Sandbox - } - return "" +type isConfigSnapshotRevision_Component interface { + isConfigSnapshotRevision_Component() } -func (x *ExposeServiceRequest) GetRequestId() string { - if x != nil { - return x.RequestId - } - return "" +type ConfigSnapshotRevision_SandboxConfig struct { + SandboxConfig *SandboxConfigRevision `protobuf:"bytes,1,opt,name=sandbox_config,json=sandboxConfig,proto3,oneof"` } -func (x *ExposeServiceRequest) GetAuthorizationMode() ServiceAuthorizationMode { - if x != nil { - return x.AuthorizationMode - } - return ServiceAuthorizationMode_SERVICE_AUTHORIZATION_MODE_UNSPECIFIED +type ConfigSnapshotRevision_ProviderEnvironment struct { + ProviderEnvironment uint64 `protobuf:"varint,2,opt,name=provider_environment,json=providerEnvironment,proto3,oneof"` } -// Request to fetch an exposed sandbox service endpoint. -type GetServiceRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - // Workspace scope. Only a named workspace selection is accepted. - WorkspaceScope *datamodelv1.WorkspaceSelector `protobuf:"bytes,3,opt,name=workspace_scope,json=workspaceScope,proto3" json:"workspace_scope,omitempty"` - // Service name within the sandbox. Empty selects the unnamed endpoint. - Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` - Sandbox string `protobuf:"bytes,1,opt,name=sandbox,proto3" json:"sandbox,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache +type ConfigSnapshotRevision_ProviderTarget struct { + // Complete desired authority for one sandbox-scoped provider mutation. + ProviderTarget *ProviderDesiredIdentity `protobuf:"bytes,3,opt,name=provider_target,json=providerTarget,proto3,oneof"` } -func (x *GetServiceRequest) Reset() { - *x = GetServiceRequest{} +func (*ConfigSnapshotRevision_SandboxConfig) isConfigSnapshotRevision_Component() {} + +func (*ConfigSnapshotRevision_ProviderEnvironment) isConfigSnapshotRevision_Component() {} + +func (*ConfigSnapshotRevision_ProviderTarget) isConfigSnapshotRevision_Component() {} + +// Identity needed to correlate effective sandbox configuration with the +// policy-history row whose apply status the gateway records. +type SandboxConfigRevision struct { + state protoimpl.MessageState `protogen:"open.v1"` + ConfigRevision uint64 `protobuf:"varint,1,opt,name=config_revision,json=configRevision,proto3" json:"config_revision,omitempty"` + PolicyVersion uint32 `protobuf:"varint,2,opt,name=policy_version,json=policyVersion,proto3" json:"policy_version,omitempty"` + PolicySource sandboxv1.PolicySource `protobuf:"varint,3,opt,name=policy_source,json=policySource,proto3,enum=openshell.sandbox.v1.PolicySource" json:"policy_source,omitempty"` + GlobalPolicyVersion uint32 `protobuf:"varint,4,opt,name=global_policy_version,json=globalPolicyVersion,proto3" json:"global_policy_version,omitempty"` + // Monotonic revision of the sandbox-scoped settings row. This disambiguates + // setting operations whose effective config fingerprint is equality-only. + SettingsRevision uint64 `protobuf:"varint,5,opt,name=settings_revision,json=settingsRevision,proto3" json:"settings_revision,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SandboxConfigRevision) Reset() { + *x = SandboxConfigRevision{} mi := &file_openshell_proto_msgTypes[70] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *GetServiceRequest) String() string { +func (x *SandboxConfigRevision) String() string { return protoimpl.X.MessageStringOf(x) } -func (*GetServiceRequest) ProtoMessage() {} +func (*SandboxConfigRevision) ProtoMessage() {} -func (x *GetServiceRequest) ProtoReflect() protoreflect.Message { +func (x *SandboxConfigRevision) ProtoReflect() protoreflect.Message { mi := &file_openshell_proto_msgTypes[70] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -6360,63 +6036,79 @@ func (x *GetServiceRequest) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use GetServiceRequest.ProtoReflect.Descriptor instead. -func (*GetServiceRequest) Descriptor() ([]byte, []int) { +// Deprecated: Use SandboxConfigRevision.ProtoReflect.Descriptor instead. +func (*SandboxConfigRevision) Descriptor() ([]byte, []int) { return file_openshell_proto_rawDescGZIP(), []int{70} } -func (x *GetServiceRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { +func (x *SandboxConfigRevision) GetConfigRevision() uint64 { if x != nil { - return x.WorkspaceScope + return x.ConfigRevision } - return nil + return 0 } -func (x *GetServiceRequest) GetName() string { +func (x *SandboxConfigRevision) GetPolicyVersion() uint32 { if x != nil { - return x.Name + return x.PolicyVersion } - return "" + return 0 } -func (x *GetServiceRequest) GetSandbox() string { +func (x *SandboxConfigRevision) GetPolicySource() sandboxv1.PolicySource { if x != nil { - return x.Sandbox + return x.PolicySource } - return "" + return sandboxv1.PolicySource(0) } -// Request to list exposed sandbox service endpoints. -type ListServicesRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - // Workspace scope. Named and all-workspaces selections are accepted. - WorkspaceScope *datamodelv1.WorkspaceSelector `protobuf:"bytes,4,opt,name=workspace_scope,json=workspaceScope,proto3" json:"workspace_scope,omitempty"` - // The maximum number of services to return. Zero uses 100. Values above - // 1000 are coerced to 1000; negative values are invalid. - PageSize int32 `protobuf:"varint,2,opt,name=page_size,json=pageSize,proto3" json:"page_size,omitempty"` - // Token from a previous ListServices response. All other request parameters - // except page_size must match the request that produced it. - PageToken string `protobuf:"bytes,3,opt,name=page_token,json=pageToken,proto3" json:"page_token,omitempty"` - // Optional sandbox name. Empty lists endpoints for all sandboxes. - Sandbox string `protobuf:"bytes,1,opt,name=sandbox,proto3" json:"sandbox,omitempty"` +func (x *SandboxConfigRevision) GetGlobalPolicyVersion() uint32 { + if x != nil { + return x.GlobalPolicyVersion + } + return 0 +} + +func (x *SandboxConfigRevision) GetSettingsRevision() uint64 { + if x != nil { + return x.SettingsRevision + } + return 0 +} + +// Durable progress for one sandbox-scoped desired-state mutation. Snapshot +// contents and credentials are never stored in this resource. +type ConfigUpdateOperation struct { + state protoimpl.MessageState `protogen:"open.v1"` + OperationId string `protobuf:"bytes,1,opt,name=operation_id,json=operationId,proto3" json:"operation_id,omitempty"` + SandboxId string `protobuf:"bytes,2,opt,name=sandbox_id,json=sandboxId,proto3" json:"sandbox_id,omitempty"` + Component ConfigComponent `protobuf:"varint,3,opt,name=component,proto3,enum=openshell.v1.ConfigComponent" json:"component,omitempty"` + TargetRevision *ConfigSnapshotRevision `protobuf:"bytes,4,opt,name=target_revision,json=targetRevision,proto3" json:"target_revision,omitempty"` + State ConfigUpdateOperationState `protobuf:"varint,5,opt,name=state,proto3,enum=openshell.v1.ConfigUpdateOperationState" json:"state,omitempty"` + Outcome ConfigApplyOutcome `protobuf:"varint,6,opt,name=outcome,proto3,enum=openshell.v1.ConfigApplyOutcome" json:"outcome,omitempty"` + SanitizedError string `protobuf:"bytes,7,opt,name=sanitized_error,json=sanitizedError,proto3" json:"sanitized_error,omitempty"` + CreatedTime *timestamppb.Timestamp `protobuf:"bytes,108,opt,name=created_time,json=createdTime,proto3" json:"created_time,omitempty"` + UpdatedTime *timestamppb.Timestamp `protobuf:"bytes,109,opt,name=updated_time,json=updatedTime,proto3" json:"updated_time,omitempty"` + // Absent until the operation reaches a terminal state. + CompletedTime *timestamppb.Timestamp `protobuf:"bytes,110,opt,name=completed_time,json=completedTime,proto3" json:"completed_time,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } -func (x *ListServicesRequest) Reset() { - *x = ListServicesRequest{} +func (x *ConfigUpdateOperation) Reset() { + *x = ConfigUpdateOperation{} mi := &file_openshell_proto_msgTypes[71] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *ListServicesRequest) String() string { +func (x *ConfigUpdateOperation) String() string { return protoimpl.X.MessageStringOf(x) } -func (*ListServicesRequest) ProtoMessage() {} +func (*ConfigUpdateOperation) ProtoMessage() {} -func (x *ListServicesRequest) ProtoReflect() protoreflect.Message { +func (x *ConfigUpdateOperation) ProtoReflect() protoreflect.Message { mi := &file_openshell_proto_msgTypes[71] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -6428,124 +6120,111 @@ func (x *ListServicesRequest) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use ListServicesRequest.ProtoReflect.Descriptor instead. -func (*ListServicesRequest) Descriptor() ([]byte, []int) { +// Deprecated: Use ConfigUpdateOperation.ProtoReflect.Descriptor instead. +func (*ConfigUpdateOperation) Descriptor() ([]byte, []int) { return file_openshell_proto_rawDescGZIP(), []int{71} } -func (x *ListServicesRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { +func (x *ConfigUpdateOperation) GetOperationId() string { if x != nil { - return x.WorkspaceScope + return x.OperationId } - return nil + return "" } -func (x *ListServicesRequest) GetPageSize() int32 { +func (x *ConfigUpdateOperation) GetSandboxId() string { if x != nil { - return x.PageSize + return x.SandboxId } - return 0 + return "" } -func (x *ListServicesRequest) GetPageToken() string { +func (x *ConfigUpdateOperation) GetComponent() ConfigComponent { if x != nil { - return x.PageToken + return x.Component } - return "" + return ConfigComponent_CONFIG_COMPONENT_UNSPECIFIED } -func (x *ListServicesRequest) GetSandbox() string { +func (x *ConfigUpdateOperation) GetTargetRevision() *ConfigSnapshotRevision { if x != nil { - return x.Sandbox + return x.TargetRevision } - return "" + return nil } -// Response containing exposed sandbox service endpoints. -type ListServicesResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - Services []*ServiceEndpointResponse `protobuf:"bytes,1,rep,name=services,proto3" json:"services,omitempty"` - // Token for the next page. Empty when there are no subsequent pages. - NextPageToken string `protobuf:"bytes,2,opt,name=next_page_token,json=nextPageToken,proto3" json:"next_page_token,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache +func (x *ConfigUpdateOperation) GetState() ConfigUpdateOperationState { + if x != nil { + return x.State + } + return ConfigUpdateOperationState_CONFIG_UPDATE_OPERATION_STATE_UNSPECIFIED } -func (x *ListServicesResponse) Reset() { - *x = ListServicesResponse{} - mi := &file_openshell_proto_msgTypes[72] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) +func (x *ConfigUpdateOperation) GetOutcome() ConfigApplyOutcome { + if x != nil { + return x.Outcome + } + return ConfigApplyOutcome_CONFIG_APPLY_OUTCOME_UNSPECIFIED } -func (x *ListServicesResponse) String() string { - return protoimpl.X.MessageStringOf(x) +func (x *ConfigUpdateOperation) GetSanitizedError() string { + if x != nil { + return x.SanitizedError + } + return "" } -func (*ListServicesResponse) ProtoMessage() {} - -func (x *ListServicesResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[72] +func (x *ConfigUpdateOperation) GetCreatedTime() *timestamppb.Timestamp { if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms + return x.CreatedTime } - return mi.MessageOf(x) -} - -// Deprecated: Use ListServicesResponse.ProtoReflect.Descriptor instead. -func (*ListServicesResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{72} + return nil } -func (x *ListServicesResponse) GetServices() []*ServiceEndpointResponse { +func (x *ConfigUpdateOperation) GetUpdatedTime() *timestamppb.Timestamp { if x != nil { - return x.Services + return x.UpdatedTime } return nil } -func (x *ListServicesResponse) GetNextPageToken() string { +func (x *ConfigUpdateOperation) GetCompletedTime() *timestamppb.Timestamp { if x != nil { - return x.NextPageToken + return x.CompletedTime } - return "" + return nil } -// Request to delete an exposed sandbox service endpoint. -type DeleteServiceRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - // Workspace scope. Only a named workspace selection is accepted. - WorkspaceScope *datamodelv1.WorkspaceSelector `protobuf:"bytes,3,opt,name=workspace_scope,json=workspaceScope,proto3" json:"workspace_scope,omitempty"` - // Service name within the sandbox. Empty selects the unnamed endpoint. - Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` - Sandbox string `protobuf:"bytes,1,opt,name=sandbox,proto3" json:"sandbox,omitempty"` - AllowMissing bool `protobuf:"varint,4,opt,name=allow_missing,json=allowMissing,proto3" json:"allow_missing,omitempty"` - // Optional nonzero UUID for durable at-most-once admission. Successful results - // can be replayed for 24 hours; see the API errors and retries reference. - RequestId string `protobuf:"bytes,5,opt,name=request_id,json=requestId,proto3" json:"request_id,omitempty"` +// Immutable, secret-free record of one sandbox's intended provider mutation. +type ProviderMutationReceipt struct { + state protoimpl.MessageState `protogen:"open.v1"` + ReceiptId string `protobuf:"bytes,1,opt,name=receipt_id,json=receiptId,proto3" json:"receipt_id,omitempty"` + // Shared by all sandbox receipts from one provider update. + MutationId string `protobuf:"bytes,2,opt,name=mutation_id,json=mutationId,proto3" json:"mutation_id,omitempty"` + Provider string `protobuf:"bytes,3,opt,name=provider,proto3" json:"provider,omitempty"` + Workspace string `protobuf:"bytes,4,opt,name=workspace,proto3" json:"workspace,omitempty"` + Kind ProviderMutationKind `protobuf:"varint,5,opt,name=kind,proto3,enum=openshell.v1.ProviderMutationKind" json:"kind,omitempty"` + Desired *ProviderDesiredIdentity `protobuf:"bytes,6,opt,name=desired,proto3" json:"desired,omitempty"` + PersistedTime *timestamppb.Timestamp `protobuf:"bytes,107,opt,name=persisted_time,json=persistedTime,proto3" json:"persisted_time,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } -func (x *DeleteServiceRequest) Reset() { - *x = DeleteServiceRequest{} - mi := &file_openshell_proto_msgTypes[73] +func (x *ProviderMutationReceipt) Reset() { + *x = ProviderMutationReceipt{} + mi := &file_openshell_proto_msgTypes[72] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *DeleteServiceRequest) String() string { +func (x *ProviderMutationReceipt) String() string { return protoimpl.X.MessageStringOf(x) } -func (*DeleteServiceRequest) ProtoMessage() {} +func (*ProviderMutationReceipt) ProtoMessage() {} -func (x *DeleteServiceRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[73] +func (x *ProviderMutationReceipt) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[72] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6556,127 +6235,97 @@ func (x *DeleteServiceRequest) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use DeleteServiceRequest.ProtoReflect.Descriptor instead. -func (*DeleteServiceRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{73} +// Deprecated: Use ProviderMutationReceipt.ProtoReflect.Descriptor instead. +func (*ProviderMutationReceipt) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{72} } -func (x *DeleteServiceRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { +func (x *ProviderMutationReceipt) GetReceiptId() string { if x != nil { - return x.WorkspaceScope + return x.ReceiptId } - return nil + return "" } -func (x *DeleteServiceRequest) GetName() string { +func (x *ProviderMutationReceipt) GetMutationId() string { if x != nil { - return x.Name + return x.MutationId } return "" } -func (x *DeleteServiceRequest) GetSandbox() string { +func (x *ProviderMutationReceipt) GetProvider() string { if x != nil { - return x.Sandbox + return x.Provider } return "" } -func (x *DeleteServiceRequest) GetAllowMissing() bool { +func (x *ProviderMutationReceipt) GetWorkspace() string { if x != nil { - return x.AllowMissing + return x.Workspace } - return false + return "" } -func (x *DeleteServiceRequest) GetRequestId() string { +func (x *ProviderMutationReceipt) GetKind() ProviderMutationKind { if x != nil { - return x.RequestId + return x.Kind } - return "" -} - -// Response for deleting an exposed sandbox service endpoint. -type DeleteServiceResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - Outcome DeletionOutcome `protobuf:"varint,2,opt,name=outcome,proto3,enum=openshell.v1.DeletionOutcome" json:"outcome,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *DeleteServiceResponse) Reset() { - *x = DeleteServiceResponse{} - mi := &file_openshell_proto_msgTypes[74] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *DeleteServiceResponse) String() string { - return protoimpl.X.MessageStringOf(x) + return ProviderMutationKind_PROVIDER_MUTATION_KIND_UNSPECIFIED } -func (*DeleteServiceResponse) ProtoMessage() {} - -func (x *DeleteServiceResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[74] +func (x *ProviderMutationReceipt) GetDesired() *ProviderDesiredIdentity { if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms + return x.Desired } - return mi.MessageOf(x) -} - -// Deprecated: Use DeleteServiceResponse.ProtoReflect.Descriptor instead. -func (*DeleteServiceResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{74} + return nil } -func (x *DeleteServiceResponse) GetOutcome() DeletionOutcome { +func (x *ProviderMutationReceipt) GetPersistedTime() *timestamppb.Timestamp { if x != nil { - return x.Outcome + return x.PersistedTime } - return DeletionOutcome_DELETION_OUTCOME_UNSPECIFIED + return nil } -// Persisted sandbox service endpoint. -type ServiceEndpoint struct { +// Installed state reported by the current supervisor. Process installation is +// acknowledged by the authenticated sandbox boundary after replacing its +// environment for future process launches. +type ProviderReadinessObservation struct { state protoimpl.MessageState `protogen:"open.v1"` - // Kubernetes-style metadata. - Metadata *datamodelv1.ObjectMeta `protobuf:"bytes,1,opt,name=metadata,proto3" json:"metadata,omitempty"` - // Sandbox object ID. - SandboxId string `protobuf:"bytes,2,opt,name=sandbox_id,json=sandboxId,proto3" json:"sandbox_id,omitempty"` - // Sandbox name. - Sandbox string `protobuf:"bytes,3,opt,name=sandbox,proto3" json:"sandbox,omitempty"` - // Service name within the sandbox. - Name string `protobuf:"bytes,4,opt,name=name,proto3" json:"name,omitempty"` - // Loopback TCP port inside the sandbox. - TargetPort uint32 `protobuf:"varint,5,opt,name=target_port,json=targetPort,proto3" json:"target_port,omitempty"` - // Whether browser-facing service routing is enabled for this endpoint. - Domain bool `protobuf:"varint,6,opt,name=domain,proto3" json:"domain,omitempty"` - // Effective application authorization behavior for ingress requests. - AuthorizationMode ServiceAuthorizationMode `protobuf:"varint,7,opt,name=authorization_mode,json=authorizationMode,proto3,enum=openshell.v1.ServiceAuthorizationMode" json:"authorization_mode,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + // Gateway-issued identifier from the current ConnectSupervisor response. + SessionId string `protobuf:"bytes,1,opt,name=session_id,json=sessionId,proto3" json:"session_id,omitempty"` + // Monotonic only within this connection; unrelated to revision fingerprints. + Sequence uint64 `protobuf:"varint,2,opt,name=sequence,proto3" json:"sequence,omitempty"` + AttachmentEpoch string `protobuf:"bytes,3,opt,name=attachment_epoch,json=attachmentEpoch,proto3" json:"attachment_epoch,omitempty"` + ProviderEnvRevision uint64 `protobuf:"varint,4,opt,name=provider_env_revision,json=providerEnvRevision,proto3" json:"provider_env_revision,omitempty"` + ConfigRevision uint64 `protobuf:"varint,5,opt,name=config_revision,json=configRevision,proto3" json:"config_revision,omitempty"` + PolicyHash string `protobuf:"bytes,6,opt,name=policy_hash,json=policyHash,proto3" json:"policy_hash,omitempty"` + CredentialsInstalled bool `protobuf:"varint,7,opt,name=credentials_installed,json=credentialsInstalled,proto3" json:"credentials_installed,omitempty"` + PolicyActive bool `protobuf:"varint,8,opt,name=policy_active,json=policyActive,proto3" json:"policy_active,omitempty"` + LaunchEnvironmentInstalled bool `protobuf:"varint,9,opt,name=launch_environment_installed,json=launchEnvironmentInstalled,proto3" json:"launch_environment_installed,omitempty"` + ProcessInstanceId string `protobuf:"bytes,10,opt,name=process_instance_id,json=processInstanceId,proto3" json:"process_instance_id,omitempty"` + Reason ProviderReadinessReason `protobuf:"varint,11,opt,name=reason,proto3,enum=openshell.v1.ProviderReadinessReason" json:"reason,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } -func (x *ServiceEndpoint) Reset() { - *x = ServiceEndpoint{} - mi := &file_openshell_proto_msgTypes[75] +func (x *ProviderReadinessObservation) Reset() { + *x = ProviderReadinessObservation{} + mi := &file_openshell_proto_msgTypes[73] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *ServiceEndpoint) String() string { +func (x *ProviderReadinessObservation) String() string { return protoimpl.X.MessageStringOf(x) } -func (*ServiceEndpoint) ProtoMessage() {} +func (*ProviderReadinessObservation) ProtoMessage() {} -func (x *ServiceEndpoint) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[75] +func (x *ProviderReadinessObservation) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[73] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6687,140 +6336,120 @@ func (x *ServiceEndpoint) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use ServiceEndpoint.ProtoReflect.Descriptor instead. -func (*ServiceEndpoint) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{75} +// Deprecated: Use ProviderReadinessObservation.ProtoReflect.Descriptor instead. +func (*ProviderReadinessObservation) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{73} } -func (x *ServiceEndpoint) GetMetadata() *datamodelv1.ObjectMeta { +func (x *ProviderReadinessObservation) GetSessionId() string { if x != nil { - return x.Metadata + return x.SessionId } - return nil + return "" } -func (x *ServiceEndpoint) GetSandboxId() string { +func (x *ProviderReadinessObservation) GetSequence() uint64 { if x != nil { - return x.SandboxId + return x.Sequence } - return "" + return 0 } -func (x *ServiceEndpoint) GetSandbox() string { +func (x *ProviderReadinessObservation) GetAttachmentEpoch() string { if x != nil { - return x.Sandbox + return x.AttachmentEpoch } return "" } -func (x *ServiceEndpoint) GetName() string { +func (x *ProviderReadinessObservation) GetProviderEnvRevision() uint64 { if x != nil { - return x.Name + return x.ProviderEnvRevision } - return "" + return 0 } -func (x *ServiceEndpoint) GetTargetPort() uint32 { +func (x *ProviderReadinessObservation) GetConfigRevision() uint64 { if x != nil { - return x.TargetPort + return x.ConfigRevision } return 0 } -func (x *ServiceEndpoint) GetDomain() bool { +func (x *ProviderReadinessObservation) GetPolicyHash() string { if x != nil { - return x.Domain + return x.PolicyHash } - return false + return "" } -func (x *ServiceEndpoint) GetAuthorizationMode() ServiceAuthorizationMode { +func (x *ProviderReadinessObservation) GetCredentialsInstalled() bool { if x != nil { - return x.AuthorizationMode + return x.CredentialsInstalled } - return ServiceAuthorizationMode_SERVICE_AUTHORIZATION_MODE_UNSPECIFIED -} - -// Response containing a service endpoint and, when available, its local URL. -type ServiceEndpointResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - Endpoint *ServiceEndpoint `protobuf:"bytes,1,opt,name=endpoint,proto3" json:"endpoint,omitempty"` - Url string `protobuf:"bytes,2,opt,name=url,proto3" json:"url,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + return false } -func (x *ServiceEndpointResponse) Reset() { - *x = ServiceEndpointResponse{} - mi := &file_openshell_proto_msgTypes[76] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) +func (x *ProviderReadinessObservation) GetPolicyActive() bool { + if x != nil { + return x.PolicyActive + } + return false } -func (x *ServiceEndpointResponse) String() string { - return protoimpl.X.MessageStringOf(x) +func (x *ProviderReadinessObservation) GetLaunchEnvironmentInstalled() bool { + if x != nil { + return x.LaunchEnvironmentInstalled + } + return false } -func (*ServiceEndpointResponse) ProtoMessage() {} - -func (x *ServiceEndpointResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[76] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use ServiceEndpointResponse.ProtoReflect.Descriptor instead. -func (*ServiceEndpointResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{76} -} - -func (x *ServiceEndpointResponse) GetEndpoint() *ServiceEndpoint { +func (x *ProviderReadinessObservation) GetProcessInstanceId() string { if x != nil { - return x.Endpoint + return x.ProcessInstanceId } - return nil + return "" } -func (x *ServiceEndpointResponse) GetUrl() string { +func (x *ProviderReadinessObservation) GetReason() ProviderReadinessReason { if x != nil { - return x.Url + return x.Reason } - return "" + return ProviderReadinessReason_PROVIDER_READINESS_REASON_UNSPECIFIED } -// Revoke SSH session request. -type RevokeSshSessionRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - // Session token to revoke. - Token string `protobuf:"bytes,1,opt,name=token,proto3" json:"token,omitempty"` - // A missing token is NOT_FOUND unless this is true. Revoking an existing, - // already-revoked session succeeds with COMPLETED. - AllowMissing bool `protobuf:"varint,2,opt,name=allow_missing,json=allowMissing,proto3" json:"allow_missing,omitempty"` +// Operator view of desired and observed state; contains no credential material. +type ProviderReadinessStatus struct { + state protoimpl.MessageState `protogen:"open.v1"` + Receipt *ProviderMutationReceipt `protobuf:"bytes,1,opt,name=receipt,proto3" json:"receipt,omitempty"` + State ProviderReadinessState `protobuf:"varint,2,opt,name=state,proto3,enum=openshell.v1.ProviderReadinessState" json:"state,omitempty"` + Reason ProviderReadinessReason `protobuf:"varint,3,opt,name=reason,proto3,enum=openshell.v1.ProviderReadinessReason" json:"reason,omitempty"` + Observed *ProviderReadinessObservation `protobuf:"bytes,4,opt,name=observed,proto3" json:"observed,omitempty"` + NetworkInstanceId string `protobuf:"bytes,5,opt,name=network_instance_id,json=networkInstanceId,proto3" json:"network_instance_id,omitempty"` + // Absent until the current supervisor session supplies accepted evidence. + ObservedTime *timestamppb.Timestamp `protobuf:"bytes,106,opt,name=observed_time,json=observedTime,proto3" json:"observed_time,omitempty"` + EvaluatedTime *timestamppb.Timestamp `protobuf:"bytes,107,opt,name=evaluated_time,json=evaluatedTime,proto3" json:"evaluated_time,omitempty"` + // Durable operation for the receipt, including its terminal apply outcome. + Operation *ConfigUpdateOperation `protobuf:"bytes,8,opt,name=operation,proto3" json:"operation,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } -func (x *RevokeSshSessionRequest) Reset() { - *x = RevokeSshSessionRequest{} - mi := &file_openshell_proto_msgTypes[77] +func (x *ProviderReadinessStatus) Reset() { + *x = ProviderReadinessStatus{} + mi := &file_openshell_proto_msgTypes[74] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *RevokeSshSessionRequest) String() string { +func (x *ProviderReadinessStatus) String() string { return protoimpl.X.MessageStringOf(x) } -func (*RevokeSshSessionRequest) ProtoMessage() {} +func (*ProviderReadinessStatus) ProtoMessage() {} -func (x *RevokeSshSessionRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[77] +func (x *ProviderReadinessStatus) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[74] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6831,123 +6460,95 @@ func (x *RevokeSshSessionRequest) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use RevokeSshSessionRequest.ProtoReflect.Descriptor instead. -func (*RevokeSshSessionRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{77} +// Deprecated: Use ProviderReadinessStatus.ProtoReflect.Descriptor instead. +func (*ProviderReadinessStatus) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{74} } -func (x *RevokeSshSessionRequest) GetToken() string { +func (x *ProviderReadinessStatus) GetReceipt() *ProviderMutationReceipt { if x != nil { - return x.Token + return x.Receipt } - return "" + return nil } -func (x *RevokeSshSessionRequest) GetAllowMissing() bool { +func (x *ProviderReadinessStatus) GetState() ProviderReadinessState { if x != nil { - return x.AllowMissing + return x.State } - return false + return ProviderReadinessState_PROVIDER_READINESS_STATE_UNSPECIFIED } -// Revoke SSH session response. -type RevokeSshSessionResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - Outcome DeletionOutcome `protobuf:"varint,2,opt,name=outcome,proto3,enum=openshell.v1.DeletionOutcome" json:"outcome,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache +func (x *ProviderReadinessStatus) GetReason() ProviderReadinessReason { + if x != nil { + return x.Reason + } + return ProviderReadinessReason_PROVIDER_READINESS_REASON_UNSPECIFIED } -func (x *RevokeSshSessionResponse) Reset() { - *x = RevokeSshSessionResponse{} - mi := &file_openshell_proto_msgTypes[78] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) +func (x *ProviderReadinessStatus) GetObserved() *ProviderReadinessObservation { + if x != nil { + return x.Observed + } + return nil } -func (x *RevokeSshSessionResponse) String() string { - return protoimpl.X.MessageStringOf(x) +func (x *ProviderReadinessStatus) GetNetworkInstanceId() string { + if x != nil { + return x.NetworkInstanceId + } + return "" } -func (*RevokeSshSessionResponse) ProtoMessage() {} - -func (x *RevokeSshSessionResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[78] +func (x *ProviderReadinessStatus) GetObservedTime() *timestamppb.Timestamp { if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms + return x.ObservedTime } - return mi.MessageOf(x) + return nil } -// Deprecated: Use RevokeSshSessionResponse.ProtoReflect.Descriptor instead. -func (*RevokeSshSessionResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{78} +func (x *ProviderReadinessStatus) GetEvaluatedTime() *timestamppb.Timestamp { + if x != nil { + return x.EvaluatedTime + } + return nil } -func (x *RevokeSshSessionResponse) GetOutcome() DeletionOutcome { +func (x *ProviderReadinessStatus) GetOperation() *ConfigUpdateOperation { if x != nil { - return x.Outcome + return x.Operation } - return DeletionOutcome_DELETION_OUTCOME_UNSPECIFIED + return nil } -// Execute command request. -type ExecSandboxRequest struct { +// Query an immutable receipt, or reconstruct the current desired state when +// receipt_id is empty. The sandbox identity must match the receipt. +type GetSandboxProviderStatusRequest struct { state protoimpl.MessageState `protogen:"open.v1"` // Workspace scope. Only a named workspace selection is accepted. - WorkspaceScope *datamodelv1.WorkspaceSelector `protobuf:"bytes,12,opt,name=workspace_scope,json=workspaceScope,proto3" json:"workspace_scope,omitempty"` - // Canonical sandbox name. - Sandbox string `protobuf:"bytes,1,opt,name=sandbox,proto3" json:"sandbox,omitempty"` - // Command and arguments. - Command []string `protobuf:"bytes,2,rep,name=command,proto3" json:"command,omitempty"` - // Optional working directory. - Workdir string `protobuf:"bytes,3,opt,name=workdir,proto3" json:"workdir,omitempty"` - // Optional environment overrides. - Environment map[string]string `protobuf:"bytes,4,rep,name=environment,proto3" json:"environment,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` - // Optional execution timeout. Absence means no timeout. - ExecutionTimeout *durationpb.Duration `protobuf:"bytes,105,opt,name=execution_timeout,json=executionTimeout,proto3" json:"execution_timeout,omitempty"` - // Optional stdin payload passed to the command. - Stdin []byte `protobuf:"bytes,6,opt,name=stdin,proto3" json:"stdin,omitempty"` - // Request a pseudo-terminal for the remote command. - Tty bool `protobuf:"varint,7,opt,name=tty,proto3" json:"tty,omitempty"` - // Initial terminal columns (used when tty=true, 0 = use default). - Cols uint32 `protobuf:"varint,8,opt,name=cols,proto3" json:"cols,omitempty"` - // Initial terminal rows (used when tty=true, 0 = use default). - Rows uint32 `protobuf:"varint,9,opt,name=rows,proto3" json:"rows,omitempty"` - // Skip sourcing shell login/profile startup files before running the command. - // When false (the default), the command runs through a login shell - // (`bash -lc`) so user startup files (.bash_profile/.profile, and .bashrc if - // sourced by them) are applied. When true, the command runs without those - // files (`bash -c`), for automation that needs predictable startup behavior. - NoLoginShell bool `protobuf:"varint,10,opt,name=no_login_shell,json=noLoginShell,proto3" json:"no_login_shell,omitempty"` - // Optional nonzero UUID for durable launch admission. Also applies to the - // initial ExecSandboxInteractive start message. Duplicates never relaunch, - // reattach, or replay output/stdin. Unconfirmed executions remain fenced; - // confirmed terminal executions retain the fence for 24 hours. - RequestId string `protobuf:"bytes,11,opt,name=request_id,json=requestId,proto3" json:"request_id,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + WorkspaceScope *datamodelv1.WorkspaceSelector `protobuf:"bytes,4,opt,name=workspace_scope,json=workspaceScope,proto3" json:"workspace_scope,omitempty"` + Sandbox string `protobuf:"bytes,1,opt,name=sandbox,proto3" json:"sandbox,omitempty"` + Provider string `protobuf:"bytes,2,opt,name=provider,proto3" json:"provider,omitempty"` + ReceiptId string `protobuf:"bytes,3,opt,name=receipt_id,json=receiptId,proto3" json:"receipt_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } -func (x *ExecSandboxRequest) Reset() { - *x = ExecSandboxRequest{} - mi := &file_openshell_proto_msgTypes[79] +func (x *GetSandboxProviderStatusRequest) Reset() { + *x = GetSandboxProviderStatusRequest{} + mi := &file_openshell_proto_msgTypes[75] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *ExecSandboxRequest) String() string { +func (x *GetSandboxProviderStatusRequest) String() string { return protoimpl.X.MessageStringOf(x) } -func (*ExecSandboxRequest) ProtoMessage() {} +func (*GetSandboxProviderStatusRequest) ProtoMessage() {} -func (x *ExecSandboxRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[79] +func (x *GetSandboxProviderStatusRequest) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[75] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6958,118 +6559,108 @@ func (x *ExecSandboxRequest) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use ExecSandboxRequest.ProtoReflect.Descriptor instead. -func (*ExecSandboxRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{79} +// Deprecated: Use GetSandboxProviderStatusRequest.ProtoReflect.Descriptor instead. +func (*GetSandboxProviderStatusRequest) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{75} } -func (x *ExecSandboxRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { +func (x *GetSandboxProviderStatusRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { if x != nil { return x.WorkspaceScope } return nil } -func (x *ExecSandboxRequest) GetSandbox() string { +func (x *GetSandboxProviderStatusRequest) GetSandbox() string { if x != nil { return x.Sandbox } return "" } -func (x *ExecSandboxRequest) GetCommand() []string { +func (x *GetSandboxProviderStatusRequest) GetProvider() string { if x != nil { - return x.Command + return x.Provider } - return nil + return "" } -func (x *ExecSandboxRequest) GetWorkdir() string { +func (x *GetSandboxProviderStatusRequest) GetReceiptId() string { if x != nil { - return x.Workdir + return x.ReceiptId } return "" } -func (x *ExecSandboxRequest) GetEnvironment() map[string]string { - if x != nil { - return x.Environment - } - return nil +type GetSandboxProviderStatusResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Status *ProviderReadinessStatus `protobuf:"bytes,1,opt,name=status,proto3" json:"status,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } -func (x *ExecSandboxRequest) GetExecutionTimeout() *durationpb.Duration { - if x != nil { - return x.ExecutionTimeout - } - return nil +func (x *GetSandboxProviderStatusResponse) Reset() { + *x = GetSandboxProviderStatusResponse{} + mi := &file_openshell_proto_msgTypes[76] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } -func (x *ExecSandboxRequest) GetStdin() []byte { - if x != nil { - return x.Stdin - } - return nil +func (x *GetSandboxProviderStatusResponse) String() string { + return protoimpl.X.MessageStringOf(x) } -func (x *ExecSandboxRequest) GetTty() bool { +func (*GetSandboxProviderStatusResponse) ProtoMessage() {} + +func (x *GetSandboxProviderStatusResponse) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[76] if x != nil { - return x.Tty + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms } - return false + return mi.MessageOf(x) } -func (x *ExecSandboxRequest) GetCols() uint32 { - if x != nil { - return x.Cols - } - return 0 -} - -func (x *ExecSandboxRequest) GetRows() uint32 { - if x != nil { - return x.Rows - } - return 0 -} - -func (x *ExecSandboxRequest) GetNoLoginShell() bool { - if x != nil { - return x.NoLoginShell - } - return false +// Deprecated: Use GetSandboxProviderStatusResponse.ProtoReflect.Descriptor instead. +func (*GetSandboxProviderStatusResponse) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{76} } -func (x *ExecSandboxRequest) GetRequestId() string { +func (x *GetSandboxProviderStatusResponse) GetStatus() *ProviderReadinessStatus { if x != nil { - return x.RequestId + return x.Status } - return "" + return nil } -// One stdout chunk from a sandbox exec. -type ExecSandboxStdout struct { - state protoimpl.MessageState `protogen:"open.v1"` - Data []byte `protobuf:"bytes,1,opt,name=data,proto3" json:"data,omitempty"` +// Installation evidence from the authenticated supervisor for this sandbox. +// A caller-provided instance identifier alone never establishes authority. +type ReportProviderReadinessRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + SandboxId string `protobuf:"bytes,1,opt,name=sandbox_id,json=sandboxId,proto3" json:"sandbox_id,omitempty"` + Observation *ProviderReadinessObservation `protobuf:"bytes,2,opt,name=observation,proto3" json:"observation,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } -func (x *ExecSandboxStdout) Reset() { - *x = ExecSandboxStdout{} - mi := &file_openshell_proto_msgTypes[80] +func (x *ReportProviderReadinessRequest) Reset() { + *x = ReportProviderReadinessRequest{} + mi := &file_openshell_proto_msgTypes[77] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *ExecSandboxStdout) String() string { +func (x *ReportProviderReadinessRequest) String() string { return protoimpl.X.MessageStringOf(x) } -func (*ExecSandboxStdout) ProtoMessage() {} +func (*ReportProviderReadinessRequest) ProtoMessage() {} -func (x *ExecSandboxStdout) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[80] +func (x *ReportProviderReadinessRequest) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[77] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7080,41 +6671,51 @@ func (x *ExecSandboxStdout) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use ExecSandboxStdout.ProtoReflect.Descriptor instead. -func (*ExecSandboxStdout) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{80} +// Deprecated: Use ReportProviderReadinessRequest.ProtoReflect.Descriptor instead. +func (*ReportProviderReadinessRequest) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{77} } -func (x *ExecSandboxStdout) GetData() []byte { +func (x *ReportProviderReadinessRequest) GetSandboxId() string { if x != nil { - return x.Data + return x.SandboxId + } + return "" +} + +func (x *ReportProviderReadinessRequest) GetObservation() *ProviderReadinessObservation { + if x != nil { + return x.Observation } return nil } -// One stderr chunk from a sandbox exec. -type ExecSandboxStderr struct { - state protoimpl.MessageState `protogen:"open.v1"` - Data []byte `protobuf:"bytes,1,opt,name=data,proto3" json:"data,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache +// Acknowledges accepted evidence without granting a separate session authority. +// Identical retries do not extend the evidence's original acceptance time. +type ReportProviderReadinessResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + AcceptedSequence uint64 `protobuf:"varint,1,opt,name=accepted_sequence,json=acceptedSequence,proto3" json:"accepted_sequence,omitempty"` + ReportInterval *durationpb.Duration `protobuf:"bytes,102,opt,name=report_interval,json=reportInterval,proto3" json:"report_interval,omitempty"` + ObservationTtl *durationpb.Duration `protobuf:"bytes,103,opt,name=observation_ttl,json=observationTtl,proto3" json:"observation_ttl,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } -func (x *ExecSandboxStderr) Reset() { - *x = ExecSandboxStderr{} - mi := &file_openshell_proto_msgTypes[81] +func (x *ReportProviderReadinessResponse) Reset() { + *x = ReportProviderReadinessResponse{} + mi := &file_openshell_proto_msgTypes[78] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *ExecSandboxStderr) String() string { +func (x *ReportProviderReadinessResponse) String() string { return protoimpl.X.MessageStringOf(x) } -func (*ExecSandboxStderr) ProtoMessage() {} +func (*ReportProviderReadinessResponse) ProtoMessage() {} -func (x *ExecSandboxStderr) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[81] +func (x *ReportProviderReadinessResponse) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[78] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7125,41 +6726,58 @@ func (x *ExecSandboxStderr) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use ExecSandboxStderr.ProtoReflect.Descriptor instead. -func (*ExecSandboxStderr) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{81} +// Deprecated: Use ReportProviderReadinessResponse.ProtoReflect.Descriptor instead. +func (*ReportProviderReadinessResponse) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{78} } -func (x *ExecSandboxStderr) GetData() []byte { +func (x *ReportProviderReadinessResponse) GetAcceptedSequence() uint64 { if x != nil { - return x.Data + return x.AcceptedSequence + } + return 0 +} + +func (x *ReportProviderReadinessResponse) GetReportInterval() *durationpb.Duration { + if x != nil { + return x.ReportInterval } return nil } -// Final exit status for a sandbox exec. -type ExecSandboxExit struct { - state protoimpl.MessageState `protogen:"open.v1"` - ExitCode int32 `protobuf:"varint,1,opt,name=exit_code,json=exitCode,proto3" json:"exit_code,omitempty"` +func (x *ReportProviderReadinessResponse) GetObservationTtl() *durationpb.Duration { + if x != nil { + return x.ObservationTtl + } + return nil +} + +// Delete sandbox response. +type DeleteSandboxResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Outcome DeletionOutcome `protobuf:"varint,2,opt,name=outcome,proto3,enum=openshell.v1.DeletionOutcome" json:"outcome,omitempty"` + // Immutable identity of the targeted sandbox, empty for ALREADY_ABSENT. + // A same-name replacement is not part of this deletion. + SandboxId string `protobuf:"bytes,3,opt,name=sandbox_id,json=sandboxId,proto3" json:"sandbox_id,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } -func (x *ExecSandboxExit) Reset() { - *x = ExecSandboxExit{} - mi := &file_openshell_proto_msgTypes[82] +func (x *DeleteSandboxResponse) Reset() { + *x = DeleteSandboxResponse{} + mi := &file_openshell_proto_msgTypes[79] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *ExecSandboxExit) String() string { +func (x *DeleteSandboxResponse) String() string { return protoimpl.X.MessageStringOf(x) } -func (*ExecSandboxExit) ProtoMessage() {} +func (*DeleteSandboxResponse) ProtoMessage() {} -func (x *ExecSandboxExit) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[82] +func (x *DeleteSandboxResponse) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[79] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7170,46 +6788,50 @@ func (x *ExecSandboxExit) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use ExecSandboxExit.ProtoReflect.Descriptor instead. -func (*ExecSandboxExit) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{82} +// Deprecated: Use DeleteSandboxResponse.ProtoReflect.Descriptor instead. +func (*DeleteSandboxResponse) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{79} } -func (x *ExecSandboxExit) GetExitCode() int32 { +func (x *DeleteSandboxResponse) GetOutcome() DeletionOutcome { if x != nil { - return x.ExitCode + return x.Outcome } - return 0 + return DeletionOutcome_DELETION_OUTCOME_UNSPECIFIED } -// One event in a sandbox exec stream. -type ExecSandboxEvent struct { +func (x *DeleteSandboxResponse) GetSandboxId() string { + if x != nil { + return x.SandboxId + } + return "" +} + +// Create SSH session request. +type CreateSshSessionRequest struct { state protoimpl.MessageState `protogen:"open.v1"` - // Types that are valid to be assigned to Payload: - // - // *ExecSandboxEvent_Stdout - // *ExecSandboxEvent_Stderr - // *ExecSandboxEvent_Exit - Payload isExecSandboxEvent_Payload `protobuf_oneof:"payload"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + // Workspace scope. Only a named workspace selection is accepted. + WorkspaceScope *datamodelv1.WorkspaceSelector `protobuf:"bytes,3,opt,name=workspace_scope,json=workspaceScope,proto3" json:"workspace_scope,omitempty"` + Sandbox string `protobuf:"bytes,1,opt,name=sandbox,proto3" json:"sandbox,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } -func (x *ExecSandboxEvent) Reset() { - *x = ExecSandboxEvent{} - mi := &file_openshell_proto_msgTypes[83] +func (x *CreateSshSessionRequest) Reset() { + *x = CreateSshSessionRequest{} + mi := &file_openshell_proto_msgTypes[80] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *ExecSandboxEvent) String() string { +func (x *CreateSshSessionRequest) String() string { return protoimpl.X.MessageStringOf(x) } -func (*ExecSandboxEvent) ProtoMessage() {} +func (*CreateSshSessionRequest) ProtoMessage() {} -func (x *ExecSandboxEvent) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[83] +func (x *CreateSshSessionRequest) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[80] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7220,103 +6842,72 @@ func (x *ExecSandboxEvent) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use ExecSandboxEvent.ProtoReflect.Descriptor instead. -func (*ExecSandboxEvent) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{83} +// Deprecated: Use CreateSshSessionRequest.ProtoReflect.Descriptor instead. +func (*CreateSshSessionRequest) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{80} } -func (x *ExecSandboxEvent) GetPayload() isExecSandboxEvent_Payload { +func (x *CreateSshSessionRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { if x != nil { - return x.Payload + return x.WorkspaceScope } return nil } -func (x *ExecSandboxEvent) GetStdout() *ExecSandboxStdout { +func (x *CreateSshSessionRequest) GetSandbox() string { if x != nil { - if x, ok := x.Payload.(*ExecSandboxEvent_Stdout); ok { - return x.Stdout - } + return x.Sandbox } - return nil + return "" } -func (x *ExecSandboxEvent) GetStderr() *ExecSandboxStderr { - if x != nil { - if x, ok := x.Payload.(*ExecSandboxEvent_Stderr); ok { - return x.Stderr - } - } - return nil +// Create SSH session response. +// +// Fields are interpolated into an SSH `ProxyCommand` string that OpenSSH +// executes through `/bin/sh -c` on the caller's workstation. Servers MUST +// uphold the charset contract below; clients MUST reject responses that +// violate it. The client's own escaping provides defense-in-depth, but +// narrow charsets close injection vectors at the trust boundary. +type CreateSshSessionResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Sandbox id. [A-Za-z0-9._-]{1,128}. + SandboxId string `protobuf:"bytes,1,opt,name=sandbox_id,json=sandboxId,proto3" json:"sandbox_id,omitempty"` + // Session token for the gateway tunnel. URL-safe ASCII + // ([A-Za-z0-9._~+/=-]) up to 4096 bytes. No shell metacharacters or + // whitespace. + Token string `protobuf:"bytes,2,opt,name=token,proto3" json:"token,omitempty"` + // Gateway host for SSH proxy connection. IPv4 address, bracketed IPv6 + // address, or DNS hostname (Punycode-encoded for IDN). Alphanumeric plus + // `.-:[]` only, up to 253 bytes. + GatewayHost string `protobuf:"bytes,3,opt,name=gateway_host,json=gatewayHost,proto3" json:"gateway_host,omitempty"` + // Gateway port for SSH proxy connection. Must be in range 1..=65535. + GatewayPort uint32 `protobuf:"varint,4,opt,name=gateway_port,json=gatewayPort,proto3" json:"gateway_port,omitempty"` + // Gateway scheme. Must be exactly "http" or "https". + GatewayScheme string `protobuf:"bytes,5,opt,name=gateway_scheme,json=gatewayScheme,proto3" json:"gateway_scheme,omitempty"` + // Expected sandbox SSH host identity in OpenSSH SHA256: form. + // Stable for the sandbox's lifetime. Empty only for legacy runtimes. + HostKeyFingerprint string `protobuf:"bytes,7,opt,name=host_key_fingerprint,json=hostKeyFingerprint,proto3" json:"host_key_fingerprint,omitempty"` + // Absolute expiry. Absence means no expiry. + ExpirationTime *timestamppb.Timestamp `protobuf:"bytes,108,opt,name=expiration_time,json=expirationTime,proto3" json:"expiration_time,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } -func (x *ExecSandboxEvent) GetExit() *ExecSandboxExit { - if x != nil { - if x, ok := x.Payload.(*ExecSandboxEvent_Exit); ok { - return x.Exit - } - } - return nil -} - -type isExecSandboxEvent_Payload interface { - isExecSandboxEvent_Payload() -} - -type ExecSandboxEvent_Stdout struct { - Stdout *ExecSandboxStdout `protobuf:"bytes,1,opt,name=stdout,proto3,oneof"` -} - -type ExecSandboxEvent_Stderr struct { - Stderr *ExecSandboxStderr `protobuf:"bytes,2,opt,name=stderr,proto3,oneof"` -} - -type ExecSandboxEvent_Exit struct { - Exit *ExecSandboxExit `protobuf:"bytes,3,opt,name=exit,proto3,oneof"` -} - -func (*ExecSandboxEvent_Stdout) isExecSandboxEvent_Payload() {} - -func (*ExecSandboxEvent_Stderr) isExecSandboxEvent_Payload() {} - -func (*ExecSandboxEvent_Exit) isExecSandboxEvent_Payload() {} - -// Initial frame for one TCP forward stream. -type TcpForwardInit struct { - state protoimpl.MessageState `protogen:"open.v1"` - Sandbox string `protobuf:"bytes,1,opt,name=sandbox,proto3" json:"sandbox,omitempty"` - Workspace string `protobuf:"bytes,3,opt,name=workspace,proto3" json:"workspace,omitempty"` - // Optional service identifier for audit/correlation. - ServiceId string `protobuf:"bytes,4,opt,name=service_id,json=serviceId,proto3" json:"service_id,omitempty"` - // Target the gateway should request from the supervisor. - // - // Types that are valid to be assigned to Target: - // - // *TcpForwardInit_Ssh - // *TcpForwardInit_Tcp - Target isTcpForwardInit_Target `protobuf_oneof:"target"` - // Optional target-specific authorization token. SSH targets use this as the - // short-lived SSH session token issued by CreateSshSession. - AuthorizationToken string `protobuf:"bytes,7,opt,name=authorization_token,json=authorizationToken,proto3" json:"authorization_token,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *TcpForwardInit) Reset() { - *x = TcpForwardInit{} - mi := &file_openshell_proto_msgTypes[84] +func (x *CreateSshSessionResponse) Reset() { + *x = CreateSshSessionResponse{} + mi := &file_openshell_proto_msgTypes[81] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *TcpForwardInit) String() string { +func (x *CreateSshSessionResponse) String() string { return protoimpl.X.MessageStringOf(x) } -func (*TcpForwardInit) ProtoMessage() {} +func (*CreateSshSessionResponse) ProtoMessage() {} -func (x *TcpForwardInit) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[84] +func (x *CreateSshSessionResponse) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[81] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7327,107 +6918,96 @@ func (x *TcpForwardInit) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use TcpForwardInit.ProtoReflect.Descriptor instead. -func (*TcpForwardInit) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{84} +// Deprecated: Use CreateSshSessionResponse.ProtoReflect.Descriptor instead. +func (*CreateSshSessionResponse) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{81} } -func (x *TcpForwardInit) GetSandbox() string { +func (x *CreateSshSessionResponse) GetSandboxId() string { if x != nil { - return x.Sandbox + return x.SandboxId } return "" } -func (x *TcpForwardInit) GetWorkspace() string { +func (x *CreateSshSessionResponse) GetToken() string { if x != nil { - return x.Workspace + return x.Token } return "" } -func (x *TcpForwardInit) GetServiceId() string { +func (x *CreateSshSessionResponse) GetGatewayHost() string { if x != nil { - return x.ServiceId + return x.GatewayHost } return "" } -func (x *TcpForwardInit) GetTarget() isTcpForwardInit_Target { +func (x *CreateSshSessionResponse) GetGatewayPort() uint32 { if x != nil { - return x.Target + return x.GatewayPort } - return nil + return 0 } -func (x *TcpForwardInit) GetSsh() *SshRelayTarget { +func (x *CreateSshSessionResponse) GetGatewayScheme() string { if x != nil { - if x, ok := x.Target.(*TcpForwardInit_Ssh); ok { - return x.Ssh - } + return x.GatewayScheme } - return nil + return "" } -func (x *TcpForwardInit) GetTcp() *TcpRelayTarget { +func (x *CreateSshSessionResponse) GetHostKeyFingerprint() string { if x != nil { - if x, ok := x.Target.(*TcpForwardInit_Tcp); ok { - return x.Tcp - } + return x.HostKeyFingerprint } - return nil + return "" } -func (x *TcpForwardInit) GetAuthorizationToken() string { +func (x *CreateSshSessionResponse) GetExpirationTime() *timestamppb.Timestamp { if x != nil { - return x.AuthorizationToken + return x.ExpirationTime } - return "" -} - -type isTcpForwardInit_Target interface { - isTcpForwardInit_Target() -} - -type TcpForwardInit_Ssh struct { - Ssh *SshRelayTarget `protobuf:"bytes,5,opt,name=ssh,proto3,oneof"` -} - -type TcpForwardInit_Tcp struct { - Tcp *TcpRelayTarget `protobuf:"bytes,6,opt,name=tcp,proto3,oneof"` + return nil } -func (*TcpForwardInit_Ssh) isTcpForwardInit_Target() {} - -func (*TcpForwardInit_Tcp) isTcpForwardInit_Target() {} - -// A single frame on the CLI-to-gateway TCP forward stream. -type TcpForwardFrame struct { +// Request to expose an HTTP service running inside a sandbox. +type ExposeServiceRequest struct { state protoimpl.MessageState `protogen:"open.v1"` - // Types that are valid to be assigned to Payload: - // - // *TcpForwardFrame_Init - // *TcpForwardFrame_Data - Payload isTcpForwardFrame_Payload `protobuf_oneof:"payload"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + // Workspace scope. Only a named workspace selection is accepted. + WorkspaceScope *datamodelv1.WorkspaceSelector `protobuf:"bytes,5,opt,name=workspace_scope,json=workspaceScope,proto3" json:"workspace_scope,omitempty"` + // Service name within the sandbox. + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` + // Loopback TCP port inside the sandbox. + TargetPort uint32 `protobuf:"varint,3,opt,name=target_port,json=targetPort,proto3" json:"target_port,omitempty"` + // Whether to print/use the browser-facing service URL. + Domain bool `protobuf:"varint,4,opt,name=domain,proto3" json:"domain,omitempty"` + Sandbox string `protobuf:"bytes,1,opt,name=sandbox,proto3" json:"sandbox,omitempty"` + // Optional nonzero UUID for durable at-most-once admission. Successful results + // can be replayed for 24 hours; see the API errors and retries reference. + RequestId string `protobuf:"bytes,6,opt,name=request_id,json=requestId,proto3" json:"request_id,omitempty"` + // Application authorization behavior. Omission resolves to STRIP. + AuthorizationMode ServiceAuthorizationMode `protobuf:"varint,7,opt,name=authorization_mode,json=authorizationMode,proto3,enum=openshell.v1.ServiceAuthorizationMode" json:"authorization_mode,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } -func (x *TcpForwardFrame) Reset() { - *x = TcpForwardFrame{} - mi := &file_openshell_proto_msgTypes[85] +func (x *ExposeServiceRequest) Reset() { + *x = ExposeServiceRequest{} + mi := &file_openshell_proto_msgTypes[82] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *TcpForwardFrame) String() string { +func (x *ExposeServiceRequest) String() string { return protoimpl.X.MessageStringOf(x) } -func (*TcpForwardFrame) ProtoMessage() {} +func (*ExposeServiceRequest) ProtoMessage() {} -func (x *TcpForwardFrame) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[85] +func (x *ExposeServiceRequest) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[82] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7438,80 +7018,87 @@ func (x *TcpForwardFrame) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use TcpForwardFrame.ProtoReflect.Descriptor instead. -func (*TcpForwardFrame) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{85} +// Deprecated: Use ExposeServiceRequest.ProtoReflect.Descriptor instead. +func (*ExposeServiceRequest) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{82} } -func (x *TcpForwardFrame) GetPayload() isTcpForwardFrame_Payload { +func (x *ExposeServiceRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { if x != nil { - return x.Payload + return x.WorkspaceScope } return nil } -func (x *TcpForwardFrame) GetInit() *TcpForwardInit { +func (x *ExposeServiceRequest) GetName() string { if x != nil { - if x, ok := x.Payload.(*TcpForwardFrame_Init); ok { - return x.Init - } + return x.Name } - return nil + return "" } -func (x *TcpForwardFrame) GetData() []byte { +func (x *ExposeServiceRequest) GetTargetPort() uint32 { if x != nil { - if x, ok := x.Payload.(*TcpForwardFrame_Data); ok { - return x.Data - } + return x.TargetPort } - return nil + return 0 } -type isTcpForwardFrame_Payload interface { - isTcpForwardFrame_Payload() +func (x *ExposeServiceRequest) GetDomain() bool { + if x != nil { + return x.Domain + } + return false } -type TcpForwardFrame_Init struct { - Init *TcpForwardInit `protobuf:"bytes,1,opt,name=init,proto3,oneof"` +func (x *ExposeServiceRequest) GetSandbox() string { + if x != nil { + return x.Sandbox + } + return "" } -type TcpForwardFrame_Data struct { - Data []byte `protobuf:"bytes,2,opt,name=data,proto3,oneof"` +func (x *ExposeServiceRequest) GetRequestId() string { + if x != nil { + return x.RequestId + } + return "" } -func (*TcpForwardFrame_Init) isTcpForwardFrame_Payload() {} - -func (*TcpForwardFrame_Data) isTcpForwardFrame_Payload() {} +func (x *ExposeServiceRequest) GetAuthorizationMode() ServiceAuthorizationMode { + if x != nil { + return x.AuthorizationMode + } + return ServiceAuthorizationMode_SERVICE_AUTHORIZATION_MODE_UNSPECIFIED +} -// Client-to-server message for interactive exec. -type ExecSandboxInput struct { +// Request to fetch an exposed sandbox service endpoint. +type GetServiceRequest struct { state protoimpl.MessageState `protogen:"open.v1"` - // Types that are valid to be assigned to Payload: - // - // *ExecSandboxInput_Start - // *ExecSandboxInput_Stdin - // *ExecSandboxInput_Resize - Payload isExecSandboxInput_Payload `protobuf_oneof:"payload"` + // Workspace scope. Only a named workspace selection is accepted. + WorkspaceScope *datamodelv1.WorkspaceSelector `protobuf:"bytes,3,opt,name=workspace_scope,json=workspaceScope,proto3" json:"workspace_scope,omitempty"` + // Service name within the sandbox. Empty selects the unnamed endpoint. + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` + Sandbox string `protobuf:"bytes,1,opt,name=sandbox,proto3" json:"sandbox,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } -func (x *ExecSandboxInput) Reset() { - *x = ExecSandboxInput{} - mi := &file_openshell_proto_msgTypes[86] +func (x *GetServiceRequest) Reset() { + *x = GetServiceRequest{} + mi := &file_openshell_proto_msgTypes[83] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *ExecSandboxInput) String() string { +func (x *GetServiceRequest) String() string { return protoimpl.X.MessageStringOf(x) } -func (*ExecSandboxInput) ProtoMessage() {} +func (*GetServiceRequest) ProtoMessage() {} -func (x *ExecSandboxInput) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[86] +func (x *GetServiceRequest) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[83] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7522,94 +7109,132 @@ func (x *ExecSandboxInput) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use ExecSandboxInput.ProtoReflect.Descriptor instead. -func (*ExecSandboxInput) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{86} +// Deprecated: Use GetServiceRequest.ProtoReflect.Descriptor instead. +func (*GetServiceRequest) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{83} } -func (x *ExecSandboxInput) GetPayload() isExecSandboxInput_Payload { +func (x *GetServiceRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { if x != nil { - return x.Payload + return x.WorkspaceScope } return nil } -func (x *ExecSandboxInput) GetStart() *ExecSandboxRequest { +func (x *GetServiceRequest) GetName() string { if x != nil { - if x, ok := x.Payload.(*ExecSandboxInput_Start); ok { - return x.Start - } + return x.Name } - return nil + return "" } -func (x *ExecSandboxInput) GetStdin() []byte { +func (x *GetServiceRequest) GetSandbox() string { if x != nil { - if x, ok := x.Payload.(*ExecSandboxInput_Stdin); ok { - return x.Stdin - } + return x.Sandbox } - return nil + return "" } -func (x *ExecSandboxInput) GetResize() *ExecSandboxWindowResize { +// Request to list exposed sandbox service endpoints. +type ListServicesRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Workspace scope. Named and all-workspaces selections are accepted. + WorkspaceScope *datamodelv1.WorkspaceSelector `protobuf:"bytes,4,opt,name=workspace_scope,json=workspaceScope,proto3" json:"workspace_scope,omitempty"` + // The maximum number of services to return. Zero uses 100. Values above + // 1000 are coerced to 1000; negative values are invalid. + PageSize int32 `protobuf:"varint,2,opt,name=page_size,json=pageSize,proto3" json:"page_size,omitempty"` + // Token from a previous ListServices response. All other request parameters + // except page_size must match the request that produced it. + PageToken string `protobuf:"bytes,3,opt,name=page_token,json=pageToken,proto3" json:"page_token,omitempty"` + // Optional sandbox name. Empty lists endpoints for all sandboxes. + Sandbox string `protobuf:"bytes,1,opt,name=sandbox,proto3" json:"sandbox,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListServicesRequest) Reset() { + *x = ListServicesRequest{} + mi := &file_openshell_proto_msgTypes[84] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListServicesRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListServicesRequest) ProtoMessage() {} + +func (x *ListServicesRequest) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[84] if x != nil { - if x, ok := x.Payload.(*ExecSandboxInput_Resize); ok { - return x.Resize + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) } + return ms } - return nil + return mi.MessageOf(x) } -type isExecSandboxInput_Payload interface { - isExecSandboxInput_Payload() +// Deprecated: Use ListServicesRequest.ProtoReflect.Descriptor instead. +func (*ListServicesRequest) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{84} } -type ExecSandboxInput_Start struct { - // First message: exec request metadata. - Start *ExecSandboxRequest `protobuf:"bytes,1,opt,name=start,proto3,oneof"` +func (x *ListServicesRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { + if x != nil { + return x.WorkspaceScope + } + return nil } -type ExecSandboxInput_Stdin struct { - // Subsequent messages: raw stdin bytes. - Stdin []byte `protobuf:"bytes,2,opt,name=stdin,proto3,oneof"` +func (x *ListServicesRequest) GetPageSize() int32 { + if x != nil { + return x.PageSize + } + return 0 } -type ExecSandboxInput_Resize struct { - // Terminal window size change. - Resize *ExecSandboxWindowResize `protobuf:"bytes,3,opt,name=resize,proto3,oneof"` +func (x *ListServicesRequest) GetPageToken() string { + if x != nil { + return x.PageToken + } + return "" } -func (*ExecSandboxInput_Start) isExecSandboxInput_Payload() {} - -func (*ExecSandboxInput_Stdin) isExecSandboxInput_Payload() {} - -func (*ExecSandboxInput_Resize) isExecSandboxInput_Payload() {} +func (x *ListServicesRequest) GetSandbox() string { + if x != nil { + return x.Sandbox + } + return "" +} -// Terminal window resize event for interactive exec. -type ExecSandboxWindowResize struct { - state protoimpl.MessageState `protogen:"open.v1"` - Cols uint32 `protobuf:"varint,1,opt,name=cols,proto3" json:"cols,omitempty"` - Rows uint32 `protobuf:"varint,2,opt,name=rows,proto3" json:"rows,omitempty"` +// Response containing exposed sandbox service endpoints. +type ListServicesResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Services []*ServiceEndpointResponse `protobuf:"bytes,1,rep,name=services,proto3" json:"services,omitempty"` + // Token for the next page. Empty when there are no subsequent pages. + NextPageToken string `protobuf:"bytes,2,opt,name=next_page_token,json=nextPageToken,proto3" json:"next_page_token,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } -func (x *ExecSandboxWindowResize) Reset() { - *x = ExecSandboxWindowResize{} - mi := &file_openshell_proto_msgTypes[87] +func (x *ListServicesResponse) Reset() { + *x = ListServicesResponse{} + mi := &file_openshell_proto_msgTypes[85] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *ExecSandboxWindowResize) String() string { +func (x *ListServicesResponse) String() string { return protoimpl.X.MessageStringOf(x) } -func (*ExecSandboxWindowResize) ProtoMessage() {} +func (*ListServicesResponse) ProtoMessage() {} -func (x *ExecSandboxWindowResize) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[87] +func (x *ListServicesResponse) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[85] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7620,57 +7245,56 @@ func (x *ExecSandboxWindowResize) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use ExecSandboxWindowResize.ProtoReflect.Descriptor instead. -func (*ExecSandboxWindowResize) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{87} +// Deprecated: Use ListServicesResponse.ProtoReflect.Descriptor instead. +func (*ListServicesResponse) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{85} } -func (x *ExecSandboxWindowResize) GetCols() uint32 { +func (x *ListServicesResponse) GetServices() []*ServiceEndpointResponse { if x != nil { - return x.Cols + return x.Services } - return 0 + return nil } -func (x *ExecSandboxWindowResize) GetRows() uint32 { +func (x *ListServicesResponse) GetNextPageToken() string { if x != nil { - return x.Rows + return x.NextPageToken } - return 0 + return "" } -// SSH session record stored in persistence. -type SshSession struct { +// Request to delete an exposed sandbox service endpoint. +type DeleteServiceRequest struct { state protoimpl.MessageState `protogen:"open.v1"` - // Kubernetes-style metadata (id, name, labels, timestamps, resource version). - Metadata *datamodelv1.ObjectMeta `protobuf:"bytes,1,opt,name=metadata,proto3" json:"metadata,omitempty"` - // Sandbox id. - SandboxId string `protobuf:"bytes,2,opt,name=sandbox_id,json=sandboxId,proto3" json:"sandbox_id,omitempty"` - // Session token. - Token string `protobuf:"bytes,3,opt,name=token,proto3" json:"token,omitempty"` - // Absolute expiry. Absence means no expiry. - ExpirationTime *timestamppb.Timestamp `protobuf:"bytes,104,opt,name=expiration_time,json=expirationTime,proto3" json:"expiration_time,omitempty"` - // Revoked flag. - Revoked bool `protobuf:"varint,5,opt,name=revoked,proto3" json:"revoked,omitempty"` + // Workspace scope. Only a named workspace selection is accepted. + WorkspaceScope *datamodelv1.WorkspaceSelector `protobuf:"bytes,3,opt,name=workspace_scope,json=workspaceScope,proto3" json:"workspace_scope,omitempty"` + // Service name within the sandbox. Empty selects the unnamed endpoint. + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` + Sandbox string `protobuf:"bytes,1,opt,name=sandbox,proto3" json:"sandbox,omitempty"` + AllowMissing bool `protobuf:"varint,4,opt,name=allow_missing,json=allowMissing,proto3" json:"allow_missing,omitempty"` + // Optional nonzero UUID for durable at-most-once admission. Successful results + // can be replayed for 24 hours; see the API errors and retries reference. + RequestId string `protobuf:"bytes,5,opt,name=request_id,json=requestId,proto3" json:"request_id,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } -func (x *SshSession) Reset() { - *x = SshSession{} - mi := &file_openshell_proto_msgTypes[88] +func (x *DeleteServiceRequest) Reset() { + *x = DeleteServiceRequest{} + mi := &file_openshell_proto_msgTypes[86] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *SshSession) String() string { +func (x *DeleteServiceRequest) String() string { return protoimpl.X.MessageStringOf(x) } -func (*SshSession) ProtoMessage() {} +func (*DeleteServiceRequest) ProtoMessage() {} -func (x *SshSession) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[88] +func (x *DeleteServiceRequest) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[86] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7681,110 +7305,69 @@ func (x *SshSession) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use SshSession.ProtoReflect.Descriptor instead. -func (*SshSession) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{88} +// Deprecated: Use DeleteServiceRequest.ProtoReflect.Descriptor instead. +func (*DeleteServiceRequest) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{86} } -func (x *SshSession) GetMetadata() *datamodelv1.ObjectMeta { +func (x *DeleteServiceRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { if x != nil { - return x.Metadata + return x.WorkspaceScope } return nil } -func (x *SshSession) GetSandboxId() string { +func (x *DeleteServiceRequest) GetName() string { if x != nil { - return x.SandboxId + return x.Name } return "" } -func (x *SshSession) GetToken() string { +func (x *DeleteServiceRequest) GetSandbox() string { if x != nil { - return x.Token + return x.Sandbox } return "" } -func (x *SshSession) GetExpirationTime() *timestamppb.Timestamp { +func (x *DeleteServiceRequest) GetAllowMissing() bool { if x != nil { - return x.ExpirationTime + return x.AllowMissing } - return nil + return false } -func (x *SshSession) GetRevoked() bool { +func (x *DeleteServiceRequest) GetRequestId() string { if x != nil { - return x.Revoked + return x.RequestId } - return false + return "" } -// Watch sandbox request. -type WatchSandboxRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - // Workspace scope. Only a named workspace selection is accepted. - WorkspaceScope *datamodelv1.WorkspaceSelector `protobuf:"bytes,11,opt,name=workspace_scope,json=workspaceScope,proto3" json:"workspace_scope,omitempty"` - // Canonical sandbox name. - Sandbox string `protobuf:"bytes,1,opt,name=sandbox,proto3" json:"sandbox,omitempty"` - // Stream sandbox status snapshots. - FollowStatus bool `protobuf:"varint,2,opt,name=follow_status,json=followStatus,proto3" json:"follow_status,omitempty"` - // Stream openshell-server process logs correlated to this sandbox. - FollowLogs bool `protobuf:"varint,3,opt,name=follow_logs,json=followLogs,proto3" json:"follow_logs,omitempty"` - // Stream platform events correlated to this sandbox. - FollowEvents bool `protobuf:"varint,4,opt,name=follow_events,json=followEvents,proto3" json:"follow_events,omitempty"` - // Replay the last N log lines (best-effort) before following. - LogTailLines uint32 `protobuf:"varint,5,opt,name=log_tail_lines,json=logTailLines,proto3" json:"log_tail_lines,omitempty"` - // Replay the last N platform events (best-effort) before following. - EventTail uint32 `protobuf:"varint,6,opt,name=event_tail,json=eventTail,proto3" json:"event_tail,omitempty"` - // Stop streaming once the sandbox reaches READY or a terminal result phase - // (COMPLETED, STOPPED, or ERROR). - StopOnTerminal bool `protobuf:"varint,7,opt,name=stop_on_terminal,json=stopOnTerminal,proto3" json:"stop_on_terminal,omitempty"` - // Only include log lines at or after this time. Absence means no time filter. - // Applies to both tail replay and live streaming. - SinceTime *timestamppb.Timestamp `protobuf:"bytes,108,opt,name=since_time,json=sinceTime,proto3" json:"since_time,omitempty"` - // Filter by log source (e.g. "gateway", "sandbox"). Empty means all sources. - LogSources []string `protobuf:"bytes,9,rep,name=log_sources,json=logSources,proto3" json:"log_sources,omitempty"` - // Minimum log level to include (e.g. "INFO", "WARN", "ERROR"). Empty means all levels. - LogMinLevel string `protobuf:"bytes,10,opt,name=log_min_level,json=logMinLevel,proto3" json:"log_min_level,omitempty"` - // Resume streaming after this cursor. Empty means no cursor resume: the - // server falls back to tail-limited replay controlled by log_tail_lines and - // event_tail. Otherwise set it to the highest `SandboxStreamEvent.cursor` - // already processed; the server replays only log and platform events after - // it, merged in cursor order, before resuming live delivery. If the requested - // cursor has already been trimmed from the server's buffer, the resume is - // unrecoverable and the stream terminates with OUT_OF_RANGE (see - // SandboxStreamWarning for the recoverable case). - // - // A cursor is bound to the cursor space that issued it. A gateway restart, - // teardown of the sandbox's buffers, or a reconnect to a different gateway - // replica starts a new space, and cursors from the previous one are rejected - // with OUT_OF_RANGE rather than silently suppressing live events beneath - // them. OUT_OF_RANGE is terminal for that cursor: restart the watch with an - // empty resume_after_cursor, because retrying the same token fails - // identically. A cursor this server could not have issued is rejected with - // INVALID_ARGUMENT. - ResumeAfterCursor string `protobuf:"bytes,12,opt,name=resume_after_cursor,json=resumeAfterCursor,proto3" json:"resume_after_cursor,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache +// Response for deleting an exposed sandbox service endpoint. +type DeleteServiceResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Outcome DeletionOutcome `protobuf:"varint,2,opt,name=outcome,proto3,enum=openshell.v1.DeletionOutcome" json:"outcome,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } -func (x *WatchSandboxRequest) Reset() { - *x = WatchSandboxRequest{} - mi := &file_openshell_proto_msgTypes[89] +func (x *DeleteServiceResponse) Reset() { + *x = DeleteServiceResponse{} + mi := &file_openshell_proto_msgTypes[87] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *WatchSandboxRequest) String() string { +func (x *DeleteServiceResponse) String() string { return protoimpl.X.MessageStringOf(x) } -func (*WatchSandboxRequest) ProtoMessage() {} +func (*DeleteServiceResponse) ProtoMessage() {} -func (x *WatchSandboxRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[89] +func (x *DeleteServiceResponse) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[87] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7795,137 +7378,142 @@ func (x *WatchSandboxRequest) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use WatchSandboxRequest.ProtoReflect.Descriptor instead. -func (*WatchSandboxRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{89} +// Deprecated: Use DeleteServiceResponse.ProtoReflect.Descriptor instead. +func (*DeleteServiceResponse) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{87} } -func (x *WatchSandboxRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { +func (x *DeleteServiceResponse) GetOutcome() DeletionOutcome { if x != nil { - return x.WorkspaceScope + return x.Outcome } - return nil + return DeletionOutcome_DELETION_OUTCOME_UNSPECIFIED } -func (x *WatchSandboxRequest) GetSandbox() string { - if x != nil { - return x.Sandbox - } - return "" +// Persisted sandbox service endpoint. +type ServiceEndpoint struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Kubernetes-style metadata. + Metadata *datamodelv1.ObjectMeta `protobuf:"bytes,1,opt,name=metadata,proto3" json:"metadata,omitempty"` + // Sandbox object ID. + SandboxId string `protobuf:"bytes,2,opt,name=sandbox_id,json=sandboxId,proto3" json:"sandbox_id,omitempty"` + // Sandbox name. + Sandbox string `protobuf:"bytes,3,opt,name=sandbox,proto3" json:"sandbox,omitempty"` + // Service name within the sandbox. + Name string `protobuf:"bytes,4,opt,name=name,proto3" json:"name,omitempty"` + // Loopback TCP port inside the sandbox. + TargetPort uint32 `protobuf:"varint,5,opt,name=target_port,json=targetPort,proto3" json:"target_port,omitempty"` + // Whether browser-facing service routing is enabled for this endpoint. + Domain bool `protobuf:"varint,6,opt,name=domain,proto3" json:"domain,omitempty"` + // Effective application authorization behavior for ingress requests. + AuthorizationMode ServiceAuthorizationMode `protobuf:"varint,7,opt,name=authorization_mode,json=authorizationMode,proto3,enum=openshell.v1.ServiceAuthorizationMode" json:"authorization_mode,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } -func (x *WatchSandboxRequest) GetFollowStatus() bool { - if x != nil { - return x.FollowStatus - } - return false +func (x *ServiceEndpoint) Reset() { + *x = ServiceEndpoint{} + mi := &file_openshell_proto_msgTypes[88] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } -func (x *WatchSandboxRequest) GetFollowLogs() bool { - if x != nil { - return x.FollowLogs - } - return false +func (x *ServiceEndpoint) String() string { + return protoimpl.X.MessageStringOf(x) } -func (x *WatchSandboxRequest) GetFollowEvents() bool { +func (*ServiceEndpoint) ProtoMessage() {} + +func (x *ServiceEndpoint) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[88] if x != nil { - return x.FollowEvents + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms } - return false + return mi.MessageOf(x) } -func (x *WatchSandboxRequest) GetLogTailLines() uint32 { +// Deprecated: Use ServiceEndpoint.ProtoReflect.Descriptor instead. +func (*ServiceEndpoint) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{88} +} + +func (x *ServiceEndpoint) GetMetadata() *datamodelv1.ObjectMeta { if x != nil { - return x.LogTailLines + return x.Metadata } - return 0 + return nil } -func (x *WatchSandboxRequest) GetEventTail() uint32 { +func (x *ServiceEndpoint) GetSandboxId() string { if x != nil { - return x.EventTail + return x.SandboxId } - return 0 + return "" } -func (x *WatchSandboxRequest) GetStopOnTerminal() bool { +func (x *ServiceEndpoint) GetSandbox() string { if x != nil { - return x.StopOnTerminal + return x.Sandbox } - return false + return "" } -func (x *WatchSandboxRequest) GetSinceTime() *timestamppb.Timestamp { +func (x *ServiceEndpoint) GetName() string { if x != nil { - return x.SinceTime + return x.Name } - return nil + return "" } -func (x *WatchSandboxRequest) GetLogSources() []string { +func (x *ServiceEndpoint) GetTargetPort() uint32 { if x != nil { - return x.LogSources + return x.TargetPort } - return nil + return 0 } -func (x *WatchSandboxRequest) GetLogMinLevel() string { +func (x *ServiceEndpoint) GetDomain() bool { if x != nil { - return x.LogMinLevel + return x.Domain } - return "" + return false } -func (x *WatchSandboxRequest) GetResumeAfterCursor() string { +func (x *ServiceEndpoint) GetAuthorizationMode() ServiceAuthorizationMode { if x != nil { - return x.ResumeAfterCursor + return x.AuthorizationMode } - return "" + return ServiceAuthorizationMode_SERVICE_AUTHORIZATION_MODE_UNSPECIFIED } -// One event in a sandbox watch stream. -type SandboxStreamEvent struct { - state protoimpl.MessageState `protogen:"open.v1"` - // Types that are valid to be assigned to Payload: - // - // *SandboxStreamEvent_Sandbox - // *SandboxStreamEvent_Log - // *SandboxStreamEvent_Event - // *SandboxStreamEvent_Warning - // *SandboxStreamEvent_DraftPolicyUpdate - Payload isSandboxStreamEvent_Payload `protobuf_oneof:"payload"` - // Opaque position in this sandbox's cursor space, shared across the resumable - // log and platform event sources. Empty for non-resumable events (status - // snapshots, warnings). - // - // Do not parse this token; its encoding is not part of the contract. The only - // supported operation is comparing two non-empty cursors observed on the same - // stream and keeping the greater one, then passing it as - // WatchSandboxRequest.resume_after_cursor to resume without loss or - // duplication. That comparison is a plain byte-wise string comparison. It is - // well defined only within one stream: a stream never spans two cursor - // spaces, because a reset ends it. - Cursor string `protobuf:"bytes,6,opt,name=cursor,proto3" json:"cursor,omitempty"` +// Response containing a service endpoint and, when available, its local URL. +type ServiceEndpointResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Endpoint *ServiceEndpoint `protobuf:"bytes,1,opt,name=endpoint,proto3" json:"endpoint,omitempty"` + Url string `protobuf:"bytes,2,opt,name=url,proto3" json:"url,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } -func (x *SandboxStreamEvent) Reset() { - *x = SandboxStreamEvent{} - mi := &file_openshell_proto_msgTypes[90] +func (x *ServiceEndpointResponse) Reset() { + *x = ServiceEndpointResponse{} + mi := &file_openshell_proto_msgTypes[89] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *SandboxStreamEvent) String() string { +func (x *ServiceEndpointResponse) String() string { return protoimpl.X.MessageStringOf(x) } -func (*SandboxStreamEvent) ProtoMessage() {} +func (*ServiceEndpointResponse) ProtoMessage() {} -func (x *SandboxStreamEvent) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[90] +func (x *ServiceEndpointResponse) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[89] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7936,143 +7524,179 @@ func (x *SandboxStreamEvent) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use SandboxStreamEvent.ProtoReflect.Descriptor instead. -func (*SandboxStreamEvent) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{90} +// Deprecated: Use ServiceEndpointResponse.ProtoReflect.Descriptor instead. +func (*ServiceEndpointResponse) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{89} } -func (x *SandboxStreamEvent) GetPayload() isSandboxStreamEvent_Payload { +func (x *ServiceEndpointResponse) GetEndpoint() *ServiceEndpoint { if x != nil { - return x.Payload + return x.Endpoint } return nil } -func (x *SandboxStreamEvent) GetSandbox() *Sandbox { +func (x *ServiceEndpointResponse) GetUrl() string { if x != nil { - if x, ok := x.Payload.(*SandboxStreamEvent_Sandbox); ok { - return x.Sandbox - } + return x.Url } - return nil + return "" } -func (x *SandboxStreamEvent) GetLog() *SandboxLogLine { - if x != nil { - if x, ok := x.Payload.(*SandboxStreamEvent_Log); ok { - return x.Log - } - } - return nil +// Revoke SSH session request. +type RevokeSshSessionRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Session token to revoke. + Token string `protobuf:"bytes,1,opt,name=token,proto3" json:"token,omitempty"` + // A missing token is NOT_FOUND unless this is true. Revoking an existing, + // already-revoked session succeeds with COMPLETED. + AllowMissing bool `protobuf:"varint,2,opt,name=allow_missing,json=allowMissing,proto3" json:"allow_missing,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } -func (x *SandboxStreamEvent) GetEvent() *PlatformEvent { - if x != nil { - if x, ok := x.Payload.(*SandboxStreamEvent_Event); ok { - return x.Event - } - } - return nil +func (x *RevokeSshSessionRequest) Reset() { + *x = RevokeSshSessionRequest{} + mi := &file_openshell_proto_msgTypes[90] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } -func (x *SandboxStreamEvent) GetWarning() *SandboxStreamWarning { - if x != nil { - if x, ok := x.Payload.(*SandboxStreamEvent_Warning); ok { - return x.Warning - } - } - return nil +func (x *RevokeSshSessionRequest) String() string { + return protoimpl.X.MessageStringOf(x) } -func (x *SandboxStreamEvent) GetDraftPolicyUpdate() *DraftPolicyUpdate { +func (*RevokeSshSessionRequest) ProtoMessage() {} + +func (x *RevokeSshSessionRequest) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[90] if x != nil { - if x, ok := x.Payload.(*SandboxStreamEvent_DraftPolicyUpdate); ok { - return x.DraftPolicyUpdate + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) } + return ms } - return nil + return mi.MessageOf(x) } -func (x *SandboxStreamEvent) GetCursor() string { +// Deprecated: Use RevokeSshSessionRequest.ProtoReflect.Descriptor instead. +func (*RevokeSshSessionRequest) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{90} +} + +func (x *RevokeSshSessionRequest) GetToken() string { if x != nil { - return x.Cursor + return x.Token } return "" } -type isSandboxStreamEvent_Payload interface { - isSandboxStreamEvent_Payload() +func (x *RevokeSshSessionRequest) GetAllowMissing() bool { + if x != nil { + return x.AllowMissing + } + return false } -type SandboxStreamEvent_Sandbox struct { - // Latest sandbox snapshot. - Sandbox *Sandbox `protobuf:"bytes,1,opt,name=sandbox,proto3,oneof"` +// Revoke SSH session response. +type RevokeSshSessionResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Outcome DeletionOutcome `protobuf:"varint,2,opt,name=outcome,proto3,enum=openshell.v1.DeletionOutcome" json:"outcome,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } -type SandboxStreamEvent_Log struct { - // One server log line/event. - Log *SandboxLogLine `protobuf:"bytes,2,opt,name=log,proto3,oneof"` +func (x *RevokeSshSessionResponse) Reset() { + *x = RevokeSshSessionResponse{} + mi := &file_openshell_proto_msgTypes[91] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } -type SandboxStreamEvent_Event struct { - // One platform event. - Event *PlatformEvent `protobuf:"bytes,3,opt,name=event,proto3,oneof"` +func (x *RevokeSshSessionResponse) String() string { + return protoimpl.X.MessageStringOf(x) } -type SandboxStreamEvent_Warning struct { - // Recoverable warning from the server, e.g. messages dropped because a - // broadcast receiver lagged. The stream continues after this warning; the - // client can detect the gap from the warning itself. - Warning *SandboxStreamWarning `protobuf:"bytes,4,opt,name=warning,proto3,oneof"` -} +func (*RevokeSshSessionResponse) ProtoMessage() {} -type SandboxStreamEvent_DraftPolicyUpdate struct { - // Draft policy update notification. - DraftPolicyUpdate *DraftPolicyUpdate `protobuf:"bytes,5,opt,name=draft_policy_update,json=draftPolicyUpdate,proto3,oneof"` +func (x *RevokeSshSessionResponse) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[91] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) } -func (*SandboxStreamEvent_Sandbox) isSandboxStreamEvent_Payload() {} - -func (*SandboxStreamEvent_Log) isSandboxStreamEvent_Payload() {} - -func (*SandboxStreamEvent_Event) isSandboxStreamEvent_Payload() {} - -func (*SandboxStreamEvent_Warning) isSandboxStreamEvent_Payload() {} +// Deprecated: Use RevokeSshSessionResponse.ProtoReflect.Descriptor instead. +func (*RevokeSshSessionResponse) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{91} +} -func (*SandboxStreamEvent_DraftPolicyUpdate) isSandboxStreamEvent_Payload() {} +func (x *RevokeSshSessionResponse) GetOutcome() DeletionOutcome { + if x != nil { + return x.Outcome + } + return DeletionOutcome_DELETION_OUTCOME_UNSPECIFIED +} -// Log line correlated to a sandbox. -type SandboxLogLine struct { - state protoimpl.MessageState `protogen:"open.v1"` - SandboxId string `protobuf:"bytes,1,opt,name=sandbox_id,json=sandboxId,proto3" json:"sandbox_id,omitempty"` - EventTime *timestamppb.Timestamp `protobuf:"bytes,102,opt,name=event_time,json=eventTime,proto3" json:"event_time,omitempty"` - Level string `protobuf:"bytes,3,opt,name=level,proto3" json:"level,omitempty"` - Target string `protobuf:"bytes,4,opt,name=target,proto3" json:"target,omitempty"` - Message string `protobuf:"bytes,5,opt,name=message,proto3" json:"message,omitempty"` - // Log source: "gateway" (server-side) or "sandbox" (supervisor). - // Empty is treated as "gateway" for backward compatibility. - Source string `protobuf:"bytes,6,opt,name=source,proto3" json:"source,omitempty"` - // Structured key-value fields from the tracing event (e.g. dst_host, action). - Fields map[string]string `protobuf:"bytes,7,rep,name=fields,proto3" json:"fields,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` +// Execute command request. +type ExecSandboxRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Workspace scope. Only a named workspace selection is accepted. + WorkspaceScope *datamodelv1.WorkspaceSelector `protobuf:"bytes,12,opt,name=workspace_scope,json=workspaceScope,proto3" json:"workspace_scope,omitempty"` + // Canonical sandbox name. + Sandbox string `protobuf:"bytes,1,opt,name=sandbox,proto3" json:"sandbox,omitempty"` + // Command and arguments. + Command []string `protobuf:"bytes,2,rep,name=command,proto3" json:"command,omitempty"` + // Optional working directory. + Workdir string `protobuf:"bytes,3,opt,name=workdir,proto3" json:"workdir,omitempty"` + // Optional environment overrides. + Environment map[string]string `protobuf:"bytes,4,rep,name=environment,proto3" json:"environment,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + // Optional execution timeout. Absence means no timeout. + ExecutionTimeout *durationpb.Duration `protobuf:"bytes,105,opt,name=execution_timeout,json=executionTimeout,proto3" json:"execution_timeout,omitempty"` + // Optional stdin payload passed to the command. + Stdin []byte `protobuf:"bytes,6,opt,name=stdin,proto3" json:"stdin,omitempty"` + // Request a pseudo-terminal for the remote command. + Tty bool `protobuf:"varint,7,opt,name=tty,proto3" json:"tty,omitempty"` + // Initial terminal columns (used when tty=true, 0 = use default). + Cols uint32 `protobuf:"varint,8,opt,name=cols,proto3" json:"cols,omitempty"` + // Initial terminal rows (used when tty=true, 0 = use default). + Rows uint32 `protobuf:"varint,9,opt,name=rows,proto3" json:"rows,omitempty"` + // Skip sourcing shell login/profile startup files before running the command. + // When false (the default), the command runs through a login shell + // (`bash -lc`) so user startup files (.bash_profile/.profile, and .bashrc if + // sourced by them) are applied. When true, the command runs without those + // files (`bash -c`), for automation that needs predictable startup behavior. + NoLoginShell bool `protobuf:"varint,10,opt,name=no_login_shell,json=noLoginShell,proto3" json:"no_login_shell,omitempty"` + // Optional nonzero UUID for durable launch admission. Also applies to the + // initial ExecSandboxInteractive start message. Duplicates never relaunch, + // reattach, or replay output/stdin. Unconfirmed executions remain fenced; + // confirmed terminal executions retain the fence for 24 hours. + RequestId string `protobuf:"bytes,11,opt,name=request_id,json=requestId,proto3" json:"request_id,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } -func (x *SandboxLogLine) Reset() { - *x = SandboxLogLine{} - mi := &file_openshell_proto_msgTypes[91] +func (x *ExecSandboxRequest) Reset() { + *x = ExecSandboxRequest{} + mi := &file_openshell_proto_msgTypes[92] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *SandboxLogLine) String() string { +func (x *ExecSandboxRequest) String() string { return protoimpl.X.MessageStringOf(x) } -func (*SandboxLogLine) ProtoMessage() {} +func (*ExecSandboxRequest) ProtoMessage() {} -func (x *SandboxLogLine) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[91] +func (x *ExecSandboxRequest) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[92] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8083,137 +7707,1713 @@ func (x *SandboxLogLine) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use SandboxLogLine.ProtoReflect.Descriptor instead. -func (*SandboxLogLine) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{91} +// Deprecated: Use ExecSandboxRequest.ProtoReflect.Descriptor instead. +func (*ExecSandboxRequest) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{92} } -func (x *SandboxLogLine) GetSandboxId() string { +func (x *ExecSandboxRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { if x != nil { - return x.SandboxId + return x.WorkspaceScope + } + return nil +} + +func (x *ExecSandboxRequest) GetSandbox() string { + if x != nil { + return x.Sandbox } return "" } -func (x *SandboxLogLine) GetEventTime() *timestamppb.Timestamp { +func (x *ExecSandboxRequest) GetCommand() []string { if x != nil { - return x.EventTime + return x.Command } return nil } -func (x *SandboxLogLine) GetLevel() string { +func (x *ExecSandboxRequest) GetWorkdir() string { if x != nil { - return x.Level + return x.Workdir } return "" } -func (x *SandboxLogLine) GetTarget() string { +func (x *ExecSandboxRequest) GetEnvironment() map[string]string { if x != nil { - return x.Target + return x.Environment } - return "" + return nil } -func (x *SandboxLogLine) GetMessage() string { +func (x *ExecSandboxRequest) GetExecutionTimeout() *durationpb.Duration { if x != nil { - return x.Message + return x.ExecutionTimeout } - return "" + return nil } -func (x *SandboxLogLine) GetSource() string { +func (x *ExecSandboxRequest) GetStdin() []byte { if x != nil { - return x.Source + return x.Stdin } - return "" + return nil } -func (x *SandboxLogLine) GetFields() map[string]string { +func (x *ExecSandboxRequest) GetTty() bool { if x != nil { - return x.Fields + return x.Tty } - return nil + return false } -// Recoverable loss notification on a watch stream. Emitted when the server -// skips ahead after a broadcast lag instead of terminating; the stream keeps -// running. Cursors are opaque, so this message is the only signal that events -// were skipped. Unrecoverable loss (a trimmed or foreign resume cursor) is -// reported as an OUT_OF_RANGE stream status, not this message. -type SandboxStreamWarning struct { +func (x *ExecSandboxRequest) GetCols() uint32 { + if x != nil { + return x.Cols + } + return 0 +} + +func (x *ExecSandboxRequest) GetRows() uint32 { + if x != nil { + return x.Rows + } + return 0 +} + +func (x *ExecSandboxRequest) GetNoLoginShell() bool { + if x != nil { + return x.NoLoginShell + } + return false +} + +func (x *ExecSandboxRequest) GetRequestId() string { + if x != nil { + return x.RequestId + } + return "" +} + +// One stdout chunk from a sandbox exec. +type ExecSandboxStdout struct { state protoimpl.MessageState `protogen:"open.v1"` - Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"` + Data []byte `protobuf:"bytes,1,opt,name=data,proto3" json:"data,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } -func (x *SandboxStreamWarning) Reset() { - *x = SandboxStreamWarning{} - mi := &file_openshell_proto_msgTypes[92] +func (x *ExecSandboxStdout) Reset() { + *x = ExecSandboxStdout{} + mi := &file_openshell_proto_msgTypes[93] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *SandboxStreamWarning) String() string { +func (x *ExecSandboxStdout) String() string { return protoimpl.X.MessageStringOf(x) } -func (*SandboxStreamWarning) ProtoMessage() {} +func (*ExecSandboxStdout) ProtoMessage() {} + +func (x *ExecSandboxStdout) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[93] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ExecSandboxStdout.ProtoReflect.Descriptor instead. +func (*ExecSandboxStdout) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{93} +} + +func (x *ExecSandboxStdout) GetData() []byte { + if x != nil { + return x.Data + } + return nil +} + +// One stderr chunk from a sandbox exec. +type ExecSandboxStderr struct { + state protoimpl.MessageState `protogen:"open.v1"` + Data []byte `protobuf:"bytes,1,opt,name=data,proto3" json:"data,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ExecSandboxStderr) Reset() { + *x = ExecSandboxStderr{} + mi := &file_openshell_proto_msgTypes[94] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ExecSandboxStderr) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ExecSandboxStderr) ProtoMessage() {} + +func (x *ExecSandboxStderr) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[94] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ExecSandboxStderr.ProtoReflect.Descriptor instead. +func (*ExecSandboxStderr) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{94} +} + +func (x *ExecSandboxStderr) GetData() []byte { + if x != nil { + return x.Data + } + return nil +} + +// Final exit status for a sandbox exec. +type ExecSandboxExit struct { + state protoimpl.MessageState `protogen:"open.v1"` + ExitCode int32 `protobuf:"varint,1,opt,name=exit_code,json=exitCode,proto3" json:"exit_code,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ExecSandboxExit) Reset() { + *x = ExecSandboxExit{} + mi := &file_openshell_proto_msgTypes[95] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ExecSandboxExit) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ExecSandboxExit) ProtoMessage() {} + +func (x *ExecSandboxExit) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[95] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ExecSandboxExit.ProtoReflect.Descriptor instead. +func (*ExecSandboxExit) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{95} +} + +func (x *ExecSandboxExit) GetExitCode() int32 { + if x != nil { + return x.ExitCode + } + return 0 +} + +// One event in a sandbox exec stream. +type ExecSandboxEvent struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Types that are valid to be assigned to Payload: + // + // *ExecSandboxEvent_Stdout + // *ExecSandboxEvent_Stderr + // *ExecSandboxEvent_Exit + Payload isExecSandboxEvent_Payload `protobuf_oneof:"payload"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ExecSandboxEvent) Reset() { + *x = ExecSandboxEvent{} + mi := &file_openshell_proto_msgTypes[96] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ExecSandboxEvent) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ExecSandboxEvent) ProtoMessage() {} + +func (x *ExecSandboxEvent) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[96] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ExecSandboxEvent.ProtoReflect.Descriptor instead. +func (*ExecSandboxEvent) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{96} +} + +func (x *ExecSandboxEvent) GetPayload() isExecSandboxEvent_Payload { + if x != nil { + return x.Payload + } + return nil +} + +func (x *ExecSandboxEvent) GetStdout() *ExecSandboxStdout { + if x != nil { + if x, ok := x.Payload.(*ExecSandboxEvent_Stdout); ok { + return x.Stdout + } + } + return nil +} + +func (x *ExecSandboxEvent) GetStderr() *ExecSandboxStderr { + if x != nil { + if x, ok := x.Payload.(*ExecSandboxEvent_Stderr); ok { + return x.Stderr + } + } + return nil +} + +func (x *ExecSandboxEvent) GetExit() *ExecSandboxExit { + if x != nil { + if x, ok := x.Payload.(*ExecSandboxEvent_Exit); ok { + return x.Exit + } + } + return nil +} + +type isExecSandboxEvent_Payload interface { + isExecSandboxEvent_Payload() +} + +type ExecSandboxEvent_Stdout struct { + Stdout *ExecSandboxStdout `protobuf:"bytes,1,opt,name=stdout,proto3,oneof"` +} + +type ExecSandboxEvent_Stderr struct { + Stderr *ExecSandboxStderr `protobuf:"bytes,2,opt,name=stderr,proto3,oneof"` +} + +type ExecSandboxEvent_Exit struct { + Exit *ExecSandboxExit `protobuf:"bytes,3,opt,name=exit,proto3,oneof"` +} + +func (*ExecSandboxEvent_Stdout) isExecSandboxEvent_Payload() {} + +func (*ExecSandboxEvent_Stderr) isExecSandboxEvent_Payload() {} + +func (*ExecSandboxEvent_Exit) isExecSandboxEvent_Payload() {} + +// Initial frame for one TCP forward stream. +type TcpForwardInit struct { + state protoimpl.MessageState `protogen:"open.v1"` + Sandbox string `protobuf:"bytes,1,opt,name=sandbox,proto3" json:"sandbox,omitempty"` + Workspace string `protobuf:"bytes,3,opt,name=workspace,proto3" json:"workspace,omitempty"` + // Optional service identifier for audit/correlation. + ServiceId string `protobuf:"bytes,4,opt,name=service_id,json=serviceId,proto3" json:"service_id,omitempty"` + // Target the gateway should request from the supervisor. + // + // Types that are valid to be assigned to Target: + // + // *TcpForwardInit_Ssh + // *TcpForwardInit_Tcp + Target isTcpForwardInit_Target `protobuf_oneof:"target"` + // Optional target-specific authorization token. SSH targets use this as the + // short-lived SSH session token issued by CreateSshSession. + AuthorizationToken string `protobuf:"bytes,7,opt,name=authorization_token,json=authorizationToken,proto3" json:"authorization_token,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TcpForwardInit) Reset() { + *x = TcpForwardInit{} + mi := &file_openshell_proto_msgTypes[97] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TcpForwardInit) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TcpForwardInit) ProtoMessage() {} + +func (x *TcpForwardInit) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[97] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TcpForwardInit.ProtoReflect.Descriptor instead. +func (*TcpForwardInit) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{97} +} + +func (x *TcpForwardInit) GetSandbox() string { + if x != nil { + return x.Sandbox + } + return "" +} + +func (x *TcpForwardInit) GetWorkspace() string { + if x != nil { + return x.Workspace + } + return "" +} + +func (x *TcpForwardInit) GetServiceId() string { + if x != nil { + return x.ServiceId + } + return "" +} + +func (x *TcpForwardInit) GetTarget() isTcpForwardInit_Target { + if x != nil { + return x.Target + } + return nil +} + +func (x *TcpForwardInit) GetSsh() *SshRelayTarget { + if x != nil { + if x, ok := x.Target.(*TcpForwardInit_Ssh); ok { + return x.Ssh + } + } + return nil +} + +func (x *TcpForwardInit) GetTcp() *TcpRelayTarget { + if x != nil { + if x, ok := x.Target.(*TcpForwardInit_Tcp); ok { + return x.Tcp + } + } + return nil +} + +func (x *TcpForwardInit) GetAuthorizationToken() string { + if x != nil { + return x.AuthorizationToken + } + return "" +} + +type isTcpForwardInit_Target interface { + isTcpForwardInit_Target() +} + +type TcpForwardInit_Ssh struct { + Ssh *SshRelayTarget `protobuf:"bytes,5,opt,name=ssh,proto3,oneof"` +} + +type TcpForwardInit_Tcp struct { + Tcp *TcpRelayTarget `protobuf:"bytes,6,opt,name=tcp,proto3,oneof"` +} + +func (*TcpForwardInit_Ssh) isTcpForwardInit_Target() {} + +func (*TcpForwardInit_Tcp) isTcpForwardInit_Target() {} + +// A single frame on the CLI-to-gateway TCP forward stream. +type TcpForwardFrame struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Types that are valid to be assigned to Payload: + // + // *TcpForwardFrame_Init + // *TcpForwardFrame_Data + Payload isTcpForwardFrame_Payload `protobuf_oneof:"payload"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TcpForwardFrame) Reset() { + *x = TcpForwardFrame{} + mi := &file_openshell_proto_msgTypes[98] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TcpForwardFrame) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TcpForwardFrame) ProtoMessage() {} + +func (x *TcpForwardFrame) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[98] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TcpForwardFrame.ProtoReflect.Descriptor instead. +func (*TcpForwardFrame) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{98} +} + +func (x *TcpForwardFrame) GetPayload() isTcpForwardFrame_Payload { + if x != nil { + return x.Payload + } + return nil +} + +func (x *TcpForwardFrame) GetInit() *TcpForwardInit { + if x != nil { + if x, ok := x.Payload.(*TcpForwardFrame_Init); ok { + return x.Init + } + } + return nil +} + +func (x *TcpForwardFrame) GetData() []byte { + if x != nil { + if x, ok := x.Payload.(*TcpForwardFrame_Data); ok { + return x.Data + } + } + return nil +} + +type isTcpForwardFrame_Payload interface { + isTcpForwardFrame_Payload() +} + +type TcpForwardFrame_Init struct { + Init *TcpForwardInit `protobuf:"bytes,1,opt,name=init,proto3,oneof"` +} + +type TcpForwardFrame_Data struct { + Data []byte `protobuf:"bytes,2,opt,name=data,proto3,oneof"` +} + +func (*TcpForwardFrame_Init) isTcpForwardFrame_Payload() {} + +func (*TcpForwardFrame_Data) isTcpForwardFrame_Payload() {} + +// Client-to-server message for interactive exec. +type ExecSandboxInput struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Types that are valid to be assigned to Payload: + // + // *ExecSandboxInput_Start + // *ExecSandboxInput_Stdin + // *ExecSandboxInput_Resize + Payload isExecSandboxInput_Payload `protobuf_oneof:"payload"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ExecSandboxInput) Reset() { + *x = ExecSandboxInput{} + mi := &file_openshell_proto_msgTypes[99] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ExecSandboxInput) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ExecSandboxInput) ProtoMessage() {} + +func (x *ExecSandboxInput) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[99] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ExecSandboxInput.ProtoReflect.Descriptor instead. +func (*ExecSandboxInput) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{99} +} + +func (x *ExecSandboxInput) GetPayload() isExecSandboxInput_Payload { + if x != nil { + return x.Payload + } + return nil +} + +func (x *ExecSandboxInput) GetStart() *ExecSandboxRequest { + if x != nil { + if x, ok := x.Payload.(*ExecSandboxInput_Start); ok { + return x.Start + } + } + return nil +} + +func (x *ExecSandboxInput) GetStdin() []byte { + if x != nil { + if x, ok := x.Payload.(*ExecSandboxInput_Stdin); ok { + return x.Stdin + } + } + return nil +} + +func (x *ExecSandboxInput) GetResize() *ExecSandboxWindowResize { + if x != nil { + if x, ok := x.Payload.(*ExecSandboxInput_Resize); ok { + return x.Resize + } + } + return nil +} + +type isExecSandboxInput_Payload interface { + isExecSandboxInput_Payload() +} + +type ExecSandboxInput_Start struct { + // First message: exec request metadata. + Start *ExecSandboxRequest `protobuf:"bytes,1,opt,name=start,proto3,oneof"` +} + +type ExecSandboxInput_Stdin struct { + // Subsequent messages: raw stdin bytes. + Stdin []byte `protobuf:"bytes,2,opt,name=stdin,proto3,oneof"` +} + +type ExecSandboxInput_Resize struct { + // Terminal window size change. + Resize *ExecSandboxWindowResize `protobuf:"bytes,3,opt,name=resize,proto3,oneof"` +} + +func (*ExecSandboxInput_Start) isExecSandboxInput_Payload() {} + +func (*ExecSandboxInput_Stdin) isExecSandboxInput_Payload() {} + +func (*ExecSandboxInput_Resize) isExecSandboxInput_Payload() {} + +// Terminal window resize event for interactive exec. +type ExecSandboxWindowResize struct { + state protoimpl.MessageState `protogen:"open.v1"` + Cols uint32 `protobuf:"varint,1,opt,name=cols,proto3" json:"cols,omitempty"` + Rows uint32 `protobuf:"varint,2,opt,name=rows,proto3" json:"rows,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ExecSandboxWindowResize) Reset() { + *x = ExecSandboxWindowResize{} + mi := &file_openshell_proto_msgTypes[100] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ExecSandboxWindowResize) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ExecSandboxWindowResize) ProtoMessage() {} + +func (x *ExecSandboxWindowResize) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[100] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ExecSandboxWindowResize.ProtoReflect.Descriptor instead. +func (*ExecSandboxWindowResize) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{100} +} + +func (x *ExecSandboxWindowResize) GetCols() uint32 { + if x != nil { + return x.Cols + } + return 0 +} + +func (x *ExecSandboxWindowResize) GetRows() uint32 { + if x != nil { + return x.Rows + } + return 0 +} + +// SSH session record stored in persistence. +type SshSession struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Kubernetes-style metadata (id, name, labels, timestamps, resource version). + Metadata *datamodelv1.ObjectMeta `protobuf:"bytes,1,opt,name=metadata,proto3" json:"metadata,omitempty"` + // Sandbox id. + SandboxId string `protobuf:"bytes,2,opt,name=sandbox_id,json=sandboxId,proto3" json:"sandbox_id,omitempty"` + // Session token. + Token string `protobuf:"bytes,3,opt,name=token,proto3" json:"token,omitempty"` + // Absolute expiry. Absence means no expiry. + ExpirationTime *timestamppb.Timestamp `protobuf:"bytes,104,opt,name=expiration_time,json=expirationTime,proto3" json:"expiration_time,omitempty"` + // Revoked flag. + Revoked bool `protobuf:"varint,5,opt,name=revoked,proto3" json:"revoked,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SshSession) Reset() { + *x = SshSession{} + mi := &file_openshell_proto_msgTypes[101] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SshSession) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SshSession) ProtoMessage() {} + +func (x *SshSession) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[101] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SshSession.ProtoReflect.Descriptor instead. +func (*SshSession) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{101} +} + +func (x *SshSession) GetMetadata() *datamodelv1.ObjectMeta { + if x != nil { + return x.Metadata + } + return nil +} + +func (x *SshSession) GetSandboxId() string { + if x != nil { + return x.SandboxId + } + return "" +} + +func (x *SshSession) GetToken() string { + if x != nil { + return x.Token + } + return "" +} + +func (x *SshSession) GetExpirationTime() *timestamppb.Timestamp { + if x != nil { + return x.ExpirationTime + } + return nil +} + +func (x *SshSession) GetRevoked() bool { + if x != nil { + return x.Revoked + } + return false +} + +// Watch sandbox request. +type WatchSandboxRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Workspace scope. Only a named workspace selection is accepted. + WorkspaceScope *datamodelv1.WorkspaceSelector `protobuf:"bytes,11,opt,name=workspace_scope,json=workspaceScope,proto3" json:"workspace_scope,omitempty"` + // Canonical sandbox name. + Sandbox string `protobuf:"bytes,1,opt,name=sandbox,proto3" json:"sandbox,omitempty"` + // Stream sandbox status snapshots. + FollowStatus bool `protobuf:"varint,2,opt,name=follow_status,json=followStatus,proto3" json:"follow_status,omitempty"` + // Stream openshell-server process logs correlated to this sandbox. + FollowLogs bool `protobuf:"varint,3,opt,name=follow_logs,json=followLogs,proto3" json:"follow_logs,omitempty"` + // Stream platform events correlated to this sandbox. + FollowEvents bool `protobuf:"varint,4,opt,name=follow_events,json=followEvents,proto3" json:"follow_events,omitempty"` + // Replay the last N log lines (best-effort) before following. + LogTailLines uint32 `protobuf:"varint,5,opt,name=log_tail_lines,json=logTailLines,proto3" json:"log_tail_lines,omitempty"` + // Replay the last N platform events (best-effort) before following. + EventTail uint32 `protobuf:"varint,6,opt,name=event_tail,json=eventTail,proto3" json:"event_tail,omitempty"` + // Stop streaming once the sandbox reaches READY or a terminal result phase + // (COMPLETED, STOPPED, or ERROR). + StopOnTerminal bool `protobuf:"varint,7,opt,name=stop_on_terminal,json=stopOnTerminal,proto3" json:"stop_on_terminal,omitempty"` + // Only include log lines at or after this time. Absence means no time filter. + // Applies to both tail replay and live streaming. + SinceTime *timestamppb.Timestamp `protobuf:"bytes,108,opt,name=since_time,json=sinceTime,proto3" json:"since_time,omitempty"` + // Filter by log source (e.g. "gateway", "sandbox"). Empty means all sources. + LogSources []string `protobuf:"bytes,9,rep,name=log_sources,json=logSources,proto3" json:"log_sources,omitempty"` + // Minimum log level to include (e.g. "INFO", "WARN", "ERROR"). Empty means all levels. + LogMinLevel string `protobuf:"bytes,10,opt,name=log_min_level,json=logMinLevel,proto3" json:"log_min_level,omitempty"` + // Resume streaming after this cursor. Empty means no cursor resume: the + // server falls back to tail-limited replay controlled by log_tail_lines and + // event_tail. Otherwise set it to the highest `SandboxStreamEvent.cursor` + // already processed; the server replays only log and platform events after + // it, merged in cursor order, before resuming live delivery. If the requested + // cursor has already been trimmed from the server's buffer, the resume is + // unrecoverable and the stream terminates with OUT_OF_RANGE (see + // SandboxStreamWarning for the recoverable case). + // + // A cursor is bound to the cursor space that issued it. A gateway restart, + // teardown of the sandbox's buffers, or a reconnect to a different gateway + // replica starts a new space, and cursors from the previous one are rejected + // with OUT_OF_RANGE rather than silently suppressing live events beneath + // them. OUT_OF_RANGE is terminal for that cursor: restart the watch with an + // empty resume_after_cursor, because retrying the same token fails + // identically. A cursor this server could not have issued is rejected with + // INVALID_ARGUMENT. + ResumeAfterCursor string `protobuf:"bytes,12,opt,name=resume_after_cursor,json=resumeAfterCursor,proto3" json:"resume_after_cursor,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *WatchSandboxRequest) Reset() { + *x = WatchSandboxRequest{} + mi := &file_openshell_proto_msgTypes[102] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *WatchSandboxRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*WatchSandboxRequest) ProtoMessage() {} + +func (x *WatchSandboxRequest) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[102] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use WatchSandboxRequest.ProtoReflect.Descriptor instead. +func (*WatchSandboxRequest) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{102} +} + +func (x *WatchSandboxRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { + if x != nil { + return x.WorkspaceScope + } + return nil +} + +func (x *WatchSandboxRequest) GetSandbox() string { + if x != nil { + return x.Sandbox + } + return "" +} + +func (x *WatchSandboxRequest) GetFollowStatus() bool { + if x != nil { + return x.FollowStatus + } + return false +} + +func (x *WatchSandboxRequest) GetFollowLogs() bool { + if x != nil { + return x.FollowLogs + } + return false +} + +func (x *WatchSandboxRequest) GetFollowEvents() bool { + if x != nil { + return x.FollowEvents + } + return false +} + +func (x *WatchSandboxRequest) GetLogTailLines() uint32 { + if x != nil { + return x.LogTailLines + } + return 0 +} + +func (x *WatchSandboxRequest) GetEventTail() uint32 { + if x != nil { + return x.EventTail + } + return 0 +} + +func (x *WatchSandboxRequest) GetStopOnTerminal() bool { + if x != nil { + return x.StopOnTerminal + } + return false +} + +func (x *WatchSandboxRequest) GetSinceTime() *timestamppb.Timestamp { + if x != nil { + return x.SinceTime + } + return nil +} + +func (x *WatchSandboxRequest) GetLogSources() []string { + if x != nil { + return x.LogSources + } + return nil +} + +func (x *WatchSandboxRequest) GetLogMinLevel() string { + if x != nil { + return x.LogMinLevel + } + return "" +} + +func (x *WatchSandboxRequest) GetResumeAfterCursor() string { + if x != nil { + return x.ResumeAfterCursor + } + return "" +} + +// One event in a sandbox watch stream. +type SandboxStreamEvent struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Types that are valid to be assigned to Payload: + // + // *SandboxStreamEvent_Sandbox + // *SandboxStreamEvent_Log + // *SandboxStreamEvent_Event + // *SandboxStreamEvent_Warning + // *SandboxStreamEvent_DraftPolicyUpdate + Payload isSandboxStreamEvent_Payload `protobuf_oneof:"payload"` + // Opaque position in this sandbox's cursor space, shared across the resumable + // log and platform event sources. Empty for non-resumable events (status + // snapshots, warnings). + // + // Do not parse this token; its encoding is not part of the contract. The only + // supported operation is comparing two non-empty cursors observed on the same + // stream and keeping the greater one, then passing it as + // WatchSandboxRequest.resume_after_cursor to resume without loss or + // duplication. That comparison is a plain byte-wise string comparison. It is + // well defined only within one stream: a stream never spans two cursor + // spaces, because a reset ends it. + Cursor string `protobuf:"bytes,6,opt,name=cursor,proto3" json:"cursor,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SandboxStreamEvent) Reset() { + *x = SandboxStreamEvent{} + mi := &file_openshell_proto_msgTypes[103] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SandboxStreamEvent) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SandboxStreamEvent) ProtoMessage() {} + +func (x *SandboxStreamEvent) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[103] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SandboxStreamEvent.ProtoReflect.Descriptor instead. +func (*SandboxStreamEvent) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{103} +} + +func (x *SandboxStreamEvent) GetPayload() isSandboxStreamEvent_Payload { + if x != nil { + return x.Payload + } + return nil +} + +func (x *SandboxStreamEvent) GetSandbox() *Sandbox { + if x != nil { + if x, ok := x.Payload.(*SandboxStreamEvent_Sandbox); ok { + return x.Sandbox + } + } + return nil +} + +func (x *SandboxStreamEvent) GetLog() *SandboxLogLine { + if x != nil { + if x, ok := x.Payload.(*SandboxStreamEvent_Log); ok { + return x.Log + } + } + return nil +} + +func (x *SandboxStreamEvent) GetEvent() *PlatformEvent { + if x != nil { + if x, ok := x.Payload.(*SandboxStreamEvent_Event); ok { + return x.Event + } + } + return nil +} + +func (x *SandboxStreamEvent) GetWarning() *SandboxStreamWarning { + if x != nil { + if x, ok := x.Payload.(*SandboxStreamEvent_Warning); ok { + return x.Warning + } + } + return nil +} + +func (x *SandboxStreamEvent) GetDraftPolicyUpdate() *DraftPolicyUpdate { + if x != nil { + if x, ok := x.Payload.(*SandboxStreamEvent_DraftPolicyUpdate); ok { + return x.DraftPolicyUpdate + } + } + return nil +} + +func (x *SandboxStreamEvent) GetCursor() string { + if x != nil { + return x.Cursor + } + return "" +} + +type isSandboxStreamEvent_Payload interface { + isSandboxStreamEvent_Payload() +} + +type SandboxStreamEvent_Sandbox struct { + // Latest sandbox snapshot. + Sandbox *Sandbox `protobuf:"bytes,1,opt,name=sandbox,proto3,oneof"` +} + +type SandboxStreamEvent_Log struct { + // One server log line/event. + Log *SandboxLogLine `protobuf:"bytes,2,opt,name=log,proto3,oneof"` +} + +type SandboxStreamEvent_Event struct { + // One platform event. + Event *PlatformEvent `protobuf:"bytes,3,opt,name=event,proto3,oneof"` +} + +type SandboxStreamEvent_Warning struct { + // Recoverable warning from the server, e.g. messages dropped because a + // broadcast receiver lagged. The stream continues after this warning; the + // client can detect the gap from the warning itself. + Warning *SandboxStreamWarning `protobuf:"bytes,4,opt,name=warning,proto3,oneof"` +} + +type SandboxStreamEvent_DraftPolicyUpdate struct { + // Draft policy update notification. + DraftPolicyUpdate *DraftPolicyUpdate `protobuf:"bytes,5,opt,name=draft_policy_update,json=draftPolicyUpdate,proto3,oneof"` +} + +func (*SandboxStreamEvent_Sandbox) isSandboxStreamEvent_Payload() {} + +func (*SandboxStreamEvent_Log) isSandboxStreamEvent_Payload() {} + +func (*SandboxStreamEvent_Event) isSandboxStreamEvent_Payload() {} + +func (*SandboxStreamEvent_Warning) isSandboxStreamEvent_Payload() {} + +func (*SandboxStreamEvent_DraftPolicyUpdate) isSandboxStreamEvent_Payload() {} + +// Log line correlated to a sandbox. +type SandboxLogLine struct { + state protoimpl.MessageState `protogen:"open.v1"` + SandboxId string `protobuf:"bytes,1,opt,name=sandbox_id,json=sandboxId,proto3" json:"sandbox_id,omitempty"` + EventTime *timestamppb.Timestamp `protobuf:"bytes,102,opt,name=event_time,json=eventTime,proto3" json:"event_time,omitempty"` + Level string `protobuf:"bytes,3,opt,name=level,proto3" json:"level,omitempty"` + Target string `protobuf:"bytes,4,opt,name=target,proto3" json:"target,omitempty"` + Message string `protobuf:"bytes,5,opt,name=message,proto3" json:"message,omitempty"` + // Log source: "gateway" (server-side) or "sandbox" (supervisor). + // Empty is treated as "gateway" for backward compatibility. + Source string `protobuf:"bytes,6,opt,name=source,proto3" json:"source,omitempty"` + // Structured key-value fields from the tracing event (e.g. dst_host, action). + Fields map[string]string `protobuf:"bytes,7,rep,name=fields,proto3" json:"fields,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SandboxLogLine) Reset() { + *x = SandboxLogLine{} + mi := &file_openshell_proto_msgTypes[104] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SandboxLogLine) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SandboxLogLine) ProtoMessage() {} + +func (x *SandboxLogLine) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[104] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SandboxLogLine.ProtoReflect.Descriptor instead. +func (*SandboxLogLine) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{104} +} + +func (x *SandboxLogLine) GetSandboxId() string { + if x != nil { + return x.SandboxId + } + return "" +} + +func (x *SandboxLogLine) GetEventTime() *timestamppb.Timestamp { + if x != nil { + return x.EventTime + } + return nil +} + +func (x *SandboxLogLine) GetLevel() string { + if x != nil { + return x.Level + } + return "" +} + +func (x *SandboxLogLine) GetTarget() string { + if x != nil { + return x.Target + } + return "" +} + +func (x *SandboxLogLine) GetMessage() string { + if x != nil { + return x.Message + } + return "" +} + +func (x *SandboxLogLine) GetSource() string { + if x != nil { + return x.Source + } + return "" +} + +func (x *SandboxLogLine) GetFields() map[string]string { + if x != nil { + return x.Fields + } + return nil +} + +// Recoverable loss notification on a watch stream. Emitted when the server +// skips ahead after a broadcast lag instead of terminating; the stream keeps +// running. Cursors are opaque, so this message is the only signal that events +// were skipped. Unrecoverable loss (a trimmed or foreign resume cursor) is +// reported as an OUT_OF_RANGE stream status, not this message. +type SandboxStreamWarning struct { + state protoimpl.MessageState `protogen:"open.v1"` + Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SandboxStreamWarning) Reset() { + *x = SandboxStreamWarning{} + mi := &file_openshell_proto_msgTypes[105] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SandboxStreamWarning) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SandboxStreamWarning) ProtoMessage() {} + +func (x *SandboxStreamWarning) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[105] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SandboxStreamWarning.ProtoReflect.Descriptor instead. +func (*SandboxStreamWarning) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{105} +} + +func (x *SandboxStreamWarning) GetMessage() string { + if x != nil { + return x.Message + } + return "" +} + +// Create provider request. +type CreateProviderRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Workspace scope. Only a named workspace selection is accepted. + WorkspaceScope *datamodelv1.WorkspaceSelector `protobuf:"bytes,2,opt,name=workspace_scope,json=workspaceScope,proto3" json:"workspace_scope,omitempty"` + Provider *datamodelv1.Provider `protobuf:"bytes,1,opt,name=provider,proto3" json:"provider,omitempty"` + // Optional nonzero UUID for durable at-most-once admission. Successful results + // can be replayed for 24 hours; see the API errors and retries reference. + RequestId string `protobuf:"bytes,3,opt,name=request_id,json=requestId,proto3" json:"request_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *CreateProviderRequest) Reset() { + *x = CreateProviderRequest{} + mi := &file_openshell_proto_msgTypes[106] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *CreateProviderRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreateProviderRequest) ProtoMessage() {} + +func (x *CreateProviderRequest) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[106] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CreateProviderRequest.ProtoReflect.Descriptor instead. +func (*CreateProviderRequest) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{106} +} + +func (x *CreateProviderRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { + if x != nil { + return x.WorkspaceScope + } + return nil +} + +func (x *CreateProviderRequest) GetProvider() *datamodelv1.Provider { + if x != nil { + return x.Provider + } + return nil +} + +func (x *CreateProviderRequest) GetRequestId() string { + if x != nil { + return x.RequestId + } + return "" +} + +// Get provider request. +type GetProviderRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Workspace scope. Only a named workspace selection is accepted. + WorkspaceScope *datamodelv1.WorkspaceSelector `protobuf:"bytes,2,opt,name=workspace_scope,json=workspaceScope,proto3" json:"workspace_scope,omitempty"` + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetProviderRequest) Reset() { + *x = GetProviderRequest{} + mi := &file_openshell_proto_msgTypes[107] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetProviderRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetProviderRequest) ProtoMessage() {} + +func (x *GetProviderRequest) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[107] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetProviderRequest.ProtoReflect.Descriptor instead. +func (*GetProviderRequest) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{107} +} + +func (x *GetProviderRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { + if x != nil { + return x.WorkspaceScope + } + return nil +} + +func (x *GetProviderRequest) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +// Retrieve selected runtime credentials from one provider in one workspace. +// Refresh side effects can persist even when the complete request fails. +type GetProviderCredentialsRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Required named workspace selection; all_workspaces is not accepted. + WorkspaceScope *datamodelv1.WorkspaceSelector `protobuf:"bytes,1,opt,name=workspace_scope,json=workspaceScope,proto3" json:"workspace_scope,omitempty"` + // Provider canonical name. + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` + // Nonempty selection of runtime environment keys (maximum 32). + CredentialKeys []string `protobuf:"bytes,3,rep,name=credential_keys,json=credentialKeys,proto3" json:"credential_keys,omitempty"` + // Omitted/zero uses a five-minute safety margin. Maximum is 24 hours. + MinimumRemainingLifetime *durationpb.Duration `protobuf:"bytes,4,opt,name=minimum_remaining_lifetime,json=minimumRemainingLifetime,proto3" json:"minimum_remaining_lifetime,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetProviderCredentialsRequest) Reset() { + *x = GetProviderCredentialsRequest{} + mi := &file_openshell_proto_msgTypes[108] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetProviderCredentialsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetProviderCredentialsRequest) ProtoMessage() {} + +func (x *GetProviderCredentialsRequest) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[108] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetProviderCredentialsRequest.ProtoReflect.Descriptor instead. +func (*GetProviderCredentialsRequest) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{108} +} + +func (x *GetProviderCredentialsRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { + if x != nil { + return x.WorkspaceScope + } + return nil +} + +func (x *GetProviderCredentialsRequest) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *GetProviderCredentialsRequest) GetCredentialKeys() []string { + if x != nil { + return x.CredentialKeys + } + return nil +} + +func (x *GetProviderCredentialsRequest) GetMinimumRemainingLifetime() *durationpb.Duration { + if x != nil { + return x.MinimumRemainingLifetime + } + return nil +} + +// Usable runtime value, never gateway-only minting material. +type ProviderCredentialValue struct { + state protoimpl.MessageState `protogen:"open.v1"` + Value string `protobuf:"bytes,1,opt,name=value,proto3" json:"value,omitempty"` + // Absent for credentials with no declared expiration. + ExpirationTime *timestamppb.Timestamp `protobuf:"bytes,2,opt,name=expiration_time,json=expirationTime,proto3" json:"expiration_time,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ProviderCredentialValue) Reset() { + *x = ProviderCredentialValue{} + mi := &file_openshell_proto_msgTypes[109] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ProviderCredentialValue) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ProviderCredentialValue) ProtoMessage() {} + +func (x *ProviderCredentialValue) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[109] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ProviderCredentialValue.ProtoReflect.Descriptor instead. +func (*ProviderCredentialValue) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{109} +} + +func (x *ProviderCredentialValue) GetValue() string { + if x != nil { + return x.Value + } + return "" +} + +func (x *ProviderCredentialValue) GetExpirationTime() *timestamppb.Timestamp { + if x != nil { + return x.ExpirationTime + } + return nil +} + +type GetProviderCredentialsResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + // All requested values or an error; no partial response. + Credentials map[string]*ProviderCredentialValue `protobuf:"bytes,1,rep,name=credentials,proto3" json:"credentials,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetProviderCredentialsResponse) Reset() { + *x = GetProviderCredentialsResponse{} + mi := &file_openshell_proto_msgTypes[110] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetProviderCredentialsResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetProviderCredentialsResponse) ProtoMessage() {} + +func (x *GetProviderCredentialsResponse) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[110] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetProviderCredentialsResponse.ProtoReflect.Descriptor instead. +func (*GetProviderCredentialsResponse) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{110} +} + +func (x *GetProviderCredentialsResponse) GetCredentials() map[string]*ProviderCredentialValue { + if x != nil { + return x.Credentials + } + return nil +} + +// List providers request. +type ListProvidersRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Workspace scope. Named and all-workspaces selections are accepted. + WorkspaceScope *datamodelv1.WorkspaceSelector `protobuf:"bytes,3,opt,name=workspace_scope,json=workspaceScope,proto3" json:"workspace_scope,omitempty"` + // The maximum number of providers to return. Zero uses 100. Values above + // 1000 are coerced to 1000; negative values are invalid. + PageSize int32 `protobuf:"varint,1,opt,name=page_size,json=pageSize,proto3" json:"page_size,omitempty"` + // Token from a previous ListProviders response. All other request parameters + // except page_size must match the request that produced it. + PageToken string `protobuf:"bytes,2,opt,name=page_token,json=pageToken,proto3" json:"page_token,omitempty"` + // Optional label selector for filtering (format: "key1=value1,key2=value2"). + LabelSelector string `protobuf:"bytes,4,opt,name=label_selector,json=labelSelector,proto3" json:"label_selector,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListProvidersRequest) Reset() { + *x = ListProvidersRequest{} + mi := &file_openshell_proto_msgTypes[111] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListProvidersRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListProvidersRequest) ProtoMessage() {} + +func (x *ListProvidersRequest) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[111] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListProvidersRequest.ProtoReflect.Descriptor instead. +func (*ListProvidersRequest) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{111} +} + +func (x *ListProvidersRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { + if x != nil { + return x.WorkspaceScope + } + return nil +} + +func (x *ListProvidersRequest) GetPageSize() int32 { + if x != nil { + return x.PageSize + } + return 0 +} + +func (x *ListProvidersRequest) GetPageToken() string { + if x != nil { + return x.PageToken + } + return "" +} + +func (x *ListProvidersRequest) GetLabelSelector() string { + if x != nil { + return x.LabelSelector + } + return "" +} + +// Update provider request. +type UpdateProviderRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Workspace scope. Only a named workspace selection is accepted. + WorkspaceScope *datamodelv1.WorkspaceSelector `protobuf:"bytes,3,opt,name=workspace_scope,json=workspaceScope,proto3" json:"workspace_scope,omitempty"` + Provider *datamodelv1.Provider `protobuf:"bytes,1,opt,name=provider,proto3" json:"provider,omitempty"` + // Optional per-credential expiry timestamps to merge into the provider. + // Omitted keys are unchanged. Use clear_credential_expiration_keys to remove + // an existing expiry. + CredentialExpirationTimes map[string]*timestamppb.Timestamp `protobuf:"bytes,102,rep,name=credential_expiration_times,json=credentialExpirationTimes,proto3" json:"credential_expiration_times,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + // Credential keys whose existing expiry should be removed. + ClearCredentialExpirationKeys []string `protobuf:"bytes,103,rep,name=clear_credential_expiration_keys,json=clearCredentialExpirationKeys,proto3" json:"clear_credential_expiration_keys,omitempty"` + // Optional nonzero UUID for durable at-most-once admission. Successful results + // can be replayed for 24 hours; see the API errors and retries reference. + RequestId string `protobuf:"bytes,4,opt,name=request_id,json=requestId,proto3" json:"request_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *UpdateProviderRequest) Reset() { + *x = UpdateProviderRequest{} + mi := &file_openshell_proto_msgTypes[112] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *UpdateProviderRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UpdateProviderRequest) ProtoMessage() {} + +func (x *UpdateProviderRequest) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[112] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use UpdateProviderRequest.ProtoReflect.Descriptor instead. +func (*UpdateProviderRequest) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{112} +} + +func (x *UpdateProviderRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { + if x != nil { + return x.WorkspaceScope + } + return nil +} + +func (x *UpdateProviderRequest) GetProvider() *datamodelv1.Provider { + if x != nil { + return x.Provider + } + return nil +} -func (x *SandboxStreamWarning) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[92] +func (x *UpdateProviderRequest) GetCredentialExpirationTimes() map[string]*timestamppb.Timestamp { if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms + return x.CredentialExpirationTimes } - return mi.MessageOf(x) + return nil } -// Deprecated: Use SandboxStreamWarning.ProtoReflect.Descriptor instead. -func (*SandboxStreamWarning) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{92} +func (x *UpdateProviderRequest) GetClearCredentialExpirationKeys() []string { + if x != nil { + return x.ClearCredentialExpirationKeys + } + return nil } -func (x *SandboxStreamWarning) GetMessage() string { +func (x *UpdateProviderRequest) GetRequestId() string { if x != nil { - return x.Message + return x.RequestId } return "" } -// Create provider request. -type CreateProviderRequest struct { +// Delete provider request. +type DeleteProviderRequest struct { state protoimpl.MessageState `protogen:"open.v1"` // Workspace scope. Only a named workspace selection is accepted. - WorkspaceScope *datamodelv1.WorkspaceSelector `protobuf:"bytes,2,opt,name=workspace_scope,json=workspaceScope,proto3" json:"workspace_scope,omitempty"` - Provider *datamodelv1.Provider `protobuf:"bytes,1,opt,name=provider,proto3" json:"provider,omitempty"` + WorkspaceScope *datamodelv1.WorkspaceSelector `protobuf:"bytes,3,opt,name=workspace_scope,json=workspaceScope,proto3" json:"workspace_scope,omitempty"` + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + AllowMissing bool `protobuf:"varint,4,opt,name=allow_missing,json=allowMissing,proto3" json:"allow_missing,omitempty"` // Optional nonzero UUID for durable at-most-once admission. Successful results // can be replayed for 24 hours; see the API errors and retries reference. - RequestId string `protobuf:"bytes,3,opt,name=request_id,json=requestId,proto3" json:"request_id,omitempty"` + RequestId string `protobuf:"bytes,5,opt,name=request_id,json=requestId,proto3" json:"request_id,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } -func (x *CreateProviderRequest) Reset() { - *x = CreateProviderRequest{} - mi := &file_openshell_proto_msgTypes[93] +func (x *DeleteProviderRequest) Reset() { + *x = DeleteProviderRequest{} + mi := &file_openshell_proto_msgTypes[113] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *CreateProviderRequest) String() string { +func (x *DeleteProviderRequest) String() string { return protoimpl.X.MessageStringOf(x) } -func (*CreateProviderRequest) ProtoMessage() {} +func (*DeleteProviderRequest) ProtoMessage() {} -func (x *CreateProviderRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[93] +func (x *DeleteProviderRequest) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[113] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8224,57 +9424,67 @@ func (x *CreateProviderRequest) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use CreateProviderRequest.ProtoReflect.Descriptor instead. -func (*CreateProviderRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{93} +// Deprecated: Use DeleteProviderRequest.ProtoReflect.Descriptor instead. +func (*DeleteProviderRequest) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{113} } -func (x *CreateProviderRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { +func (x *DeleteProviderRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { if x != nil { return x.WorkspaceScope } return nil } -func (x *CreateProviderRequest) GetProvider() *datamodelv1.Provider { +func (x *DeleteProviderRequest) GetName() string { if x != nil { - return x.Provider + return x.Name } - return nil + return "" } -func (x *CreateProviderRequest) GetRequestId() string { +func (x *DeleteProviderRequest) GetAllowMissing() bool { + if x != nil { + return x.AllowMissing + } + return false +} + +func (x *DeleteProviderRequest) GetRequestId() string { if x != nil { return x.RequestId } return "" } -// Get provider request. -type GetProviderRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - // Workspace scope. Only a named workspace selection is accepted. - WorkspaceScope *datamodelv1.WorkspaceSelector `protobuf:"bytes,2,opt,name=workspace_scope,json=workspaceScope,proto3" json:"workspace_scope,omitempty"` - Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache +// Provider response. +type ProviderResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Provider *datamodelv1.Provider `protobuf:"bytes,1,opt,name=provider,proto3" json:"provider,omitempty"` + // Selection-time sandbox target set for an update, with one receipt per target. + // Sandboxes attached later are outside this operation's readiness result. + TargetReceipts []*ProviderMutationReceipt `protobuf:"bytes,2,rep,name=target_receipts,json=targetReceipts,proto3" json:"target_receipts,omitempty"` + // Identifies the update even when its target set is empty. + MutationId string `protobuf:"bytes,3,opt,name=mutation_id,json=mutationId,proto3" json:"mutation_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } -func (x *GetProviderRequest) Reset() { - *x = GetProviderRequest{} - mi := &file_openshell_proto_msgTypes[94] +func (x *ProviderResponse) Reset() { + *x = ProviderResponse{} + mi := &file_openshell_proto_msgTypes[114] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *GetProviderRequest) String() string { +func (x *ProviderResponse) String() string { return protoimpl.X.MessageStringOf(x) } -func (*GetProviderRequest) ProtoMessage() {} +func (*ProviderResponse) ProtoMessage() {} -func (x *GetProviderRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[94] +func (x *ProviderResponse) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[114] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8285,56 +9495,57 @@ func (x *GetProviderRequest) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use GetProviderRequest.ProtoReflect.Descriptor instead. -func (*GetProviderRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{94} +// Deprecated: Use ProviderResponse.ProtoReflect.Descriptor instead. +func (*ProviderResponse) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{114} } -func (x *GetProviderRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { +func (x *ProviderResponse) GetProvider() *datamodelv1.Provider { if x != nil { - return x.WorkspaceScope + return x.Provider } return nil } -func (x *GetProviderRequest) GetName() string { +func (x *ProviderResponse) GetTargetReceipts() []*ProviderMutationReceipt { if x != nil { - return x.Name + return x.TargetReceipts + } + return nil +} + +func (x *ProviderResponse) GetMutationId() string { + if x != nil { + return x.MutationId } return "" } -// Retrieve selected runtime credentials from one provider in one workspace. -// Refresh side effects can persist even when the complete request fails. -type GetProviderCredentialsRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - // Required named workspace selection; all_workspaces is not accepted. - WorkspaceScope *datamodelv1.WorkspaceSelector `protobuf:"bytes,1,opt,name=workspace_scope,json=workspaceScope,proto3" json:"workspace_scope,omitempty"` - // Provider canonical name. - Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` - // Nonempty selection of runtime environment keys (maximum 32). - CredentialKeys []string `protobuf:"bytes,3,rep,name=credential_keys,json=credentialKeys,proto3" json:"credential_keys,omitempty"` - // Omitted/zero uses a five-minute safety margin. Maximum is 24 hours. - MinimumRemainingLifetime *durationpb.Duration `protobuf:"bytes,4,opt,name=minimum_remaining_lifetime,json=minimumRemainingLifetime,proto3" json:"minimum_remaining_lifetime,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache +// List providers response. +type ListProvidersResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Providers []*datamodelv1.Provider `protobuf:"bytes,1,rep,name=providers,proto3" json:"providers,omitempty"` + // Token for the next page. Empty when there are no subsequent pages. + NextPageToken string `protobuf:"bytes,2,opt,name=next_page_token,json=nextPageToken,proto3" json:"next_page_token,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } -func (x *GetProviderCredentialsRequest) Reset() { - *x = GetProviderCredentialsRequest{} - mi := &file_openshell_proto_msgTypes[95] +func (x *ListProvidersResponse) Reset() { + *x = ListProvidersResponse{} + mi := &file_openshell_proto_msgTypes[115] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *GetProviderCredentialsRequest) String() string { +func (x *ListProvidersResponse) String() string { return protoimpl.X.MessageStringOf(x) } -func (*GetProviderCredentialsRequest) ProtoMessage() {} +func (*ListProvidersResponse) ProtoMessage() {} -func (x *GetProviderCredentialsRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[95] +func (x *ListProvidersResponse) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[115] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8345,64 +9556,55 @@ func (x *GetProviderCredentialsRequest) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use GetProviderCredentialsRequest.ProtoReflect.Descriptor instead. -func (*GetProviderCredentialsRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{95} +// Deprecated: Use ListProvidersResponse.ProtoReflect.Descriptor instead. +func (*ListProvidersResponse) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{115} } -func (x *GetProviderCredentialsRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { +func (x *ListProvidersResponse) GetProviders() []*datamodelv1.Provider { if x != nil { - return x.WorkspaceScope + return x.Providers } return nil } -func (x *GetProviderCredentialsRequest) GetName() string { +func (x *ListProvidersResponse) GetNextPageToken() string { if x != nil { - return x.Name + return x.NextPageToken } return "" } -func (x *GetProviderCredentialsRequest) GetCredentialKeys() []string { - if x != nil { - return x.CredentialKeys - } - return nil -} - -func (x *GetProviderCredentialsRequest) GetMinimumRemainingLifetime() *durationpb.Duration { - if x != nil { - return x.MinimumRemainingLifetime - } - return nil -} - -// Usable runtime value, never gateway-only minting material. -type ProviderCredentialValue struct { +// List provider type profiles request. +type ListProviderProfilesRequest struct { state protoimpl.MessageState `protogen:"open.v1"` - Value string `protobuf:"bytes,1,opt,name=value,proto3" json:"value,omitempty"` - // Absent for credentials with no declared expiration. - ExpirationTime *timestamppb.Timestamp `protobuf:"bytes,2,opt,name=expiration_time,json=expirationTime,proto3" json:"expiration_time,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + // Workspace scope. Omit for platform profiles; otherwise select one named workspace. + WorkspaceScope *datamodelv1.WorkspaceSelector `protobuf:"bytes,3,opt,name=workspace_scope,json=workspaceScope,proto3" json:"workspace_scope,omitempty"` + // The maximum number of profiles to return. Zero uses 100. Values above + // 1000 are coerced to 1000; negative values are invalid. + PageSize int32 `protobuf:"varint,1,opt,name=page_size,json=pageSize,proto3" json:"page_size,omitempty"` + // Token from a previous ListProviderProfiles response. All other request + // parameters except page_size must match the request that produced it. + PageToken string `protobuf:"bytes,2,opt,name=page_token,json=pageToken,proto3" json:"page_token,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } -func (x *ProviderCredentialValue) Reset() { - *x = ProviderCredentialValue{} - mi := &file_openshell_proto_msgTypes[96] +func (x *ListProviderProfilesRequest) Reset() { + *x = ListProviderProfilesRequest{} + mi := &file_openshell_proto_msgTypes[116] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *ProviderCredentialValue) String() string { +func (x *ListProviderProfilesRequest) String() string { return protoimpl.X.MessageStringOf(x) } -func (*ProviderCredentialValue) ProtoMessage() {} +func (*ListProviderProfilesRequest) ProtoMessage() {} -func (x *ProviderCredentialValue) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[96] +func (x *ListProviderProfilesRequest) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[116] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8413,48 +9615,57 @@ func (x *ProviderCredentialValue) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use ProviderCredentialValue.ProtoReflect.Descriptor instead. -func (*ProviderCredentialValue) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{96} +// Deprecated: Use ListProviderProfilesRequest.ProtoReflect.Descriptor instead. +func (*ListProviderProfilesRequest) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{116} } -func (x *ProviderCredentialValue) GetValue() string { +func (x *ListProviderProfilesRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { if x != nil { - return x.Value + return x.WorkspaceScope } - return "" + return nil } -func (x *ProviderCredentialValue) GetExpirationTime() *timestamppb.Timestamp { +func (x *ListProviderProfilesRequest) GetPageSize() int32 { if x != nil { - return x.ExpirationTime + return x.PageSize } - return nil + return 0 } -type GetProviderCredentialsResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - // All requested values or an error; no partial response. - Credentials map[string]*ProviderCredentialValue `protobuf:"bytes,1,rep,name=credentials,proto3" json:"credentials,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache +func (x *ListProviderProfilesRequest) GetPageToken() string { + if x != nil { + return x.PageToken + } + return "" +} + +// Fetch provider type profile request. +type GetProviderProfileRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Workspace scope. Omit for platform profiles; otherwise select one named workspace. + WorkspaceScope *datamodelv1.WorkspaceSelector `protobuf:"bytes,2,opt,name=workspace_scope,json=workspaceScope,proto3" json:"workspace_scope,omitempty"` + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } -func (x *GetProviderCredentialsResponse) Reset() { - *x = GetProviderCredentialsResponse{} - mi := &file_openshell_proto_msgTypes[97] +func (x *GetProviderProfileRequest) Reset() { + *x = GetProviderProfileRequest{} + mi := &file_openshell_proto_msgTypes[117] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *GetProviderCredentialsResponse) String() string { +func (x *GetProviderProfileRequest) String() string { return protoimpl.X.MessageStringOf(x) } -func (*GetProviderCredentialsResponse) ProtoMessage() {} +func (*GetProviderProfileRequest) ProtoMessage() {} -func (x *GetProviderCredentialsResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[97] +func (x *GetProviderProfileRequest) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[117] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8465,50 +9676,49 @@ func (x *GetProviderCredentialsResponse) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use GetProviderCredentialsResponse.ProtoReflect.Descriptor instead. -func (*GetProviderCredentialsResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{97} +// Deprecated: Use GetProviderProfileRequest.ProtoReflect.Descriptor instead. +func (*GetProviderProfileRequest) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{117} } -func (x *GetProviderCredentialsResponse) GetCredentials() map[string]*ProviderCredentialValue { +func (x *GetProviderProfileRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { if x != nil { - return x.Credentials + return x.WorkspaceScope } return nil } -// List providers request. -type ListProvidersRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - // Workspace scope. Named and all-workspaces selections are accepted. - WorkspaceScope *datamodelv1.WorkspaceSelector `protobuf:"bytes,3,opt,name=workspace_scope,json=workspaceScope,proto3" json:"workspace_scope,omitempty"` - // The maximum number of providers to return. Zero uses 100. Values above - // 1000 are coerced to 1000; negative values are invalid. - PageSize int32 `protobuf:"varint,1,opt,name=page_size,json=pageSize,proto3" json:"page_size,omitempty"` - // Token from a previous ListProviders response. All other request parameters - // except page_size must match the request that produced it. - PageToken string `protobuf:"bytes,2,opt,name=page_token,json=pageToken,proto3" json:"page_token,omitempty"` - // Optional label selector for filtering (format: "key1=value1,key2=value2"). - LabelSelector string `protobuf:"bytes,4,opt,name=label_selector,json=labelSelector,proto3" json:"label_selector,omitempty"` +func (x *GetProviderProfileRequest) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +// Provider profile payload with optional source metadata for diagnostics. +type ProviderProfileImportItem struct { + state protoimpl.MessageState `protogen:"open.v1"` + Profile *ProviderProfile `protobuf:"bytes,1,opt,name=profile,proto3" json:"profile,omitempty"` + Source string `protobuf:"bytes,2,opt,name=source,proto3" json:"source,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } -func (x *ListProvidersRequest) Reset() { - *x = ListProvidersRequest{} - mi := &file_openshell_proto_msgTypes[98] +func (x *ProviderProfileImportItem) Reset() { + *x = ProviderProfileImportItem{} + mi := &file_openshell_proto_msgTypes[118] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *ListProvidersRequest) String() string { +func (x *ProviderProfileImportItem) String() string { return protoimpl.X.MessageStringOf(x) } -func (*ListProvidersRequest) ProtoMessage() {} +func (*ProviderProfileImportItem) ProtoMessage() {} -func (x *ListProvidersRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[98] +func (x *ProviderProfileImportItem) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[118] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8519,73 +9729,52 @@ func (x *ListProvidersRequest) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use ListProvidersRequest.ProtoReflect.Descriptor instead. -func (*ListProvidersRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{98} +// Deprecated: Use ProviderProfileImportItem.ProtoReflect.Descriptor instead. +func (*ProviderProfileImportItem) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{118} } -func (x *ListProvidersRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { +func (x *ProviderProfileImportItem) GetProfile() *ProviderProfile { if x != nil { - return x.WorkspaceScope + return x.Profile } return nil } -func (x *ListProvidersRequest) GetPageSize() int32 { - if x != nil { - return x.PageSize - } - return 0 -} - -func (x *ListProvidersRequest) GetPageToken() string { - if x != nil { - return x.PageToken - } - return "" -} - -func (x *ListProvidersRequest) GetLabelSelector() string { +func (x *ProviderProfileImportItem) GetSource() string { if x != nil { - return x.LabelSelector + return x.Source } return "" } -// Update provider request. -type UpdateProviderRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - // Workspace scope. Only a named workspace selection is accepted. - WorkspaceScope *datamodelv1.WorkspaceSelector `protobuf:"bytes,3,opt,name=workspace_scope,json=workspaceScope,proto3" json:"workspace_scope,omitempty"` - Provider *datamodelv1.Provider `protobuf:"bytes,1,opt,name=provider,proto3" json:"provider,omitempty"` - // Optional per-credential expiry timestamps to merge into the provider. - // Omitted keys are unchanged. Use clear_credential_expiration_keys to remove - // an existing expiry. - CredentialExpirationTimes map[string]*timestamppb.Timestamp `protobuf:"bytes,102,rep,name=credential_expiration_times,json=credentialExpirationTimes,proto3" json:"credential_expiration_times,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` - // Credential keys whose existing expiry should be removed. - ClearCredentialExpirationKeys []string `protobuf:"bytes,103,rep,name=clear_credential_expiration_keys,json=clearCredentialExpirationKeys,proto3" json:"clear_credential_expiration_keys,omitempty"` - // Optional nonzero UUID for durable at-most-once admission. Successful results - // can be replayed for 24 hours; see the API errors and retries reference. - RequestId string `protobuf:"bytes,4,opt,name=request_id,json=requestId,proto3" json:"request_id,omitempty"` +// Provider profile validation diagnostic. +type ProviderProfileDiagnostic struct { + state protoimpl.MessageState `protogen:"open.v1"` + Source string `protobuf:"bytes,1,opt,name=source,proto3" json:"source,omitempty"` + ProfileId string `protobuf:"bytes,2,opt,name=profile_id,json=profileId,proto3" json:"profile_id,omitempty"` + Field string `protobuf:"bytes,3,opt,name=field,proto3" json:"field,omitempty"` + Message string `protobuf:"bytes,4,opt,name=message,proto3" json:"message,omitempty"` + Severity string `protobuf:"bytes,5,opt,name=severity,proto3" json:"severity,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } -func (x *UpdateProviderRequest) Reset() { - *x = UpdateProviderRequest{} - mi := &file_openshell_proto_msgTypes[99] +func (x *ProviderProfileDiagnostic) Reset() { + *x = ProviderProfileDiagnostic{} + mi := &file_openshell_proto_msgTypes[119] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *UpdateProviderRequest) String() string { +func (x *ProviderProfileDiagnostic) String() string { return protoimpl.X.MessageStringOf(x) } -func (*UpdateProviderRequest) ProtoMessage() {} +func (*ProviderProfileDiagnostic) ProtoMessage() {} -func (x *UpdateProviderRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[99] +func (x *ProviderProfileDiagnostic) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[119] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8596,75 +9785,78 @@ func (x *UpdateProviderRequest) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use UpdateProviderRequest.ProtoReflect.Descriptor instead. -func (*UpdateProviderRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{99} +// Deprecated: Use ProviderProfileDiagnostic.ProtoReflect.Descriptor instead. +func (*ProviderProfileDiagnostic) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{119} } -func (x *UpdateProviderRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { +func (x *ProviderProfileDiagnostic) GetSource() string { if x != nil { - return x.WorkspaceScope + return x.Source } - return nil + return "" } -func (x *UpdateProviderRequest) GetProvider() *datamodelv1.Provider { +func (x *ProviderProfileDiagnostic) GetProfileId() string { if x != nil { - return x.Provider + return x.ProfileId } - return nil + return "" } -func (x *UpdateProviderRequest) GetCredentialExpirationTimes() map[string]*timestamppb.Timestamp { +func (x *ProviderProfileDiagnostic) GetField() string { if x != nil { - return x.CredentialExpirationTimes + return x.Field } - return nil + return "" } -func (x *UpdateProviderRequest) GetClearCredentialExpirationKeys() []string { +func (x *ProviderProfileDiagnostic) GetMessage() string { if x != nil { - return x.ClearCredentialExpirationKeys + return x.Message } - return nil + return "" } -func (x *UpdateProviderRequest) GetRequestId() string { +func (x *ProviderProfileDiagnostic) GetSeverity() string { if x != nil { - return x.RequestId + return x.Severity } return "" } -// Delete provider request. -type DeleteProviderRequest struct { +// Endpoint selector for token grant audience overrides. +type ProviderCredentialTokenGrantAudienceOverride struct { state protoimpl.MessageState `protogen:"open.v1"` - // Workspace scope. Only a named workspace selection is accepted. - WorkspaceScope *datamodelv1.WorkspaceSelector `protobuf:"bytes,3,opt,name=workspace_scope,json=workspaceScope,proto3" json:"workspace_scope,omitempty"` - Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` - AllowMissing bool `protobuf:"varint,4,opt,name=allow_missing,json=allowMissing,proto3" json:"allow_missing,omitempty"` - // Optional nonzero UUID for durable at-most-once admission. Successful results - // can be replayed for 24 hours; see the API errors and retries reference. - RequestId string `protobuf:"bytes,5,opt,name=request_id,json=requestId,proto3" json:"request_id,omitempty"` + // Optional: endpoint host selector. If omitted, inherits the profile endpoint host. + Host string `protobuf:"bytes,1,opt,name=host,proto3" json:"host,omitempty"` + // Optional: endpoint port selector. If omitted, matches the expanded profile endpoint port. + Port uint32 `protobuf:"varint,2,opt,name=port,proto3" json:"port,omitempty"` + // Optional: endpoint path selector. If omitted, inherits the profile endpoint path. + Path string `protobuf:"bytes,3,opt,name=path,proto3" json:"path,omitempty"` + // Resource audience to request for matching endpoints. + Audience string `protobuf:"bytes,4,opt,name=audience,proto3" json:"audience,omitempty"` + // Optional: OAuth2 scopes to request. If omitted, inherits the token grant scopes. + Scopes []string `protobuf:"bytes,5,rep,name=scopes,proto3" json:"scopes,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } -func (x *DeleteProviderRequest) Reset() { - *x = DeleteProviderRequest{} - mi := &file_openshell_proto_msgTypes[100] +func (x *ProviderCredentialTokenGrantAudienceOverride) Reset() { + *x = ProviderCredentialTokenGrantAudienceOverride{} + mi := &file_openshell_proto_msgTypes[120] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *DeleteProviderRequest) String() string { +func (x *ProviderCredentialTokenGrantAudienceOverride) String() string { return protoimpl.X.MessageStringOf(x) } -func (*DeleteProviderRequest) ProtoMessage() {} +func (*ProviderCredentialTokenGrantAudienceOverride) ProtoMessage() {} -func (x *DeleteProviderRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[100] +func (x *ProviderCredentialTokenGrantAudienceOverride) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[120] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8675,67 +9867,75 @@ func (x *DeleteProviderRequest) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use DeleteProviderRequest.ProtoReflect.Descriptor instead. -func (*DeleteProviderRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{100} +// Deprecated: Use ProviderCredentialTokenGrantAudienceOverride.ProtoReflect.Descriptor instead. +func (*ProviderCredentialTokenGrantAudienceOverride) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{120} } -func (x *DeleteProviderRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { +func (x *ProviderCredentialTokenGrantAudienceOverride) GetHost() string { if x != nil { - return x.WorkspaceScope + return x.Host } - return nil + return "" } -func (x *DeleteProviderRequest) GetName() string { +func (x *ProviderCredentialTokenGrantAudienceOverride) GetPort() uint32 { if x != nil { - return x.Name + return x.Port } - return "" + return 0 } -func (x *DeleteProviderRequest) GetAllowMissing() bool { +func (x *ProviderCredentialTokenGrantAudienceOverride) GetPath() string { if x != nil { - return x.AllowMissing + return x.Path } - return false + return "" } -func (x *DeleteProviderRequest) GetRequestId() string { +func (x *ProviderCredentialTokenGrantAudienceOverride) GetAudience() string { if x != nil { - return x.RequestId + return x.Audience } return "" } -// Provider response. -type ProviderResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - Provider *datamodelv1.Provider `protobuf:"bytes,1,opt,name=provider,proto3" json:"provider,omitempty"` - // Selection-time sandbox target set for an update, with one receipt per target. - // Sandboxes attached later are outside this operation's readiness result. - TargetReceipts []*ProviderMutationReceipt `protobuf:"bytes,2,rep,name=target_receipts,json=targetReceipts,proto3" json:"target_receipts,omitempty"` - // Identifies the update even when its target set is empty. - MutationId string `protobuf:"bytes,3,opt,name=mutation_id,json=mutationId,proto3" json:"mutation_id,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache +func (x *ProviderCredentialTokenGrantAudienceOverride) GetScopes() []string { + if x != nil { + return x.Scopes + } + return nil +} + +type ProviderCredentialTokenGrantSubjectToken struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Source for the token exchange subject token. Phase one supports + // "provider_credential". + Source string `protobuf:"bytes,1,opt,name=source,proto3" json:"source,omitempty"` + // Provider credential key that stores the subject token. + Credential string `protobuf:"bytes,2,opt,name=credential,proto3" json:"credential,omitempty"` + // OAuth2 subject_token_type. If omitted, OpenShell uses + // urn:ietf:params:oauth:token-type:access_token. + SubjectTokenType string `protobuf:"bytes,3,opt,name=subject_token_type,json=subjectTokenType,proto3" json:"subject_token_type,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } -func (x *ProviderResponse) Reset() { - *x = ProviderResponse{} - mi := &file_openshell_proto_msgTypes[101] +func (x *ProviderCredentialTokenGrantSubjectToken) Reset() { + *x = ProviderCredentialTokenGrantSubjectToken{} + mi := &file_openshell_proto_msgTypes[121] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *ProviderResponse) String() string { +func (x *ProviderCredentialTokenGrantSubjectToken) String() string { return protoimpl.X.MessageStringOf(x) } -func (*ProviderResponse) ProtoMessage() {} +func (*ProviderCredentialTokenGrantSubjectToken) ProtoMessage() {} -func (x *ProviderResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[101] +func (x *ProviderCredentialTokenGrantSubjectToken) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[121] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8746,57 +9946,77 @@ func (x *ProviderResponse) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use ProviderResponse.ProtoReflect.Descriptor instead. -func (*ProviderResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{101} +// Deprecated: Use ProviderCredentialTokenGrantSubjectToken.ProtoReflect.Descriptor instead. +func (*ProviderCredentialTokenGrantSubjectToken) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{121} } -func (x *ProviderResponse) GetProvider() *datamodelv1.Provider { +func (x *ProviderCredentialTokenGrantSubjectToken) GetSource() string { if x != nil { - return x.Provider + return x.Source } - return nil + return "" } -func (x *ProviderResponse) GetTargetReceipts() []*ProviderMutationReceipt { +func (x *ProviderCredentialTokenGrantSubjectToken) GetCredential() string { if x != nil { - return x.TargetReceipts + return x.Credential } - return nil + return "" } -func (x *ProviderResponse) GetMutationId() string { +func (x *ProviderCredentialTokenGrantSubjectToken) GetSubjectTokenType() string { if x != nil { - return x.MutationId + return x.SubjectTokenType } return "" } -// List providers response. -type ListProvidersResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - Providers []*datamodelv1.Provider `protobuf:"bytes,1,rep,name=providers,proto3" json:"providers,omitempty"` - // Token for the next page. Empty when there are no subsequent pages. - NextPageToken string `protobuf:"bytes,2,opt,name=next_page_token,json=nextPageToken,proto3" json:"next_page_token,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache +type ProviderCredentialTokenGrant struct { + state protoimpl.MessageState `protogen:"open.v1"` + // OAuth2 token endpoint URL (e.g., https://keycloak.example.com/realms/my-realm/protocol/openid-connect/token) + TokenEndpoint string `protobuf:"bytes,1,opt,name=token_endpoint,json=tokenEndpoint,proto3" json:"token_endpoint,omitempty"` + // Optional: default resource audience to request from the token service + Audience string `protobuf:"bytes,2,opt,name=audience,proto3" json:"audience,omitempty"` + // Optional: audience to request when fetching the JWT-SVID from SPIRE. + // If omitted, the sandbox derives this from token_endpoint. + JwtSvidAudience string `protobuf:"bytes,6,opt,name=jwt_svid_audience,json=jwtSvidAudience,proto3" json:"jwt_svid_audience,omitempty"` + // Optional: OAuth2 scopes to request + Scopes []string `protobuf:"bytes,3,rep,name=scopes,proto3" json:"scopes,omitempty"` + // Optional token cache TTL override. If absent, use expires_in from the token response. + CacheTtl *durationpb.Duration `protobuf:"bytes,104,opt,name=cache_ttl,json=cacheTtl,proto3" json:"cache_ttl,omitempty"` + // Optional: endpoint-specific resource audience overrides. + AudienceOverrides []*ProviderCredentialTokenGrantAudienceOverride `protobuf:"bytes,5,rep,name=audience_overrides,json=audienceOverrides,proto3" json:"audience_overrides,omitempty"` + // Optional: OAuth2 client_assertion_type value. If omitted, OpenShell uses + // urn:ietf:params:oauth:client-assertion-type:jwt-bearer. + ClientAssertionType string `protobuf:"bytes,7,opt,name=client_assertion_type,json=clientAssertionType,proto3" json:"client_assertion_type,omitempty"` + // Grant type. If omitted/unspecified, OpenShell treats this as client_credentials + // for backwards compatibility. + GrantType ProviderCredentialTokenGrantType `protobuf:"varint,8,opt,name=grant_type,json=grantType,proto3,enum=openshell.v1.ProviderCredentialTokenGrantType" json:"grant_type,omitempty"` + // Subject token metadata for token_exchange grants. + SubjectToken *ProviderCredentialTokenGrantSubjectToken `protobuf:"bytes,9,opt,name=subject_token,json=subjectToken,proto3" json:"subject_token,omitempty"` + // OAuth2 requested_token_type. If omitted for token_exchange, OpenShell uses + // urn:ietf:params:oauth:token-type:access_token. + RequestedTokenType string `protobuf:"bytes,10,opt,name=requested_token_type,json=requestedTokenType,proto3" json:"requested_token_type,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } -func (x *ListProvidersResponse) Reset() { - *x = ListProvidersResponse{} - mi := &file_openshell_proto_msgTypes[102] +func (x *ProviderCredentialTokenGrant) Reset() { + *x = ProviderCredentialTokenGrant{} + mi := &file_openshell_proto_msgTypes[122] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *ListProvidersResponse) String() string { +func (x *ProviderCredentialTokenGrant) String() string { return protoimpl.X.MessageStringOf(x) } -func (*ListProvidersResponse) ProtoMessage() {} +func (*ProviderCredentialTokenGrant) ProtoMessage() {} -func (x *ListProvidersResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[102] +func (x *ProviderCredentialTokenGrant) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[122] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8807,116 +10027,125 @@ func (x *ListProvidersResponse) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use ListProvidersResponse.ProtoReflect.Descriptor instead. -func (*ListProvidersResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{102} +// Deprecated: Use ProviderCredentialTokenGrant.ProtoReflect.Descriptor instead. +func (*ProviderCredentialTokenGrant) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{122} } -func (x *ListProvidersResponse) GetProviders() []*datamodelv1.Provider { +func (x *ProviderCredentialTokenGrant) GetTokenEndpoint() string { if x != nil { - return x.Providers + return x.TokenEndpoint } - return nil + return "" } -func (x *ListProvidersResponse) GetNextPageToken() string { +func (x *ProviderCredentialTokenGrant) GetAudience() string { if x != nil { - return x.NextPageToken + return x.Audience } return "" } -// List provider type profiles request. -type ListProviderProfilesRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - // Workspace scope. Omit for platform profiles; otherwise select one named workspace. - WorkspaceScope *datamodelv1.WorkspaceSelector `protobuf:"bytes,3,opt,name=workspace_scope,json=workspaceScope,proto3" json:"workspace_scope,omitempty"` - // The maximum number of profiles to return. Zero uses 100. Values above - // 1000 are coerced to 1000; negative values are invalid. - PageSize int32 `protobuf:"varint,1,opt,name=page_size,json=pageSize,proto3" json:"page_size,omitempty"` - // Token from a previous ListProviderProfiles response. All other request - // parameters except page_size must match the request that produced it. - PageToken string `protobuf:"bytes,2,opt,name=page_token,json=pageToken,proto3" json:"page_token,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache +func (x *ProviderCredentialTokenGrant) GetJwtSvidAudience() string { + if x != nil { + return x.JwtSvidAudience + } + return "" } -func (x *ListProviderProfilesRequest) Reset() { - *x = ListProviderProfilesRequest{} - mi := &file_openshell_proto_msgTypes[103] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) +func (x *ProviderCredentialTokenGrant) GetScopes() []string { + if x != nil { + return x.Scopes + } + return nil } -func (x *ListProviderProfilesRequest) String() string { - return protoimpl.X.MessageStringOf(x) +func (x *ProviderCredentialTokenGrant) GetCacheTtl() *durationpb.Duration { + if x != nil { + return x.CacheTtl + } + return nil } -func (*ListProviderProfilesRequest) ProtoMessage() {} - -func (x *ListProviderProfilesRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[103] +func (x *ProviderCredentialTokenGrant) GetAudienceOverrides() []*ProviderCredentialTokenGrantAudienceOverride { if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms + return x.AudienceOverrides } - return mi.MessageOf(x) + return nil } -// Deprecated: Use ListProviderProfilesRequest.ProtoReflect.Descriptor instead. -func (*ListProviderProfilesRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{103} +func (x *ProviderCredentialTokenGrant) GetClientAssertionType() string { + if x != nil { + return x.ClientAssertionType + } + return "" } -func (x *ListProviderProfilesRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { +func (x *ProviderCredentialTokenGrant) GetGrantType() ProviderCredentialTokenGrantType { if x != nil { - return x.WorkspaceScope + return x.GrantType } - return nil + return ProviderCredentialTokenGrantType_PROVIDER_CREDENTIAL_TOKEN_GRANT_TYPE_UNSPECIFIED } -func (x *ListProviderProfilesRequest) GetPageSize() int32 { +func (x *ProviderCredentialTokenGrant) GetSubjectToken() *ProviderCredentialTokenGrantSubjectToken { if x != nil { - return x.PageSize + return x.SubjectToken } - return 0 + return nil } -func (x *ListProviderProfilesRequest) GetPageToken() string { +func (x *ProviderCredentialTokenGrant) GetRequestedTokenType() string { if x != nil { - return x.PageToken + return x.RequestedTokenType } return "" } -// Fetch provider type profile request. -type GetProviderProfileRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - // Workspace scope. Omit for platform profiles; otherwise select one named workspace. - WorkspaceScope *datamodelv1.WorkspaceSelector `protobuf:"bytes,2,opt,name=workspace_scope,json=workspaceScope,proto3" json:"workspace_scope,omitempty"` - Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache +// Provider credential declaration. +type ProviderProfileCredential struct { + state protoimpl.MessageState `protogen:"open.v1"` + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + Description string `protobuf:"bytes,2,opt,name=description,proto3" json:"description,omitempty"` + EnvVars []string `protobuf:"bytes,3,rep,name=env_vars,json=envVars,proto3" json:"env_vars,omitempty"` + Required bool `protobuf:"varint,4,opt,name=required,proto3" json:"required,omitempty"` + AuthStyle string `protobuf:"bytes,5,opt,name=auth_style,json=authStyle,proto3" json:"auth_style,omitempty"` + HeaderName string `protobuf:"bytes,6,opt,name=header_name,json=headerName,proto3" json:"header_name,omitempty"` + QueryParam string `protobuf:"bytes,7,opt,name=query_param,json=queryParam,proto3" json:"query_param,omitempty"` + Refresh *ProviderCredentialRefresh `protobuf:"bytes,8,opt,name=refresh,proto3" json:"refresh,omitempty"` + PathTemplate string `protobuf:"bytes,9,opt,name=path_template,json=pathTemplate,proto3" json:"path_template,omitempty"` + TokenGrant *ProviderCredentialTokenGrant `protobuf:"bytes,10,opt,name=token_grant,json=tokenGrant,proto3" json:"token_grant,omitempty"` + // Output-only gateway-derived endpoint authorities for resolved token grants. + // For inspected L7 requests, empty means no endpoint may obtain this grant. + // L4 injection uses selectors without checking these authorities. + // Profile-authored values are ignored; normal policies name the originating + // provider endpoint, while a global policy uses its own endpoint authorities. + TokenGrantOwners []string `protobuf:"bytes,11,rep,name=token_grant_owners,json=tokenGrantOwners,proto3" json:"token_grant_owners,omitempty"` + // Opt in to an opaque identity-bound environment placeholder for every env + // var declared by this credential. Use this only when an external credential + // updater requires one placeholder across value-only provider revisions. + // Gateway-managed refresh and token-grant credentials cannot opt in because + // they require identity-bearing handles for authorization-epoch revocation. + StablePlaceholder bool `protobuf:"varint,12,opt,name=stable_placeholder,json=stablePlaceholder,proto3" json:"stable_placeholder,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } -func (x *GetProviderProfileRequest) Reset() { - *x = GetProviderProfileRequest{} - mi := &file_openshell_proto_msgTypes[104] +func (x *ProviderProfileCredential) Reset() { + *x = ProviderProfileCredential{} + mi := &file_openshell_proto_msgTypes[123] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *GetProviderProfileRequest) String() string { +func (x *ProviderProfileCredential) String() string { return protoimpl.X.MessageStringOf(x) } -func (*GetProviderProfileRequest) ProtoMessage() {} +func (*ProviderProfileCredential) ProtoMessage() {} -func (x *GetProviderProfileRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[104] +func (x *ProviderProfileCredential) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[123] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8927,105 +10156,120 @@ func (x *GetProviderProfileRequest) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use GetProviderProfileRequest.ProtoReflect.Descriptor instead. -func (*GetProviderProfileRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{104} +// Deprecated: Use ProviderProfileCredential.ProtoReflect.Descriptor instead. +func (*ProviderProfileCredential) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{123} } -func (x *GetProviderProfileRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { +func (x *ProviderProfileCredential) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *ProviderProfileCredential) GetDescription() string { + if x != nil { + return x.Description + } + return "" +} + +func (x *ProviderProfileCredential) GetEnvVars() []string { + if x != nil { + return x.EnvVars + } + return nil +} + +func (x *ProviderProfileCredential) GetRequired() bool { if x != nil { - return x.WorkspaceScope + return x.Required } - return nil + return false } -func (x *GetProviderProfileRequest) GetId() string { +func (x *ProviderProfileCredential) GetAuthStyle() string { if x != nil { - return x.Id + return x.AuthStyle } return "" } -// Provider profile payload with optional source metadata for diagnostics. -type ProviderProfileImportItem struct { - state protoimpl.MessageState `protogen:"open.v1"` - Profile *ProviderProfile `protobuf:"bytes,1,opt,name=profile,proto3" json:"profile,omitempty"` - Source string `protobuf:"bytes,2,opt,name=source,proto3" json:"source,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache +func (x *ProviderProfileCredential) GetHeaderName() string { + if x != nil { + return x.HeaderName + } + return "" } -func (x *ProviderProfileImportItem) Reset() { - *x = ProviderProfileImportItem{} - mi := &file_openshell_proto_msgTypes[105] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) +func (x *ProviderProfileCredential) GetQueryParam() string { + if x != nil { + return x.QueryParam + } + return "" } -func (x *ProviderProfileImportItem) String() string { - return protoimpl.X.MessageStringOf(x) +func (x *ProviderProfileCredential) GetRefresh() *ProviderCredentialRefresh { + if x != nil { + return x.Refresh + } + return nil } -func (*ProviderProfileImportItem) ProtoMessage() {} - -func (x *ProviderProfileImportItem) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[105] +func (x *ProviderProfileCredential) GetPathTemplate() string { if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms + return x.PathTemplate } - return mi.MessageOf(x) + return "" } -// Deprecated: Use ProviderProfileImportItem.ProtoReflect.Descriptor instead. -func (*ProviderProfileImportItem) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{105} +func (x *ProviderProfileCredential) GetTokenGrant() *ProviderCredentialTokenGrant { + if x != nil { + return x.TokenGrant + } + return nil } -func (x *ProviderProfileImportItem) GetProfile() *ProviderProfile { +func (x *ProviderProfileCredential) GetTokenGrantOwners() []string { if x != nil { - return x.Profile + return x.TokenGrantOwners } return nil } -func (x *ProviderProfileImportItem) GetSource() string { +func (x *ProviderProfileCredential) GetStablePlaceholder() bool { if x != nil { - return x.Source + return x.StablePlaceholder } - return "" + return false } -// Provider profile validation diagnostic. -type ProviderProfileDiagnostic struct { +type ProviderCredentialRefreshMaterial struct { state protoimpl.MessageState `protogen:"open.v1"` - Source string `protobuf:"bytes,1,opt,name=source,proto3" json:"source,omitempty"` - ProfileId string `protobuf:"bytes,2,opt,name=profile_id,json=profileId,proto3" json:"profile_id,omitempty"` - Field string `protobuf:"bytes,3,opt,name=field,proto3" json:"field,omitempty"` - Message string `protobuf:"bytes,4,opt,name=message,proto3" json:"message,omitempty"` - Severity string `protobuf:"bytes,5,opt,name=severity,proto3" json:"severity,omitempty"` + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + Description string `protobuf:"bytes,2,opt,name=description,proto3" json:"description,omitempty"` + Required bool `protobuf:"varint,3,opt,name=required,proto3" json:"required,omitempty"` + Secret bool `protobuf:"varint,4,opt,name=secret,proto3" json:"secret,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } -func (x *ProviderProfileDiagnostic) Reset() { - *x = ProviderProfileDiagnostic{} - mi := &file_openshell_proto_msgTypes[106] +func (x *ProviderCredentialRefreshMaterial) Reset() { + *x = ProviderCredentialRefreshMaterial{} + mi := &file_openshell_proto_msgTypes[124] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *ProviderProfileDiagnostic) String() string { +func (x *ProviderCredentialRefreshMaterial) String() string { return protoimpl.X.MessageStringOf(x) } -func (*ProviderProfileDiagnostic) ProtoMessage() {} +func (*ProviderCredentialRefreshMaterial) ProtoMessage() {} -func (x *ProviderProfileDiagnostic) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[106] +func (x *ProviderCredentialRefreshMaterial) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[124] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9036,78 +10280,66 @@ func (x *ProviderProfileDiagnostic) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use ProviderProfileDiagnostic.ProtoReflect.Descriptor instead. -func (*ProviderProfileDiagnostic) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{106} -} - -func (x *ProviderProfileDiagnostic) GetSource() string { - if x != nil { - return x.Source - } - return "" +// Deprecated: Use ProviderCredentialRefreshMaterial.ProtoReflect.Descriptor instead. +func (*ProviderCredentialRefreshMaterial) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{124} } -func (x *ProviderProfileDiagnostic) GetProfileId() string { +func (x *ProviderCredentialRefreshMaterial) GetName() string { if x != nil { - return x.ProfileId + return x.Name } return "" } -func (x *ProviderProfileDiagnostic) GetField() string { +func (x *ProviderCredentialRefreshMaterial) GetDescription() string { if x != nil { - return x.Field + return x.Description } return "" } -func (x *ProviderProfileDiagnostic) GetMessage() string { +func (x *ProviderCredentialRefreshMaterial) GetRequired() bool { if x != nil { - return x.Message + return x.Required } - return "" + return false } -func (x *ProviderProfileDiagnostic) GetSeverity() string { +func (x *ProviderCredentialRefreshMaterial) GetSecret() bool { if x != nil { - return x.Severity + return x.Secret } - return "" + return false } -// Endpoint selector for token grant audience overrides. -type ProviderCredentialTokenGrantAudienceOverride struct { - state protoimpl.MessageState `protogen:"open.v1"` - // Optional: endpoint host selector. If omitted, inherits the profile endpoint host. - Host string `protobuf:"bytes,1,opt,name=host,proto3" json:"host,omitempty"` - // Optional: endpoint port selector. If omitted, matches the expanded profile endpoint port. - Port uint32 `protobuf:"varint,2,opt,name=port,proto3" json:"port,omitempty"` - // Optional: endpoint path selector. If omitted, inherits the profile endpoint path. - Path string `protobuf:"bytes,3,opt,name=path,proto3" json:"path,omitempty"` - // Resource audience to request for matching endpoints. - Audience string `protobuf:"bytes,4,opt,name=audience,proto3" json:"audience,omitempty"` - // Optional: OAuth2 scopes to request. If omitted, inherits the token grant scopes. - Scopes []string `protobuf:"bytes,5,rep,name=scopes,proto3" json:"scopes,omitempty"` +// Declares that a single refresh operation mints more than one credential. +// The refresh is attached to a primary credential; each additional output +// maps a strategy-defined semantic output id to a sibling credential whose +// env_vars receive the minted value. +type ProviderCredentialRefreshOutput struct { + state protoimpl.MessageState `protogen:"open.v1"` + Output string `protobuf:"bytes,1,opt,name=output,proto3" json:"output,omitempty"` // strategy-defined semantic output id (e.g. "session_token") + Credential string `protobuf:"bytes,2,opt,name=credential,proto3" json:"credential,omitempty"` // sibling credential name whose env_vars receive this output unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } -func (x *ProviderCredentialTokenGrantAudienceOverride) Reset() { - *x = ProviderCredentialTokenGrantAudienceOverride{} - mi := &file_openshell_proto_msgTypes[107] +func (x *ProviderCredentialRefreshOutput) Reset() { + *x = ProviderCredentialRefreshOutput{} + mi := &file_openshell_proto_msgTypes[125] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *ProviderCredentialTokenGrantAudienceOverride) String() string { +func (x *ProviderCredentialRefreshOutput) String() string { return protoimpl.X.MessageStringOf(x) } -func (*ProviderCredentialTokenGrantAudienceOverride) ProtoMessage() {} +func (*ProviderCredentialRefreshOutput) ProtoMessage() {} -func (x *ProviderCredentialTokenGrantAudienceOverride) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[107] +func (x *ProviderCredentialRefreshOutput) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[125] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9118,75 +10350,53 @@ func (x *ProviderCredentialTokenGrantAudienceOverride) ProtoReflect() protorefle return mi.MessageOf(x) } -// Deprecated: Use ProviderCredentialTokenGrantAudienceOverride.ProtoReflect.Descriptor instead. -func (*ProviderCredentialTokenGrantAudienceOverride) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{107} -} - -func (x *ProviderCredentialTokenGrantAudienceOverride) GetHost() string { - if x != nil { - return x.Host - } - return "" -} - -func (x *ProviderCredentialTokenGrantAudienceOverride) GetPort() uint32 { - if x != nil { - return x.Port - } - return 0 +// Deprecated: Use ProviderCredentialRefreshOutput.ProtoReflect.Descriptor instead. +func (*ProviderCredentialRefreshOutput) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{125} } -func (x *ProviderCredentialTokenGrantAudienceOverride) GetPath() string { +func (x *ProviderCredentialRefreshOutput) GetOutput() string { if x != nil { - return x.Path + return x.Output } return "" } -func (x *ProviderCredentialTokenGrantAudienceOverride) GetAudience() string { +func (x *ProviderCredentialRefreshOutput) GetCredential() string { if x != nil { - return x.Audience + return x.Credential } return "" } -func (x *ProviderCredentialTokenGrantAudienceOverride) GetScopes() []string { - if x != nil { - return x.Scopes - } - return nil -} - -type ProviderCredentialTokenGrantSubjectToken struct { - state protoimpl.MessageState `protogen:"open.v1"` - // Source for the token exchange subject token. Phase one supports - // "provider_credential". - Source string `protobuf:"bytes,1,opt,name=source,proto3" json:"source,omitempty"` - // Provider credential key that stores the subject token. - Credential string `protobuf:"bytes,2,opt,name=credential,proto3" json:"credential,omitempty"` - // OAuth2 subject_token_type. If omitted, OpenShell uses - // urn:ietf:params:oauth:token-type:access_token. - SubjectTokenType string `protobuf:"bytes,3,opt,name=subject_token_type,json=subjectTokenType,proto3" json:"subject_token_type,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache +type ProviderCredentialRefresh struct { + state protoimpl.MessageState `protogen:"open.v1"` + Strategy ProviderCredentialRefreshStrategy `protobuf:"varint,1,opt,name=strategy,proto3,enum=openshell.v1.ProviderCredentialRefreshStrategy" json:"strategy,omitempty"` + TokenUrl string `protobuf:"bytes,2,opt,name=token_url,json=tokenUrl,proto3" json:"token_url,omitempty"` + Scopes []string `protobuf:"bytes,3,rep,name=scopes,proto3" json:"scopes,omitempty"` + RefreshBefore *durationpb.Duration `protobuf:"bytes,104,opt,name=refresh_before,json=refreshBefore,proto3" json:"refresh_before,omitempty"` + MaxLifetime *durationpb.Duration `protobuf:"bytes,105,opt,name=max_lifetime,json=maxLifetime,proto3" json:"max_lifetime,omitempty"` + Material []*ProviderCredentialRefreshMaterial `protobuf:"bytes,6,rep,name=material,proto3" json:"material,omitempty"` + AdditionalOutputs []*ProviderCredentialRefreshOutput `protobuf:"bytes,7,rep,name=additional_outputs,json=additionalOutputs,proto3" json:"additional_outputs,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } -func (x *ProviderCredentialTokenGrantSubjectToken) Reset() { - *x = ProviderCredentialTokenGrantSubjectToken{} - mi := &file_openshell_proto_msgTypes[108] +func (x *ProviderCredentialRefresh) Reset() { + *x = ProviderCredentialRefresh{} + mi := &file_openshell_proto_msgTypes[126] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *ProviderCredentialTokenGrantSubjectToken) String() string { +func (x *ProviderCredentialRefresh) String() string { return protoimpl.X.MessageStringOf(x) } -func (*ProviderCredentialTokenGrantSubjectToken) ProtoMessage() {} +func (*ProviderCredentialRefresh) ProtoMessage() {} -func (x *ProviderCredentialTokenGrantSubjectToken) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[108] +func (x *ProviderCredentialRefresh) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[126] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9194,80 +10404,105 @@ func (x *ProviderCredentialTokenGrantSubjectToken) ProtoReflect() protoreflect.M } return ms } - return mi.MessageOf(x) + return mi.MessageOf(x) +} + +// Deprecated: Use ProviderCredentialRefresh.ProtoReflect.Descriptor instead. +func (*ProviderCredentialRefresh) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{126} +} + +func (x *ProviderCredentialRefresh) GetStrategy() ProviderCredentialRefreshStrategy { + if x != nil { + return x.Strategy + } + return ProviderCredentialRefreshStrategy_PROVIDER_CREDENTIAL_REFRESH_STRATEGY_UNSPECIFIED +} + +func (x *ProviderCredentialRefresh) GetTokenUrl() string { + if x != nil { + return x.TokenUrl + } + return "" +} + +func (x *ProviderCredentialRefresh) GetScopes() []string { + if x != nil { + return x.Scopes + } + return nil } -// Deprecated: Use ProviderCredentialTokenGrantSubjectToken.ProtoReflect.Descriptor instead. -func (*ProviderCredentialTokenGrantSubjectToken) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{108} +func (x *ProviderCredentialRefresh) GetRefreshBefore() *durationpb.Duration { + if x != nil { + return x.RefreshBefore + } + return nil } -func (x *ProviderCredentialTokenGrantSubjectToken) GetSource() string { +func (x *ProviderCredentialRefresh) GetMaxLifetime() *durationpb.Duration { if x != nil { - return x.Source + return x.MaxLifetime } - return "" + return nil } -func (x *ProviderCredentialTokenGrantSubjectToken) GetCredential() string { +func (x *ProviderCredentialRefresh) GetMaterial() []*ProviderCredentialRefreshMaterial { if x != nil { - return x.Credential + return x.Material } - return "" + return nil } -func (x *ProviderCredentialTokenGrantSubjectToken) GetSubjectTokenType() string { +func (x *ProviderCredentialRefresh) GetAdditionalOutputs() []*ProviderCredentialRefreshOutput { if x != nil { - return x.SubjectTokenType + return x.AdditionalOutputs } - return "" + return nil } -type ProviderCredentialTokenGrant struct { - state protoimpl.MessageState `protogen:"open.v1"` - // OAuth2 token endpoint URL (e.g., https://keycloak.example.com/realms/my-realm/protocol/openid-connect/token) - TokenEndpoint string `protobuf:"bytes,1,opt,name=token_endpoint,json=tokenEndpoint,proto3" json:"token_endpoint,omitempty"` - // Optional: default resource audience to request from the token service - Audience string `protobuf:"bytes,2,opt,name=audience,proto3" json:"audience,omitempty"` - // Optional: audience to request when fetching the JWT-SVID from SPIRE. - // If omitted, the sandbox derives this from token_endpoint. - JwtSvidAudience string `protobuf:"bytes,6,opt,name=jwt_svid_audience,json=jwtSvidAudience,proto3" json:"jwt_svid_audience,omitempty"` - // Optional: OAuth2 scopes to request - Scopes []string `protobuf:"bytes,3,rep,name=scopes,proto3" json:"scopes,omitempty"` - // Optional token cache TTL override. If absent, use expires_in from the token response. - CacheTtl *durationpb.Duration `protobuf:"bytes,104,opt,name=cache_ttl,json=cacheTtl,proto3" json:"cache_ttl,omitempty"` - // Optional: endpoint-specific resource audience overrides. - AudienceOverrides []*ProviderCredentialTokenGrantAudienceOverride `protobuf:"bytes,5,rep,name=audience_overrides,json=audienceOverrides,proto3" json:"audience_overrides,omitempty"` - // Optional: OAuth2 client_assertion_type value. If omitted, OpenShell uses - // urn:ietf:params:oauth:client-assertion-type:jwt-bearer. - ClientAssertionType string `protobuf:"bytes,7,opt,name=client_assertion_type,json=clientAssertionType,proto3" json:"client_assertion_type,omitempty"` - // Grant type. If omitted/unspecified, OpenShell treats this as client_credentials - // for backwards compatibility. - GrantType ProviderCredentialTokenGrantType `protobuf:"varint,8,opt,name=grant_type,json=grantType,proto3,enum=openshell.v1.ProviderCredentialTokenGrantType" json:"grant_type,omitempty"` - // Subject token metadata for token_exchange grants. - SubjectToken *ProviderCredentialTokenGrantSubjectToken `protobuf:"bytes,9,opt,name=subject_token,json=subjectToken,proto3" json:"subject_token,omitempty"` - // OAuth2 requested_token_type. If omitted for token_exchange, OpenShell uses - // urn:ietf:params:oauth:token-type:access_token. - RequestedTokenType string `protobuf:"bytes,10,opt,name=requested_token_type,json=requestedTokenType,proto3" json:"requested_token_type,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache +type ProviderCredentialRefreshStatus struct { + state protoimpl.MessageState `protogen:"open.v1"` + Provider string `protobuf:"bytes,1,opt,name=provider,proto3" json:"provider,omitempty"` + ProviderId string `protobuf:"bytes,2,opt,name=provider_id,json=providerId,proto3" json:"provider_id,omitempty"` + CredentialKey string `protobuf:"bytes,3,opt,name=credential_key,json=credentialKey,proto3" json:"credential_key,omitempty"` + Strategy ProviderCredentialRefreshStrategy `protobuf:"varint,4,opt,name=strategy,proto3,enum=openshell.v1.ProviderCredentialRefreshStrategy" json:"strategy,omitempty"` + Status string `protobuf:"bytes,5,opt,name=status,proto3" json:"status,omitempty"` + ExpirationTime *timestamppb.Timestamp `protobuf:"bytes,106,opt,name=expiration_time,json=expirationTime,proto3" json:"expiration_time,omitempty"` + // Next automatic refresh time. Absence means no automatic retry is scheduled; + // use recovery_action to determine the required recovery workflow. + NextRefreshTime *timestamppb.Timestamp `protobuf:"bytes,107,opt,name=next_refresh_time,json=nextRefreshTime,proto3" json:"next_refresh_time,omitempty"` + LastRefreshTime *timestamppb.Timestamp `protobuf:"bytes,108,opt,name=last_refresh_time,json=lastRefreshTime,proto3" json:"last_refresh_time,omitempty"` + LastError string `protobuf:"bytes,9,opt,name=last_error,json=lastError,proto3" json:"last_error,omitempty"` + RecoveryAction ProviderCredentialRefreshRecoveryAction `protobuf:"varint,10,opt,name=recovery_action,json=recoveryAction,proto3,enum=openshell.v1.ProviderCredentialRefreshRecoveryAction" json:"recovery_action,omitempty"` + // Stable gateway-owned failure identifier, for example + // "oauth_invalid_grant". This is not provider-controlled prose and + // incorporates any recognized top-level OAuth error classification. + FailureCode string `protobuf:"bytes,11,opt,name=failure_code,json=failureCode,proto3" json:"failure_code,omitempty"` + // A bounded, recognized provider subtype that refines failure_code; clients + // do not need a separate provider_error field. Unknown provider-controlled + // values are not persisted or returned. + ProviderErrorSubtype string `protobuf:"bytes,12,opt,name=provider_error_subtype,json=providerErrorSubtype,proto3" json:"provider_error_subtype,omitempty"` + LastErrorTime *timestamppb.Timestamp `protobuf:"bytes,113,opt,name=last_error_time,json=lastErrorTime,proto3" json:"last_error_time,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } -func (x *ProviderCredentialTokenGrant) Reset() { - *x = ProviderCredentialTokenGrant{} - mi := &file_openshell_proto_msgTypes[109] +func (x *ProviderCredentialRefreshStatus) Reset() { + *x = ProviderCredentialRefreshStatus{} + mi := &file_openshell_proto_msgTypes[127] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *ProviderCredentialTokenGrant) String() string { +func (x *ProviderCredentialRefreshStatus) String() string { return protoimpl.X.MessageStringOf(x) } -func (*ProviderCredentialTokenGrant) ProtoMessage() {} +func (*ProviderCredentialRefreshStatus) ProtoMessage() {} -func (x *ProviderCredentialTokenGrant) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[109] +func (x *ProviderCredentialRefreshStatus) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[127] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9278,125 +10513,126 @@ func (x *ProviderCredentialTokenGrant) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use ProviderCredentialTokenGrant.ProtoReflect.Descriptor instead. -func (*ProviderCredentialTokenGrant) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{109} +// Deprecated: Use ProviderCredentialRefreshStatus.ProtoReflect.Descriptor instead. +func (*ProviderCredentialRefreshStatus) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{127} } -func (x *ProviderCredentialTokenGrant) GetTokenEndpoint() string { +func (x *ProviderCredentialRefreshStatus) GetProvider() string { if x != nil { - return x.TokenEndpoint + return x.Provider } return "" } -func (x *ProviderCredentialTokenGrant) GetAudience() string { +func (x *ProviderCredentialRefreshStatus) GetProviderId() string { if x != nil { - return x.Audience + return x.ProviderId } return "" } -func (x *ProviderCredentialTokenGrant) GetJwtSvidAudience() string { +func (x *ProviderCredentialRefreshStatus) GetCredentialKey() string { if x != nil { - return x.JwtSvidAudience + return x.CredentialKey } return "" } -func (x *ProviderCredentialTokenGrant) GetScopes() []string { +func (x *ProviderCredentialRefreshStatus) GetStrategy() ProviderCredentialRefreshStrategy { if x != nil { - return x.Scopes + return x.Strategy + } + return ProviderCredentialRefreshStrategy_PROVIDER_CREDENTIAL_REFRESH_STRATEGY_UNSPECIFIED +} + +func (x *ProviderCredentialRefreshStatus) GetStatus() string { + if x != nil { + return x.Status + } + return "" +} + +func (x *ProviderCredentialRefreshStatus) GetExpirationTime() *timestamppb.Timestamp { + if x != nil { + return x.ExpirationTime } return nil } -func (x *ProviderCredentialTokenGrant) GetCacheTtl() *durationpb.Duration { +func (x *ProviderCredentialRefreshStatus) GetNextRefreshTime() *timestamppb.Timestamp { if x != nil { - return x.CacheTtl + return x.NextRefreshTime } return nil } -func (x *ProviderCredentialTokenGrant) GetAudienceOverrides() []*ProviderCredentialTokenGrantAudienceOverride { +func (x *ProviderCredentialRefreshStatus) GetLastRefreshTime() *timestamppb.Timestamp { if x != nil { - return x.AudienceOverrides + return x.LastRefreshTime } return nil } -func (x *ProviderCredentialTokenGrant) GetClientAssertionType() string { +func (x *ProviderCredentialRefreshStatus) GetLastError() string { if x != nil { - return x.ClientAssertionType + return x.LastError } return "" } -func (x *ProviderCredentialTokenGrant) GetGrantType() ProviderCredentialTokenGrantType { +func (x *ProviderCredentialRefreshStatus) GetRecoveryAction() ProviderCredentialRefreshRecoveryAction { if x != nil { - return x.GrantType + return x.RecoveryAction } - return ProviderCredentialTokenGrantType_PROVIDER_CREDENTIAL_TOKEN_GRANT_TYPE_UNSPECIFIED + return ProviderCredentialRefreshRecoveryAction_PROVIDER_CREDENTIAL_REFRESH_RECOVERY_ACTION_UNSPECIFIED } -func (x *ProviderCredentialTokenGrant) GetSubjectToken() *ProviderCredentialTokenGrantSubjectToken { +func (x *ProviderCredentialRefreshStatus) GetFailureCode() string { if x != nil { - return x.SubjectToken + return x.FailureCode } - return nil + return "" } -func (x *ProviderCredentialTokenGrant) GetRequestedTokenType() string { +func (x *ProviderCredentialRefreshStatus) GetProviderErrorSubtype() string { if x != nil { - return x.RequestedTokenType + return x.ProviderErrorSubtype } return "" } -// Provider credential declaration. -type ProviderProfileCredential struct { - state protoimpl.MessageState `protogen:"open.v1"` - Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` - Description string `protobuf:"bytes,2,opt,name=description,proto3" json:"description,omitempty"` - EnvVars []string `protobuf:"bytes,3,rep,name=env_vars,json=envVars,proto3" json:"env_vars,omitempty"` - Required bool `protobuf:"varint,4,opt,name=required,proto3" json:"required,omitempty"` - AuthStyle string `protobuf:"bytes,5,opt,name=auth_style,json=authStyle,proto3" json:"auth_style,omitempty"` - HeaderName string `protobuf:"bytes,6,opt,name=header_name,json=headerName,proto3" json:"header_name,omitempty"` - QueryParam string `protobuf:"bytes,7,opt,name=query_param,json=queryParam,proto3" json:"query_param,omitempty"` - Refresh *ProviderCredentialRefresh `protobuf:"bytes,8,opt,name=refresh,proto3" json:"refresh,omitempty"` - PathTemplate string `protobuf:"bytes,9,opt,name=path_template,json=pathTemplate,proto3" json:"path_template,omitempty"` - TokenGrant *ProviderCredentialTokenGrant `protobuf:"bytes,10,opt,name=token_grant,json=tokenGrant,proto3" json:"token_grant,omitempty"` - // Output-only gateway-derived endpoint authorities for resolved token grants. - // For inspected L7 requests, empty means no endpoint may obtain this grant. - // L4 injection uses selectors without checking these authorities. - // Profile-authored values are ignored; normal policies name the originating - // provider endpoint, while a global policy uses its own endpoint authorities. - TokenGrantOwners []string `protobuf:"bytes,11,rep,name=token_grant_owners,json=tokenGrantOwners,proto3" json:"token_grant_owners,omitempty"` - // Opt in to an opaque identity-bound environment placeholder for every env - // var declared by this credential. Use this only when an external credential - // updater requires one placeholder across value-only provider revisions. - // Gateway-managed refresh and token-grant credentials cannot opt in because - // they require identity-bearing handles for authorization-epoch revocation. - StablePlaceholder bool `protobuf:"varint,12,opt,name=stable_placeholder,json=stablePlaceholder,proto3" json:"stable_placeholder,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache +func (x *ProviderCredentialRefreshStatus) GetLastErrorTime() *timestamppb.Timestamp { + if x != nil { + return x.LastErrorTime + } + return nil } -func (x *ProviderProfileCredential) Reset() { - *x = ProviderProfileCredential{} - mi := &file_openshell_proto_msgTypes[110] +// Provider profile local discovery declaration. +type ProviderProfileDiscovery struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Credential names from ProviderProfile.credentials eligible for local discovery. + Credentials []string `protobuf:"bytes,1,rep,name=credentials,proto3" json:"credentials,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ProviderProfileDiscovery) Reset() { + *x = ProviderProfileDiscovery{} + mi := &file_openshell_proto_msgTypes[128] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *ProviderProfileCredential) String() string { +func (x *ProviderProfileDiscovery) String() string { return protoimpl.X.MessageStringOf(x) } -func (*ProviderProfileCredential) ProtoMessage() {} +func (*ProviderProfileDiscovery) ProtoMessage() {} -func (x *ProviderProfileCredential) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[110] +func (x *ProviderProfileDiscovery) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[128] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9407,120 +10643,168 @@ func (x *ProviderProfileCredential) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use ProviderProfileCredential.ProtoReflect.Descriptor instead. -func (*ProviderProfileCredential) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{110} +// Deprecated: Use ProviderProfileDiscovery.ProtoReflect.Descriptor instead. +func (*ProviderProfileDiscovery) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{128} } -func (x *ProviderProfileCredential) GetName() string { +func (x *ProviderProfileDiscovery) GetCredentials() []string { if x != nil { - return x.Name + return x.Credentials } - return "" + return nil } -func (x *ProviderProfileCredential) GetDescription() string { +type DelegatedIdentityCredentialSummary struct { + state protoimpl.MessageState `protogen:"open.v1"` + Metadata *datamodelv1.ObjectMeta `protobuf:"bytes,1,opt,name=metadata,proto3" json:"metadata,omitempty"` + Issuer string `protobuf:"bytes,2,opt,name=issuer,proto3" json:"issuer,omitempty"` + ClientId string `protobuf:"bytes,3,opt,name=client_id,json=clientId,proto3" json:"client_id,omitempty"` + PrincipalSubject string `protobuf:"bytes,4,opt,name=principal_subject,json=principalSubject,proto3" json:"principal_subject,omitempty"` + RefreshTokenPresent bool `protobuf:"varint,5,opt,name=refresh_token_present,json=refreshTokenPresent,proto3" json:"refresh_token_present,omitempty"` + AccessTokenPresent bool `protobuf:"varint,6,opt,name=access_token_present,json=accessTokenPresent,proto3" json:"access_token_present,omitempty"` + AccessTokenExpirationTime *timestamppb.Timestamp `protobuf:"bytes,7,opt,name=access_token_expiration_time,json=accessTokenExpirationTime,proto3" json:"access_token_expiration_time,omitempty"` + Scopes string `protobuf:"bytes,8,opt,name=scopes,proto3" json:"scopes,omitempty"` + Audience string `protobuf:"bytes,9,opt,name=audience,proto3" json:"audience,omitempty"` + LastRefreshTime *timestamppb.Timestamp `protobuf:"bytes,10,opt,name=last_refresh_time,json=lastRefreshTime,proto3" json:"last_refresh_time,omitempty"` + RevocationTime *timestamppb.Timestamp `protobuf:"bytes,11,opt,name=revocation_time,json=revocationTime,proto3" json:"revocation_time,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DelegatedIdentityCredentialSummary) Reset() { + *x = DelegatedIdentityCredentialSummary{} + mi := &file_openshell_proto_msgTypes[129] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DelegatedIdentityCredentialSummary) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DelegatedIdentityCredentialSummary) ProtoMessage() {} + +func (x *DelegatedIdentityCredentialSummary) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[129] if x != nil { - return x.Description + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms } - return "" + return mi.MessageOf(x) +} + +// Deprecated: Use DelegatedIdentityCredentialSummary.ProtoReflect.Descriptor instead. +func (*DelegatedIdentityCredentialSummary) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{129} } -func (x *ProviderProfileCredential) GetEnvVars() []string { +func (x *DelegatedIdentityCredentialSummary) GetMetadata() *datamodelv1.ObjectMeta { if x != nil { - return x.EnvVars + return x.Metadata } return nil } -func (x *ProviderProfileCredential) GetRequired() bool { +func (x *DelegatedIdentityCredentialSummary) GetIssuer() string { if x != nil { - return x.Required + return x.Issuer } - return false + return "" } -func (x *ProviderProfileCredential) GetAuthStyle() string { +func (x *DelegatedIdentityCredentialSummary) GetClientId() string { if x != nil { - return x.AuthStyle + return x.ClientId } return "" } -func (x *ProviderProfileCredential) GetHeaderName() string { +func (x *DelegatedIdentityCredentialSummary) GetPrincipalSubject() string { if x != nil { - return x.HeaderName + return x.PrincipalSubject } return "" } -func (x *ProviderProfileCredential) GetQueryParam() string { +func (x *DelegatedIdentityCredentialSummary) GetRefreshTokenPresent() bool { if x != nil { - return x.QueryParam + return x.RefreshTokenPresent } - return "" + return false } -func (x *ProviderProfileCredential) GetRefresh() *ProviderCredentialRefresh { +func (x *DelegatedIdentityCredentialSummary) GetAccessTokenPresent() bool { if x != nil { - return x.Refresh + return x.AccessTokenPresent + } + return false +} + +func (x *DelegatedIdentityCredentialSummary) GetAccessTokenExpirationTime() *timestamppb.Timestamp { + if x != nil { + return x.AccessTokenExpirationTime } return nil } -func (x *ProviderProfileCredential) GetPathTemplate() string { +func (x *DelegatedIdentityCredentialSummary) GetScopes() string { if x != nil { - return x.PathTemplate + return x.Scopes } return "" } -func (x *ProviderProfileCredential) GetTokenGrant() *ProviderCredentialTokenGrant { +func (x *DelegatedIdentityCredentialSummary) GetAudience() string { if x != nil { - return x.TokenGrant + return x.Audience } - return nil + return "" } -func (x *ProviderProfileCredential) GetTokenGrantOwners() []string { +func (x *DelegatedIdentityCredentialSummary) GetLastRefreshTime() *timestamppb.Timestamp { if x != nil { - return x.TokenGrantOwners + return x.LastRefreshTime } return nil } -func (x *ProviderProfileCredential) GetStablePlaceholder() bool { +func (x *DelegatedIdentityCredentialSummary) GetRevocationTime() *timestamppb.Timestamp { if x != nil { - return x.StablePlaceholder + return x.RevocationTime } - return false + return nil } -type ProviderCredentialRefreshMaterial struct { - state protoimpl.MessageState `protogen:"open.v1"` - Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` - Description string `protobuf:"bytes,2,opt,name=description,proto3" json:"description,omitempty"` - Required bool `protobuf:"varint,3,opt,name=required,proto3" json:"required,omitempty"` - Secret bool `protobuf:"varint,4,opt,name=secret,proto3" json:"secret,omitempty"` +type ListDelegatedIdentityCredentialsRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The maximum number of credentials to return. Zero uses 100. Values above + // 1000 are coerced to 1000; negative values are invalid. + PageSize int32 `protobuf:"varint,1,opt,name=page_size,json=pageSize,proto3" json:"page_size,omitempty"` + // Token from a previous ListDelegatedIdentityCredentials response. + PageToken string `protobuf:"bytes,2,opt,name=page_token,json=pageToken,proto3" json:"page_token,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } -func (x *ProviderCredentialRefreshMaterial) Reset() { - *x = ProviderCredentialRefreshMaterial{} - mi := &file_openshell_proto_msgTypes[111] +func (x *ListDelegatedIdentityCredentialsRequest) Reset() { + *x = ListDelegatedIdentityCredentialsRequest{} + mi := &file_openshell_proto_msgTypes[130] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *ProviderCredentialRefreshMaterial) String() string { +func (x *ListDelegatedIdentityCredentialsRequest) String() string { return protoimpl.X.MessageStringOf(x) } -func (*ProviderCredentialRefreshMaterial) ProtoMessage() {} +func (*ListDelegatedIdentityCredentialsRequest) ProtoMessage() {} -func (x *ProviderCredentialRefreshMaterial) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[111] +func (x *ListDelegatedIdentityCredentialsRequest) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[130] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9531,66 +10815,49 @@ func (x *ProviderCredentialRefreshMaterial) ProtoReflect() protoreflect.Message return mi.MessageOf(x) } -// Deprecated: Use ProviderCredentialRefreshMaterial.ProtoReflect.Descriptor instead. -func (*ProviderCredentialRefreshMaterial) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{111} +// Deprecated: Use ListDelegatedIdentityCredentialsRequest.ProtoReflect.Descriptor instead. +func (*ListDelegatedIdentityCredentialsRequest) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{130} } -func (x *ProviderCredentialRefreshMaterial) GetName() string { +func (x *ListDelegatedIdentityCredentialsRequest) GetPageSize() int32 { if x != nil { - return x.Name + return x.PageSize } - return "" + return 0 } -func (x *ProviderCredentialRefreshMaterial) GetDescription() string { +func (x *ListDelegatedIdentityCredentialsRequest) GetPageToken() string { if x != nil { - return x.Description + return x.PageToken } return "" } -func (x *ProviderCredentialRefreshMaterial) GetRequired() bool { - if x != nil { - return x.Required - } - return false -} - -func (x *ProviderCredentialRefreshMaterial) GetSecret() bool { - if x != nil { - return x.Secret - } - return false -} - -// Declares that a single refresh operation mints more than one credential. -// The refresh is attached to a primary credential; each additional output -// maps a strategy-defined semantic output id to a sibling credential whose -// env_vars receive the minted value. -type ProviderCredentialRefreshOutput struct { - state protoimpl.MessageState `protogen:"open.v1"` - Output string `protobuf:"bytes,1,opt,name=output,proto3" json:"output,omitempty"` // strategy-defined semantic output id (e.g. "session_token") - Credential string `protobuf:"bytes,2,opt,name=credential,proto3" json:"credential,omitempty"` // sibling credential name whose env_vars receive this output +type ListDelegatedIdentityCredentialsResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Credentials []*DelegatedIdentityCredentialSummary `protobuf:"bytes,1,rep,name=credentials,proto3" json:"credentials,omitempty"` + // Token for the next page. Empty when there are no subsequent pages. + NextPageToken string `protobuf:"bytes,2,opt,name=next_page_token,json=nextPageToken,proto3" json:"next_page_token,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } -func (x *ProviderCredentialRefreshOutput) Reset() { - *x = ProviderCredentialRefreshOutput{} - mi := &file_openshell_proto_msgTypes[112] +func (x *ListDelegatedIdentityCredentialsResponse) Reset() { + *x = ListDelegatedIdentityCredentialsResponse{} + mi := &file_openshell_proto_msgTypes[131] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *ProviderCredentialRefreshOutput) String() string { +func (x *ListDelegatedIdentityCredentialsResponse) String() string { return protoimpl.X.MessageStringOf(x) } -func (*ProviderCredentialRefreshOutput) ProtoMessage() {} +func (*ListDelegatedIdentityCredentialsResponse) ProtoMessage() {} -func (x *ProviderCredentialRefreshOutput) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[112] +func (x *ListDelegatedIdentityCredentialsResponse) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[131] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9601,53 +10868,47 @@ func (x *ProviderCredentialRefreshOutput) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use ProviderCredentialRefreshOutput.ProtoReflect.Descriptor instead. -func (*ProviderCredentialRefreshOutput) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{112} +// Deprecated: Use ListDelegatedIdentityCredentialsResponse.ProtoReflect.Descriptor instead. +func (*ListDelegatedIdentityCredentialsResponse) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{131} } -func (x *ProviderCredentialRefreshOutput) GetOutput() string { +func (x *ListDelegatedIdentityCredentialsResponse) GetCredentials() []*DelegatedIdentityCredentialSummary { if x != nil { - return x.Output + return x.Credentials } - return "" + return nil } -func (x *ProviderCredentialRefreshOutput) GetCredential() string { +func (x *ListDelegatedIdentityCredentialsResponse) GetNextPageToken() string { if x != nil { - return x.Credential + return x.NextPageToken } return "" } -type ProviderCredentialRefresh struct { - state protoimpl.MessageState `protogen:"open.v1"` - Strategy ProviderCredentialRefreshStrategy `protobuf:"varint,1,opt,name=strategy,proto3,enum=openshell.v1.ProviderCredentialRefreshStrategy" json:"strategy,omitempty"` - TokenUrl string `protobuf:"bytes,2,opt,name=token_url,json=tokenUrl,proto3" json:"token_url,omitempty"` - Scopes []string `protobuf:"bytes,3,rep,name=scopes,proto3" json:"scopes,omitempty"` - RefreshBefore *durationpb.Duration `protobuf:"bytes,104,opt,name=refresh_before,json=refreshBefore,proto3" json:"refresh_before,omitempty"` - MaxLifetime *durationpb.Duration `protobuf:"bytes,105,opt,name=max_lifetime,json=maxLifetime,proto3" json:"max_lifetime,omitempty"` - Material []*ProviderCredentialRefreshMaterial `protobuf:"bytes,6,rep,name=material,proto3" json:"material,omitempty"` - AdditionalOutputs []*ProviderCredentialRefreshOutput `protobuf:"bytes,7,rep,name=additional_outputs,json=additionalOutputs,proto3" json:"additional_outputs,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache +type GetDelegatedIdentityCredentialStatusRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } -func (x *ProviderCredentialRefresh) Reset() { - *x = ProviderCredentialRefresh{} - mi := &file_openshell_proto_msgTypes[113] +func (x *GetDelegatedIdentityCredentialStatusRequest) Reset() { + *x = GetDelegatedIdentityCredentialStatusRequest{} + mi := &file_openshell_proto_msgTypes[132] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *ProviderCredentialRefresh) String() string { +func (x *GetDelegatedIdentityCredentialStatusRequest) String() string { return protoimpl.X.MessageStringOf(x) } -func (*ProviderCredentialRefresh) ProtoMessage() {} +func (*GetDelegatedIdentityCredentialStatusRequest) ProtoMessage() {} -func (x *ProviderCredentialRefresh) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[113] +func (x *GetDelegatedIdentityCredentialStatusRequest) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[132] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9658,102 +10919,93 @@ func (x *ProviderCredentialRefresh) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use ProviderCredentialRefresh.ProtoReflect.Descriptor instead. -func (*ProviderCredentialRefresh) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{113} +// Deprecated: Use GetDelegatedIdentityCredentialStatusRequest.ProtoReflect.Descriptor instead. +func (*GetDelegatedIdentityCredentialStatusRequest) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{132} } -func (x *ProviderCredentialRefresh) GetStrategy() ProviderCredentialRefreshStrategy { +func (x *GetDelegatedIdentityCredentialStatusRequest) GetId() string { if x != nil { - return x.Strategy + return x.Id } - return ProviderCredentialRefreshStrategy_PROVIDER_CREDENTIAL_REFRESH_STRATEGY_UNSPECIFIED + return "" } -func (x *ProviderCredentialRefresh) GetTokenUrl() string { - if x != nil { - return x.TokenUrl - } - return "" +type GetDelegatedIdentityCredentialStatusResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Credential *DelegatedIdentityCredentialSummary `protobuf:"bytes,1,opt,name=credential,proto3" json:"credential,omitempty"` + CurrentTime *timestamppb.Timestamp `protobuf:"bytes,2,opt,name=current_time,json=currentTime,proto3" json:"current_time,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } -func (x *ProviderCredentialRefresh) GetScopes() []string { - if x != nil { - return x.Scopes - } - return nil +func (x *GetDelegatedIdentityCredentialStatusResponse) Reset() { + *x = GetDelegatedIdentityCredentialStatusResponse{} + mi := &file_openshell_proto_msgTypes[133] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } -func (x *ProviderCredentialRefresh) GetRefreshBefore() *durationpb.Duration { - if x != nil { - return x.RefreshBefore - } - return nil +func (x *GetDelegatedIdentityCredentialStatusResponse) String() string { + return protoimpl.X.MessageStringOf(x) } -func (x *ProviderCredentialRefresh) GetMaxLifetime() *durationpb.Duration { +func (*GetDelegatedIdentityCredentialStatusResponse) ProtoMessage() {} + +func (x *GetDelegatedIdentityCredentialStatusResponse) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[133] if x != nil { - return x.MaxLifetime + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms } - return nil + return mi.MessageOf(x) } -func (x *ProviderCredentialRefresh) GetMaterial() []*ProviderCredentialRefreshMaterial { +// Deprecated: Use GetDelegatedIdentityCredentialStatusResponse.ProtoReflect.Descriptor instead. +func (*GetDelegatedIdentityCredentialStatusResponse) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{133} +} + +func (x *GetDelegatedIdentityCredentialStatusResponse) GetCredential() *DelegatedIdentityCredentialSummary { if x != nil { - return x.Material + return x.Credential } return nil } -func (x *ProviderCredentialRefresh) GetAdditionalOutputs() []*ProviderCredentialRefreshOutput { +func (x *GetDelegatedIdentityCredentialStatusResponse) GetCurrentTime() *timestamppb.Timestamp { if x != nil { - return x.AdditionalOutputs + return x.CurrentTime } return nil } -type ProviderCredentialRefreshStatus struct { - state protoimpl.MessageState `protogen:"open.v1"` - Provider string `protobuf:"bytes,1,opt,name=provider,proto3" json:"provider,omitempty"` - ProviderId string `protobuf:"bytes,2,opt,name=provider_id,json=providerId,proto3" json:"provider_id,omitempty"` - CredentialKey string `protobuf:"bytes,3,opt,name=credential_key,json=credentialKey,proto3" json:"credential_key,omitempty"` - Strategy ProviderCredentialRefreshStrategy `protobuf:"varint,4,opt,name=strategy,proto3,enum=openshell.v1.ProviderCredentialRefreshStrategy" json:"strategy,omitempty"` - Status string `protobuf:"bytes,5,opt,name=status,proto3" json:"status,omitempty"` - ExpirationTime *timestamppb.Timestamp `protobuf:"bytes,106,opt,name=expiration_time,json=expirationTime,proto3" json:"expiration_time,omitempty"` - // Next automatic refresh time. Absence means no automatic retry is scheduled; - // use recovery_action to determine the required recovery workflow. - NextRefreshTime *timestamppb.Timestamp `protobuf:"bytes,107,opt,name=next_refresh_time,json=nextRefreshTime,proto3" json:"next_refresh_time,omitempty"` - LastRefreshTime *timestamppb.Timestamp `protobuf:"bytes,108,opt,name=last_refresh_time,json=lastRefreshTime,proto3" json:"last_refresh_time,omitempty"` - LastError string `protobuf:"bytes,9,opt,name=last_error,json=lastError,proto3" json:"last_error,omitempty"` - RecoveryAction ProviderCredentialRefreshRecoveryAction `protobuf:"varint,10,opt,name=recovery_action,json=recoveryAction,proto3,enum=openshell.v1.ProviderCredentialRefreshRecoveryAction" json:"recovery_action,omitempty"` - // Stable gateway-owned failure identifier, for example - // "oauth_invalid_grant". This is not provider-controlled prose and - // incorporates any recognized top-level OAuth error classification. - FailureCode string `protobuf:"bytes,11,opt,name=failure_code,json=failureCode,proto3" json:"failure_code,omitempty"` - // A bounded, recognized provider subtype that refines failure_code; clients - // do not need a separate provider_error field. Unknown provider-controlled - // values are not persisted or returned. - ProviderErrorSubtype string `protobuf:"bytes,12,opt,name=provider_error_subtype,json=providerErrorSubtype,proto3" json:"provider_error_subtype,omitempty"` - LastErrorTime *timestamppb.Timestamp `protobuf:"bytes,113,opt,name=last_error_time,json=lastErrorTime,proto3" json:"last_error_time,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache +type RevokeDelegatedIdentityCredentialRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + ExpectedResourceVersion uint64 `protobuf:"varint,2,opt,name=expected_resource_version,json=expectedResourceVersion,proto3" json:"expected_resource_version,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } -func (x *ProviderCredentialRefreshStatus) Reset() { - *x = ProviderCredentialRefreshStatus{} - mi := &file_openshell_proto_msgTypes[114] +func (x *RevokeDelegatedIdentityCredentialRequest) Reset() { + *x = RevokeDelegatedIdentityCredentialRequest{} + mi := &file_openshell_proto_msgTypes[134] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *ProviderCredentialRefreshStatus) String() string { +func (x *RevokeDelegatedIdentityCredentialRequest) String() string { return protoimpl.X.MessageStringOf(x) } -func (*ProviderCredentialRefreshStatus) ProtoMessage() {} +func (*RevokeDelegatedIdentityCredentialRequest) ProtoMessage() {} -func (x *ProviderCredentialRefreshStatus) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[114] +func (x *RevokeDelegatedIdentityCredentialRequest) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[134] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9764,126 +11016,159 @@ func (x *ProviderCredentialRefreshStatus) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use ProviderCredentialRefreshStatus.ProtoReflect.Descriptor instead. -func (*ProviderCredentialRefreshStatus) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{114} +// Deprecated: Use RevokeDelegatedIdentityCredentialRequest.ProtoReflect.Descriptor instead. +func (*RevokeDelegatedIdentityCredentialRequest) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{134} } -func (x *ProviderCredentialRefreshStatus) GetProvider() string { +func (x *RevokeDelegatedIdentityCredentialRequest) GetId() string { if x != nil { - return x.Provider + return x.Id } return "" } -func (x *ProviderCredentialRefreshStatus) GetProviderId() string { +func (x *RevokeDelegatedIdentityCredentialRequest) GetExpectedResourceVersion() uint64 { if x != nil { - return x.ProviderId + return x.ExpectedResourceVersion } - return "" + return 0 } -func (x *ProviderCredentialRefreshStatus) GetCredentialKey() string { +type RevokeDelegatedIdentityCredentialResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Revoked bool `protobuf:"varint,2,opt,name=revoked,proto3" json:"revoked,omitempty"` + RevocationTime *timestamppb.Timestamp `protobuf:"bytes,3,opt,name=revocation_time,json=revocationTime,proto3" json:"revocation_time,omitempty"` + ResourceVersion uint64 `protobuf:"varint,4,opt,name=resource_version,json=resourceVersion,proto3" json:"resource_version,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *RevokeDelegatedIdentityCredentialResponse) Reset() { + *x = RevokeDelegatedIdentityCredentialResponse{} + mi := &file_openshell_proto_msgTypes[135] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RevokeDelegatedIdentityCredentialResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RevokeDelegatedIdentityCredentialResponse) ProtoMessage() {} + +func (x *RevokeDelegatedIdentityCredentialResponse) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[135] if x != nil { - return x.CredentialKey + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms } - return "" + return mi.MessageOf(x) } -func (x *ProviderCredentialRefreshStatus) GetStrategy() ProviderCredentialRefreshStrategy { - if x != nil { - return x.Strategy - } - return ProviderCredentialRefreshStrategy_PROVIDER_CREDENTIAL_REFRESH_STRATEGY_UNSPECIFIED +// Deprecated: Use RevokeDelegatedIdentityCredentialResponse.ProtoReflect.Descriptor instead. +func (*RevokeDelegatedIdentityCredentialResponse) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{135} } -func (x *ProviderCredentialRefreshStatus) GetStatus() string { +func (x *RevokeDelegatedIdentityCredentialResponse) GetRevoked() bool { if x != nil { - return x.Status + return x.Revoked } - return "" + return false } -func (x *ProviderCredentialRefreshStatus) GetExpirationTime() *timestamppb.Timestamp { +func (x *RevokeDelegatedIdentityCredentialResponse) GetRevocationTime() *timestamppb.Timestamp { if x != nil { - return x.ExpirationTime + return x.RevocationTime } return nil } -func (x *ProviderCredentialRefreshStatus) GetNextRefreshTime() *timestamppb.Timestamp { +func (x *RevokeDelegatedIdentityCredentialResponse) GetResourceVersion() uint64 { if x != nil { - return x.NextRefreshTime + return x.ResourceVersion } - return nil + return 0 } -func (x *ProviderCredentialRefreshStatus) GetLastRefreshTime() *timestamppb.Timestamp { - if x != nil { - return x.LastRefreshTime - } - return nil +type DeleteDelegatedIdentityCredentialRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + ExpectedResourceVersion uint64 `protobuf:"varint,2,opt,name=expected_resource_version,json=expectedResourceVersion,proto3" json:"expected_resource_version,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } -func (x *ProviderCredentialRefreshStatus) GetLastError() string { - if x != nil { - return x.LastError - } - return "" +func (x *DeleteDelegatedIdentityCredentialRequest) Reset() { + *x = DeleteDelegatedIdentityCredentialRequest{} + mi := &file_openshell_proto_msgTypes[136] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } -func (x *ProviderCredentialRefreshStatus) GetRecoveryAction() ProviderCredentialRefreshRecoveryAction { - if x != nil { - return x.RecoveryAction - } - return ProviderCredentialRefreshRecoveryAction_PROVIDER_CREDENTIAL_REFRESH_RECOVERY_ACTION_UNSPECIFIED +func (x *DeleteDelegatedIdentityCredentialRequest) String() string { + return protoimpl.X.MessageStringOf(x) } -func (x *ProviderCredentialRefreshStatus) GetFailureCode() string { +func (*DeleteDelegatedIdentityCredentialRequest) ProtoMessage() {} + +func (x *DeleteDelegatedIdentityCredentialRequest) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[136] if x != nil { - return x.FailureCode + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms } - return "" + return mi.MessageOf(x) } -func (x *ProviderCredentialRefreshStatus) GetProviderErrorSubtype() string { +// Deprecated: Use DeleteDelegatedIdentityCredentialRequest.ProtoReflect.Descriptor instead. +func (*DeleteDelegatedIdentityCredentialRequest) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{136} +} + +func (x *DeleteDelegatedIdentityCredentialRequest) GetId() string { if x != nil { - return x.ProviderErrorSubtype + return x.Id } return "" } -func (x *ProviderCredentialRefreshStatus) GetLastErrorTime() *timestamppb.Timestamp { +func (x *DeleteDelegatedIdentityCredentialRequest) GetExpectedResourceVersion() uint64 { if x != nil { - return x.LastErrorTime + return x.ExpectedResourceVersion } - return nil + return 0 } -// Provider profile local discovery declaration. -type ProviderProfileDiscovery struct { - state protoimpl.MessageState `protogen:"open.v1"` - // Credential names from ProviderProfile.credentials eligible for local discovery. - Credentials []string `protobuf:"bytes,1,rep,name=credentials,proto3" json:"credentials,omitempty"` +type DeleteDelegatedIdentityCredentialResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Deleted bool `protobuf:"varint,1,opt,name=deleted,proto3" json:"deleted,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } -func (x *ProviderProfileDiscovery) Reset() { - *x = ProviderProfileDiscovery{} - mi := &file_openshell_proto_msgTypes[115] +func (x *DeleteDelegatedIdentityCredentialResponse) Reset() { + *x = DeleteDelegatedIdentityCredentialResponse{} + mi := &file_openshell_proto_msgTypes[137] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *ProviderProfileDiscovery) String() string { +func (x *DeleteDelegatedIdentityCredentialResponse) String() string { return protoimpl.X.MessageStringOf(x) } -func (*ProviderProfileDiscovery) ProtoMessage() {} +func (*DeleteDelegatedIdentityCredentialResponse) ProtoMessage() {} -func (x *ProviderProfileDiscovery) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[115] +func (x *DeleteDelegatedIdentityCredentialResponse) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[137] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9894,16 +11179,16 @@ func (x *ProviderProfileDiscovery) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use ProviderProfileDiscovery.ProtoReflect.Descriptor instead. -func (*ProviderProfileDiscovery) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{115} +// Deprecated: Use DeleteDelegatedIdentityCredentialResponse.ProtoReflect.Descriptor instead. +func (*DeleteDelegatedIdentityCredentialResponse) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{137} } -func (x *ProviderProfileDiscovery) GetCredentials() []string { +func (x *DeleteDelegatedIdentityCredentialResponse) GetDeleted() bool { if x != nil { - return x.Credentials + return x.Deleted } - return nil + return false } type GetProviderRefreshStatusRequest struct { @@ -9918,7 +11203,7 @@ type GetProviderRefreshStatusRequest struct { func (x *GetProviderRefreshStatusRequest) Reset() { *x = GetProviderRefreshStatusRequest{} - mi := &file_openshell_proto_msgTypes[116] + mi := &file_openshell_proto_msgTypes[138] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9930,7 +11215,7 @@ func (x *GetProviderRefreshStatusRequest) String() string { func (*GetProviderRefreshStatusRequest) ProtoMessage() {} func (x *GetProviderRefreshStatusRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[116] + mi := &file_openshell_proto_msgTypes[138] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9943,7 +11228,7 @@ func (x *GetProviderRefreshStatusRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetProviderRefreshStatusRequest.ProtoReflect.Descriptor instead. func (*GetProviderRefreshStatusRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{116} + return file_openshell_proto_rawDescGZIP(), []int{138} } func (x *GetProviderRefreshStatusRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { @@ -9976,7 +11261,7 @@ type GetProviderRefreshStatusResponse struct { func (x *GetProviderRefreshStatusResponse) Reset() { *x = GetProviderRefreshStatusResponse{} - mi := &file_openshell_proto_msgTypes[117] + mi := &file_openshell_proto_msgTypes[139] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9988,7 +11273,7 @@ func (x *GetProviderRefreshStatusResponse) String() string { func (*GetProviderRefreshStatusResponse) ProtoMessage() {} func (x *GetProviderRefreshStatusResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[117] + mi := &file_openshell_proto_msgTypes[139] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10001,7 +11286,7 @@ func (x *GetProviderRefreshStatusResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetProviderRefreshStatusResponse.ProtoReflect.Descriptor instead. func (*GetProviderRefreshStatusResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{117} + return file_openshell_proto_rawDescGZIP(), []int{139} } func (x *GetProviderRefreshStatusResponse) GetCredentials() []*ProviderCredentialRefreshStatus { @@ -10033,7 +11318,7 @@ type ConfigureProviderRefreshRequest struct { func (x *ConfigureProviderRefreshRequest) Reset() { *x = ConfigureProviderRefreshRequest{} - mi := &file_openshell_proto_msgTypes[118] + mi := &file_openshell_proto_msgTypes[140] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10045,7 +11330,7 @@ func (x *ConfigureProviderRefreshRequest) String() string { func (*ConfigureProviderRefreshRequest) ProtoMessage() {} func (x *ConfigureProviderRefreshRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[118] + mi := &file_openshell_proto_msgTypes[140] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10058,7 +11343,7 @@ func (x *ConfigureProviderRefreshRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ConfigureProviderRefreshRequest.ProtoReflect.Descriptor instead. func (*ConfigureProviderRefreshRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{118} + return file_openshell_proto_rawDescGZIP(), []int{140} } func (x *ConfigureProviderRefreshRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { @@ -10126,7 +11411,7 @@ type ConfigureProviderRefreshResponse struct { func (x *ConfigureProviderRefreshResponse) Reset() { *x = ConfigureProviderRefreshResponse{} - mi := &file_openshell_proto_msgTypes[119] + mi := &file_openshell_proto_msgTypes[141] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10138,7 +11423,7 @@ func (x *ConfigureProviderRefreshResponse) String() string { func (*ConfigureProviderRefreshResponse) ProtoMessage() {} func (x *ConfigureProviderRefreshResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[119] + mi := &file_openshell_proto_msgTypes[141] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10151,7 +11436,7 @@ func (x *ConfigureProviderRefreshResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ConfigureProviderRefreshResponse.ProtoReflect.Descriptor instead. func (*ConfigureProviderRefreshResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{119} + return file_openshell_proto_rawDescGZIP(), []int{141} } func (x *ConfigureProviderRefreshResponse) GetStatus() *ProviderCredentialRefreshStatus { @@ -10176,7 +11461,7 @@ type RotateProviderCredentialRequest struct { func (x *RotateProviderCredentialRequest) Reset() { *x = RotateProviderCredentialRequest{} - mi := &file_openshell_proto_msgTypes[120] + mi := &file_openshell_proto_msgTypes[142] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10188,7 +11473,7 @@ func (x *RotateProviderCredentialRequest) String() string { func (*RotateProviderCredentialRequest) ProtoMessage() {} func (x *RotateProviderCredentialRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[120] + mi := &file_openshell_proto_msgTypes[142] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10201,7 +11486,7 @@ func (x *RotateProviderCredentialRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use RotateProviderCredentialRequest.ProtoReflect.Descriptor instead. func (*RotateProviderCredentialRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{120} + return file_openshell_proto_rawDescGZIP(), []int{142} } func (x *RotateProviderCredentialRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { @@ -10241,7 +11526,7 @@ type RotateProviderCredentialResponse struct { func (x *RotateProviderCredentialResponse) Reset() { *x = RotateProviderCredentialResponse{} - mi := &file_openshell_proto_msgTypes[121] + mi := &file_openshell_proto_msgTypes[143] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10253,7 +11538,7 @@ func (x *RotateProviderCredentialResponse) String() string { func (*RotateProviderCredentialResponse) ProtoMessage() {} func (x *RotateProviderCredentialResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[121] + mi := &file_openshell_proto_msgTypes[143] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10266,7 +11551,7 @@ func (x *RotateProviderCredentialResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use RotateProviderCredentialResponse.ProtoReflect.Descriptor instead. func (*RotateProviderCredentialResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{121} + return file_openshell_proto_rawDescGZIP(), []int{143} } func (x *RotateProviderCredentialResponse) GetStatus() *ProviderCredentialRefreshStatus { @@ -10292,7 +11577,7 @@ type DeleteProviderRefreshRequest struct { func (x *DeleteProviderRefreshRequest) Reset() { *x = DeleteProviderRefreshRequest{} - mi := &file_openshell_proto_msgTypes[122] + mi := &file_openshell_proto_msgTypes[144] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10304,7 +11589,7 @@ func (x *DeleteProviderRefreshRequest) String() string { func (*DeleteProviderRefreshRequest) ProtoMessage() {} func (x *DeleteProviderRefreshRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[122] + mi := &file_openshell_proto_msgTypes[144] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10317,7 +11602,7 @@ func (x *DeleteProviderRefreshRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteProviderRefreshRequest.ProtoReflect.Descriptor instead. func (*DeleteProviderRefreshRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{122} + return file_openshell_proto_rawDescGZIP(), []int{144} } func (x *DeleteProviderRefreshRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { @@ -10364,7 +11649,7 @@ type DeleteProviderRefreshResponse struct { func (x *DeleteProviderRefreshResponse) Reset() { *x = DeleteProviderRefreshResponse{} - mi := &file_openshell_proto_msgTypes[123] + mi := &file_openshell_proto_msgTypes[145] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10376,7 +11661,7 @@ func (x *DeleteProviderRefreshResponse) String() string { func (*DeleteProviderRefreshResponse) ProtoMessage() {} func (x *DeleteProviderRefreshResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[123] + mi := &file_openshell_proto_msgTypes[145] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10389,7 +11674,7 @@ func (x *DeleteProviderRefreshResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteProviderRefreshResponse.ProtoReflect.Descriptor instead. func (*DeleteProviderRefreshResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{123} + return file_openshell_proto_rawDescGZIP(), []int{145} } func (x *DeleteProviderRefreshResponse) GetOutcome() DeletionOutcome { @@ -10432,7 +11717,7 @@ type ProviderProfile struct { func (x *ProviderProfile) Reset() { *x = ProviderProfile{} - mi := &file_openshell_proto_msgTypes[124] + mi := &file_openshell_proto_msgTypes[146] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10444,7 +11729,7 @@ func (x *ProviderProfile) String() string { func (*ProviderProfile) ProtoMessage() {} func (x *ProviderProfile) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[124] + mi := &file_openshell_proto_msgTypes[146] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10457,7 +11742,7 @@ func (x *ProviderProfile) ProtoReflect() protoreflect.Message { // Deprecated: Use ProviderProfile.ProtoReflect.Descriptor instead. func (*ProviderProfile) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{124} + return file_openshell_proto_rawDescGZIP(), []int{146} } func (x *ProviderProfile) GetId() string { @@ -10573,7 +11858,7 @@ type ProviderProfileFile struct { func (x *ProviderProfileFile) Reset() { *x = ProviderProfileFile{} - mi := &file_openshell_proto_msgTypes[125] + mi := &file_openshell_proto_msgTypes[147] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10585,7 +11870,7 @@ func (x *ProviderProfileFile) String() string { func (*ProviderProfileFile) ProtoMessage() {} func (x *ProviderProfileFile) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[125] + mi := &file_openshell_proto_msgTypes[147] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10598,7 +11883,7 @@ func (x *ProviderProfileFile) ProtoReflect() protoreflect.Message { // Deprecated: Use ProviderProfileFile.ProtoReflect.Descriptor instead. func (*ProviderProfileFile) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{125} + return file_openshell_proto_rawDescGZIP(), []int{147} } func (x *ProviderProfileFile) GetPath() string { @@ -10632,7 +11917,7 @@ type ProviderProfileResponse struct { func (x *ProviderProfileResponse) Reset() { *x = ProviderProfileResponse{} - mi := &file_openshell_proto_msgTypes[126] + mi := &file_openshell_proto_msgTypes[148] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10644,7 +11929,7 @@ func (x *ProviderProfileResponse) String() string { func (*ProviderProfileResponse) ProtoMessage() {} func (x *ProviderProfileResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[126] + mi := &file_openshell_proto_msgTypes[148] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10657,7 +11942,7 @@ func (x *ProviderProfileResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ProviderProfileResponse.ProtoReflect.Descriptor instead. func (*ProviderProfileResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{126} + return file_openshell_proto_rawDescGZIP(), []int{148} } func (x *ProviderProfileResponse) GetProfile() *ProviderProfile { @@ -10679,7 +11964,7 @@ type ListProviderProfilesResponse struct { func (x *ListProviderProfilesResponse) Reset() { *x = ListProviderProfilesResponse{} - mi := &file_openshell_proto_msgTypes[127] + mi := &file_openshell_proto_msgTypes[149] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10691,7 +11976,7 @@ func (x *ListProviderProfilesResponse) String() string { func (*ListProviderProfilesResponse) ProtoMessage() {} func (x *ListProviderProfilesResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[127] + mi := &file_openshell_proto_msgTypes[149] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10704,7 +11989,7 @@ func (x *ListProviderProfilesResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListProviderProfilesResponse.ProtoReflect.Descriptor instead. func (*ListProviderProfilesResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{127} + return file_openshell_proto_rawDescGZIP(), []int{149} } func (x *ListProviderProfilesResponse) GetProfiles() []*ProviderProfile { @@ -10736,7 +12021,7 @@ type ImportProviderProfilesRequest struct { func (x *ImportProviderProfilesRequest) Reset() { *x = ImportProviderProfilesRequest{} - mi := &file_openshell_proto_msgTypes[128] + mi := &file_openshell_proto_msgTypes[150] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10748,7 +12033,7 @@ func (x *ImportProviderProfilesRequest) String() string { func (*ImportProviderProfilesRequest) ProtoMessage() {} func (x *ImportProviderProfilesRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[128] + mi := &file_openshell_proto_msgTypes[150] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10761,7 +12046,7 @@ func (x *ImportProviderProfilesRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ImportProviderProfilesRequest.ProtoReflect.Descriptor instead. func (*ImportProviderProfilesRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{128} + return file_openshell_proto_rawDescGZIP(), []int{150} } func (x *ImportProviderProfilesRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { @@ -10797,7 +12082,7 @@ type ImportProviderProfilesResponse struct { func (x *ImportProviderProfilesResponse) Reset() { *x = ImportProviderProfilesResponse{} - mi := &file_openshell_proto_msgTypes[129] + mi := &file_openshell_proto_msgTypes[151] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10809,7 +12094,7 @@ func (x *ImportProviderProfilesResponse) String() string { func (*ImportProviderProfilesResponse) ProtoMessage() {} func (x *ImportProviderProfilesResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[129] + mi := &file_openshell_proto_msgTypes[151] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10822,7 +12107,7 @@ func (x *ImportProviderProfilesResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ImportProviderProfilesResponse.ProtoReflect.Descriptor instead. func (*ImportProviderProfilesResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{129} + return file_openshell_proto_rawDescGZIP(), []int{151} } func (x *ImportProviderProfilesResponse) GetDiagnostics() []*ProviderProfileDiagnostic { @@ -10868,7 +12153,7 @@ type UpdateProviderProfilesRequest struct { func (x *UpdateProviderProfilesRequest) Reset() { *x = UpdateProviderProfilesRequest{} - mi := &file_openshell_proto_msgTypes[130] + mi := &file_openshell_proto_msgTypes[152] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10880,7 +12165,7 @@ func (x *UpdateProviderProfilesRequest) String() string { func (*UpdateProviderProfilesRequest) ProtoMessage() {} func (x *UpdateProviderProfilesRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[130] + mi := &file_openshell_proto_msgTypes[152] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10893,7 +12178,7 @@ func (x *UpdateProviderProfilesRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateProviderProfilesRequest.ProtoReflect.Descriptor instead. func (*UpdateProviderProfilesRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{130} + return file_openshell_proto_rawDescGZIP(), []int{152} } func (x *UpdateProviderProfilesRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { @@ -10943,7 +12228,7 @@ type UpdateProviderProfilesResponse struct { func (x *UpdateProviderProfilesResponse) Reset() { *x = UpdateProviderProfilesResponse{} - mi := &file_openshell_proto_msgTypes[131] + mi := &file_openshell_proto_msgTypes[153] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10955,7 +12240,7 @@ func (x *UpdateProviderProfilesResponse) String() string { func (*UpdateProviderProfilesResponse) ProtoMessage() {} func (x *UpdateProviderProfilesResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[131] + mi := &file_openshell_proto_msgTypes[153] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10968,7 +12253,7 @@ func (x *UpdateProviderProfilesResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateProviderProfilesResponse.ProtoReflect.Descriptor instead. func (*UpdateProviderProfilesResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{131} + return file_openshell_proto_rawDescGZIP(), []int{153} } func (x *UpdateProviderProfilesResponse) GetDiagnostics() []*ProviderProfileDiagnostic { @@ -11004,7 +12289,7 @@ type LintProviderProfilesRequest struct { func (x *LintProviderProfilesRequest) Reset() { *x = LintProviderProfilesRequest{} - mi := &file_openshell_proto_msgTypes[132] + mi := &file_openshell_proto_msgTypes[154] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11016,7 +12301,7 @@ func (x *LintProviderProfilesRequest) String() string { func (*LintProviderProfilesRequest) ProtoMessage() {} func (x *LintProviderProfilesRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[132] + mi := &file_openshell_proto_msgTypes[154] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11029,7 +12314,7 @@ func (x *LintProviderProfilesRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use LintProviderProfilesRequest.ProtoReflect.Descriptor instead. func (*LintProviderProfilesRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{132} + return file_openshell_proto_rawDescGZIP(), []int{154} } func (x *LintProviderProfilesRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { @@ -11060,7 +12345,7 @@ type LintProviderProfilesResponse struct { func (x *LintProviderProfilesResponse) Reset() { *x = LintProviderProfilesResponse{} - mi := &file_openshell_proto_msgTypes[133] + mi := &file_openshell_proto_msgTypes[155] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11072,7 +12357,7 @@ func (x *LintProviderProfilesResponse) String() string { func (*LintProviderProfilesResponse) ProtoMessage() {} func (x *LintProviderProfilesResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[133] + mi := &file_openshell_proto_msgTypes[155] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11085,7 +12370,7 @@ func (x *LintProviderProfilesResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use LintProviderProfilesResponse.ProtoReflect.Descriptor instead. func (*LintProviderProfilesResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{133} + return file_openshell_proto_rawDescGZIP(), []int{155} } func (x *LintProviderProfilesResponse) GetDiagnostics() []*ProviderProfileDiagnostic { @@ -11119,7 +12404,7 @@ type DeleteProviderResponse struct { func (x *DeleteProviderResponse) Reset() { *x = DeleteProviderResponse{} - mi := &file_openshell_proto_msgTypes[134] + mi := &file_openshell_proto_msgTypes[156] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11131,7 +12416,7 @@ func (x *DeleteProviderResponse) String() string { func (*DeleteProviderResponse) ProtoMessage() {} func (x *DeleteProviderResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[134] + mi := &file_openshell_proto_msgTypes[156] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11144,7 +12429,7 @@ func (x *DeleteProviderResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteProviderResponse.ProtoReflect.Descriptor instead. func (*DeleteProviderResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{134} + return file_openshell_proto_rawDescGZIP(), []int{156} } func (x *DeleteProviderResponse) GetOutcome() DeletionOutcome { @@ -11170,7 +12455,7 @@ type DeleteProviderProfileRequest struct { func (x *DeleteProviderProfileRequest) Reset() { *x = DeleteProviderProfileRequest{} - mi := &file_openshell_proto_msgTypes[135] + mi := &file_openshell_proto_msgTypes[157] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11182,7 +12467,7 @@ func (x *DeleteProviderProfileRequest) String() string { func (*DeleteProviderProfileRequest) ProtoMessage() {} func (x *DeleteProviderProfileRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[135] + mi := &file_openshell_proto_msgTypes[157] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11195,7 +12480,7 @@ func (x *DeleteProviderProfileRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteProviderProfileRequest.ProtoReflect.Descriptor instead. func (*DeleteProviderProfileRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{135} + return file_openshell_proto_rawDescGZIP(), []int{157} } func (x *DeleteProviderProfileRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { @@ -11236,7 +12521,7 @@ type DeleteProviderProfileResponse struct { func (x *DeleteProviderProfileResponse) Reset() { *x = DeleteProviderProfileResponse{} - mi := &file_openshell_proto_msgTypes[136] + mi := &file_openshell_proto_msgTypes[158] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11248,7 +12533,7 @@ func (x *DeleteProviderProfileResponse) String() string { func (*DeleteProviderProfileResponse) ProtoMessage() {} func (x *DeleteProviderProfileResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[136] + mi := &file_openshell_proto_msgTypes[158] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11261,7 +12546,7 @@ func (x *DeleteProviderProfileResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteProviderProfileResponse.ProtoReflect.Descriptor instead. func (*DeleteProviderProfileResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{136} + return file_openshell_proto_rawDescGZIP(), []int{158} } func (x *DeleteProviderProfileResponse) GetOutcome() DeletionOutcome { @@ -11290,7 +12575,7 @@ type GetSandboxProviderEnvironmentRequest struct { func (x *GetSandboxProviderEnvironmentRequest) Reset() { *x = GetSandboxProviderEnvironmentRequest{} - mi := &file_openshell_proto_msgTypes[137] + mi := &file_openshell_proto_msgTypes[159] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11302,7 +12587,7 @@ func (x *GetSandboxProviderEnvironmentRequest) String() string { func (*GetSandboxProviderEnvironmentRequest) ProtoMessage() {} func (x *GetSandboxProviderEnvironmentRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[137] + mi := &file_openshell_proto_msgTypes[159] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11315,7 +12600,7 @@ func (x *GetSandboxProviderEnvironmentRequest) ProtoReflect() protoreflect.Messa // Deprecated: Use GetSandboxProviderEnvironmentRequest.ProtoReflect.Descriptor instead. func (*GetSandboxProviderEnvironmentRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{137} + return file_openshell_proto_rawDescGZIP(), []int{159} } func (x *GetSandboxProviderEnvironmentRequest) GetSandboxId() string { @@ -11351,7 +12636,7 @@ type StaticCredentialEndpointBinding struct { func (x *StaticCredentialEndpointBinding) Reset() { *x = StaticCredentialEndpointBinding{} - mi := &file_openshell_proto_msgTypes[138] + mi := &file_openshell_proto_msgTypes[160] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11363,7 +12648,7 @@ func (x *StaticCredentialEndpointBinding) String() string { func (*StaticCredentialEndpointBinding) ProtoMessage() {} func (x *StaticCredentialEndpointBinding) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[138] + mi := &file_openshell_proto_msgTypes[160] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11376,7 +12661,7 @@ func (x *StaticCredentialEndpointBinding) ProtoReflect() protoreflect.Message { // Deprecated: Use StaticCredentialEndpointBinding.ProtoReflect.Descriptor instead. func (*StaticCredentialEndpointBinding) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{138} + return file_openshell_proto_rawDescGZIP(), []int{160} } func (x *StaticCredentialEndpointBinding) GetHost() string { @@ -11420,7 +12705,7 @@ type StaticCredentialBinding struct { func (x *StaticCredentialBinding) Reset() { *x = StaticCredentialBinding{} - mi := &file_openshell_proto_msgTypes[139] + mi := &file_openshell_proto_msgTypes[161] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11432,7 +12717,7 @@ func (x *StaticCredentialBinding) String() string { func (*StaticCredentialBinding) ProtoMessage() {} func (x *StaticCredentialBinding) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[139] + mi := &file_openshell_proto_msgTypes[161] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11445,7 +12730,7 @@ func (x *StaticCredentialBinding) ProtoReflect() protoreflect.Message { // Deprecated: Use StaticCredentialBinding.ProtoReflect.Descriptor instead. func (*StaticCredentialBinding) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{139} + return file_openshell_proto_rawDescGZIP(), []int{161} } func (x *StaticCredentialBinding) GetEndpoints() []*StaticCredentialEndpointBinding { @@ -11513,7 +12798,7 @@ type GetSandboxProviderEnvironmentResponse struct { func (x *GetSandboxProviderEnvironmentResponse) Reset() { *x = GetSandboxProviderEnvironmentResponse{} - mi := &file_openshell_proto_msgTypes[140] + mi := &file_openshell_proto_msgTypes[162] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11525,7 +12810,7 @@ func (x *GetSandboxProviderEnvironmentResponse) String() string { func (*GetSandboxProviderEnvironmentResponse) ProtoMessage() {} func (x *GetSandboxProviderEnvironmentResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[140] + mi := &file_openshell_proto_msgTypes[162] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11538,7 +12823,7 @@ func (x *GetSandboxProviderEnvironmentResponse) ProtoReflect() protoreflect.Mess // Deprecated: Use GetSandboxProviderEnvironmentResponse.ProtoReflect.Descriptor instead. func (*GetSandboxProviderEnvironmentResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{140} + return file_openshell_proto_rawDescGZIP(), []int{162} } func (x *GetSandboxProviderEnvironmentResponse) GetEnvironment() map[string]string { @@ -11642,7 +12927,7 @@ type ExchangeProviderSubjectTokenRequest struct { func (x *ExchangeProviderSubjectTokenRequest) Reset() { *x = ExchangeProviderSubjectTokenRequest{} - mi := &file_openshell_proto_msgTypes[141] + mi := &file_openshell_proto_msgTypes[163] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11654,7 +12939,7 @@ func (x *ExchangeProviderSubjectTokenRequest) String() string { func (*ExchangeProviderSubjectTokenRequest) ProtoMessage() {} func (x *ExchangeProviderSubjectTokenRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[141] + mi := &file_openshell_proto_msgTypes[163] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11667,7 +12952,7 @@ func (x *ExchangeProviderSubjectTokenRequest) ProtoReflect() protoreflect.Messag // Deprecated: Use ExchangeProviderSubjectTokenRequest.ProtoReflect.Descriptor instead. func (*ExchangeProviderSubjectTokenRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{141} + return file_openshell_proto_rawDescGZIP(), []int{163} } func (x *ExchangeProviderSubjectTokenRequest) GetSandboxId() string { @@ -11709,7 +12994,7 @@ type ExchangeProviderSubjectTokenResponse struct { func (x *ExchangeProviderSubjectTokenResponse) Reset() { *x = ExchangeProviderSubjectTokenResponse{} - mi := &file_openshell_proto_msgTypes[142] + mi := &file_openshell_proto_msgTypes[164] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11721,7 +13006,7 @@ func (x *ExchangeProviderSubjectTokenResponse) String() string { func (*ExchangeProviderSubjectTokenResponse) ProtoMessage() {} func (x *ExchangeProviderSubjectTokenResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[142] + mi := &file_openshell_proto_msgTypes[164] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11734,7 +13019,7 @@ func (x *ExchangeProviderSubjectTokenResponse) ProtoReflect() protoreflect.Messa // Deprecated: Use ExchangeProviderSubjectTokenResponse.ProtoReflect.Descriptor instead. func (*ExchangeProviderSubjectTokenResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{142} + return file_openshell_proto_rawDescGZIP(), []int{164} } func (x *ExchangeProviderSubjectTokenResponse) GetAccessToken() string { @@ -11807,7 +13092,7 @@ type UpdateConfigRequest struct { func (x *UpdateConfigRequest) Reset() { *x = UpdateConfigRequest{} - mi := &file_openshell_proto_msgTypes[143] + mi := &file_openshell_proto_msgTypes[165] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11819,7 +13104,7 @@ func (x *UpdateConfigRequest) String() string { func (*UpdateConfigRequest) ProtoMessage() {} func (x *UpdateConfigRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[143] + mi := &file_openshell_proto_msgTypes[165] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11832,7 +13117,7 @@ func (x *UpdateConfigRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateConfigRequest.ProtoReflect.Descriptor instead. func (*UpdateConfigRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{143} + return file_openshell_proto_rawDescGZIP(), []int{165} } func (x *UpdateConfigRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { @@ -11929,7 +13214,7 @@ type PolicyMergeOperation struct { func (x *PolicyMergeOperation) Reset() { *x = PolicyMergeOperation{} - mi := &file_openshell_proto_msgTypes[144] + mi := &file_openshell_proto_msgTypes[166] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11941,7 +13226,7 @@ func (x *PolicyMergeOperation) String() string { func (*PolicyMergeOperation) ProtoMessage() {} func (x *PolicyMergeOperation) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[144] + mi := &file_openshell_proto_msgTypes[166] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11954,7 +13239,7 @@ func (x *PolicyMergeOperation) ProtoReflect() protoreflect.Message { // Deprecated: Use PolicyMergeOperation.ProtoReflect.Descriptor instead. func (*PolicyMergeOperation) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{144} + return file_openshell_proto_rawDescGZIP(), []int{166} } func (x *PolicyMergeOperation) GetOperation() isPolicyMergeOperation_Operation { @@ -12068,7 +13353,7 @@ type AddNetworkRule struct { func (x *AddNetworkRule) Reset() { *x = AddNetworkRule{} - mi := &file_openshell_proto_msgTypes[145] + mi := &file_openshell_proto_msgTypes[167] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12080,7 +13365,7 @@ func (x *AddNetworkRule) String() string { func (*AddNetworkRule) ProtoMessage() {} func (x *AddNetworkRule) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[145] + mi := &file_openshell_proto_msgTypes[167] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12093,7 +13378,7 @@ func (x *AddNetworkRule) ProtoReflect() protoreflect.Message { // Deprecated: Use AddNetworkRule.ProtoReflect.Descriptor instead. func (*AddNetworkRule) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{145} + return file_openshell_proto_rawDescGZIP(), []int{167} } func (x *AddNetworkRule) GetRuleName() string { @@ -12121,7 +13406,7 @@ type RemoveNetworkEndpoint struct { func (x *RemoveNetworkEndpoint) Reset() { *x = RemoveNetworkEndpoint{} - mi := &file_openshell_proto_msgTypes[146] + mi := &file_openshell_proto_msgTypes[168] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12133,7 +13418,7 @@ func (x *RemoveNetworkEndpoint) String() string { func (*RemoveNetworkEndpoint) ProtoMessage() {} func (x *RemoveNetworkEndpoint) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[146] + mi := &file_openshell_proto_msgTypes[168] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12146,7 +13431,7 @@ func (x *RemoveNetworkEndpoint) ProtoReflect() protoreflect.Message { // Deprecated: Use RemoveNetworkEndpoint.ProtoReflect.Descriptor instead. func (*RemoveNetworkEndpoint) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{146} + return file_openshell_proto_rawDescGZIP(), []int{168} } func (x *RemoveNetworkEndpoint) GetRuleName() string { @@ -12179,7 +13464,7 @@ type RemoveNetworkRule struct { func (x *RemoveNetworkRule) Reset() { *x = RemoveNetworkRule{} - mi := &file_openshell_proto_msgTypes[147] + mi := &file_openshell_proto_msgTypes[169] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12191,7 +13476,7 @@ func (x *RemoveNetworkRule) String() string { func (*RemoveNetworkRule) ProtoMessage() {} func (x *RemoveNetworkRule) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[147] + mi := &file_openshell_proto_msgTypes[169] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12204,7 +13489,7 @@ func (x *RemoveNetworkRule) ProtoReflect() protoreflect.Message { // Deprecated: Use RemoveNetworkRule.ProtoReflect.Descriptor instead. func (*RemoveNetworkRule) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{147} + return file_openshell_proto_rawDescGZIP(), []int{169} } func (x *RemoveNetworkRule) GetRuleName() string { @@ -12233,7 +13518,7 @@ type L7RuleTarget struct { func (x *L7RuleTarget) Reset() { *x = L7RuleTarget{} - mi := &file_openshell_proto_msgTypes[148] + mi := &file_openshell_proto_msgTypes[170] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12245,7 +13530,7 @@ func (x *L7RuleTarget) String() string { func (*L7RuleTarget) ProtoMessage() {} func (x *L7RuleTarget) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[148] + mi := &file_openshell_proto_msgTypes[170] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12258,7 +13543,7 @@ func (x *L7RuleTarget) ProtoReflect() protoreflect.Message { // Deprecated: Use L7RuleTarget.ProtoReflect.Descriptor instead. func (*L7RuleTarget) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{148} + return file_openshell_proto_rawDescGZIP(), []int{170} } func (x *L7RuleTarget) GetRuleName() string { @@ -12313,7 +13598,7 @@ type AddDenyRules struct { func (x *AddDenyRules) Reset() { *x = AddDenyRules{} - mi := &file_openshell_proto_msgTypes[149] + mi := &file_openshell_proto_msgTypes[171] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12325,7 +13610,7 @@ func (x *AddDenyRules) String() string { func (*AddDenyRules) ProtoMessage() {} func (x *AddDenyRules) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[149] + mi := &file_openshell_proto_msgTypes[171] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12338,7 +13623,7 @@ func (x *AddDenyRules) ProtoReflect() protoreflect.Message { // Deprecated: Use AddDenyRules.ProtoReflect.Descriptor instead. func (*AddDenyRules) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{149} + return file_openshell_proto_rawDescGZIP(), []int{171} } func (x *AddDenyRules) GetDenyRules() []*sandboxv1.L7DenyRule { @@ -12365,7 +13650,7 @@ type AddAllowRules struct { func (x *AddAllowRules) Reset() { *x = AddAllowRules{} - mi := &file_openshell_proto_msgTypes[150] + mi := &file_openshell_proto_msgTypes[172] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12377,7 +13662,7 @@ func (x *AddAllowRules) String() string { func (*AddAllowRules) ProtoMessage() {} func (x *AddAllowRules) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[150] + mi := &file_openshell_proto_msgTypes[172] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12390,7 +13675,7 @@ func (x *AddAllowRules) ProtoReflect() protoreflect.Message { // Deprecated: Use AddAllowRules.ProtoReflect.Descriptor instead. func (*AddAllowRules) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{150} + return file_openshell_proto_rawDescGZIP(), []int{172} } func (x *AddAllowRules) GetRules() []*sandboxv1.L7Rule { @@ -12417,7 +13702,7 @@ type RemoveNetworkBinary struct { func (x *RemoveNetworkBinary) Reset() { *x = RemoveNetworkBinary{} - mi := &file_openshell_proto_msgTypes[151] + mi := &file_openshell_proto_msgTypes[173] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12429,7 +13714,7 @@ func (x *RemoveNetworkBinary) String() string { func (*RemoveNetworkBinary) ProtoMessage() {} func (x *RemoveNetworkBinary) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[151] + mi := &file_openshell_proto_msgTypes[173] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12442,7 +13727,7 @@ func (x *RemoveNetworkBinary) ProtoReflect() protoreflect.Message { // Deprecated: Use RemoveNetworkBinary.ProtoReflect.Descriptor instead. func (*RemoveNetworkBinary) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{151} + return file_openshell_proto_rawDescGZIP(), []int{173} } func (x *RemoveNetworkBinary) GetRuleName() string { @@ -12478,7 +13763,7 @@ type UpdateConfigResponse struct { func (x *UpdateConfigResponse) Reset() { *x = UpdateConfigResponse{} - mi := &file_openshell_proto_msgTypes[152] + mi := &file_openshell_proto_msgTypes[174] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12490,7 +13775,7 @@ func (x *UpdateConfigResponse) String() string { func (*UpdateConfigResponse) ProtoMessage() {} func (x *UpdateConfigResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[152] + mi := &file_openshell_proto_msgTypes[174] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12503,7 +13788,7 @@ func (x *UpdateConfigResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateConfigResponse.ProtoReflect.Descriptor instead. func (*UpdateConfigResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{152} + return file_openshell_proto_rawDescGZIP(), []int{174} } func (x *UpdateConfigResponse) GetVersion() uint32 { @@ -12557,7 +13842,7 @@ type GetSandboxPolicyStatusRequest struct { func (x *GetSandboxPolicyStatusRequest) Reset() { *x = GetSandboxPolicyStatusRequest{} - mi := &file_openshell_proto_msgTypes[153] + mi := &file_openshell_proto_msgTypes[175] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12569,7 +13854,7 @@ func (x *GetSandboxPolicyStatusRequest) String() string { func (*GetSandboxPolicyStatusRequest) ProtoMessage() {} func (x *GetSandboxPolicyStatusRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[153] + mi := &file_openshell_proto_msgTypes[175] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12582,7 +13867,7 @@ func (x *GetSandboxPolicyStatusRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetSandboxPolicyStatusRequest.ProtoReflect.Descriptor instead. func (*GetSandboxPolicyStatusRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{153} + return file_openshell_proto_rawDescGZIP(), []int{175} } func (x *GetSandboxPolicyStatusRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { @@ -12626,7 +13911,7 @@ type GetSandboxPolicyStatusResponse struct { func (x *GetSandboxPolicyStatusResponse) Reset() { *x = GetSandboxPolicyStatusResponse{} - mi := &file_openshell_proto_msgTypes[154] + mi := &file_openshell_proto_msgTypes[176] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12638,7 +13923,7 @@ func (x *GetSandboxPolicyStatusResponse) String() string { func (*GetSandboxPolicyStatusResponse) ProtoMessage() {} func (x *GetSandboxPolicyStatusResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[154] + mi := &file_openshell_proto_msgTypes[176] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12651,7 +13936,7 @@ func (x *GetSandboxPolicyStatusResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetSandboxPolicyStatusResponse.ProtoReflect.Descriptor instead. func (*GetSandboxPolicyStatusResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{154} + return file_openshell_proto_rawDescGZIP(), []int{176} } func (x *GetSandboxPolicyStatusResponse) GetRevision() *SandboxPolicyRevision { @@ -12688,7 +13973,7 @@ type ListSandboxPoliciesRequest struct { func (x *ListSandboxPoliciesRequest) Reset() { *x = ListSandboxPoliciesRequest{} - mi := &file_openshell_proto_msgTypes[155] + mi := &file_openshell_proto_msgTypes[177] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12700,7 +13985,7 @@ func (x *ListSandboxPoliciesRequest) String() string { func (*ListSandboxPoliciesRequest) ProtoMessage() {} func (x *ListSandboxPoliciesRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[155] + mi := &file_openshell_proto_msgTypes[177] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12713,7 +13998,7 @@ func (x *ListSandboxPoliciesRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListSandboxPoliciesRequest.ProtoReflect.Descriptor instead. func (*ListSandboxPoliciesRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{155} + return file_openshell_proto_rawDescGZIP(), []int{177} } func (x *ListSandboxPoliciesRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { @@ -12765,7 +14050,7 @@ type ListSandboxPoliciesResponse struct { func (x *ListSandboxPoliciesResponse) Reset() { *x = ListSandboxPoliciesResponse{} - mi := &file_openshell_proto_msgTypes[156] + mi := &file_openshell_proto_msgTypes[178] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12777,7 +14062,7 @@ func (x *ListSandboxPoliciesResponse) String() string { func (*ListSandboxPoliciesResponse) ProtoMessage() {} func (x *ListSandboxPoliciesResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[156] + mi := &file_openshell_proto_msgTypes[178] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12790,7 +14075,7 @@ func (x *ListSandboxPoliciesResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListSandboxPoliciesResponse.ProtoReflect.Descriptor instead. func (*ListSandboxPoliciesResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{156} + return file_openshell_proto_rawDescGZIP(), []int{178} } func (x *ListSandboxPoliciesResponse) GetRevisions() []*SandboxPolicyRevision { @@ -12824,7 +14109,7 @@ type ReportPolicyStatusRequest struct { func (x *ReportPolicyStatusRequest) Reset() { *x = ReportPolicyStatusRequest{} - mi := &file_openshell_proto_msgTypes[157] + mi := &file_openshell_proto_msgTypes[179] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12836,7 +14121,7 @@ func (x *ReportPolicyStatusRequest) String() string { func (*ReportPolicyStatusRequest) ProtoMessage() {} func (x *ReportPolicyStatusRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[157] + mi := &file_openshell_proto_msgTypes[179] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12849,7 +14134,7 @@ func (x *ReportPolicyStatusRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ReportPolicyStatusRequest.ProtoReflect.Descriptor instead. func (*ReportPolicyStatusRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{157} + return file_openshell_proto_rawDescGZIP(), []int{179} } func (x *ReportPolicyStatusRequest) GetSandboxId() string { @@ -12889,7 +14174,7 @@ type ReportPolicyStatusResponse struct { func (x *ReportPolicyStatusResponse) Reset() { *x = ReportPolicyStatusResponse{} - mi := &file_openshell_proto_msgTypes[158] + mi := &file_openshell_proto_msgTypes[180] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12901,7 +14186,7 @@ func (x *ReportPolicyStatusResponse) String() string { func (*ReportPolicyStatusResponse) ProtoMessage() {} func (x *ReportPolicyStatusResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[158] + mi := &file_openshell_proto_msgTypes[180] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12914,7 +14199,7 @@ func (x *ReportPolicyStatusResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ReportPolicyStatusResponse.ProtoReflect.Descriptor instead. func (*ReportPolicyStatusResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{158} + return file_openshell_proto_rawDescGZIP(), []int{180} } type SandboxConfigurationAdmission struct { @@ -12932,7 +14217,7 @@ type SandboxConfigurationAdmission struct { func (x *SandboxConfigurationAdmission) Reset() { *x = SandboxConfigurationAdmission{} - mi := &file_openshell_proto_msgTypes[159] + mi := &file_openshell_proto_msgTypes[181] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12944,7 +14229,7 @@ func (x *SandboxConfigurationAdmission) String() string { func (*SandboxConfigurationAdmission) ProtoMessage() {} func (x *SandboxConfigurationAdmission) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[159] + mi := &file_openshell_proto_msgTypes[181] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12957,7 +14242,7 @@ func (x *SandboxConfigurationAdmission) ProtoReflect() protoreflect.Message { // Deprecated: Use SandboxConfigurationAdmission.ProtoReflect.Descriptor instead. func (*SandboxConfigurationAdmission) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{159} + return file_openshell_proto_rawDescGZIP(), []int{181} } func (x *SandboxConfigurationAdmission) GetInstanceId() string { @@ -13021,7 +14306,7 @@ type ReportSandboxConfigurationRequest struct { func (x *ReportSandboxConfigurationRequest) Reset() { *x = ReportSandboxConfigurationRequest{} - mi := &file_openshell_proto_msgTypes[160] + mi := &file_openshell_proto_msgTypes[182] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13033,7 +14318,7 @@ func (x *ReportSandboxConfigurationRequest) String() string { func (*ReportSandboxConfigurationRequest) ProtoMessage() {} func (x *ReportSandboxConfigurationRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[160] + mi := &file_openshell_proto_msgTypes[182] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13046,7 +14331,7 @@ func (x *ReportSandboxConfigurationRequest) ProtoReflect() protoreflect.Message // Deprecated: Use ReportSandboxConfigurationRequest.ProtoReflect.Descriptor instead. func (*ReportSandboxConfigurationRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{160} + return file_openshell_proto_rawDescGZIP(), []int{182} } func (x *ReportSandboxConfigurationRequest) GetSandboxId() string { @@ -13078,7 +14363,7 @@ type ReportSandboxConfigurationResponse struct { func (x *ReportSandboxConfigurationResponse) Reset() { *x = ReportSandboxConfigurationResponse{} - mi := &file_openshell_proto_msgTypes[161] + mi := &file_openshell_proto_msgTypes[183] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13090,7 +14375,7 @@ func (x *ReportSandboxConfigurationResponse) String() string { func (*ReportSandboxConfigurationResponse) ProtoMessage() {} func (x *ReportSandboxConfigurationResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[161] + mi := &file_openshell_proto_msgTypes[183] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13103,7 +14388,7 @@ func (x *ReportSandboxConfigurationResponse) ProtoReflect() protoreflect.Message // Deprecated: Use ReportSandboxConfigurationResponse.ProtoReflect.Descriptor instead. func (*ReportSandboxConfigurationResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{161} + return file_openshell_proto_rawDescGZIP(), []int{183} } // A versioned policy revision with metadata. @@ -13136,7 +14421,7 @@ type SandboxPolicyRevision struct { func (x *SandboxPolicyRevision) Reset() { *x = SandboxPolicyRevision{} - mi := &file_openshell_proto_msgTypes[162] + mi := &file_openshell_proto_msgTypes[184] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13148,7 +14433,7 @@ func (x *SandboxPolicyRevision) String() string { func (*SandboxPolicyRevision) ProtoMessage() {} func (x *SandboxPolicyRevision) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[162] + mi := &file_openshell_proto_msgTypes[184] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13161,7 +14446,7 @@ func (x *SandboxPolicyRevision) ProtoReflect() protoreflect.Message { // Deprecated: Use SandboxPolicyRevision.ProtoReflect.Descriptor instead. func (*SandboxPolicyRevision) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{162} + return file_openshell_proto_rawDescGZIP(), []int{184} } func (x *SandboxPolicyRevision) GetVersion() uint32 { @@ -13241,7 +14526,7 @@ type GetSandboxLogsRequest struct { func (x *GetSandboxLogsRequest) Reset() { *x = GetSandboxLogsRequest{} - mi := &file_openshell_proto_msgTypes[163] + mi := &file_openshell_proto_msgTypes[185] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13253,7 +14538,7 @@ func (x *GetSandboxLogsRequest) String() string { func (*GetSandboxLogsRequest) ProtoMessage() {} func (x *GetSandboxLogsRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[163] + mi := &file_openshell_proto_msgTypes[185] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13266,7 +14551,7 @@ func (x *GetSandboxLogsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetSandboxLogsRequest.ProtoReflect.Descriptor instead. func (*GetSandboxLogsRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{163} + return file_openshell_proto_rawDescGZIP(), []int{185} } func (x *GetSandboxLogsRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { @@ -13324,7 +14609,7 @@ type PushSandboxLogsRequest struct { func (x *PushSandboxLogsRequest) Reset() { *x = PushSandboxLogsRequest{} - mi := &file_openshell_proto_msgTypes[164] + mi := &file_openshell_proto_msgTypes[186] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13336,7 +14621,7 @@ func (x *PushSandboxLogsRequest) String() string { func (*PushSandboxLogsRequest) ProtoMessage() {} func (x *PushSandboxLogsRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[164] + mi := &file_openshell_proto_msgTypes[186] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13349,7 +14634,7 @@ func (x *PushSandboxLogsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use PushSandboxLogsRequest.ProtoReflect.Descriptor instead. func (*PushSandboxLogsRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{164} + return file_openshell_proto_rawDescGZIP(), []int{186} } func (x *PushSandboxLogsRequest) GetSandboxId() string { @@ -13375,7 +14660,7 @@ type PushSandboxLogsResponse struct { func (x *PushSandboxLogsResponse) Reset() { *x = PushSandboxLogsResponse{} - mi := &file_openshell_proto_msgTypes[165] + mi := &file_openshell_proto_msgTypes[187] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13387,7 +14672,7 @@ func (x *PushSandboxLogsResponse) String() string { func (*PushSandboxLogsResponse) ProtoMessage() {} func (x *PushSandboxLogsResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[165] + mi := &file_openshell_proto_msgTypes[187] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13400,7 +14685,7 @@ func (x *PushSandboxLogsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use PushSandboxLogsResponse.ProtoReflect.Descriptor instead. func (*PushSandboxLogsResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{165} + return file_openshell_proto_rawDescGZIP(), []int{187} } // Get sandbox logs response. @@ -13416,7 +14701,7 @@ type GetSandboxLogsResponse struct { func (x *GetSandboxLogsResponse) Reset() { *x = GetSandboxLogsResponse{} - mi := &file_openshell_proto_msgTypes[166] + mi := &file_openshell_proto_msgTypes[188] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13428,7 +14713,7 @@ func (x *GetSandboxLogsResponse) String() string { func (*GetSandboxLogsResponse) ProtoMessage() {} func (x *GetSandboxLogsResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[166] + mi := &file_openshell_proto_msgTypes[188] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13441,7 +14726,7 @@ func (x *GetSandboxLogsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetSandboxLogsResponse.ProtoReflect.Descriptor instead. func (*GetSandboxLogsResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{166} + return file_openshell_proto_rawDescGZIP(), []int{188} } func (x *GetSandboxLogsResponse) GetLogs() []*SandboxLogLine { @@ -13474,7 +14759,7 @@ type SupervisorMessage struct { func (x *SupervisorMessage) Reset() { *x = SupervisorMessage{} - mi := &file_openshell_proto_msgTypes[167] + mi := &file_openshell_proto_msgTypes[189] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13486,7 +14771,7 @@ func (x *SupervisorMessage) String() string { func (*SupervisorMessage) ProtoMessage() {} func (x *SupervisorMessage) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[167] + mi := &file_openshell_proto_msgTypes[189] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13499,7 +14784,7 @@ func (x *SupervisorMessage) ProtoReflect() protoreflect.Message { // Deprecated: Use SupervisorMessage.ProtoReflect.Descriptor instead. func (*SupervisorMessage) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{167} + return file_openshell_proto_rawDescGZIP(), []int{189} } func (x *SupervisorMessage) GetPayload() isSupervisorMessage_Payload { @@ -13591,7 +14876,7 @@ type GatewayMessage struct { func (x *GatewayMessage) Reset() { *x = GatewayMessage{} - mi := &file_openshell_proto_msgTypes[168] + mi := &file_openshell_proto_msgTypes[190] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13603,7 +14888,7 @@ func (x *GatewayMessage) String() string { func (*GatewayMessage) ProtoMessage() {} func (x *GatewayMessage) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[168] + mi := &file_openshell_proto_msgTypes[190] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13616,7 +14901,7 @@ func (x *GatewayMessage) ProtoReflect() protoreflect.Message { // Deprecated: Use GatewayMessage.ProtoReflect.Descriptor instead. func (*GatewayMessage) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{168} + return file_openshell_proto_rawDescGZIP(), []int{190} } func (x *GatewayMessage) GetPayload() isGatewayMessage_Payload { @@ -13746,7 +15031,7 @@ type SupervisorHello struct { func (x *SupervisorHello) Reset() { *x = SupervisorHello{} - mi := &file_openshell_proto_msgTypes[169] + mi := &file_openshell_proto_msgTypes[191] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13758,7 +15043,7 @@ func (x *SupervisorHello) String() string { func (*SupervisorHello) ProtoMessage() {} func (x *SupervisorHello) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[169] + mi := &file_openshell_proto_msgTypes[191] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13771,7 +15056,7 @@ func (x *SupervisorHello) ProtoReflect() protoreflect.Message { // Deprecated: Use SupervisorHello.ProtoReflect.Descriptor instead. func (*SupervisorHello) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{169} + return file_openshell_proto_rawDescGZIP(), []int{191} } func (x *SupervisorHello) GetSandboxId() string { @@ -13829,7 +15114,7 @@ type SessionAccepted struct { func (x *SessionAccepted) Reset() { *x = SessionAccepted{} - mi := &file_openshell_proto_msgTypes[170] + mi := &file_openshell_proto_msgTypes[192] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13841,7 +15126,7 @@ func (x *SessionAccepted) String() string { func (*SessionAccepted) ProtoMessage() {} func (x *SessionAccepted) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[170] + mi := &file_openshell_proto_msgTypes[192] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13854,7 +15139,7 @@ func (x *SessionAccepted) ProtoReflect() protoreflect.Message { // Deprecated: Use SessionAccepted.ProtoReflect.Descriptor instead. func (*SessionAccepted) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{170} + return file_openshell_proto_rawDescGZIP(), []int{192} } func (x *SessionAccepted) GetSessionId() string { @@ -13882,7 +15167,7 @@ type SessionRejected struct { func (x *SessionRejected) Reset() { *x = SessionRejected{} - mi := &file_openshell_proto_msgTypes[171] + mi := &file_openshell_proto_msgTypes[193] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13894,7 +15179,7 @@ func (x *SessionRejected) String() string { func (*SessionRejected) ProtoMessage() {} func (x *SessionRejected) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[171] + mi := &file_openshell_proto_msgTypes[193] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13907,7 +15192,7 @@ func (x *SessionRejected) ProtoReflect() protoreflect.Message { // Deprecated: Use SessionRejected.ProtoReflect.Descriptor instead. func (*SessionRejected) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{171} + return file_openshell_proto_rawDescGZIP(), []int{193} } func (x *SessionRejected) GetReason() string { @@ -13936,7 +15221,7 @@ type SessionRedirect struct { func (x *SessionRedirect) Reset() { *x = SessionRedirect{} - mi := &file_openshell_proto_msgTypes[172] + mi := &file_openshell_proto_msgTypes[194] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13948,7 +15233,7 @@ func (x *SessionRedirect) String() string { func (*SessionRedirect) ProtoMessage() {} func (x *SessionRedirect) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[172] + mi := &file_openshell_proto_msgTypes[194] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13961,7 +15246,7 @@ func (x *SessionRedirect) ProtoReflect() protoreflect.Message { // Deprecated: Use SessionRedirect.ProtoReflect.Descriptor instead. func (*SessionRedirect) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{172} + return file_openshell_proto_rawDescGZIP(), []int{194} } func (x *SessionRedirect) GetPeerEndpoint() string { @@ -13987,7 +15272,7 @@ type SupervisorHeartbeat struct { func (x *SupervisorHeartbeat) Reset() { *x = SupervisorHeartbeat{} - mi := &file_openshell_proto_msgTypes[173] + mi := &file_openshell_proto_msgTypes[195] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13999,7 +15284,7 @@ func (x *SupervisorHeartbeat) String() string { func (*SupervisorHeartbeat) ProtoMessage() {} func (x *SupervisorHeartbeat) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[173] + mi := &file_openshell_proto_msgTypes[195] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14012,7 +15297,7 @@ func (x *SupervisorHeartbeat) ProtoReflect() protoreflect.Message { // Deprecated: Use SupervisorHeartbeat.ProtoReflect.Descriptor instead. func (*SupervisorHeartbeat) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{173} + return file_openshell_proto_rawDescGZIP(), []int{195} } // Gateway heartbeat. @@ -14024,7 +15309,7 @@ type GatewayHeartbeat struct { func (x *GatewayHeartbeat) Reset() { *x = GatewayHeartbeat{} - mi := &file_openshell_proto_msgTypes[174] + mi := &file_openshell_proto_msgTypes[196] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14036,7 +15321,7 @@ func (x *GatewayHeartbeat) String() string { func (*GatewayHeartbeat) ProtoMessage() {} func (x *GatewayHeartbeat) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[174] + mi := &file_openshell_proto_msgTypes[196] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14049,7 +15334,7 @@ func (x *GatewayHeartbeat) ProtoReflect() protoreflect.Message { // Deprecated: Use GatewayHeartbeat.ProtoReflect.Descriptor instead. func (*GatewayHeartbeat) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{174} + return file_openshell_proto_rawDescGZIP(), []int{196} } // Terminal result reported before the supervisor shuts down. A successful RPC @@ -14066,7 +15351,7 @@ type ReportMainProcessExitRequest struct { func (x *ReportMainProcessExitRequest) Reset() { *x = ReportMainProcessExitRequest{} - mi := &file_openshell_proto_msgTypes[175] + mi := &file_openshell_proto_msgTypes[197] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14078,7 +15363,7 @@ func (x *ReportMainProcessExitRequest) String() string { func (*ReportMainProcessExitRequest) ProtoMessage() {} func (x *ReportMainProcessExitRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[175] + mi := &file_openshell_proto_msgTypes[197] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14091,7 +15376,7 @@ func (x *ReportMainProcessExitRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ReportMainProcessExitRequest.ProtoReflect.Descriptor instead. func (*ReportMainProcessExitRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{175} + return file_openshell_proto_rawDescGZIP(), []int{197} } func (x *ReportMainProcessExitRequest) GetSandboxId() string { @@ -14123,7 +15408,7 @@ type ReportMainProcessExitResponse struct { func (x *ReportMainProcessExitResponse) Reset() { *x = ReportMainProcessExitResponse{} - mi := &file_openshell_proto_msgTypes[176] + mi := &file_openshell_proto_msgTypes[198] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14135,7 +15420,7 @@ func (x *ReportMainProcessExitResponse) String() string { func (*ReportMainProcessExitResponse) ProtoMessage() {} func (x *ReportMainProcessExitResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[176] + mi := &file_openshell_proto_msgTypes[198] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14148,7 +15433,7 @@ func (x *ReportMainProcessExitResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ReportMainProcessExitResponse.ProtoReflect.Descriptor instead. func (*ReportMainProcessExitResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{176} + return file_openshell_proto_rawDescGZIP(), []int{198} } // Terminal-delivery completion reported after all expected foreground SSH @@ -14163,7 +15448,7 @@ type FinalizeMainProcessExitRequest struct { func (x *FinalizeMainProcessExitRequest) Reset() { *x = FinalizeMainProcessExitRequest{} - mi := &file_openshell_proto_msgTypes[177] + mi := &file_openshell_proto_msgTypes[199] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14175,7 +15460,7 @@ func (x *FinalizeMainProcessExitRequest) String() string { func (*FinalizeMainProcessExitRequest) ProtoMessage() {} func (x *FinalizeMainProcessExitRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[177] + mi := &file_openshell_proto_msgTypes[199] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14188,7 +15473,7 @@ func (x *FinalizeMainProcessExitRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use FinalizeMainProcessExitRequest.ProtoReflect.Descriptor instead. func (*FinalizeMainProcessExitRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{177} + return file_openshell_proto_rawDescGZIP(), []int{199} } func (x *FinalizeMainProcessExitRequest) GetSandboxId() string { @@ -14213,7 +15498,7 @@ type FinalizeMainProcessExitResponse struct { func (x *FinalizeMainProcessExitResponse) Reset() { *x = FinalizeMainProcessExitResponse{} - mi := &file_openshell_proto_msgTypes[178] + mi := &file_openshell_proto_msgTypes[200] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14225,7 +15510,7 @@ func (x *FinalizeMainProcessExitResponse) String() string { func (*FinalizeMainProcessExitResponse) ProtoMessage() {} func (x *FinalizeMainProcessExitResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[178] + mi := &file_openshell_proto_msgTypes[200] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14238,7 +15523,7 @@ func (x *FinalizeMainProcessExitResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use FinalizeMainProcessExitResponse.ProtoReflect.Descriptor instead. func (*FinalizeMainProcessExitResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{178} + return file_openshell_proto_rawDescGZIP(), []int{200} } // Gateway requests the supervisor to open a relay channel. @@ -14267,7 +15552,7 @@ type RelayOpen struct { func (x *RelayOpen) Reset() { *x = RelayOpen{} - mi := &file_openshell_proto_msgTypes[179] + mi := &file_openshell_proto_msgTypes[201] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14279,7 +15564,7 @@ func (x *RelayOpen) String() string { func (*RelayOpen) ProtoMessage() {} func (x *RelayOpen) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[179] + mi := &file_openshell_proto_msgTypes[201] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14292,7 +15577,7 @@ func (x *RelayOpen) ProtoReflect() protoreflect.Message { // Deprecated: Use RelayOpen.ProtoReflect.Descriptor instead. func (*RelayOpen) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{179} + return file_openshell_proto_rawDescGZIP(), []int{201} } func (x *RelayOpen) GetChannelId() string { @@ -14359,7 +15644,7 @@ type SshRelayTarget struct { func (x *SshRelayTarget) Reset() { *x = SshRelayTarget{} - mi := &file_openshell_proto_msgTypes[180] + mi := &file_openshell_proto_msgTypes[202] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14371,7 +15656,7 @@ func (x *SshRelayTarget) String() string { func (*SshRelayTarget) ProtoMessage() {} func (x *SshRelayTarget) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[180] + mi := &file_openshell_proto_msgTypes[202] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14384,7 +15669,7 @@ func (x *SshRelayTarget) ProtoReflect() protoreflect.Message { // Deprecated: Use SshRelayTarget.ProtoReflect.Descriptor instead. func (*SshRelayTarget) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{180} + return file_openshell_proto_rawDescGZIP(), []int{202} } // TCP target dialed by the supervisor from inside the sandbox. @@ -14400,7 +15685,7 @@ type TcpRelayTarget struct { func (x *TcpRelayTarget) Reset() { *x = TcpRelayTarget{} - mi := &file_openshell_proto_msgTypes[181] + mi := &file_openshell_proto_msgTypes[203] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14412,7 +15697,7 @@ func (x *TcpRelayTarget) String() string { func (*TcpRelayTarget) ProtoMessage() {} func (x *TcpRelayTarget) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[181] + mi := &file_openshell_proto_msgTypes[203] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14425,7 +15710,7 @@ func (x *TcpRelayTarget) ProtoReflect() protoreflect.Message { // Deprecated: Use TcpRelayTarget.ProtoReflect.Descriptor instead. func (*TcpRelayTarget) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{181} + return file_openshell_proto_rawDescGZIP(), []int{203} } func (x *TcpRelayTarget) GetHost() string { @@ -14453,7 +15738,7 @@ type RelayInit struct { func (x *RelayInit) Reset() { *x = RelayInit{} - mi := &file_openshell_proto_msgTypes[182] + mi := &file_openshell_proto_msgTypes[204] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14465,7 +15750,7 @@ func (x *RelayInit) String() string { func (*RelayInit) ProtoMessage() {} func (x *RelayInit) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[182] + mi := &file_openshell_proto_msgTypes[204] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14478,7 +15763,7 @@ func (x *RelayInit) ProtoReflect() protoreflect.Message { // Deprecated: Use RelayInit.ProtoReflect.Descriptor instead. func (*RelayInit) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{182} + return file_openshell_proto_rawDescGZIP(), []int{204} } func (x *RelayInit) GetChannelId() string { @@ -14505,7 +15790,7 @@ type RelayFrame struct { func (x *RelayFrame) Reset() { *x = RelayFrame{} - mi := &file_openshell_proto_msgTypes[183] + mi := &file_openshell_proto_msgTypes[205] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14517,7 +15802,7 @@ func (x *RelayFrame) String() string { func (*RelayFrame) ProtoMessage() {} func (x *RelayFrame) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[183] + mi := &file_openshell_proto_msgTypes[205] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14530,7 +15815,7 @@ func (x *RelayFrame) ProtoReflect() protoreflect.Message { // Deprecated: Use RelayFrame.ProtoReflect.Descriptor instead. func (*RelayFrame) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{183} + return file_openshell_proto_rawDescGZIP(), []int{205} } func (x *RelayFrame) GetPayload() isRelayFrame_Payload { @@ -14590,7 +15875,7 @@ type PeerRelayInit struct { func (x *PeerRelayInit) Reset() { *x = PeerRelayInit{} - mi := &file_openshell_proto_msgTypes[184] + mi := &file_openshell_proto_msgTypes[206] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14602,7 +15887,7 @@ func (x *PeerRelayInit) String() string { func (*PeerRelayInit) ProtoMessage() {} func (x *PeerRelayInit) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[184] + mi := &file_openshell_proto_msgTypes[206] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14615,7 +15900,7 @@ func (x *PeerRelayInit) ProtoReflect() protoreflect.Message { // Deprecated: Use PeerRelayInit.ProtoReflect.Descriptor instead. func (*PeerRelayInit) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{184} + return file_openshell_proto_rawDescGZIP(), []int{206} } func (x *PeerRelayInit) GetSandboxId() string { @@ -14653,7 +15938,7 @@ type PeerRelayFrame struct { func (x *PeerRelayFrame) Reset() { *x = PeerRelayFrame{} - mi := &file_openshell_proto_msgTypes[185] + mi := &file_openshell_proto_msgTypes[207] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14665,7 +15950,7 @@ func (x *PeerRelayFrame) String() string { func (*PeerRelayFrame) ProtoMessage() {} func (x *PeerRelayFrame) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[185] + mi := &file_openshell_proto_msgTypes[207] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14678,7 +15963,7 @@ func (x *PeerRelayFrame) ProtoReflect() protoreflect.Message { // Deprecated: Use PeerRelayFrame.ProtoReflect.Descriptor instead. func (*PeerRelayFrame) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{185} + return file_openshell_proto_rawDescGZIP(), []int{207} } func (x *PeerRelayFrame) GetPayload() isPeerRelayFrame_Payload { @@ -14737,7 +16022,7 @@ type RelayOpenResult struct { func (x *RelayOpenResult) Reset() { *x = RelayOpenResult{} - mi := &file_openshell_proto_msgTypes[186] + mi := &file_openshell_proto_msgTypes[208] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14749,7 +16034,7 @@ func (x *RelayOpenResult) String() string { func (*RelayOpenResult) ProtoMessage() {} func (x *RelayOpenResult) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[186] + mi := &file_openshell_proto_msgTypes[208] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14762,7 +16047,7 @@ func (x *RelayOpenResult) ProtoReflect() protoreflect.Message { // Deprecated: Use RelayOpenResult.ProtoReflect.Descriptor instead. func (*RelayOpenResult) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{186} + return file_openshell_proto_rawDescGZIP(), []int{208} } func (x *RelayOpenResult) GetChannelId() string { @@ -14799,7 +16084,7 @@ type RelayClose struct { func (x *RelayClose) Reset() { *x = RelayClose{} - mi := &file_openshell_proto_msgTypes[187] + mi := &file_openshell_proto_msgTypes[209] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14811,7 +16096,7 @@ func (x *RelayClose) String() string { func (*RelayClose) ProtoMessage() {} func (x *RelayClose) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[187] + mi := &file_openshell_proto_msgTypes[209] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14824,7 +16109,7 @@ func (x *RelayClose) ProtoReflect() protoreflect.Message { // Deprecated: Use RelayClose.ProtoReflect.Descriptor instead. func (*RelayClose) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{187} + return file_openshell_proto_rawDescGZIP(), []int{209} } func (x *RelayClose) GetChannelId() string { @@ -14858,7 +16143,7 @@ type L7RequestSample struct { func (x *L7RequestSample) Reset() { *x = L7RequestSample{} - mi := &file_openshell_proto_msgTypes[188] + mi := &file_openshell_proto_msgTypes[210] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14870,7 +16155,7 @@ func (x *L7RequestSample) String() string { func (*L7RequestSample) ProtoMessage() {} func (x *L7RequestSample) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[188] + mi := &file_openshell_proto_msgTypes[210] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14883,7 +16168,7 @@ func (x *L7RequestSample) ProtoReflect() protoreflect.Message { // Deprecated: Use L7RequestSample.ProtoReflect.Descriptor instead. func (*L7RequestSample) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{188} + return file_openshell_proto_rawDescGZIP(), []int{210} } func (x *L7RequestSample) GetMethod() string { @@ -14957,7 +16242,7 @@ type DenialSummary struct { func (x *DenialSummary) Reset() { *x = DenialSummary{} - mi := &file_openshell_proto_msgTypes[189] + mi := &file_openshell_proto_msgTypes[211] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14969,7 +16254,7 @@ func (x *DenialSummary) String() string { func (*DenialSummary) ProtoMessage() {} func (x *DenialSummary) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[189] + mi := &file_openshell_proto_msgTypes[211] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14982,7 +16267,7 @@ func (x *DenialSummary) ProtoReflect() protoreflect.Message { // Deprecated: Use DenialSummary.ProtoReflect.Descriptor instead. func (*DenialSummary) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{189} + return file_openshell_proto_rawDescGZIP(), []int{211} } func (x *DenialSummary) GetSandboxId() string { @@ -15117,7 +16402,7 @@ type DenialGroupCount struct { func (x *DenialGroupCount) Reset() { *x = DenialGroupCount{} - mi := &file_openshell_proto_msgTypes[190] + mi := &file_openshell_proto_msgTypes[212] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -15129,7 +16414,7 @@ func (x *DenialGroupCount) String() string { func (*DenialGroupCount) ProtoMessage() {} func (x *DenialGroupCount) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[190] + mi := &file_openshell_proto_msgTypes[212] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -15142,7 +16427,7 @@ func (x *DenialGroupCount) ProtoReflect() protoreflect.Message { // Deprecated: Use DenialGroupCount.ProtoReflect.Descriptor instead. func (*DenialGroupCount) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{190} + return file_openshell_proto_rawDescGZIP(), []int{212} } func (x *DenialGroupCount) GetDenyGroup() string { @@ -15175,7 +16460,7 @@ type NetworkActivitySummary struct { func (x *NetworkActivitySummary) Reset() { *x = NetworkActivitySummary{} - mi := &file_openshell_proto_msgTypes[191] + mi := &file_openshell_proto_msgTypes[213] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -15187,7 +16472,7 @@ func (x *NetworkActivitySummary) String() string { func (*NetworkActivitySummary) ProtoMessage() {} func (x *NetworkActivitySummary) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[191] + mi := &file_openshell_proto_msgTypes[213] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -15200,7 +16485,7 @@ func (x *NetworkActivitySummary) ProtoReflect() protoreflect.Message { // Deprecated: Use NetworkActivitySummary.ProtoReflect.Descriptor instead. func (*NetworkActivitySummary) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{191} + return file_openshell_proto_rawDescGZIP(), []int{213} } func (x *NetworkActivitySummary) GetNetworkActivityCount() uint32 { @@ -15288,7 +16573,7 @@ type PolicyChunk struct { func (x *PolicyChunk) Reset() { *x = PolicyChunk{} - mi := &file_openshell_proto_msgTypes[192] + mi := &file_openshell_proto_msgTypes[214] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -15300,7 +16585,7 @@ func (x *PolicyChunk) String() string { func (*PolicyChunk) ProtoMessage() {} func (x *PolicyChunk) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[192] + mi := &file_openshell_proto_msgTypes[214] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -15313,7 +16598,7 @@ func (x *PolicyChunk) ProtoReflect() protoreflect.Message { // Deprecated: Use PolicyChunk.ProtoReflect.Descriptor instead. func (*PolicyChunk) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{192} + return file_openshell_proto_rawDescGZIP(), []int{214} } func (x *PolicyChunk) GetId() string { @@ -15501,7 +16786,7 @@ type DraftPolicyUpdate struct { func (x *DraftPolicyUpdate) Reset() { *x = DraftPolicyUpdate{} - mi := &file_openshell_proto_msgTypes[193] + mi := &file_openshell_proto_msgTypes[215] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -15513,7 +16798,7 @@ func (x *DraftPolicyUpdate) String() string { func (*DraftPolicyUpdate) ProtoMessage() {} func (x *DraftPolicyUpdate) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[193] + mi := &file_openshell_proto_msgTypes[215] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -15526,7 +16811,7 @@ func (x *DraftPolicyUpdate) ProtoReflect() protoreflect.Message { // Deprecated: Use DraftPolicyUpdate.ProtoReflect.Descriptor instead. func (*DraftPolicyUpdate) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{193} + return file_openshell_proto_rawDescGZIP(), []int{215} } func (x *DraftPolicyUpdate) GetDraftVersion() uint64 { @@ -15585,7 +16870,7 @@ type SubmitPolicyAnalysisRequest struct { func (x *SubmitPolicyAnalysisRequest) Reset() { *x = SubmitPolicyAnalysisRequest{} - mi := &file_openshell_proto_msgTypes[194] + mi := &file_openshell_proto_msgTypes[216] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -15597,7 +16882,7 @@ func (x *SubmitPolicyAnalysisRequest) String() string { func (*SubmitPolicyAnalysisRequest) ProtoMessage() {} func (x *SubmitPolicyAnalysisRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[194] + mi := &file_openshell_proto_msgTypes[216] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -15610,7 +16895,7 @@ func (x *SubmitPolicyAnalysisRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use SubmitPolicyAnalysisRequest.ProtoReflect.Descriptor instead. func (*SubmitPolicyAnalysisRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{194} + return file_openshell_proto_rawDescGZIP(), []int{216} } func (x *SubmitPolicyAnalysisRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { @@ -15673,7 +16958,7 @@ type SubmitPolicyAnalysisResponse struct { func (x *SubmitPolicyAnalysisResponse) Reset() { *x = SubmitPolicyAnalysisResponse{} - mi := &file_openshell_proto_msgTypes[195] + mi := &file_openshell_proto_msgTypes[217] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -15685,7 +16970,7 @@ func (x *SubmitPolicyAnalysisResponse) String() string { func (*SubmitPolicyAnalysisResponse) ProtoMessage() {} func (x *SubmitPolicyAnalysisResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[195] + mi := &file_openshell_proto_msgTypes[217] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -15698,7 +16983,7 @@ func (x *SubmitPolicyAnalysisResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use SubmitPolicyAnalysisResponse.ProtoReflect.Descriptor instead. func (*SubmitPolicyAnalysisResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{195} + return file_openshell_proto_rawDescGZIP(), []int{217} } func (x *SubmitPolicyAnalysisResponse) GetAcceptedChunks() uint32 { @@ -15743,7 +17028,7 @@ type GetDraftPolicyRequest struct { func (x *GetDraftPolicyRequest) Reset() { *x = GetDraftPolicyRequest{} - mi := &file_openshell_proto_msgTypes[196] + mi := &file_openshell_proto_msgTypes[218] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -15755,7 +17040,7 @@ func (x *GetDraftPolicyRequest) String() string { func (*GetDraftPolicyRequest) ProtoMessage() {} func (x *GetDraftPolicyRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[196] + mi := &file_openshell_proto_msgTypes[218] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -15768,7 +17053,7 @@ func (x *GetDraftPolicyRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetDraftPolicyRequest.ProtoReflect.Descriptor instead. func (*GetDraftPolicyRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{196} + return file_openshell_proto_rawDescGZIP(), []int{218} } func (x *GetDraftPolicyRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { @@ -15808,7 +17093,7 @@ type GetDraftPolicyResponse struct { func (x *GetDraftPolicyResponse) Reset() { *x = GetDraftPolicyResponse{} - mi := &file_openshell_proto_msgTypes[197] + mi := &file_openshell_proto_msgTypes[219] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -15820,7 +17105,7 @@ func (x *GetDraftPolicyResponse) String() string { func (*GetDraftPolicyResponse) ProtoMessage() {} func (x *GetDraftPolicyResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[197] + mi := &file_openshell_proto_msgTypes[219] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -15833,7 +17118,7 @@ func (x *GetDraftPolicyResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetDraftPolicyResponse.ProtoReflect.Descriptor instead. func (*GetDraftPolicyResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{197} + return file_openshell_proto_rawDescGZIP(), []int{219} } func (x *GetDraftPolicyResponse) GetChunks() []*PolicyChunk { @@ -15884,7 +17169,7 @@ type ApproveDraftChunkRequest struct { func (x *ApproveDraftChunkRequest) Reset() { *x = ApproveDraftChunkRequest{} - mi := &file_openshell_proto_msgTypes[198] + mi := &file_openshell_proto_msgTypes[220] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -15896,7 +17181,7 @@ func (x *ApproveDraftChunkRequest) String() string { func (*ApproveDraftChunkRequest) ProtoMessage() {} func (x *ApproveDraftChunkRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[198] + mi := &file_openshell_proto_msgTypes[220] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -15909,7 +17194,7 @@ func (x *ApproveDraftChunkRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ApproveDraftChunkRequest.ProtoReflect.Descriptor instead. func (*ApproveDraftChunkRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{198} + return file_openshell_proto_rawDescGZIP(), []int{220} } func (x *ApproveDraftChunkRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { @@ -15959,7 +17244,7 @@ type ApproveDraftChunkResponse struct { func (x *ApproveDraftChunkResponse) Reset() { *x = ApproveDraftChunkResponse{} - mi := &file_openshell_proto_msgTypes[199] + mi := &file_openshell_proto_msgTypes[221] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -15971,7 +17256,7 @@ func (x *ApproveDraftChunkResponse) String() string { func (*ApproveDraftChunkResponse) ProtoMessage() {} func (x *ApproveDraftChunkResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[199] + mi := &file_openshell_proto_msgTypes[221] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -15984,7 +17269,7 @@ func (x *ApproveDraftChunkResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ApproveDraftChunkResponse.ProtoReflect.Descriptor instead. func (*ApproveDraftChunkResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{199} + return file_openshell_proto_rawDescGZIP(), []int{221} } func (x *ApproveDraftChunkResponse) GetPolicyVersion() uint32 { @@ -16020,7 +17305,7 @@ type RejectDraftChunkRequest struct { func (x *RejectDraftChunkRequest) Reset() { *x = RejectDraftChunkRequest{} - mi := &file_openshell_proto_msgTypes[200] + mi := &file_openshell_proto_msgTypes[222] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -16032,7 +17317,7 @@ func (x *RejectDraftChunkRequest) String() string { func (*RejectDraftChunkRequest) ProtoMessage() {} func (x *RejectDraftChunkRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[200] + mi := &file_openshell_proto_msgTypes[222] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -16045,7 +17330,7 @@ func (x *RejectDraftChunkRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use RejectDraftChunkRequest.ProtoReflect.Descriptor instead. func (*RejectDraftChunkRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{200} + return file_openshell_proto_rawDescGZIP(), []int{222} } func (x *RejectDraftChunkRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { @@ -16091,7 +17376,7 @@ type RejectDraftChunkResponse struct { func (x *RejectDraftChunkResponse) Reset() { *x = RejectDraftChunkResponse{} - mi := &file_openshell_proto_msgTypes[201] + mi := &file_openshell_proto_msgTypes[223] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -16103,7 +17388,7 @@ func (x *RejectDraftChunkResponse) String() string { func (*RejectDraftChunkResponse) ProtoMessage() {} func (x *RejectDraftChunkResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[201] + mi := &file_openshell_proto_msgTypes[223] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -16116,7 +17401,7 @@ func (x *RejectDraftChunkResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use RejectDraftChunkResponse.ProtoReflect.Descriptor instead. func (*RejectDraftChunkResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{201} + return file_openshell_proto_rawDescGZIP(), []int{223} } // Approve all pending chunks. @@ -16130,7 +17415,7 @@ type DraftChunkApproval struct { func (x *DraftChunkApproval) Reset() { *x = DraftChunkApproval{} - mi := &file_openshell_proto_msgTypes[202] + mi := &file_openshell_proto_msgTypes[224] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -16142,7 +17427,7 @@ func (x *DraftChunkApproval) String() string { func (*DraftChunkApproval) ProtoMessage() {} func (x *DraftChunkApproval) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[202] + mi := &file_openshell_proto_msgTypes[224] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -16155,7 +17440,7 @@ func (x *DraftChunkApproval) ProtoReflect() protoreflect.Message { // Deprecated: Use DraftChunkApproval.ProtoReflect.Descriptor instead. func (*DraftChunkApproval) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{202} + return file_openshell_proto_rawDescGZIP(), []int{224} } func (x *DraftChunkApproval) GetChunkId() string { @@ -16191,7 +17476,7 @@ type ApproveAllDraftChunksRequest struct { func (x *ApproveAllDraftChunksRequest) Reset() { *x = ApproveAllDraftChunksRequest{} - mi := &file_openshell_proto_msgTypes[203] + mi := &file_openshell_proto_msgTypes[225] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -16203,7 +17488,7 @@ func (x *ApproveAllDraftChunksRequest) String() string { func (*ApproveAllDraftChunksRequest) ProtoMessage() {} func (x *ApproveAllDraftChunksRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[203] + mi := &file_openshell_proto_msgTypes[225] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -16216,7 +17501,7 @@ func (x *ApproveAllDraftChunksRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ApproveAllDraftChunksRequest.ProtoReflect.Descriptor instead. func (*ApproveAllDraftChunksRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{203} + return file_openshell_proto_rawDescGZIP(), []int{225} } func (x *ApproveAllDraftChunksRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { @@ -16271,7 +17556,7 @@ type ApproveAllDraftChunksResponse struct { func (x *ApproveAllDraftChunksResponse) Reset() { *x = ApproveAllDraftChunksResponse{} - mi := &file_openshell_proto_msgTypes[204] + mi := &file_openshell_proto_msgTypes[226] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -16283,7 +17568,7 @@ func (x *ApproveAllDraftChunksResponse) String() string { func (*ApproveAllDraftChunksResponse) ProtoMessage() {} func (x *ApproveAllDraftChunksResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[204] + mi := &file_openshell_proto_msgTypes[226] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -16296,7 +17581,7 @@ func (x *ApproveAllDraftChunksResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ApproveAllDraftChunksResponse.ProtoReflect.Descriptor instead. func (*ApproveAllDraftChunksResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{204} + return file_openshell_proto_rawDescGZIP(), []int{226} } func (x *ApproveAllDraftChunksResponse) GetPolicyVersion() uint32 { @@ -16346,7 +17631,7 @@ type EditDraftChunkRequest struct { func (x *EditDraftChunkRequest) Reset() { *x = EditDraftChunkRequest{} - mi := &file_openshell_proto_msgTypes[205] + mi := &file_openshell_proto_msgTypes[227] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -16358,7 +17643,7 @@ func (x *EditDraftChunkRequest) String() string { func (*EditDraftChunkRequest) ProtoMessage() {} func (x *EditDraftChunkRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[205] + mi := &file_openshell_proto_msgTypes[227] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -16371,7 +17656,7 @@ func (x *EditDraftChunkRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use EditDraftChunkRequest.ProtoReflect.Descriptor instead. func (*EditDraftChunkRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{205} + return file_openshell_proto_rawDescGZIP(), []int{227} } func (x *EditDraftChunkRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { @@ -16417,7 +17702,7 @@ type EditDraftChunkResponse struct { func (x *EditDraftChunkResponse) Reset() { *x = EditDraftChunkResponse{} - mi := &file_openshell_proto_msgTypes[206] + mi := &file_openshell_proto_msgTypes[228] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -16429,7 +17714,7 @@ func (x *EditDraftChunkResponse) String() string { func (*EditDraftChunkResponse) ProtoMessage() {} func (x *EditDraftChunkResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[206] + mi := &file_openshell_proto_msgTypes[228] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -16442,7 +17727,7 @@ func (x *EditDraftChunkResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use EditDraftChunkResponse.ProtoReflect.Descriptor instead. func (*EditDraftChunkResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{206} + return file_openshell_proto_rawDescGZIP(), []int{228} } // Reverse an approval (remove merged rule from active policy). @@ -16462,7 +17747,7 @@ type UndoDraftChunkRequest struct { func (x *UndoDraftChunkRequest) Reset() { *x = UndoDraftChunkRequest{} - mi := &file_openshell_proto_msgTypes[207] + mi := &file_openshell_proto_msgTypes[229] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -16474,7 +17759,7 @@ func (x *UndoDraftChunkRequest) String() string { func (*UndoDraftChunkRequest) ProtoMessage() {} func (x *UndoDraftChunkRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[207] + mi := &file_openshell_proto_msgTypes[229] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -16487,7 +17772,7 @@ func (x *UndoDraftChunkRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UndoDraftChunkRequest.ProtoReflect.Descriptor instead. func (*UndoDraftChunkRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{207} + return file_openshell_proto_rawDescGZIP(), []int{229} } func (x *UndoDraftChunkRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { @@ -16530,7 +17815,7 @@ type UndoDraftChunkResponse struct { func (x *UndoDraftChunkResponse) Reset() { *x = UndoDraftChunkResponse{} - mi := &file_openshell_proto_msgTypes[208] + mi := &file_openshell_proto_msgTypes[230] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -16542,7 +17827,7 @@ func (x *UndoDraftChunkResponse) String() string { func (*UndoDraftChunkResponse) ProtoMessage() {} func (x *UndoDraftChunkResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[208] + mi := &file_openshell_proto_msgTypes[230] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -16555,7 +17840,7 @@ func (x *UndoDraftChunkResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use UndoDraftChunkResponse.ProtoReflect.Descriptor instead. func (*UndoDraftChunkResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{208} + return file_openshell_proto_rawDescGZIP(), []int{230} } func (x *UndoDraftChunkResponse) GetPolicyVersion() uint32 { @@ -16587,7 +17872,7 @@ type ClearDraftChunksRequest struct { func (x *ClearDraftChunksRequest) Reset() { *x = ClearDraftChunksRequest{} - mi := &file_openshell_proto_msgTypes[209] + mi := &file_openshell_proto_msgTypes[231] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -16599,7 +17884,7 @@ func (x *ClearDraftChunksRequest) String() string { func (*ClearDraftChunksRequest) ProtoMessage() {} func (x *ClearDraftChunksRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[209] + mi := &file_openshell_proto_msgTypes[231] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -16612,7 +17897,7 @@ func (x *ClearDraftChunksRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ClearDraftChunksRequest.ProtoReflect.Descriptor instead. func (*ClearDraftChunksRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{209} + return file_openshell_proto_rawDescGZIP(), []int{231} } func (x *ClearDraftChunksRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { @@ -16646,7 +17931,7 @@ type ClearDraftChunksResponse struct { func (x *ClearDraftChunksResponse) Reset() { *x = ClearDraftChunksResponse{} - mi := &file_openshell_proto_msgTypes[210] + mi := &file_openshell_proto_msgTypes[232] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -16658,7 +17943,7 @@ func (x *ClearDraftChunksResponse) String() string { func (*ClearDraftChunksResponse) ProtoMessage() {} func (x *ClearDraftChunksResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[210] + mi := &file_openshell_proto_msgTypes[232] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -16671,7 +17956,7 @@ func (x *ClearDraftChunksResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ClearDraftChunksResponse.ProtoReflect.Descriptor instead. func (*ClearDraftChunksResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{210} + return file_openshell_proto_rawDescGZIP(), []int{232} } func (x *ClearDraftChunksResponse) GetChunksCleared() uint32 { @@ -16693,7 +17978,7 @@ type GetDraftHistoryRequest struct { func (x *GetDraftHistoryRequest) Reset() { *x = GetDraftHistoryRequest{} - mi := &file_openshell_proto_msgTypes[211] + mi := &file_openshell_proto_msgTypes[233] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -16705,7 +17990,7 @@ func (x *GetDraftHistoryRequest) String() string { func (*GetDraftHistoryRequest) ProtoMessage() {} func (x *GetDraftHistoryRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[211] + mi := &file_openshell_proto_msgTypes[233] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -16718,7 +18003,7 @@ func (x *GetDraftHistoryRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetDraftHistoryRequest.ProtoReflect.Descriptor instead. func (*GetDraftHistoryRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{211} + return file_openshell_proto_rawDescGZIP(), []int{233} } func (x *GetDraftHistoryRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { @@ -16752,7 +18037,7 @@ type DraftHistoryEntry struct { func (x *DraftHistoryEntry) Reset() { *x = DraftHistoryEntry{} - mi := &file_openshell_proto_msgTypes[212] + mi := &file_openshell_proto_msgTypes[234] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -16764,7 +18049,7 @@ func (x *DraftHistoryEntry) String() string { func (*DraftHistoryEntry) ProtoMessage() {} func (x *DraftHistoryEntry) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[212] + mi := &file_openshell_proto_msgTypes[234] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -16777,7 +18062,7 @@ func (x *DraftHistoryEntry) ProtoReflect() protoreflect.Message { // Deprecated: Use DraftHistoryEntry.ProtoReflect.Descriptor instead. func (*DraftHistoryEntry) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{212} + return file_openshell_proto_rawDescGZIP(), []int{234} } func (x *DraftHistoryEntry) GetEventTime() *timestamppb.Timestamp { @@ -16818,7 +18103,7 @@ type GetDraftHistoryResponse struct { func (x *GetDraftHistoryResponse) Reset() { *x = GetDraftHistoryResponse{} - mi := &file_openshell_proto_msgTypes[213] + mi := &file_openshell_proto_msgTypes[235] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -16830,7 +18115,7 @@ func (x *GetDraftHistoryResponse) String() string { func (*GetDraftHistoryResponse) ProtoMessage() {} func (x *GetDraftHistoryResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[213] + mi := &file_openshell_proto_msgTypes[235] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -16843,7 +18128,7 @@ func (x *GetDraftHistoryResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetDraftHistoryResponse.ProtoReflect.Descriptor instead. func (*GetDraftHistoryResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{213} + return file_openshell_proto_rawDescGZIP(), []int{235} } func (x *GetDraftHistoryResponse) GetEntries() []*DraftHistoryEntry { @@ -16868,7 +18153,7 @@ type CreateWorkspaceRequest struct { func (x *CreateWorkspaceRequest) Reset() { *x = CreateWorkspaceRequest{} - mi := &file_openshell_proto_msgTypes[214] + mi := &file_openshell_proto_msgTypes[236] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -16880,7 +18165,7 @@ func (x *CreateWorkspaceRequest) String() string { func (*CreateWorkspaceRequest) ProtoMessage() {} func (x *CreateWorkspaceRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[214] + mi := &file_openshell_proto_msgTypes[236] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -16893,7 +18178,7 @@ func (x *CreateWorkspaceRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use CreateWorkspaceRequest.ProtoReflect.Descriptor instead. func (*CreateWorkspaceRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{214} + return file_openshell_proto_rawDescGZIP(), []int{236} } func (x *CreateWorkspaceRequest) GetName() string { @@ -16927,7 +18212,7 @@ type CreateWorkspaceResponse struct { func (x *CreateWorkspaceResponse) Reset() { *x = CreateWorkspaceResponse{} - mi := &file_openshell_proto_msgTypes[215] + mi := &file_openshell_proto_msgTypes[237] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -16939,7 +18224,7 @@ func (x *CreateWorkspaceResponse) String() string { func (*CreateWorkspaceResponse) ProtoMessage() {} func (x *CreateWorkspaceResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[215] + mi := &file_openshell_proto_msgTypes[237] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -16952,7 +18237,7 @@ func (x *CreateWorkspaceResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use CreateWorkspaceResponse.ProtoReflect.Descriptor instead. func (*CreateWorkspaceResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{215} + return file_openshell_proto_rawDescGZIP(), []int{237} } func (x *CreateWorkspaceResponse) GetWorkspace() *datamodelv1.Workspace { @@ -16973,7 +18258,7 @@ type GetWorkspaceRequest struct { func (x *GetWorkspaceRequest) Reset() { *x = GetWorkspaceRequest{} - mi := &file_openshell_proto_msgTypes[216] + mi := &file_openshell_proto_msgTypes[238] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -16985,7 +18270,7 @@ func (x *GetWorkspaceRequest) String() string { func (*GetWorkspaceRequest) ProtoMessage() {} func (x *GetWorkspaceRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[216] + mi := &file_openshell_proto_msgTypes[238] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -16998,7 +18283,7 @@ func (x *GetWorkspaceRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetWorkspaceRequest.ProtoReflect.Descriptor instead. func (*GetWorkspaceRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{216} + return file_openshell_proto_rawDescGZIP(), []int{238} } func (x *GetWorkspaceRequest) GetName() string { @@ -17018,7 +18303,7 @@ type GetWorkspaceResponse struct { func (x *GetWorkspaceResponse) Reset() { *x = GetWorkspaceResponse{} - mi := &file_openshell_proto_msgTypes[217] + mi := &file_openshell_proto_msgTypes[239] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -17030,7 +18315,7 @@ func (x *GetWorkspaceResponse) String() string { func (*GetWorkspaceResponse) ProtoMessage() {} func (x *GetWorkspaceResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[217] + mi := &file_openshell_proto_msgTypes[239] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -17043,7 +18328,7 @@ func (x *GetWorkspaceResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetWorkspaceResponse.ProtoReflect.Descriptor instead. func (*GetWorkspaceResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{217} + return file_openshell_proto_rawDescGZIP(), []int{239} } func (x *GetWorkspaceResponse) GetWorkspace() *datamodelv1.Workspace { @@ -17070,7 +18355,7 @@ type ListWorkspacesRequest struct { func (x *ListWorkspacesRequest) Reset() { *x = ListWorkspacesRequest{} - mi := &file_openshell_proto_msgTypes[218] + mi := &file_openshell_proto_msgTypes[240] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -17082,7 +18367,7 @@ func (x *ListWorkspacesRequest) String() string { func (*ListWorkspacesRequest) ProtoMessage() {} func (x *ListWorkspacesRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[218] + mi := &file_openshell_proto_msgTypes[240] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -17095,7 +18380,7 @@ func (x *ListWorkspacesRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListWorkspacesRequest.ProtoReflect.Descriptor instead. func (*ListWorkspacesRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{218} + return file_openshell_proto_rawDescGZIP(), []int{240} } func (x *ListWorkspacesRequest) GetPageSize() int32 { @@ -17131,7 +18416,7 @@ type ListWorkspacesResponse struct { func (x *ListWorkspacesResponse) Reset() { *x = ListWorkspacesResponse{} - mi := &file_openshell_proto_msgTypes[219] + mi := &file_openshell_proto_msgTypes[241] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -17143,7 +18428,7 @@ func (x *ListWorkspacesResponse) String() string { func (*ListWorkspacesResponse) ProtoMessage() {} func (x *ListWorkspacesResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[219] + mi := &file_openshell_proto_msgTypes[241] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -17156,7 +18441,7 @@ func (x *ListWorkspacesResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListWorkspacesResponse.ProtoReflect.Descriptor instead. func (*ListWorkspacesResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{219} + return file_openshell_proto_rawDescGZIP(), []int{241} } func (x *ListWorkspacesResponse) GetWorkspaces() []*datamodelv1.Workspace { @@ -17187,7 +18472,7 @@ type DeleteWorkspaceRequest struct { func (x *DeleteWorkspaceRequest) Reset() { *x = DeleteWorkspaceRequest{} - mi := &file_openshell_proto_msgTypes[220] + mi := &file_openshell_proto_msgTypes[242] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -17199,7 +18484,7 @@ func (x *DeleteWorkspaceRequest) String() string { func (*DeleteWorkspaceRequest) ProtoMessage() {} func (x *DeleteWorkspaceRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[220] + mi := &file_openshell_proto_msgTypes[242] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -17212,7 +18497,7 @@ func (x *DeleteWorkspaceRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteWorkspaceRequest.ProtoReflect.Descriptor instead. func (*DeleteWorkspaceRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{220} + return file_openshell_proto_rawDescGZIP(), []int{242} } func (x *DeleteWorkspaceRequest) GetName() string { @@ -17246,7 +18531,7 @@ type DeleteWorkspaceResponse struct { func (x *DeleteWorkspaceResponse) Reset() { *x = DeleteWorkspaceResponse{} - mi := &file_openshell_proto_msgTypes[221] + mi := &file_openshell_proto_msgTypes[243] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -17258,7 +18543,7 @@ func (x *DeleteWorkspaceResponse) String() string { func (*DeleteWorkspaceResponse) ProtoMessage() {} func (x *DeleteWorkspaceResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[221] + mi := &file_openshell_proto_msgTypes[243] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -17271,7 +18556,7 @@ func (x *DeleteWorkspaceResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteWorkspaceResponse.ProtoReflect.Descriptor instead. func (*DeleteWorkspaceResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{221} + return file_openshell_proto_rawDescGZIP(), []int{243} } func (x *DeleteWorkspaceResponse) GetOutcome() DeletionOutcome { @@ -17295,7 +18580,7 @@ type WorkspaceMember struct { func (x *WorkspaceMember) Reset() { *x = WorkspaceMember{} - mi := &file_openshell_proto_msgTypes[222] + mi := &file_openshell_proto_msgTypes[244] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -17307,7 +18592,7 @@ func (x *WorkspaceMember) String() string { func (*WorkspaceMember) ProtoMessage() {} func (x *WorkspaceMember) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[222] + mi := &file_openshell_proto_msgTypes[244] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -17320,7 +18605,7 @@ func (x *WorkspaceMember) ProtoReflect() protoreflect.Message { // Deprecated: Use WorkspaceMember.ProtoReflect.Descriptor instead. func (*WorkspaceMember) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{222} + return file_openshell_proto_rawDescGZIP(), []int{244} } func (x *WorkspaceMember) GetMetadata() *datamodelv1.ObjectMeta { @@ -17361,7 +18646,7 @@ type AddWorkspaceMemberRequest struct { func (x *AddWorkspaceMemberRequest) Reset() { *x = AddWorkspaceMemberRequest{} - mi := &file_openshell_proto_msgTypes[223] + mi := &file_openshell_proto_msgTypes[245] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -17373,7 +18658,7 @@ func (x *AddWorkspaceMemberRequest) String() string { func (*AddWorkspaceMemberRequest) ProtoMessage() {} func (x *AddWorkspaceMemberRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[223] + mi := &file_openshell_proto_msgTypes[245] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -17386,7 +18671,7 @@ func (x *AddWorkspaceMemberRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use AddWorkspaceMemberRequest.ProtoReflect.Descriptor instead. func (*AddWorkspaceMemberRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{223} + return file_openshell_proto_rawDescGZIP(), []int{245} } func (x *AddWorkspaceMemberRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { @@ -17427,7 +18712,7 @@ type AddWorkspaceMemberResponse struct { func (x *AddWorkspaceMemberResponse) Reset() { *x = AddWorkspaceMemberResponse{} - mi := &file_openshell_proto_msgTypes[224] + mi := &file_openshell_proto_msgTypes[246] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -17439,7 +18724,7 @@ func (x *AddWorkspaceMemberResponse) String() string { func (*AddWorkspaceMemberResponse) ProtoMessage() {} func (x *AddWorkspaceMemberResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[224] + mi := &file_openshell_proto_msgTypes[246] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -17452,7 +18737,7 @@ func (x *AddWorkspaceMemberResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use AddWorkspaceMemberResponse.ProtoReflect.Descriptor instead. func (*AddWorkspaceMemberResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{224} + return file_openshell_proto_rawDescGZIP(), []int{246} } func (x *AddWorkspaceMemberResponse) GetMember() *WorkspaceMember { @@ -17478,7 +18763,7 @@ type RemoveWorkspaceMemberRequest struct { func (x *RemoveWorkspaceMemberRequest) Reset() { *x = RemoveWorkspaceMemberRequest{} - mi := &file_openshell_proto_msgTypes[225] + mi := &file_openshell_proto_msgTypes[247] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -17490,7 +18775,7 @@ func (x *RemoveWorkspaceMemberRequest) String() string { func (*RemoveWorkspaceMemberRequest) ProtoMessage() {} func (x *RemoveWorkspaceMemberRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[225] + mi := &file_openshell_proto_msgTypes[247] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -17503,7 +18788,7 @@ func (x *RemoveWorkspaceMemberRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use RemoveWorkspaceMemberRequest.ProtoReflect.Descriptor instead. func (*RemoveWorkspaceMemberRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{225} + return file_openshell_proto_rawDescGZIP(), []int{247} } func (x *RemoveWorkspaceMemberRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { @@ -17544,7 +18829,7 @@ type RemoveWorkspaceMemberResponse struct { func (x *RemoveWorkspaceMemberResponse) Reset() { *x = RemoveWorkspaceMemberResponse{} - mi := &file_openshell_proto_msgTypes[226] + mi := &file_openshell_proto_msgTypes[248] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -17556,7 +18841,7 @@ func (x *RemoveWorkspaceMemberResponse) String() string { func (*RemoveWorkspaceMemberResponse) ProtoMessage() {} func (x *RemoveWorkspaceMemberResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[226] + mi := &file_openshell_proto_msgTypes[248] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -17569,7 +18854,7 @@ func (x *RemoveWorkspaceMemberResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use RemoveWorkspaceMemberResponse.ProtoReflect.Descriptor instead. func (*RemoveWorkspaceMemberResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{226} + return file_openshell_proto_rawDescGZIP(), []int{248} } func (x *RemoveWorkspaceMemberResponse) GetOutcome() DeletionOutcome { @@ -17596,7 +18881,7 @@ type ListWorkspaceMembersRequest struct { func (x *ListWorkspaceMembersRequest) Reset() { *x = ListWorkspaceMembersRequest{} - mi := &file_openshell_proto_msgTypes[227] + mi := &file_openshell_proto_msgTypes[249] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -17608,7 +18893,7 @@ func (x *ListWorkspaceMembersRequest) String() string { func (*ListWorkspaceMembersRequest) ProtoMessage() {} func (x *ListWorkspaceMembersRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[227] + mi := &file_openshell_proto_msgTypes[249] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -17621,7 +18906,7 @@ func (x *ListWorkspaceMembersRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListWorkspaceMembersRequest.ProtoReflect.Descriptor instead. func (*ListWorkspaceMembersRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{227} + return file_openshell_proto_rawDescGZIP(), []int{249} } func (x *ListWorkspaceMembersRequest) GetWorkspaceScope() *datamodelv1.WorkspaceSelector { @@ -17657,7 +18942,7 @@ type ListWorkspaceMembersResponse struct { func (x *ListWorkspaceMembersResponse) Reset() { *x = ListWorkspaceMembersResponse{} - mi := &file_openshell_proto_msgTypes[228] + mi := &file_openshell_proto_msgTypes[250] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -17669,7 +18954,7 @@ func (x *ListWorkspaceMembersResponse) String() string { func (*ListWorkspaceMembersResponse) ProtoMessage() {} func (x *ListWorkspaceMembersResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[228] + mi := &file_openshell_proto_msgTypes[250] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -17682,7 +18967,7 @@ func (x *ListWorkspaceMembersResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListWorkspaceMembersResponse.ProtoReflect.Descriptor instead. func (*ListWorkspaceMembersResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{228} + return file_openshell_proto_rawDescGZIP(), []int{250} } func (x *ListWorkspaceMembersResponse) GetMembers() []*WorkspaceMember { @@ -17717,7 +19002,7 @@ type ExtensionServiceCredential struct { func (x *ExtensionServiceCredential) Reset() { *x = ExtensionServiceCredential{} - mi := &file_openshell_proto_msgTypes[229] + mi := &file_openshell_proto_msgTypes[251] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -17729,7 +19014,7 @@ func (x *ExtensionServiceCredential) String() string { func (*ExtensionServiceCredential) ProtoMessage() {} func (x *ExtensionServiceCredential) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[229] + mi := &file_openshell_proto_msgTypes[251] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -17742,7 +19027,7 @@ func (x *ExtensionServiceCredential) ProtoReflect() protoreflect.Message { // Deprecated: Use ExtensionServiceCredential.ProtoReflect.Descriptor instead. func (*ExtensionServiceCredential) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{229} + return file_openshell_proto_rawDescGZIP(), []int{251} } func (x *ExtensionServiceCredential) GetServiceName() string { @@ -17782,7 +19067,7 @@ type EndpointObservation struct { func (x *EndpointObservation) Reset() { *x = EndpointObservation{} - mi := &file_openshell_proto_msgTypes[230] + mi := &file_openshell_proto_msgTypes[252] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -17794,7 +19079,7 @@ func (x *EndpointObservation) String() string { func (*EndpointObservation) ProtoMessage() {} func (x *EndpointObservation) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[230] + mi := &file_openshell_proto_msgTypes[252] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -17807,7 +19092,7 @@ func (x *EndpointObservation) ProtoReflect() protoreflect.Message { // Deprecated: Use EndpointObservation.ProtoReflect.Descriptor instead. func (*EndpointObservation) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{230} + return file_openshell_proto_rawDescGZIP(), []int{252} } func (x *EndpointObservation) GetEndpointId() string { @@ -17860,7 +19145,7 @@ type ReportEndpointStatusRequest struct { func (x *ReportEndpointStatusRequest) Reset() { *x = ReportEndpointStatusRequest{} - mi := &file_openshell_proto_msgTypes[231] + mi := &file_openshell_proto_msgTypes[253] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -17872,7 +19157,7 @@ func (x *ReportEndpointStatusRequest) String() string { func (*ReportEndpointStatusRequest) ProtoMessage() {} func (x *ReportEndpointStatusRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[231] + mi := &file_openshell_proto_msgTypes[253] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -17885,7 +19170,7 @@ func (x *ReportEndpointStatusRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ReportEndpointStatusRequest.ProtoReflect.Descriptor instead. func (*ReportEndpointStatusRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{231} + return file_openshell_proto_rawDescGZIP(), []int{253} } func (x *ReportEndpointStatusRequest) GetSandboxId() string { @@ -17954,7 +19239,7 @@ type TokenGrantStatus struct { func (x *TokenGrantStatus) Reset() { *x = TokenGrantStatus{} - mi := &file_openshell_proto_msgTypes[232] + mi := &file_openshell_proto_msgTypes[254] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -17966,7 +19251,7 @@ func (x *TokenGrantStatus) String() string { func (*TokenGrantStatus) ProtoMessage() {} func (x *TokenGrantStatus) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[232] + mi := &file_openshell_proto_msgTypes[254] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -17979,7 +19264,7 @@ func (x *TokenGrantStatus) ProtoReflect() protoreflect.Message { // Deprecated: Use TokenGrantStatus.ProtoReflect.Descriptor instead. func (*TokenGrantStatus) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{232} + return file_openshell_proto_rawDescGZIP(), []int{254} } func (x *TokenGrantStatus) GetLastFailure() *TokenGrantFailureObservation { @@ -18006,7 +19291,7 @@ type TokenGrantFailureObservation struct { func (x *TokenGrantFailureObservation) Reset() { *x = TokenGrantFailureObservation{} - mi := &file_openshell_proto_msgTypes[233] + mi := &file_openshell_proto_msgTypes[255] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -18018,7 +19303,7 @@ func (x *TokenGrantFailureObservation) String() string { func (*TokenGrantFailureObservation) ProtoMessage() {} func (x *TokenGrantFailureObservation) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[233] + mi := &file_openshell_proto_msgTypes[255] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -18031,7 +19316,7 @@ func (x *TokenGrantFailureObservation) ProtoReflect() protoreflect.Message { // Deprecated: Use TokenGrantFailureObservation.ProtoReflect.Descriptor instead. func (*TokenGrantFailureObservation) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{233} + return file_openshell_proto_rawDescGZIP(), []int{255} } func (x *TokenGrantFailureObservation) GetDestinationHost() string { @@ -18064,7 +19349,7 @@ type ReportEndpointStatusResponse struct { func (x *ReportEndpointStatusResponse) Reset() { *x = ReportEndpointStatusResponse{} - mi := &file_openshell_proto_msgTypes[234] + mi := &file_openshell_proto_msgTypes[256] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -18076,7 +19361,7 @@ func (x *ReportEndpointStatusResponse) String() string { func (*ReportEndpointStatusResponse) ProtoMessage() {} func (x *ReportEndpointStatusResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[234] + mi := &file_openshell_proto_msgTypes[256] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -18089,7 +19374,7 @@ func (x *ReportEndpointStatusResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ReportEndpointStatusResponse.ProtoReflect.Descriptor instead. func (*ReportEndpointStatusResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{234} + return file_openshell_proto_rawDescGZIP(), []int{256} } // A configured endpoint and its last accepted network result in one record. @@ -18123,7 +19408,7 @@ type EndpointStatus struct { func (x *EndpointStatus) Reset() { *x = EndpointStatus{} - mi := &file_openshell_proto_msgTypes[235] + mi := &file_openshell_proto_msgTypes[257] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -18135,7 +19420,7 @@ func (x *EndpointStatus) String() string { func (*EndpointStatus) ProtoMessage() {} func (x *EndpointStatus) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[235] + mi := &file_openshell_proto_msgTypes[257] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -18148,7 +19433,7 @@ func (x *EndpointStatus) ProtoReflect() protoreflect.Message { // Deprecated: Use EndpointStatus.ProtoReflect.Descriptor instead. func (*EndpointStatus) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{235} + return file_openshell_proto_rawDescGZIP(), []int{257} } func (x *EndpointStatus) GetEndpointId() string { @@ -18243,7 +19528,7 @@ type SandboxProvisioning struct { func (x *SandboxProvisioning) Reset() { *x = SandboxProvisioning{} - mi := &file_openshell_proto_msgTypes[236] + mi := &file_openshell_proto_msgTypes[258] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -18255,7 +19540,7 @@ func (x *SandboxProvisioning) String() string { func (*SandboxProvisioning) ProtoMessage() {} func (x *SandboxProvisioning) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[236] + mi := &file_openshell_proto_msgTypes[258] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -18268,7 +19553,7 @@ func (x *SandboxProvisioning) ProtoReflect() protoreflect.Message { // Deprecated: Use SandboxProvisioning.ProtoReflect.Descriptor instead. func (*SandboxProvisioning) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{236} + return file_openshell_proto_rawDescGZIP(), []int{258} } func (x *SandboxProvisioning) GetAttemptId() string { @@ -18391,7 +19676,7 @@ type SandboxServiceExposure struct { func (x *SandboxServiceExposure) Reset() { *x = SandboxServiceExposure{} - mi := &file_openshell_proto_msgTypes[237] + mi := &file_openshell_proto_msgTypes[259] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -18403,7 +19688,7 @@ func (x *SandboxServiceExposure) String() string { func (*SandboxServiceExposure) ProtoMessage() {} func (x *SandboxServiceExposure) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[237] + mi := &file_openshell_proto_msgTypes[259] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -18416,7 +19701,7 @@ func (x *SandboxServiceExposure) ProtoReflect() protoreflect.Message { // Deprecated: Use SandboxServiceExposure.ProtoReflect.Descriptor instead. func (*SandboxServiceExposure) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{237} + return file_openshell_proto_rawDescGZIP(), []int{259} } func (x *SandboxServiceExposure) GetService() string { @@ -18470,7 +19755,7 @@ type EndpointFailure struct { func (x *EndpointFailure) Reset() { *x = EndpointFailure{} - mi := &file_openshell_proto_msgTypes[238] + mi := &file_openshell_proto_msgTypes[260] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -18482,7 +19767,7 @@ func (x *EndpointFailure) String() string { func (*EndpointFailure) ProtoMessage() {} func (x *EndpointFailure) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[238] + mi := &file_openshell_proto_msgTypes[260] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -18495,7 +19780,7 @@ func (x *EndpointFailure) ProtoReflect() protoreflect.Message { // Deprecated: Use EndpointFailure.ProtoReflect.Descriptor instead. func (*EndpointFailure) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{238} + return file_openshell_proto_rawDescGZIP(), []int{260} } func (x *EndpointFailure) GetDomain() EndpointFailureDomain { @@ -18620,13 +19905,18 @@ const file_openshell_proto_rawDesc = "" + "\x0flimit_supported\x18\x01 \x01(\bR\x0elimitSupported\"\x95\x01\n" + "\x17GpuResourceCapabilities\x12>\n" + "\x1bdefault_selection_supported\x18\x01 \x01(\bR\x19defaultSelectionSupported\x12:\n" + - "\x19count_selection_supported\x18\x02 \x01(\bR\x17countSelectionSupported\"\x80\x03\n" + + "\x19count_selection_supported\x18\x02 \x01(\bR\x17countSelectionSupported\"\x94\x03\n" + "\aSandbox\x12>\n" + "\bmetadata\x18\x01 \x01(\v2\".openshell.datamodel.v1.ObjectMetaR\bmetadata\x12-\n" + "\x04spec\x18\x02 \x01(\v2\x19.openshell.v1.SandboxSpecR\x04spec\x123\n" + "\x06status\x18\x03 \x01(\v2\x1b.openshell.v1.SandboxStatusR\x06status\x120\n" + "\x14host_key_fingerprint\x18\x06 \x01(\tR\x12hostKeyFingerprint\x12t\n" + - "\x1ecreated_from_workload_template\x18\x14 \x01(\v2/.openshell.v1.SandboxWorkloadTemplateProvenanceR\x1bcreatedFromWorkloadTemplateJ\x04\b\x04\x10\x05J\x04\b\x05\x10\x06R\x05phaseR\x16current_policy_version\"\x8a\x05\n" + + "\x1ecreated_from_workload_template\x18\x14 \x01(\v2/.openshell.v1.SandboxWorkloadTemplateProvenanceR\x1bcreatedFromWorkloadTemplateJ\x04\b\x04\x10\x05J\x04\b\x05\x10\x06R\x05phaseR\x16current_policy_versionR\x12delegated_identity\"\x8b\x02\n" + + "\x18SandboxDelegatedIdentity\x12#\n" + + "\rcredential_id\x18\x01 \x01(\tR\fcredentialId\x12+\n" + + "\x11principal_subject\x18\x02 \x01(\tR\x10principalSubject\x12X\n" + + "\x1adelegation_expiration_time\x18\x03 \x01(\v2\x1a.google.protobuf.TimestampR\x18delegationExpirationTime\x12C\n" + + "\x0fwithdrawal_time\x18\x04 \x01(\v2\x1a.google.protobuf.TimestampR\x0ewithdrawalTime\"\x8a\x05\n" + "\vSandboxSpec\x12\x1b\n" + "\tlog_level\x18\x01 \x01(\tR\blogLevel\x12L\n" + "\venvironment\x18\x05 \x03(\v2*.openshell.v1.SandboxSpec.EnvironmentEntryR\venvironment\x129\n" + @@ -18736,7 +20026,7 @@ const file_openshell_proto_rawDesc = "" + "\bmetadata\x18\x06 \x03(\v2).openshell.v1.PlatformEvent.MetadataEntryR\bmetadata\x1a;\n" + "\rMetadataEntry\x12\x10\n" + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + - "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01J\x04\b\x01\x10\x02R\ftimestamp_ms\"\xa9\x05\n" + + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01J\x04\b\x01\x10\x02R\ftimestamp_ms\"\x80\x06\n" + "\x14CreateSandboxRequest\x12R\n" + "\x0fworkspace_scope\x18\a \x01(\v2).openshell.datamodel.v1.WorkspaceSelectorR\x0eworkspaceScope\x12-\n" + "\x04spec\x18\x01 \x01(\v2\x19.openshell.v1.SandboxSpecR\x04spec\x12\x12\n" + @@ -18747,13 +20037,60 @@ const file_openshell_proto_rawDesc = "" + "\x11workload_template\x18\x06 \x01(\tR\x10workloadTemplate\x12\x1d\n" + "\n" + "request_id\x18\b \x01(\tR\trequestId\x12Q\n" + - "\x11service_exposures\x18\t \x03(\v2$.openshell.v1.SandboxServiceExposureR\x10serviceExposures\x1a9\n" + + "\x11service_exposures\x18\t \x03(\v2$.openshell.v1.SandboxServiceExposureR\x10serviceExposures\x12U\n" + + "\x12delegated_identity\x18\n" + + " \x01(\v2&.openshell.v1.DelegatedIdentityRequestR\x11delegatedIdentity\x1a9\n" + "\vLabelsEntry\x12\x10\n" + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\x1a>\n" + "\x10AnnotationsEntry\x12\x10\n" + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + - "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\xd4\x01\n" + + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\xd9\x01\n" + + "\x18DelegatedIdentityRequest\x125\n" + + "\bduration\x18\x01 \x01(\v2\x19.google.protobuf.DurationR\bdurationJ\x04\b\x02\x10\x03J\x04\b\x03\x10\x04J\x04\b\x04\x10\x05J\x04\b\x05\x10\x06J\x04\b\x06\x10\aJ\x04\b\a\x10\bJ\x04\b\b\x10\tR\x06issuerR\tclient_idR\rrefresh_tokenR\faccess_tokenR\x1aaccess_token_expires_at_msR\x06scopesR\baudience\"\xe2\x01\n" + + "#DelegatedIdentityAuthorizationGrant\x12\x16\n" + + "\x06issuer\x18\x01 \x01(\tR\x06issuer\x12\x1b\n" + + "\tclient_id\x18\x02 \x01(\tR\bclientId\x12)\n" + + "\rrefresh_token\x18\x03 \x01(\tB\x04\x88\xb5\x18\x01R\frefreshToken\x12'\n" + + "\faccess_token\x18\x04 \x01(\tB\x04\x88\xb5\x18\x01R\vaccessToken\x12\x16\n" + + "\x06scopes\x18\x05 \x01(\tR\x06scopes\x12\x1a\n" + + "\baudience\x18\x06 \x01(\tR\baudience\"0\n" + + ".GetDelegatedIdentityAuthorizationStatusRequest\"\xad\x02\n" + + "/GetDelegatedIdentityAuthorizationStatusResponse\x12\x16\n" + + "\x06usable\x18\x01 \x01(\bR\x06usable\x129\n" + + "\x18reauthorization_required\x18\x02 \x01(\bR\x17reauthorizationRequired\x12\x16\n" + + "\x06reason\x18\x03 \x01(\tR\x06reason\x12P\n" + + "\n" + + "credential\x18\x04 \x01(\v20.openshell.v1.DelegatedIdentityCredentialSummaryR\n" + + "credential\x12=\n" + + "\fcurrent_time\x18\x05 \x01(\v2\x1a.google.protobuf.TimestampR\vcurrentTime\"l\n" + + "!AuthorizeDelegatedIdentityRequest\x12G\n" + + "\x05grant\x18\x01 \x01(\v21.openshell.v1.DelegatedIdentityAuthorizationGrantR\x05grant\"\xb5\x01\n" + + "\"AuthorizeDelegatedIdentityResponse\x12P\n" + + "\n" + + "credential\x18\x01 \x01(\v20.openshell.v1.DelegatedIdentityCredentialSummaryR\n" + + "credential\x12=\n" + + "\fcurrent_time\x18\x02 \x01(\v2\x1a.google.protobuf.TimestampR\vcurrentTime\"\xa3\x01\n" + + "(GetSandboxDelegatedIdentityStatusRequest\x12\x12\n" + + "\x04name\x18\x01 \x01(\tR\x04name\x12R\n" + + "\x0fworkspace_scope\x18\x03 \x01(\v2).openshell.datamodel.v1.WorkspaceSelectorR\x0eworkspaceScopeJ\x04\b\x02\x10\x03R\tworkspace\"\xca\x02\n" + + ")GetSandboxDelegatedIdentityStatusResponse\x12U\n" + + "\x12delegated_identity\x18\x01 \x01(\v2&.openshell.v1.SandboxDelegatedIdentityR\x11delegatedIdentity\x12=\n" + + "\fcurrent_time\x18\x02 \x01(\v2\x1a.google.protobuf.TimestampR\vcurrentTime\x12X\n" + + "\x1acredential_revocation_time\x18\x03 \x01(\v2\x1a.google.protobuf.TimestampR\x18credentialRevocationTime\x12-\n" + + "\x12credential_missing\x18\x04 \x01(\bR\x11credentialMissing\"\xa2\x01\n" + + "'WithdrawSandboxDelegatedIdentityRequest\x12\x12\n" + + "\x04name\x18\x01 \x01(\tR\x04name\x12R\n" + + "\x0fworkspace_scope\x18\x03 \x01(\v2).openshell.datamodel.v1.WorkspaceSelectorR\x0eworkspaceScopeJ\x04\b\x02\x10\x03R\tworkspace\"y\n" + + "(WithdrawSandboxDelegatedIdentityResponse\x12/\n" + + "\asandbox\x18\x01 \x01(\v2\x15.openshell.v1.SandboxR\asandbox\x12\x1c\n" + + "\twithdrawn\x18\x02 \x01(\bR\twithdrawn\"\xf7\x01\n" + + "%ExtendSandboxDelegatedIdentityRequest\x12\x12\n" + + "\x04name\x18\x01 \x01(\tR\x04name\x12U\n" + + "\x12delegated_identity\x18\x03 \x01(\v2&.openshell.v1.DelegatedIdentityRequestR\x11delegatedIdentity\x12R\n" + + "\x0fworkspace_scope\x18\x04 \x01(\v2).openshell.datamodel.v1.WorkspaceSelectorR\x0eworkspaceScopeJ\x04\b\x02\x10\x03R\tworkspace\"Y\n" + + "&ExtendSandboxDelegatedIdentityResponse\x12/\n" + + "\asandbox\x18\x01 \x01(\v2\x15.openshell.v1.SandboxR\asandbox\"\xd4\x01\n" + "\x1cCreateSandboxTemplateRequest\x12R\n" + "\x0fworkspace_scope\x18\x02 \x01(\v2).openshell.datamodel.v1.WorkspaceSelectorR\x0eworkspaceScope\x12A\n" + "\btemplate\x18\x01 \x01(\v2%.openshell.v1.SandboxWorkloadTemplateR\btemplate\x12\x1d\n" + @@ -19257,7 +20594,46 @@ const file_openshell_proto_rawDesc = "" + "\x16provider_error_subtype\x18\f \x01(\tR\x14providerErrorSubtype\x12B\n" + "\x0flast_error_time\x18q \x01(\v2\x1a.google.protobuf.TimestampR\rlastErrorTimeJ\x04\b\x06\x10\aJ\x04\b\a\x10\bJ\x04\b\b\x10\tJ\x04\b\r\x10\x0eR\rexpires_at_msR\x12next_refresh_at_msR\x12last_refresh_at_msR\x10last_error_at_ms\"<\n" + "\x18ProviderProfileDiscovery\x12 \n" + - "\vcredentials\x18\x01 \x03(\tR\vcredentials\"\xb8\x01\n" + + "\vcredentials\x18\x01 \x03(\tR\vcredentials\"\xca\x04\n" + + "\"DelegatedIdentityCredentialSummary\x12>\n" + + "\bmetadata\x18\x01 \x01(\v2\".openshell.datamodel.v1.ObjectMetaR\bmetadata\x12\x16\n" + + "\x06issuer\x18\x02 \x01(\tR\x06issuer\x12\x1b\n" + + "\tclient_id\x18\x03 \x01(\tR\bclientId\x12+\n" + + "\x11principal_subject\x18\x04 \x01(\tR\x10principalSubject\x122\n" + + "\x15refresh_token_present\x18\x05 \x01(\bR\x13refreshTokenPresent\x120\n" + + "\x14access_token_present\x18\x06 \x01(\bR\x12accessTokenPresent\x12[\n" + + "\x1caccess_token_expiration_time\x18\a \x01(\v2\x1a.google.protobuf.TimestampR\x19accessTokenExpirationTime\x12\x16\n" + + "\x06scopes\x18\b \x01(\tR\x06scopes\x12\x1a\n" + + "\baudience\x18\t \x01(\tR\baudience\x12F\n" + + "\x11last_refresh_time\x18\n" + + " \x01(\v2\x1a.google.protobuf.TimestampR\x0flastRefreshTime\x12C\n" + + "\x0frevocation_time\x18\v \x01(\v2\x1a.google.protobuf.TimestampR\x0erevocationTime\"e\n" + + "'ListDelegatedIdentityCredentialsRequest\x12\x1b\n" + + "\tpage_size\x18\x01 \x01(\x05R\bpageSize\x12\x1d\n" + + "\n" + + "page_token\x18\x02 \x01(\tR\tpageToken\"\xa6\x01\n" + + "(ListDelegatedIdentityCredentialsResponse\x12R\n" + + "\vcredentials\x18\x01 \x03(\v20.openshell.v1.DelegatedIdentityCredentialSummaryR\vcredentials\x12&\n" + + "\x0fnext_page_token\x18\x02 \x01(\tR\rnextPageToken\"=\n" + + "+GetDelegatedIdentityCredentialStatusRequest\x12\x0e\n" + + "\x02id\x18\x01 \x01(\tR\x02id\"\xbf\x01\n" + + ",GetDelegatedIdentityCredentialStatusResponse\x12P\n" + + "\n" + + "credential\x18\x01 \x01(\v20.openshell.v1.DelegatedIdentityCredentialSummaryR\n" + + "credential\x12=\n" + + "\fcurrent_time\x18\x02 \x01(\v2\x1a.google.protobuf.TimestampR\vcurrentTime\"v\n" + + "(RevokeDelegatedIdentityCredentialRequest\x12\x0e\n" + + "\x02id\x18\x01 \x01(\tR\x02id\x12:\n" + + "\x19expected_resource_version\x18\x02 \x01(\x04R\x17expectedResourceVersion\"\xbb\x01\n" + + ")RevokeDelegatedIdentityCredentialResponse\x12\x18\n" + + "\arevoked\x18\x02 \x01(\bR\arevoked\x12C\n" + + "\x0frevocation_time\x18\x03 \x01(\v2\x1a.google.protobuf.TimestampR\x0erevocationTime\x12)\n" + + "\x10resource_version\x18\x04 \x01(\x04R\x0fresourceVersionJ\x04\b\x01\x10\x02\"v\n" + + "(DeleteDelegatedIdentityCredentialRequest\x12\x0e\n" + + "\x02id\x18\x01 \x01(\tR\x02id\x12:\n" + + "\x19expected_resource_version\x18\x02 \x01(\x04R\x17expectedResourceVersion\"E\n" + + ")DeleteDelegatedIdentityCredentialResponse\x12\x18\n" + + "\adeleted\x18\x01 \x01(\bR\adeleted\"\xb8\x01\n" + "\x1fGetProviderRefreshStatusRequest\x12R\n" + "\x0fworkspace_scope\x18\x03 \x01(\v2).openshell.datamodel.v1.WorkspaceSelectorR\x0eworkspaceScope\x12\x1a\n" + "\bprovider\x18\x01 \x01(\tR\bprovider\x12%\n" + @@ -20091,7 +21467,7 @@ const file_openshell_proto_rawDesc = "" + "-SERVICE_AUTHORIZATION_MODE_BEARER_PASSTHROUGH\x10\x02*i\n" + "\x15EndpointFailureDomain\x12'\n" + "#ENDPOINT_FAILURE_DOMAIN_UNSPECIFIED\x10\x00\x12'\n" + - "#ENDPOINT_FAILURE_DOMAIN_TOKEN_GRANT\x10\x012\xb5V\n" + + "#ENDPOINT_FAILURE_DOMAIN_TOKEN_GRANT\x10\x012\xd3c\n" + "\tOpenShell\x12Z\n" + "\x06Health\x12\x1b.openshell.v1.HealthRequest\x1a\x1c.openshell.v1.HealthResponse\"\x15\x82\xb5\x18\x11\n" + "\x0funauthenticated\x12i\n" + @@ -20102,6 +21478,16 @@ const file_openshell_proto_rawDesc = "" + "\rCreateSandbox\x12\".openshell.v1.CreateSandboxRequest\x1a\x1d.openshell.v1.SandboxResponse\"!\x82\xb5\x18\x1d\n" + "\x06bearer\x12\x04user\"\rsandbox:write\x12\x93\x01\n" + "\x15BeginRootfsTarStaging\x12*.openshell.v1.BeginRootfsTarStagingRequest\x1a+.openshell.v1.BeginRootfsTarStagingResponse\"!\x82\xb5\x18\x1d\n" + + "\x06bearer\x12\x04user\"\rsandbox:write\x12\xc3\x01\n" + + "'GetDelegatedIdentityAuthorizationStatus\x12<.openshell.v1.GetDelegatedIdentityAuthorizationStatusRequest\x1a=.openshell.v1.GetDelegatedIdentityAuthorizationStatusResponse\"\x1b\x82\xb5\x18\x17\n" + + "\x06bearer\"\rsandbox:write\x12\x9c\x01\n" + + "\x1aAuthorizeDelegatedIdentity\x12/.openshell.v1.AuthorizeDelegatedIdentityRequest\x1a0.openshell.v1.AuthorizeDelegatedIdentityResponse\"\x1b\x82\xb5\x18\x17\n" + + "\x06bearer\"\rsandbox:write\x12\xb6\x01\n" + + "!GetSandboxDelegatedIdentityStatus\x126.openshell.v1.GetSandboxDelegatedIdentityStatusRequest\x1a7.openshell.v1.GetSandboxDelegatedIdentityStatusResponse\" \x82\xb5\x18\x1c\n" + + "\x06bearer\x12\x04user\"\fsandbox:read\x12\xb4\x01\n" + + " WithdrawSandboxDelegatedIdentity\x125.openshell.v1.WithdrawSandboxDelegatedIdentityRequest\x1a6.openshell.v1.WithdrawSandboxDelegatedIdentityResponse\"!\x82\xb5\x18\x1d\n" + + "\x06bearer\x12\x04user\"\rsandbox:write\x12\xae\x01\n" + + "\x1eExtendSandboxDelegatedIdentity\x123.openshell.v1.ExtendSandboxDelegatedIdentityRequest\x1a4.openshell.v1.ExtendSandboxDelegatedIdentityResponse\"!\x82\xb5\x18\x1d\n" + "\x06bearer\x12\x04user\"\rsandbox:write\x12n\n" + "\n" + "GetSandbox\x12\x1f.openshell.v1.GetSandboxRequest\x1a\x1d.openshell.v1.SandboxResponse\" \x82\xb5\x18\x1c\n" + @@ -20176,7 +21562,15 @@ const file_openshell_proto_rawDesc = "" + "\x18ConfigureProviderRefresh\x12-.openshell.v1.ConfigureProviderRefreshRequest\x1a..openshell.v1.ConfigureProviderRefreshResponse\"#\x82\xb5\x18\x1f\n" + "\x06bearer\x12\x05admin\"\x0eprovider:write\x12\x9e\x01\n" + "\x18RotateProviderCredential\x12-.openshell.v1.RotateProviderCredentialRequest\x1a..openshell.v1.RotateProviderCredentialResponse\"#\x82\xb5\x18\x1f\n" + - "\x06bearer\x12\x05admin\"\x0eprovider:write\x12\x95\x01\n" + + "\x06bearer\x12\x05admin\"\x0eprovider:write\x12\xbe\x01\n" + + " ListDelegatedIdentityCredentials\x125.openshell.v1.ListDelegatedIdentityCredentialsRequest\x1a6.openshell.v1.ListDelegatedIdentityCredentialsResponse\"+\x82\xb5\x18'\n" + + "\x06bearer\x1a\x0eplatform_admin\"\rprovider:read\x12\xca\x01\n" + + "$GetDelegatedIdentityCredentialStatus\x129.openshell.v1.GetDelegatedIdentityCredentialStatusRequest\x1a:.openshell.v1.GetDelegatedIdentityCredentialStatusResponse\"+\x82\xb5\x18'\n" + + "\x06bearer\x1a\x0eplatform_admin\"\rprovider:read\x12\xc2\x01\n" + + "!RevokeDelegatedIdentityCredential\x126.openshell.v1.RevokeDelegatedIdentityCredentialRequest\x1a7.openshell.v1.RevokeDelegatedIdentityCredentialResponse\",\x82\xb5\x18(\n" + + "\x06bearer\x1a\x0eplatform_admin\"\x0eprovider:write\x12\xc2\x01\n" + + "!DeleteDelegatedIdentityCredential\x126.openshell.v1.DeleteDelegatedIdentityCredentialRequest\x1a7.openshell.v1.DeleteDelegatedIdentityCredentialResponse\",\x82\xb5\x18(\n" + + "\x06bearer\x1a\x0eplatform_admin\"\x0eprovider:write\x12\x95\x01\n" + "\x15DeleteProviderRefresh\x12*.openshell.v1.DeleteProviderRefreshRequest\x1a+.openshell.v1.DeleteProviderRefreshResponse\"#\x82\xb5\x18\x1f\n" + "\x06bearer\x12\x05admin\"\x0eprovider:write\x12\x80\x01\n" + "\x0eDeleteProvider\x12#.openshell.v1.DeleteProviderRequest\x1a$.openshell.v1.DeleteProviderResponse\"#\x82\xb5\x18\x1f\n" + @@ -20281,317 +21675,339 @@ func file_openshell_proto_rawDescGZIP() []byte { } var file_openshell_proto_enumTypes = make([]protoimpl.EnumInfo, 21) -var file_openshell_proto_msgTypes = make([]protoimpl.MessageInfo, 263) +var file_openshell_proto_msgTypes = make([]protoimpl.MessageInfo, 285) var file_openshell_proto_goTypes = []any{ - (ExtensionKind)(0), // 0: openshell.v1.ExtensionKind - (SandboxPhase)(0), // 1: openshell.v1.SandboxPhase - (ProviderMutationKind)(0), // 2: openshell.v1.ProviderMutationKind - (ProviderReadinessState)(0), // 3: openshell.v1.ProviderReadinessState - (ProviderReadinessReason)(0), // 4: openshell.v1.ProviderReadinessReason - (ConfigComponent)(0), // 5: openshell.v1.ConfigComponent - (ConfigApplyOutcome)(0), // 6: openshell.v1.ConfigApplyOutcome - (ConfigUpdateOperationState)(0), // 7: openshell.v1.ConfigUpdateOperationState - (ProviderCredentialTokenGrantType)(0), // 8: openshell.v1.ProviderCredentialTokenGrantType - (ProviderCredentialRefreshStrategy)(0), // 9: openshell.v1.ProviderCredentialRefreshStrategy - (ProviderProfileCategory)(0), // 10: openshell.v1.ProviderProfileCategory - (ConfigurationAdmissionState)(0), // 11: openshell.v1.ConfigurationAdmissionState - (PolicyStatus)(0), // 12: openshell.v1.PolicyStatus - (ServiceStatus)(0), // 13: openshell.v1.ServiceStatus - (WorkspaceRole)(0), // 14: openshell.v1.WorkspaceRole - (ProviderCredentialRefreshRecoveryAction)(0), // 15: openshell.v1.ProviderCredentialRefreshRecoveryAction - (SandboxRestartPolicy)(0), // 16: openshell.v1.SandboxRestartPolicy - (DeletionOutcome)(0), // 17: openshell.v1.DeletionOutcome - (EndpointResult)(0), // 18: openshell.v1.EndpointResult - (ServiceAuthorizationMode)(0), // 19: openshell.v1.ServiceAuthorizationMode - (EndpointFailureDomain)(0), // 20: openshell.v1.EndpointFailureDomain - (*IssueSandboxTokenRequest)(nil), // 21: openshell.v1.IssueSandboxTokenRequest - (*IssueSandboxTokenResponse)(nil), // 22: openshell.v1.IssueSandboxTokenResponse - (*RefreshSandboxTokenRequest)(nil), // 23: openshell.v1.RefreshSandboxTokenRequest - (*RefreshSandboxTokenResponse)(nil), // 24: openshell.v1.RefreshSandboxTokenResponse - (*HealthRequest)(nil), // 25: openshell.v1.HealthRequest - (*HealthResponse)(nil), // 26: openshell.v1.HealthResponse - (*GetCurrentUserRequest)(nil), // 27: openshell.v1.GetCurrentUserRequest - (*GetCurrentUserResponse)(nil), // 28: openshell.v1.GetCurrentUserResponse - (*GetGatewayInfoRequest)(nil), // 29: openshell.v1.GetGatewayInfoRequest - (*GetGatewayInfoResponse)(nil), // 30: openshell.v1.GetGatewayInfoResponse - (*NegotiatedExtensionInfo)(nil), // 31: openshell.v1.NegotiatedExtensionInfo - (*ComputeDriverInfo)(nil), // 32: openshell.v1.ComputeDriverInfo - (*ComputeDriverCapabilities)(nil), // 33: openshell.v1.ComputeDriverCapabilities - (*ResourceCapabilities)(nil), // 34: openshell.v1.ResourceCapabilities - (*CpuResourceCapabilities)(nil), // 35: openshell.v1.CpuResourceCapabilities - (*MemoryResourceCapabilities)(nil), // 36: openshell.v1.MemoryResourceCapabilities - (*GpuResourceCapabilities)(nil), // 37: openshell.v1.GpuResourceCapabilities - (*Sandbox)(nil), // 38: openshell.v1.Sandbox - (*SandboxSpec)(nil), // 39: openshell.v1.SandboxSpec - (*ResourceRequirements)(nil), // 40: openshell.v1.ResourceRequirements - (*GpuResourceRequirements)(nil), // 41: openshell.v1.GpuResourceRequirements - (*SandboxTemplate)(nil), // 42: openshell.v1.SandboxTemplate - (*SandboxWorkloadTemplate)(nil), // 43: openshell.v1.SandboxWorkloadTemplate - (*SandboxWorkloadTemplateSpec)(nil), // 44: openshell.v1.SandboxWorkloadTemplateSpec - (*SandboxWorkloadConfig)(nil), // 45: openshell.v1.SandboxWorkloadConfig - (*SandboxResources)(nil), // 46: openshell.v1.SandboxResources - (*SandboxServiceLevel)(nil), // 47: openshell.v1.SandboxServiceLevel - (*SandboxStartup)(nil), // 48: openshell.v1.SandboxStartup - (*SandboxWorkloadTemplateProvenance)(nil), // 49: openshell.v1.SandboxWorkloadTemplateProvenance - (*SandboxStatus)(nil), // 50: openshell.v1.SandboxStatus - (*SandboxCondition)(nil), // 51: openshell.v1.SandboxCondition - (*PlatformEvent)(nil), // 52: openshell.v1.PlatformEvent - (*CreateSandboxRequest)(nil), // 53: openshell.v1.CreateSandboxRequest - (*CreateSandboxTemplateRequest)(nil), // 54: openshell.v1.CreateSandboxTemplateRequest - (*GetSandboxTemplateRequest)(nil), // 55: openshell.v1.GetSandboxTemplateRequest - (*ListSandboxTemplatesRequest)(nil), // 56: openshell.v1.ListSandboxTemplatesRequest - (*DeleteSandboxTemplateRequest)(nil), // 57: openshell.v1.DeleteSandboxTemplateRequest - (*SandboxTemplateResponse)(nil), // 58: openshell.v1.SandboxTemplateResponse - (*ListSandboxTemplatesResponse)(nil), // 59: openshell.v1.ListSandboxTemplatesResponse - (*DeleteSandboxTemplateResponse)(nil), // 60: openshell.v1.DeleteSandboxTemplateResponse - (*BeginRootfsTarStagingRequest)(nil), // 61: openshell.v1.BeginRootfsTarStagingRequest - (*BeginRootfsTarStagingResponse)(nil), // 62: openshell.v1.BeginRootfsTarStagingResponse - (*GetSandboxRequest)(nil), // 63: openshell.v1.GetSandboxRequest - (*ListSandboxesRequest)(nil), // 64: openshell.v1.ListSandboxesRequest - (*ListSandboxProvidersRequest)(nil), // 65: openshell.v1.ListSandboxProvidersRequest - (*AttachSandboxProviderRequest)(nil), // 66: openshell.v1.AttachSandboxProviderRequest - (*DetachSandboxProviderRequest)(nil), // 67: openshell.v1.DetachSandboxProviderRequest - (*DeleteSandboxRequest)(nil), // 68: openshell.v1.DeleteSandboxRequest - (*StopSandboxRequest)(nil), // 69: openshell.v1.StopSandboxRequest - (*StartSandboxRequest)(nil), // 70: openshell.v1.StartSandboxRequest - (*SandboxResponse)(nil), // 71: openshell.v1.SandboxResponse - (*ListSandboxesResponse)(nil), // 72: openshell.v1.ListSandboxesResponse - (*ListSandboxProvidersResponse)(nil), // 73: openshell.v1.ListSandboxProvidersResponse - (*AttachSandboxProviderResponse)(nil), // 74: openshell.v1.AttachSandboxProviderResponse - (*DetachSandboxProviderResponse)(nil), // 75: openshell.v1.DetachSandboxProviderResponse - (*ProviderDesiredIdentity)(nil), // 76: openshell.v1.ProviderDesiredIdentity - (*ConfigSnapshotRevision)(nil), // 77: openshell.v1.ConfigSnapshotRevision - (*SandboxConfigRevision)(nil), // 78: openshell.v1.SandboxConfigRevision - (*ConfigUpdateOperation)(nil), // 79: openshell.v1.ConfigUpdateOperation - (*ProviderMutationReceipt)(nil), // 80: openshell.v1.ProviderMutationReceipt - (*ProviderReadinessObservation)(nil), // 81: openshell.v1.ProviderReadinessObservation - (*ProviderReadinessStatus)(nil), // 82: openshell.v1.ProviderReadinessStatus - (*GetSandboxProviderStatusRequest)(nil), // 83: openshell.v1.GetSandboxProviderStatusRequest - (*GetSandboxProviderStatusResponse)(nil), // 84: openshell.v1.GetSandboxProviderStatusResponse - (*ReportProviderReadinessRequest)(nil), // 85: openshell.v1.ReportProviderReadinessRequest - (*ReportProviderReadinessResponse)(nil), // 86: openshell.v1.ReportProviderReadinessResponse - (*DeleteSandboxResponse)(nil), // 87: openshell.v1.DeleteSandboxResponse - (*CreateSshSessionRequest)(nil), // 88: openshell.v1.CreateSshSessionRequest - (*CreateSshSessionResponse)(nil), // 89: openshell.v1.CreateSshSessionResponse - (*ExposeServiceRequest)(nil), // 90: openshell.v1.ExposeServiceRequest - (*GetServiceRequest)(nil), // 91: openshell.v1.GetServiceRequest - (*ListServicesRequest)(nil), // 92: openshell.v1.ListServicesRequest - (*ListServicesResponse)(nil), // 93: openshell.v1.ListServicesResponse - (*DeleteServiceRequest)(nil), // 94: openshell.v1.DeleteServiceRequest - (*DeleteServiceResponse)(nil), // 95: openshell.v1.DeleteServiceResponse - (*ServiceEndpoint)(nil), // 96: openshell.v1.ServiceEndpoint - (*ServiceEndpointResponse)(nil), // 97: openshell.v1.ServiceEndpointResponse - (*RevokeSshSessionRequest)(nil), // 98: openshell.v1.RevokeSshSessionRequest - (*RevokeSshSessionResponse)(nil), // 99: openshell.v1.RevokeSshSessionResponse - (*ExecSandboxRequest)(nil), // 100: openshell.v1.ExecSandboxRequest - (*ExecSandboxStdout)(nil), // 101: openshell.v1.ExecSandboxStdout - (*ExecSandboxStderr)(nil), // 102: openshell.v1.ExecSandboxStderr - (*ExecSandboxExit)(nil), // 103: openshell.v1.ExecSandboxExit - (*ExecSandboxEvent)(nil), // 104: openshell.v1.ExecSandboxEvent - (*TcpForwardInit)(nil), // 105: openshell.v1.TcpForwardInit - (*TcpForwardFrame)(nil), // 106: openshell.v1.TcpForwardFrame - (*ExecSandboxInput)(nil), // 107: openshell.v1.ExecSandboxInput - (*ExecSandboxWindowResize)(nil), // 108: openshell.v1.ExecSandboxWindowResize - (*SshSession)(nil), // 109: openshell.v1.SshSession - (*WatchSandboxRequest)(nil), // 110: openshell.v1.WatchSandboxRequest - (*SandboxStreamEvent)(nil), // 111: openshell.v1.SandboxStreamEvent - (*SandboxLogLine)(nil), // 112: openshell.v1.SandboxLogLine - (*SandboxStreamWarning)(nil), // 113: openshell.v1.SandboxStreamWarning - (*CreateProviderRequest)(nil), // 114: openshell.v1.CreateProviderRequest - (*GetProviderRequest)(nil), // 115: openshell.v1.GetProviderRequest - (*GetProviderCredentialsRequest)(nil), // 116: openshell.v1.GetProviderCredentialsRequest - (*ProviderCredentialValue)(nil), // 117: openshell.v1.ProviderCredentialValue - (*GetProviderCredentialsResponse)(nil), // 118: openshell.v1.GetProviderCredentialsResponse - (*ListProvidersRequest)(nil), // 119: openshell.v1.ListProvidersRequest - (*UpdateProviderRequest)(nil), // 120: openshell.v1.UpdateProviderRequest - (*DeleteProviderRequest)(nil), // 121: openshell.v1.DeleteProviderRequest - (*ProviderResponse)(nil), // 122: openshell.v1.ProviderResponse - (*ListProvidersResponse)(nil), // 123: openshell.v1.ListProvidersResponse - (*ListProviderProfilesRequest)(nil), // 124: openshell.v1.ListProviderProfilesRequest - (*GetProviderProfileRequest)(nil), // 125: openshell.v1.GetProviderProfileRequest - (*ProviderProfileImportItem)(nil), // 126: openshell.v1.ProviderProfileImportItem - (*ProviderProfileDiagnostic)(nil), // 127: openshell.v1.ProviderProfileDiagnostic - (*ProviderCredentialTokenGrantAudienceOverride)(nil), // 128: openshell.v1.ProviderCredentialTokenGrantAudienceOverride - (*ProviderCredentialTokenGrantSubjectToken)(nil), // 129: openshell.v1.ProviderCredentialTokenGrantSubjectToken - (*ProviderCredentialTokenGrant)(nil), // 130: openshell.v1.ProviderCredentialTokenGrant - (*ProviderProfileCredential)(nil), // 131: openshell.v1.ProviderProfileCredential - (*ProviderCredentialRefreshMaterial)(nil), // 132: openshell.v1.ProviderCredentialRefreshMaterial - (*ProviderCredentialRefreshOutput)(nil), // 133: openshell.v1.ProviderCredentialRefreshOutput - (*ProviderCredentialRefresh)(nil), // 134: openshell.v1.ProviderCredentialRefresh - (*ProviderCredentialRefreshStatus)(nil), // 135: openshell.v1.ProviderCredentialRefreshStatus - (*ProviderProfileDiscovery)(nil), // 136: openshell.v1.ProviderProfileDiscovery - (*GetProviderRefreshStatusRequest)(nil), // 137: openshell.v1.GetProviderRefreshStatusRequest - (*GetProviderRefreshStatusResponse)(nil), // 138: openshell.v1.GetProviderRefreshStatusResponse - (*ConfigureProviderRefreshRequest)(nil), // 139: openshell.v1.ConfigureProviderRefreshRequest - (*ConfigureProviderRefreshResponse)(nil), // 140: openshell.v1.ConfigureProviderRefreshResponse - (*RotateProviderCredentialRequest)(nil), // 141: openshell.v1.RotateProviderCredentialRequest - (*RotateProviderCredentialResponse)(nil), // 142: openshell.v1.RotateProviderCredentialResponse - (*DeleteProviderRefreshRequest)(nil), // 143: openshell.v1.DeleteProviderRefreshRequest - (*DeleteProviderRefreshResponse)(nil), // 144: openshell.v1.DeleteProviderRefreshResponse - (*ProviderProfile)(nil), // 145: openshell.v1.ProviderProfile - (*ProviderProfileFile)(nil), // 146: openshell.v1.ProviderProfileFile - (*ProviderProfileResponse)(nil), // 147: openshell.v1.ProviderProfileResponse - (*ListProviderProfilesResponse)(nil), // 148: openshell.v1.ListProviderProfilesResponse - (*ImportProviderProfilesRequest)(nil), // 149: openshell.v1.ImportProviderProfilesRequest - (*ImportProviderProfilesResponse)(nil), // 150: openshell.v1.ImportProviderProfilesResponse - (*UpdateProviderProfilesRequest)(nil), // 151: openshell.v1.UpdateProviderProfilesRequest - (*UpdateProviderProfilesResponse)(nil), // 152: openshell.v1.UpdateProviderProfilesResponse - (*LintProviderProfilesRequest)(nil), // 153: openshell.v1.LintProviderProfilesRequest - (*LintProviderProfilesResponse)(nil), // 154: openshell.v1.LintProviderProfilesResponse - (*DeleteProviderResponse)(nil), // 155: openshell.v1.DeleteProviderResponse - (*DeleteProviderProfileRequest)(nil), // 156: openshell.v1.DeleteProviderProfileRequest - (*DeleteProviderProfileResponse)(nil), // 157: openshell.v1.DeleteProviderProfileResponse - (*GetSandboxProviderEnvironmentRequest)(nil), // 158: openshell.v1.GetSandboxProviderEnvironmentRequest - (*StaticCredentialEndpointBinding)(nil), // 159: openshell.v1.StaticCredentialEndpointBinding - (*StaticCredentialBinding)(nil), // 160: openshell.v1.StaticCredentialBinding - (*GetSandboxProviderEnvironmentResponse)(nil), // 161: openshell.v1.GetSandboxProviderEnvironmentResponse - (*ExchangeProviderSubjectTokenRequest)(nil), // 162: openshell.v1.ExchangeProviderSubjectTokenRequest - (*ExchangeProviderSubjectTokenResponse)(nil), // 163: openshell.v1.ExchangeProviderSubjectTokenResponse - (*UpdateConfigRequest)(nil), // 164: openshell.v1.UpdateConfigRequest - (*PolicyMergeOperation)(nil), // 165: openshell.v1.PolicyMergeOperation - (*AddNetworkRule)(nil), // 166: openshell.v1.AddNetworkRule - (*RemoveNetworkEndpoint)(nil), // 167: openshell.v1.RemoveNetworkEndpoint - (*RemoveNetworkRule)(nil), // 168: openshell.v1.RemoveNetworkRule - (*L7RuleTarget)(nil), // 169: openshell.v1.L7RuleTarget - (*AddDenyRules)(nil), // 170: openshell.v1.AddDenyRules - (*AddAllowRules)(nil), // 171: openshell.v1.AddAllowRules - (*RemoveNetworkBinary)(nil), // 172: openshell.v1.RemoveNetworkBinary - (*UpdateConfigResponse)(nil), // 173: openshell.v1.UpdateConfigResponse - (*GetSandboxPolicyStatusRequest)(nil), // 174: openshell.v1.GetSandboxPolicyStatusRequest - (*GetSandboxPolicyStatusResponse)(nil), // 175: openshell.v1.GetSandboxPolicyStatusResponse - (*ListSandboxPoliciesRequest)(nil), // 176: openshell.v1.ListSandboxPoliciesRequest - (*ListSandboxPoliciesResponse)(nil), // 177: openshell.v1.ListSandboxPoliciesResponse - (*ReportPolicyStatusRequest)(nil), // 178: openshell.v1.ReportPolicyStatusRequest - (*ReportPolicyStatusResponse)(nil), // 179: openshell.v1.ReportPolicyStatusResponse - (*SandboxConfigurationAdmission)(nil), // 180: openshell.v1.SandboxConfigurationAdmission - (*ReportSandboxConfigurationRequest)(nil), // 181: openshell.v1.ReportSandboxConfigurationRequest - (*ReportSandboxConfigurationResponse)(nil), // 182: openshell.v1.ReportSandboxConfigurationResponse - (*SandboxPolicyRevision)(nil), // 183: openshell.v1.SandboxPolicyRevision - (*GetSandboxLogsRequest)(nil), // 184: openshell.v1.GetSandboxLogsRequest - (*PushSandboxLogsRequest)(nil), // 185: openshell.v1.PushSandboxLogsRequest - (*PushSandboxLogsResponse)(nil), // 186: openshell.v1.PushSandboxLogsResponse - (*GetSandboxLogsResponse)(nil), // 187: openshell.v1.GetSandboxLogsResponse - (*SupervisorMessage)(nil), // 188: openshell.v1.SupervisorMessage - (*GatewayMessage)(nil), // 189: openshell.v1.GatewayMessage - (*SupervisorHello)(nil), // 190: openshell.v1.SupervisorHello - (*SessionAccepted)(nil), // 191: openshell.v1.SessionAccepted - (*SessionRejected)(nil), // 192: openshell.v1.SessionRejected - (*SessionRedirect)(nil), // 193: openshell.v1.SessionRedirect - (*SupervisorHeartbeat)(nil), // 194: openshell.v1.SupervisorHeartbeat - (*GatewayHeartbeat)(nil), // 195: openshell.v1.GatewayHeartbeat - (*ReportMainProcessExitRequest)(nil), // 196: openshell.v1.ReportMainProcessExitRequest - (*ReportMainProcessExitResponse)(nil), // 197: openshell.v1.ReportMainProcessExitResponse - (*FinalizeMainProcessExitRequest)(nil), // 198: openshell.v1.FinalizeMainProcessExitRequest - (*FinalizeMainProcessExitResponse)(nil), // 199: openshell.v1.FinalizeMainProcessExitResponse - (*RelayOpen)(nil), // 200: openshell.v1.RelayOpen - (*SshRelayTarget)(nil), // 201: openshell.v1.SshRelayTarget - (*TcpRelayTarget)(nil), // 202: openshell.v1.TcpRelayTarget - (*RelayInit)(nil), // 203: openshell.v1.RelayInit - (*RelayFrame)(nil), // 204: openshell.v1.RelayFrame - (*PeerRelayInit)(nil), // 205: openshell.v1.PeerRelayInit - (*PeerRelayFrame)(nil), // 206: openshell.v1.PeerRelayFrame - (*RelayOpenResult)(nil), // 207: openshell.v1.RelayOpenResult - (*RelayClose)(nil), // 208: openshell.v1.RelayClose - (*L7RequestSample)(nil), // 209: openshell.v1.L7RequestSample - (*DenialSummary)(nil), // 210: openshell.v1.DenialSummary - (*DenialGroupCount)(nil), // 211: openshell.v1.DenialGroupCount - (*NetworkActivitySummary)(nil), // 212: openshell.v1.NetworkActivitySummary - (*PolicyChunk)(nil), // 213: openshell.v1.PolicyChunk - (*DraftPolicyUpdate)(nil), // 214: openshell.v1.DraftPolicyUpdate - (*SubmitPolicyAnalysisRequest)(nil), // 215: openshell.v1.SubmitPolicyAnalysisRequest - (*SubmitPolicyAnalysisResponse)(nil), // 216: openshell.v1.SubmitPolicyAnalysisResponse - (*GetDraftPolicyRequest)(nil), // 217: openshell.v1.GetDraftPolicyRequest - (*GetDraftPolicyResponse)(nil), // 218: openshell.v1.GetDraftPolicyResponse - (*ApproveDraftChunkRequest)(nil), // 219: openshell.v1.ApproveDraftChunkRequest - (*ApproveDraftChunkResponse)(nil), // 220: openshell.v1.ApproveDraftChunkResponse - (*RejectDraftChunkRequest)(nil), // 221: openshell.v1.RejectDraftChunkRequest - (*RejectDraftChunkResponse)(nil), // 222: openshell.v1.RejectDraftChunkResponse - (*DraftChunkApproval)(nil), // 223: openshell.v1.DraftChunkApproval - (*ApproveAllDraftChunksRequest)(nil), // 224: openshell.v1.ApproveAllDraftChunksRequest - (*ApproveAllDraftChunksResponse)(nil), // 225: openshell.v1.ApproveAllDraftChunksResponse - (*EditDraftChunkRequest)(nil), // 226: openshell.v1.EditDraftChunkRequest - (*EditDraftChunkResponse)(nil), // 227: openshell.v1.EditDraftChunkResponse - (*UndoDraftChunkRequest)(nil), // 228: openshell.v1.UndoDraftChunkRequest - (*UndoDraftChunkResponse)(nil), // 229: openshell.v1.UndoDraftChunkResponse - (*ClearDraftChunksRequest)(nil), // 230: openshell.v1.ClearDraftChunksRequest - (*ClearDraftChunksResponse)(nil), // 231: openshell.v1.ClearDraftChunksResponse - (*GetDraftHistoryRequest)(nil), // 232: openshell.v1.GetDraftHistoryRequest - (*DraftHistoryEntry)(nil), // 233: openshell.v1.DraftHistoryEntry - (*GetDraftHistoryResponse)(nil), // 234: openshell.v1.GetDraftHistoryResponse - (*CreateWorkspaceRequest)(nil), // 235: openshell.v1.CreateWorkspaceRequest - (*CreateWorkspaceResponse)(nil), // 236: openshell.v1.CreateWorkspaceResponse - (*GetWorkspaceRequest)(nil), // 237: openshell.v1.GetWorkspaceRequest - (*GetWorkspaceResponse)(nil), // 238: openshell.v1.GetWorkspaceResponse - (*ListWorkspacesRequest)(nil), // 239: openshell.v1.ListWorkspacesRequest - (*ListWorkspacesResponse)(nil), // 240: openshell.v1.ListWorkspacesResponse - (*DeleteWorkspaceRequest)(nil), // 241: openshell.v1.DeleteWorkspaceRequest - (*DeleteWorkspaceResponse)(nil), // 242: openshell.v1.DeleteWorkspaceResponse - (*WorkspaceMember)(nil), // 243: openshell.v1.WorkspaceMember - (*AddWorkspaceMemberRequest)(nil), // 244: openshell.v1.AddWorkspaceMemberRequest - (*AddWorkspaceMemberResponse)(nil), // 245: openshell.v1.AddWorkspaceMemberResponse - (*RemoveWorkspaceMemberRequest)(nil), // 246: openshell.v1.RemoveWorkspaceMemberRequest - (*RemoveWorkspaceMemberResponse)(nil), // 247: openshell.v1.RemoveWorkspaceMemberResponse - (*ListWorkspaceMembersRequest)(nil), // 248: openshell.v1.ListWorkspaceMembersRequest - (*ListWorkspaceMembersResponse)(nil), // 249: openshell.v1.ListWorkspaceMembersResponse - (*ExtensionServiceCredential)(nil), // 250: openshell.v1.ExtensionServiceCredential - (*EndpointObservation)(nil), // 251: openshell.v1.EndpointObservation - (*ReportEndpointStatusRequest)(nil), // 252: openshell.v1.ReportEndpointStatusRequest - (*TokenGrantStatus)(nil), // 253: openshell.v1.TokenGrantStatus - (*TokenGrantFailureObservation)(nil), // 254: openshell.v1.TokenGrantFailureObservation - (*ReportEndpointStatusResponse)(nil), // 255: openshell.v1.ReportEndpointStatusResponse - (*EndpointStatus)(nil), // 256: openshell.v1.EndpointStatus - (*SandboxProvisioning)(nil), // 257: openshell.v1.SandboxProvisioning - (*SandboxServiceExposure)(nil), // 258: openshell.v1.SandboxServiceExposure - (*EndpointFailure)(nil), // 259: openshell.v1.EndpointFailure - nil, // 260: openshell.v1.SandboxSpec.EnvironmentEntry - nil, // 261: openshell.v1.SandboxTemplate.LabelsEntry - nil, // 262: openshell.v1.SandboxTemplate.AnnotationsEntry - nil, // 263: openshell.v1.SandboxTemplate.EnvironmentEntry - nil, // 264: openshell.v1.SandboxWorkloadConfig.EnvironmentEntry - nil, // 265: openshell.v1.PlatformEvent.MetadataEntry - nil, // 266: openshell.v1.CreateSandboxRequest.LabelsEntry - nil, // 267: openshell.v1.CreateSandboxRequest.AnnotationsEntry - nil, // 268: openshell.v1.SandboxResponse.ServiceUrlsEntry - nil, // 269: openshell.v1.ExecSandboxRequest.EnvironmentEntry - nil, // 270: openshell.v1.SandboxLogLine.FieldsEntry - nil, // 271: openshell.v1.GetProviderCredentialsResponse.CredentialsEntry - nil, // 272: openshell.v1.UpdateProviderRequest.CredentialExpirationTimesEntry - nil, // 273: openshell.v1.ConfigureProviderRefreshRequest.MaterialEntry - nil, // 274: openshell.v1.ProviderProfile.AnnotationsEntry - nil, // 275: openshell.v1.GetSandboxProviderEnvironmentResponse.EnvironmentEntry - nil, // 276: openshell.v1.GetSandboxProviderEnvironmentResponse.CredentialExpirationTimesEntry - nil, // 277: openshell.v1.GetSandboxProviderEnvironmentResponse.DynamicCredentialsEntry - nil, // 278: openshell.v1.GetSandboxProviderEnvironmentResponse.StaticCredentialBindingsEntry - nil, // 279: openshell.v1.GetSandboxProviderEnvironmentResponse.FilesEntry - nil, // 280: openshell.v1.UpdateConfigRequest.AnnotationsEntry - nil, // 281: openshell.v1.UpdateConfigResponse.AnnotationsEntry - nil, // 282: openshell.v1.SandboxPolicyRevision.ProvenanceEntry - nil, // 283: openshell.v1.CreateWorkspaceRequest.LabelsEntry - (*timestamppb.Timestamp)(nil), // 284: google.protobuf.Timestamp - (*datamodelv1.ObjectMeta)(nil), // 285: openshell.datamodel.v1.ObjectMeta - (*sandboxv1.SandboxPolicy)(nil), // 286: openshell.sandbox.v1.SandboxPolicy - (*structpb.Struct)(nil), // 287: google.protobuf.Struct - (*durationpb.Duration)(nil), // 288: google.protobuf.Duration - (*datamodelv1.WorkspaceSelector)(nil), // 289: openshell.datamodel.v1.WorkspaceSelector - (*datamodelv1.Provider)(nil), // 290: openshell.datamodel.v1.Provider - (sandboxv1.PolicySource)(0), // 291: openshell.sandbox.v1.PolicySource - (*sandboxv1.NetworkEndpoint)(nil), // 292: openshell.sandbox.v1.NetworkEndpoint - (*sandboxv1.NetworkBinary)(nil), // 293: openshell.sandbox.v1.NetworkBinary - (*sandboxv1.SettingValue)(nil), // 294: openshell.sandbox.v1.SettingValue - (*sandboxv1.NetworkPolicyRule)(nil), // 295: openshell.sandbox.v1.NetworkPolicyRule - (*sandboxv1.L7DenyRule)(nil), // 296: openshell.sandbox.v1.L7DenyRule - (*sandboxv1.L7Rule)(nil), // 297: openshell.sandbox.v1.L7Rule - (*datamodelv1.Workspace)(nil), // 298: openshell.datamodel.v1.Workspace - (*sandboxv1.GetSandboxConfigRequest)(nil), // 299: openshell.sandbox.v1.GetSandboxConfigRequest - (*sandboxv1.GetGatewayConfigRequest)(nil), // 300: openshell.sandbox.v1.GetGatewayConfigRequest - (*sandboxv1.GetSandboxConfigResponse)(nil), // 301: openshell.sandbox.v1.GetSandboxConfigResponse - (*sandboxv1.GetGatewayConfigResponse)(nil), // 302: openshell.sandbox.v1.GetGatewayConfigResponse + (ExtensionKind)(0), // 0: openshell.v1.ExtensionKind + (SandboxPhase)(0), // 1: openshell.v1.SandboxPhase + (ProviderMutationKind)(0), // 2: openshell.v1.ProviderMutationKind + (ProviderReadinessState)(0), // 3: openshell.v1.ProviderReadinessState + (ProviderReadinessReason)(0), // 4: openshell.v1.ProviderReadinessReason + (ConfigComponent)(0), // 5: openshell.v1.ConfigComponent + (ConfigApplyOutcome)(0), // 6: openshell.v1.ConfigApplyOutcome + (ConfigUpdateOperationState)(0), // 7: openshell.v1.ConfigUpdateOperationState + (ProviderCredentialTokenGrantType)(0), // 8: openshell.v1.ProviderCredentialTokenGrantType + (ProviderCredentialRefreshStrategy)(0), // 9: openshell.v1.ProviderCredentialRefreshStrategy + (ProviderProfileCategory)(0), // 10: openshell.v1.ProviderProfileCategory + (ConfigurationAdmissionState)(0), // 11: openshell.v1.ConfigurationAdmissionState + (PolicyStatus)(0), // 12: openshell.v1.PolicyStatus + (ServiceStatus)(0), // 13: openshell.v1.ServiceStatus + (WorkspaceRole)(0), // 14: openshell.v1.WorkspaceRole + (ProviderCredentialRefreshRecoveryAction)(0), // 15: openshell.v1.ProviderCredentialRefreshRecoveryAction + (SandboxRestartPolicy)(0), // 16: openshell.v1.SandboxRestartPolicy + (DeletionOutcome)(0), // 17: openshell.v1.DeletionOutcome + (EndpointResult)(0), // 18: openshell.v1.EndpointResult + (ServiceAuthorizationMode)(0), // 19: openshell.v1.ServiceAuthorizationMode + (EndpointFailureDomain)(0), // 20: openshell.v1.EndpointFailureDomain + (*IssueSandboxTokenRequest)(nil), // 21: openshell.v1.IssueSandboxTokenRequest + (*IssueSandboxTokenResponse)(nil), // 22: openshell.v1.IssueSandboxTokenResponse + (*RefreshSandboxTokenRequest)(nil), // 23: openshell.v1.RefreshSandboxTokenRequest + (*RefreshSandboxTokenResponse)(nil), // 24: openshell.v1.RefreshSandboxTokenResponse + (*HealthRequest)(nil), // 25: openshell.v1.HealthRequest + (*HealthResponse)(nil), // 26: openshell.v1.HealthResponse + (*GetCurrentUserRequest)(nil), // 27: openshell.v1.GetCurrentUserRequest + (*GetCurrentUserResponse)(nil), // 28: openshell.v1.GetCurrentUserResponse + (*GetGatewayInfoRequest)(nil), // 29: openshell.v1.GetGatewayInfoRequest + (*GetGatewayInfoResponse)(nil), // 30: openshell.v1.GetGatewayInfoResponse + (*NegotiatedExtensionInfo)(nil), // 31: openshell.v1.NegotiatedExtensionInfo + (*ComputeDriverInfo)(nil), // 32: openshell.v1.ComputeDriverInfo + (*ComputeDriverCapabilities)(nil), // 33: openshell.v1.ComputeDriverCapabilities + (*ResourceCapabilities)(nil), // 34: openshell.v1.ResourceCapabilities + (*CpuResourceCapabilities)(nil), // 35: openshell.v1.CpuResourceCapabilities + (*MemoryResourceCapabilities)(nil), // 36: openshell.v1.MemoryResourceCapabilities + (*GpuResourceCapabilities)(nil), // 37: openshell.v1.GpuResourceCapabilities + (*Sandbox)(nil), // 38: openshell.v1.Sandbox + (*SandboxDelegatedIdentity)(nil), // 39: openshell.v1.SandboxDelegatedIdentity + (*SandboxSpec)(nil), // 40: openshell.v1.SandboxSpec + (*ResourceRequirements)(nil), // 41: openshell.v1.ResourceRequirements + (*GpuResourceRequirements)(nil), // 42: openshell.v1.GpuResourceRequirements + (*SandboxTemplate)(nil), // 43: openshell.v1.SandboxTemplate + (*SandboxWorkloadTemplate)(nil), // 44: openshell.v1.SandboxWorkloadTemplate + (*SandboxWorkloadTemplateSpec)(nil), // 45: openshell.v1.SandboxWorkloadTemplateSpec + (*SandboxWorkloadConfig)(nil), // 46: openshell.v1.SandboxWorkloadConfig + (*SandboxResources)(nil), // 47: openshell.v1.SandboxResources + (*SandboxServiceLevel)(nil), // 48: openshell.v1.SandboxServiceLevel + (*SandboxStartup)(nil), // 49: openshell.v1.SandboxStartup + (*SandboxWorkloadTemplateProvenance)(nil), // 50: openshell.v1.SandboxWorkloadTemplateProvenance + (*SandboxStatus)(nil), // 51: openshell.v1.SandboxStatus + (*SandboxCondition)(nil), // 52: openshell.v1.SandboxCondition + (*PlatformEvent)(nil), // 53: openshell.v1.PlatformEvent + (*CreateSandboxRequest)(nil), // 54: openshell.v1.CreateSandboxRequest + (*DelegatedIdentityRequest)(nil), // 55: openshell.v1.DelegatedIdentityRequest + (*DelegatedIdentityAuthorizationGrant)(nil), // 56: openshell.v1.DelegatedIdentityAuthorizationGrant + (*GetDelegatedIdentityAuthorizationStatusRequest)(nil), // 57: openshell.v1.GetDelegatedIdentityAuthorizationStatusRequest + (*GetDelegatedIdentityAuthorizationStatusResponse)(nil), // 58: openshell.v1.GetDelegatedIdentityAuthorizationStatusResponse + (*AuthorizeDelegatedIdentityRequest)(nil), // 59: openshell.v1.AuthorizeDelegatedIdentityRequest + (*AuthorizeDelegatedIdentityResponse)(nil), // 60: openshell.v1.AuthorizeDelegatedIdentityResponse + (*GetSandboxDelegatedIdentityStatusRequest)(nil), // 61: openshell.v1.GetSandboxDelegatedIdentityStatusRequest + (*GetSandboxDelegatedIdentityStatusResponse)(nil), // 62: openshell.v1.GetSandboxDelegatedIdentityStatusResponse + (*WithdrawSandboxDelegatedIdentityRequest)(nil), // 63: openshell.v1.WithdrawSandboxDelegatedIdentityRequest + (*WithdrawSandboxDelegatedIdentityResponse)(nil), // 64: openshell.v1.WithdrawSandboxDelegatedIdentityResponse + (*ExtendSandboxDelegatedIdentityRequest)(nil), // 65: openshell.v1.ExtendSandboxDelegatedIdentityRequest + (*ExtendSandboxDelegatedIdentityResponse)(nil), // 66: openshell.v1.ExtendSandboxDelegatedIdentityResponse + (*CreateSandboxTemplateRequest)(nil), // 67: openshell.v1.CreateSandboxTemplateRequest + (*GetSandboxTemplateRequest)(nil), // 68: openshell.v1.GetSandboxTemplateRequest + (*ListSandboxTemplatesRequest)(nil), // 69: openshell.v1.ListSandboxTemplatesRequest + (*DeleteSandboxTemplateRequest)(nil), // 70: openshell.v1.DeleteSandboxTemplateRequest + (*SandboxTemplateResponse)(nil), // 71: openshell.v1.SandboxTemplateResponse + (*ListSandboxTemplatesResponse)(nil), // 72: openshell.v1.ListSandboxTemplatesResponse + (*DeleteSandboxTemplateResponse)(nil), // 73: openshell.v1.DeleteSandboxTemplateResponse + (*BeginRootfsTarStagingRequest)(nil), // 74: openshell.v1.BeginRootfsTarStagingRequest + (*BeginRootfsTarStagingResponse)(nil), // 75: openshell.v1.BeginRootfsTarStagingResponse + (*GetSandboxRequest)(nil), // 76: openshell.v1.GetSandboxRequest + (*ListSandboxesRequest)(nil), // 77: openshell.v1.ListSandboxesRequest + (*ListSandboxProvidersRequest)(nil), // 78: openshell.v1.ListSandboxProvidersRequest + (*AttachSandboxProviderRequest)(nil), // 79: openshell.v1.AttachSandboxProviderRequest + (*DetachSandboxProviderRequest)(nil), // 80: openshell.v1.DetachSandboxProviderRequest + (*DeleteSandboxRequest)(nil), // 81: openshell.v1.DeleteSandboxRequest + (*StopSandboxRequest)(nil), // 82: openshell.v1.StopSandboxRequest + (*StartSandboxRequest)(nil), // 83: openshell.v1.StartSandboxRequest + (*SandboxResponse)(nil), // 84: openshell.v1.SandboxResponse + (*ListSandboxesResponse)(nil), // 85: openshell.v1.ListSandboxesResponse + (*ListSandboxProvidersResponse)(nil), // 86: openshell.v1.ListSandboxProvidersResponse + (*AttachSandboxProviderResponse)(nil), // 87: openshell.v1.AttachSandboxProviderResponse + (*DetachSandboxProviderResponse)(nil), // 88: openshell.v1.DetachSandboxProviderResponse + (*ProviderDesiredIdentity)(nil), // 89: openshell.v1.ProviderDesiredIdentity + (*ConfigSnapshotRevision)(nil), // 90: openshell.v1.ConfigSnapshotRevision + (*SandboxConfigRevision)(nil), // 91: openshell.v1.SandboxConfigRevision + (*ConfigUpdateOperation)(nil), // 92: openshell.v1.ConfigUpdateOperation + (*ProviderMutationReceipt)(nil), // 93: openshell.v1.ProviderMutationReceipt + (*ProviderReadinessObservation)(nil), // 94: openshell.v1.ProviderReadinessObservation + (*ProviderReadinessStatus)(nil), // 95: openshell.v1.ProviderReadinessStatus + (*GetSandboxProviderStatusRequest)(nil), // 96: openshell.v1.GetSandboxProviderStatusRequest + (*GetSandboxProviderStatusResponse)(nil), // 97: openshell.v1.GetSandboxProviderStatusResponse + (*ReportProviderReadinessRequest)(nil), // 98: openshell.v1.ReportProviderReadinessRequest + (*ReportProviderReadinessResponse)(nil), // 99: openshell.v1.ReportProviderReadinessResponse + (*DeleteSandboxResponse)(nil), // 100: openshell.v1.DeleteSandboxResponse + (*CreateSshSessionRequest)(nil), // 101: openshell.v1.CreateSshSessionRequest + (*CreateSshSessionResponse)(nil), // 102: openshell.v1.CreateSshSessionResponse + (*ExposeServiceRequest)(nil), // 103: openshell.v1.ExposeServiceRequest + (*GetServiceRequest)(nil), // 104: openshell.v1.GetServiceRequest + (*ListServicesRequest)(nil), // 105: openshell.v1.ListServicesRequest + (*ListServicesResponse)(nil), // 106: openshell.v1.ListServicesResponse + (*DeleteServiceRequest)(nil), // 107: openshell.v1.DeleteServiceRequest + (*DeleteServiceResponse)(nil), // 108: openshell.v1.DeleteServiceResponse + (*ServiceEndpoint)(nil), // 109: openshell.v1.ServiceEndpoint + (*ServiceEndpointResponse)(nil), // 110: openshell.v1.ServiceEndpointResponse + (*RevokeSshSessionRequest)(nil), // 111: openshell.v1.RevokeSshSessionRequest + (*RevokeSshSessionResponse)(nil), // 112: openshell.v1.RevokeSshSessionResponse + (*ExecSandboxRequest)(nil), // 113: openshell.v1.ExecSandboxRequest + (*ExecSandboxStdout)(nil), // 114: openshell.v1.ExecSandboxStdout + (*ExecSandboxStderr)(nil), // 115: openshell.v1.ExecSandboxStderr + (*ExecSandboxExit)(nil), // 116: openshell.v1.ExecSandboxExit + (*ExecSandboxEvent)(nil), // 117: openshell.v1.ExecSandboxEvent + (*TcpForwardInit)(nil), // 118: openshell.v1.TcpForwardInit + (*TcpForwardFrame)(nil), // 119: openshell.v1.TcpForwardFrame + (*ExecSandboxInput)(nil), // 120: openshell.v1.ExecSandboxInput + (*ExecSandboxWindowResize)(nil), // 121: openshell.v1.ExecSandboxWindowResize + (*SshSession)(nil), // 122: openshell.v1.SshSession + (*WatchSandboxRequest)(nil), // 123: openshell.v1.WatchSandboxRequest + (*SandboxStreamEvent)(nil), // 124: openshell.v1.SandboxStreamEvent + (*SandboxLogLine)(nil), // 125: openshell.v1.SandboxLogLine + (*SandboxStreamWarning)(nil), // 126: openshell.v1.SandboxStreamWarning + (*CreateProviderRequest)(nil), // 127: openshell.v1.CreateProviderRequest + (*GetProviderRequest)(nil), // 128: openshell.v1.GetProviderRequest + (*GetProviderCredentialsRequest)(nil), // 129: openshell.v1.GetProviderCredentialsRequest + (*ProviderCredentialValue)(nil), // 130: openshell.v1.ProviderCredentialValue + (*GetProviderCredentialsResponse)(nil), // 131: openshell.v1.GetProviderCredentialsResponse + (*ListProvidersRequest)(nil), // 132: openshell.v1.ListProvidersRequest + (*UpdateProviderRequest)(nil), // 133: openshell.v1.UpdateProviderRequest + (*DeleteProviderRequest)(nil), // 134: openshell.v1.DeleteProviderRequest + (*ProviderResponse)(nil), // 135: openshell.v1.ProviderResponse + (*ListProvidersResponse)(nil), // 136: openshell.v1.ListProvidersResponse + (*ListProviderProfilesRequest)(nil), // 137: openshell.v1.ListProviderProfilesRequest + (*GetProviderProfileRequest)(nil), // 138: openshell.v1.GetProviderProfileRequest + (*ProviderProfileImportItem)(nil), // 139: openshell.v1.ProviderProfileImportItem + (*ProviderProfileDiagnostic)(nil), // 140: openshell.v1.ProviderProfileDiagnostic + (*ProviderCredentialTokenGrantAudienceOverride)(nil), // 141: openshell.v1.ProviderCredentialTokenGrantAudienceOverride + (*ProviderCredentialTokenGrantSubjectToken)(nil), // 142: openshell.v1.ProviderCredentialTokenGrantSubjectToken + (*ProviderCredentialTokenGrant)(nil), // 143: openshell.v1.ProviderCredentialTokenGrant + (*ProviderProfileCredential)(nil), // 144: openshell.v1.ProviderProfileCredential + (*ProviderCredentialRefreshMaterial)(nil), // 145: openshell.v1.ProviderCredentialRefreshMaterial + (*ProviderCredentialRefreshOutput)(nil), // 146: openshell.v1.ProviderCredentialRefreshOutput + (*ProviderCredentialRefresh)(nil), // 147: openshell.v1.ProviderCredentialRefresh + (*ProviderCredentialRefreshStatus)(nil), // 148: openshell.v1.ProviderCredentialRefreshStatus + (*ProviderProfileDiscovery)(nil), // 149: openshell.v1.ProviderProfileDiscovery + (*DelegatedIdentityCredentialSummary)(nil), // 150: openshell.v1.DelegatedIdentityCredentialSummary + (*ListDelegatedIdentityCredentialsRequest)(nil), // 151: openshell.v1.ListDelegatedIdentityCredentialsRequest + (*ListDelegatedIdentityCredentialsResponse)(nil), // 152: openshell.v1.ListDelegatedIdentityCredentialsResponse + (*GetDelegatedIdentityCredentialStatusRequest)(nil), // 153: openshell.v1.GetDelegatedIdentityCredentialStatusRequest + (*GetDelegatedIdentityCredentialStatusResponse)(nil), // 154: openshell.v1.GetDelegatedIdentityCredentialStatusResponse + (*RevokeDelegatedIdentityCredentialRequest)(nil), // 155: openshell.v1.RevokeDelegatedIdentityCredentialRequest + (*RevokeDelegatedIdentityCredentialResponse)(nil), // 156: openshell.v1.RevokeDelegatedIdentityCredentialResponse + (*DeleteDelegatedIdentityCredentialRequest)(nil), // 157: openshell.v1.DeleteDelegatedIdentityCredentialRequest + (*DeleteDelegatedIdentityCredentialResponse)(nil), // 158: openshell.v1.DeleteDelegatedIdentityCredentialResponse + (*GetProviderRefreshStatusRequest)(nil), // 159: openshell.v1.GetProviderRefreshStatusRequest + (*GetProviderRefreshStatusResponse)(nil), // 160: openshell.v1.GetProviderRefreshStatusResponse + (*ConfigureProviderRefreshRequest)(nil), // 161: openshell.v1.ConfigureProviderRefreshRequest + (*ConfigureProviderRefreshResponse)(nil), // 162: openshell.v1.ConfigureProviderRefreshResponse + (*RotateProviderCredentialRequest)(nil), // 163: openshell.v1.RotateProviderCredentialRequest + (*RotateProviderCredentialResponse)(nil), // 164: openshell.v1.RotateProviderCredentialResponse + (*DeleteProviderRefreshRequest)(nil), // 165: openshell.v1.DeleteProviderRefreshRequest + (*DeleteProviderRefreshResponse)(nil), // 166: openshell.v1.DeleteProviderRefreshResponse + (*ProviderProfile)(nil), // 167: openshell.v1.ProviderProfile + (*ProviderProfileFile)(nil), // 168: openshell.v1.ProviderProfileFile + (*ProviderProfileResponse)(nil), // 169: openshell.v1.ProviderProfileResponse + (*ListProviderProfilesResponse)(nil), // 170: openshell.v1.ListProviderProfilesResponse + (*ImportProviderProfilesRequest)(nil), // 171: openshell.v1.ImportProviderProfilesRequest + (*ImportProviderProfilesResponse)(nil), // 172: openshell.v1.ImportProviderProfilesResponse + (*UpdateProviderProfilesRequest)(nil), // 173: openshell.v1.UpdateProviderProfilesRequest + (*UpdateProviderProfilesResponse)(nil), // 174: openshell.v1.UpdateProviderProfilesResponse + (*LintProviderProfilesRequest)(nil), // 175: openshell.v1.LintProviderProfilesRequest + (*LintProviderProfilesResponse)(nil), // 176: openshell.v1.LintProviderProfilesResponse + (*DeleteProviderResponse)(nil), // 177: openshell.v1.DeleteProviderResponse + (*DeleteProviderProfileRequest)(nil), // 178: openshell.v1.DeleteProviderProfileRequest + (*DeleteProviderProfileResponse)(nil), // 179: openshell.v1.DeleteProviderProfileResponse + (*GetSandboxProviderEnvironmentRequest)(nil), // 180: openshell.v1.GetSandboxProviderEnvironmentRequest + (*StaticCredentialEndpointBinding)(nil), // 181: openshell.v1.StaticCredentialEndpointBinding + (*StaticCredentialBinding)(nil), // 182: openshell.v1.StaticCredentialBinding + (*GetSandboxProviderEnvironmentResponse)(nil), // 183: openshell.v1.GetSandboxProviderEnvironmentResponse + (*ExchangeProviderSubjectTokenRequest)(nil), // 184: openshell.v1.ExchangeProviderSubjectTokenRequest + (*ExchangeProviderSubjectTokenResponse)(nil), // 185: openshell.v1.ExchangeProviderSubjectTokenResponse + (*UpdateConfigRequest)(nil), // 186: openshell.v1.UpdateConfigRequest + (*PolicyMergeOperation)(nil), // 187: openshell.v1.PolicyMergeOperation + (*AddNetworkRule)(nil), // 188: openshell.v1.AddNetworkRule + (*RemoveNetworkEndpoint)(nil), // 189: openshell.v1.RemoveNetworkEndpoint + (*RemoveNetworkRule)(nil), // 190: openshell.v1.RemoveNetworkRule + (*L7RuleTarget)(nil), // 191: openshell.v1.L7RuleTarget + (*AddDenyRules)(nil), // 192: openshell.v1.AddDenyRules + (*AddAllowRules)(nil), // 193: openshell.v1.AddAllowRules + (*RemoveNetworkBinary)(nil), // 194: openshell.v1.RemoveNetworkBinary + (*UpdateConfigResponse)(nil), // 195: openshell.v1.UpdateConfigResponse + (*GetSandboxPolicyStatusRequest)(nil), // 196: openshell.v1.GetSandboxPolicyStatusRequest + (*GetSandboxPolicyStatusResponse)(nil), // 197: openshell.v1.GetSandboxPolicyStatusResponse + (*ListSandboxPoliciesRequest)(nil), // 198: openshell.v1.ListSandboxPoliciesRequest + (*ListSandboxPoliciesResponse)(nil), // 199: openshell.v1.ListSandboxPoliciesResponse + (*ReportPolicyStatusRequest)(nil), // 200: openshell.v1.ReportPolicyStatusRequest + (*ReportPolicyStatusResponse)(nil), // 201: openshell.v1.ReportPolicyStatusResponse + (*SandboxConfigurationAdmission)(nil), // 202: openshell.v1.SandboxConfigurationAdmission + (*ReportSandboxConfigurationRequest)(nil), // 203: openshell.v1.ReportSandboxConfigurationRequest + (*ReportSandboxConfigurationResponse)(nil), // 204: openshell.v1.ReportSandboxConfigurationResponse + (*SandboxPolicyRevision)(nil), // 205: openshell.v1.SandboxPolicyRevision + (*GetSandboxLogsRequest)(nil), // 206: openshell.v1.GetSandboxLogsRequest + (*PushSandboxLogsRequest)(nil), // 207: openshell.v1.PushSandboxLogsRequest + (*PushSandboxLogsResponse)(nil), // 208: openshell.v1.PushSandboxLogsResponse + (*GetSandboxLogsResponse)(nil), // 209: openshell.v1.GetSandboxLogsResponse + (*SupervisorMessage)(nil), // 210: openshell.v1.SupervisorMessage + (*GatewayMessage)(nil), // 211: openshell.v1.GatewayMessage + (*SupervisorHello)(nil), // 212: openshell.v1.SupervisorHello + (*SessionAccepted)(nil), // 213: openshell.v1.SessionAccepted + (*SessionRejected)(nil), // 214: openshell.v1.SessionRejected + (*SessionRedirect)(nil), // 215: openshell.v1.SessionRedirect + (*SupervisorHeartbeat)(nil), // 216: openshell.v1.SupervisorHeartbeat + (*GatewayHeartbeat)(nil), // 217: openshell.v1.GatewayHeartbeat + (*ReportMainProcessExitRequest)(nil), // 218: openshell.v1.ReportMainProcessExitRequest + (*ReportMainProcessExitResponse)(nil), // 219: openshell.v1.ReportMainProcessExitResponse + (*FinalizeMainProcessExitRequest)(nil), // 220: openshell.v1.FinalizeMainProcessExitRequest + (*FinalizeMainProcessExitResponse)(nil), // 221: openshell.v1.FinalizeMainProcessExitResponse + (*RelayOpen)(nil), // 222: openshell.v1.RelayOpen + (*SshRelayTarget)(nil), // 223: openshell.v1.SshRelayTarget + (*TcpRelayTarget)(nil), // 224: openshell.v1.TcpRelayTarget + (*RelayInit)(nil), // 225: openshell.v1.RelayInit + (*RelayFrame)(nil), // 226: openshell.v1.RelayFrame + (*PeerRelayInit)(nil), // 227: openshell.v1.PeerRelayInit + (*PeerRelayFrame)(nil), // 228: openshell.v1.PeerRelayFrame + (*RelayOpenResult)(nil), // 229: openshell.v1.RelayOpenResult + (*RelayClose)(nil), // 230: openshell.v1.RelayClose + (*L7RequestSample)(nil), // 231: openshell.v1.L7RequestSample + (*DenialSummary)(nil), // 232: openshell.v1.DenialSummary + (*DenialGroupCount)(nil), // 233: openshell.v1.DenialGroupCount + (*NetworkActivitySummary)(nil), // 234: openshell.v1.NetworkActivitySummary + (*PolicyChunk)(nil), // 235: openshell.v1.PolicyChunk + (*DraftPolicyUpdate)(nil), // 236: openshell.v1.DraftPolicyUpdate + (*SubmitPolicyAnalysisRequest)(nil), // 237: openshell.v1.SubmitPolicyAnalysisRequest + (*SubmitPolicyAnalysisResponse)(nil), // 238: openshell.v1.SubmitPolicyAnalysisResponse + (*GetDraftPolicyRequest)(nil), // 239: openshell.v1.GetDraftPolicyRequest + (*GetDraftPolicyResponse)(nil), // 240: openshell.v1.GetDraftPolicyResponse + (*ApproveDraftChunkRequest)(nil), // 241: openshell.v1.ApproveDraftChunkRequest + (*ApproveDraftChunkResponse)(nil), // 242: openshell.v1.ApproveDraftChunkResponse + (*RejectDraftChunkRequest)(nil), // 243: openshell.v1.RejectDraftChunkRequest + (*RejectDraftChunkResponse)(nil), // 244: openshell.v1.RejectDraftChunkResponse + (*DraftChunkApproval)(nil), // 245: openshell.v1.DraftChunkApproval + (*ApproveAllDraftChunksRequest)(nil), // 246: openshell.v1.ApproveAllDraftChunksRequest + (*ApproveAllDraftChunksResponse)(nil), // 247: openshell.v1.ApproveAllDraftChunksResponse + (*EditDraftChunkRequest)(nil), // 248: openshell.v1.EditDraftChunkRequest + (*EditDraftChunkResponse)(nil), // 249: openshell.v1.EditDraftChunkResponse + (*UndoDraftChunkRequest)(nil), // 250: openshell.v1.UndoDraftChunkRequest + (*UndoDraftChunkResponse)(nil), // 251: openshell.v1.UndoDraftChunkResponse + (*ClearDraftChunksRequest)(nil), // 252: openshell.v1.ClearDraftChunksRequest + (*ClearDraftChunksResponse)(nil), // 253: openshell.v1.ClearDraftChunksResponse + (*GetDraftHistoryRequest)(nil), // 254: openshell.v1.GetDraftHistoryRequest + (*DraftHistoryEntry)(nil), // 255: openshell.v1.DraftHistoryEntry + (*GetDraftHistoryResponse)(nil), // 256: openshell.v1.GetDraftHistoryResponse + (*CreateWorkspaceRequest)(nil), // 257: openshell.v1.CreateWorkspaceRequest + (*CreateWorkspaceResponse)(nil), // 258: openshell.v1.CreateWorkspaceResponse + (*GetWorkspaceRequest)(nil), // 259: openshell.v1.GetWorkspaceRequest + (*GetWorkspaceResponse)(nil), // 260: openshell.v1.GetWorkspaceResponse + (*ListWorkspacesRequest)(nil), // 261: openshell.v1.ListWorkspacesRequest + (*ListWorkspacesResponse)(nil), // 262: openshell.v1.ListWorkspacesResponse + (*DeleteWorkspaceRequest)(nil), // 263: openshell.v1.DeleteWorkspaceRequest + (*DeleteWorkspaceResponse)(nil), // 264: openshell.v1.DeleteWorkspaceResponse + (*WorkspaceMember)(nil), // 265: openshell.v1.WorkspaceMember + (*AddWorkspaceMemberRequest)(nil), // 266: openshell.v1.AddWorkspaceMemberRequest + (*AddWorkspaceMemberResponse)(nil), // 267: openshell.v1.AddWorkspaceMemberResponse + (*RemoveWorkspaceMemberRequest)(nil), // 268: openshell.v1.RemoveWorkspaceMemberRequest + (*RemoveWorkspaceMemberResponse)(nil), // 269: openshell.v1.RemoveWorkspaceMemberResponse + (*ListWorkspaceMembersRequest)(nil), // 270: openshell.v1.ListWorkspaceMembersRequest + (*ListWorkspaceMembersResponse)(nil), // 271: openshell.v1.ListWorkspaceMembersResponse + (*ExtensionServiceCredential)(nil), // 272: openshell.v1.ExtensionServiceCredential + (*EndpointObservation)(nil), // 273: openshell.v1.EndpointObservation + (*ReportEndpointStatusRequest)(nil), // 274: openshell.v1.ReportEndpointStatusRequest + (*TokenGrantStatus)(nil), // 275: openshell.v1.TokenGrantStatus + (*TokenGrantFailureObservation)(nil), // 276: openshell.v1.TokenGrantFailureObservation + (*ReportEndpointStatusResponse)(nil), // 277: openshell.v1.ReportEndpointStatusResponse + (*EndpointStatus)(nil), // 278: openshell.v1.EndpointStatus + (*SandboxProvisioning)(nil), // 279: openshell.v1.SandboxProvisioning + (*SandboxServiceExposure)(nil), // 280: openshell.v1.SandboxServiceExposure + (*EndpointFailure)(nil), // 281: openshell.v1.EndpointFailure + nil, // 282: openshell.v1.SandboxSpec.EnvironmentEntry + nil, // 283: openshell.v1.SandboxTemplate.LabelsEntry + nil, // 284: openshell.v1.SandboxTemplate.AnnotationsEntry + nil, // 285: openshell.v1.SandboxTemplate.EnvironmentEntry + nil, // 286: openshell.v1.SandboxWorkloadConfig.EnvironmentEntry + nil, // 287: openshell.v1.PlatformEvent.MetadataEntry + nil, // 288: openshell.v1.CreateSandboxRequest.LabelsEntry + nil, // 289: openshell.v1.CreateSandboxRequest.AnnotationsEntry + nil, // 290: openshell.v1.SandboxResponse.ServiceUrlsEntry + nil, // 291: openshell.v1.ExecSandboxRequest.EnvironmentEntry + nil, // 292: openshell.v1.SandboxLogLine.FieldsEntry + nil, // 293: openshell.v1.GetProviderCredentialsResponse.CredentialsEntry + nil, // 294: openshell.v1.UpdateProviderRequest.CredentialExpirationTimesEntry + nil, // 295: openshell.v1.ConfigureProviderRefreshRequest.MaterialEntry + nil, // 296: openshell.v1.ProviderProfile.AnnotationsEntry + nil, // 297: openshell.v1.GetSandboxProviderEnvironmentResponse.EnvironmentEntry + nil, // 298: openshell.v1.GetSandboxProviderEnvironmentResponse.CredentialExpirationTimesEntry + nil, // 299: openshell.v1.GetSandboxProviderEnvironmentResponse.DynamicCredentialsEntry + nil, // 300: openshell.v1.GetSandboxProviderEnvironmentResponse.StaticCredentialBindingsEntry + nil, // 301: openshell.v1.GetSandboxProviderEnvironmentResponse.FilesEntry + nil, // 302: openshell.v1.UpdateConfigRequest.AnnotationsEntry + nil, // 303: openshell.v1.UpdateConfigResponse.AnnotationsEntry + nil, // 304: openshell.v1.SandboxPolicyRevision.ProvenanceEntry + nil, // 305: openshell.v1.CreateWorkspaceRequest.LabelsEntry + (*timestamppb.Timestamp)(nil), // 306: google.protobuf.Timestamp + (*datamodelv1.ObjectMeta)(nil), // 307: openshell.datamodel.v1.ObjectMeta + (*sandboxv1.SandboxPolicy)(nil), // 308: openshell.sandbox.v1.SandboxPolicy + (*structpb.Struct)(nil), // 309: google.protobuf.Struct + (*durationpb.Duration)(nil), // 310: google.protobuf.Duration + (*datamodelv1.WorkspaceSelector)(nil), // 311: openshell.datamodel.v1.WorkspaceSelector + (*datamodelv1.Provider)(nil), // 312: openshell.datamodel.v1.Provider + (sandboxv1.PolicySource)(0), // 313: openshell.sandbox.v1.PolicySource + (*sandboxv1.NetworkEndpoint)(nil), // 314: openshell.sandbox.v1.NetworkEndpoint + (*sandboxv1.NetworkBinary)(nil), // 315: openshell.sandbox.v1.NetworkBinary + (*sandboxv1.SettingValue)(nil), // 316: openshell.sandbox.v1.SettingValue + (*sandboxv1.NetworkPolicyRule)(nil), // 317: openshell.sandbox.v1.NetworkPolicyRule + (*sandboxv1.L7DenyRule)(nil), // 318: openshell.sandbox.v1.L7DenyRule + (*sandboxv1.L7Rule)(nil), // 319: openshell.sandbox.v1.L7Rule + (*datamodelv1.Workspace)(nil), // 320: openshell.datamodel.v1.Workspace + (*sandboxv1.GetSandboxConfigRequest)(nil), // 321: openshell.sandbox.v1.GetSandboxConfigRequest + (*sandboxv1.GetGatewayConfigRequest)(nil), // 322: openshell.sandbox.v1.GetGatewayConfigRequest + (*sandboxv1.GetSandboxConfigResponse)(nil), // 323: openshell.sandbox.v1.GetSandboxConfigResponse + (*sandboxv1.GetGatewayConfigResponse)(nil), // 324: openshell.sandbox.v1.GetGatewayConfigResponse } var file_openshell_proto_depIdxs = []int32{ - 284, // 0: openshell.v1.IssueSandboxTokenResponse.expiration_time:type_name -> google.protobuf.Timestamp - 284, // 1: openshell.v1.RefreshSandboxTokenResponse.expiration_time:type_name -> google.protobuf.Timestamp - 250, // 2: openshell.v1.RefreshSandboxTokenResponse.extension_credentials:type_name -> openshell.v1.ExtensionServiceCredential - 284, // 3: openshell.v1.RefreshSandboxTokenResponse.sandbox_expiration_time:type_name -> google.protobuf.Timestamp + 306, // 0: openshell.v1.IssueSandboxTokenResponse.expiration_time:type_name -> google.protobuf.Timestamp + 306, // 1: openshell.v1.RefreshSandboxTokenResponse.expiration_time:type_name -> google.protobuf.Timestamp + 272, // 2: openshell.v1.RefreshSandboxTokenResponse.extension_credentials:type_name -> openshell.v1.ExtensionServiceCredential + 306, // 3: openshell.v1.RefreshSandboxTokenResponse.sandbox_expiration_time:type_name -> google.protobuf.Timestamp 13, // 4: openshell.v1.HealthResponse.status:type_name -> openshell.v1.ServiceStatus 13, // 5: openshell.v1.GetGatewayInfoResponse.status:type_name -> openshell.v1.ServiceStatus 32, // 6: openshell.v1.GetGatewayInfoResponse.compute_drivers:type_name -> openshell.v1.ComputeDriverInfo @@ -20602,503 +22018,547 @@ var file_openshell_proto_depIdxs = []int32{ 35, // 11: openshell.v1.ResourceCapabilities.cpu:type_name -> openshell.v1.CpuResourceCapabilities 36, // 12: openshell.v1.ResourceCapabilities.memory:type_name -> openshell.v1.MemoryResourceCapabilities 37, // 13: openshell.v1.ResourceCapabilities.gpu:type_name -> openshell.v1.GpuResourceCapabilities - 285, // 14: openshell.v1.Sandbox.metadata:type_name -> openshell.datamodel.v1.ObjectMeta - 39, // 15: openshell.v1.Sandbox.spec:type_name -> openshell.v1.SandboxSpec - 50, // 16: openshell.v1.Sandbox.status:type_name -> openshell.v1.SandboxStatus - 49, // 17: openshell.v1.Sandbox.created_from_workload_template:type_name -> openshell.v1.SandboxWorkloadTemplateProvenance - 260, // 18: openshell.v1.SandboxSpec.environment:type_name -> openshell.v1.SandboxSpec.EnvironmentEntry - 42, // 19: openshell.v1.SandboxSpec.template:type_name -> openshell.v1.SandboxTemplate - 286, // 20: openshell.v1.SandboxSpec.policy:type_name -> openshell.sandbox.v1.SandboxPolicy - 40, // 21: openshell.v1.SandboxSpec.resource_requirements:type_name -> openshell.v1.ResourceRequirements - 16, // 22: openshell.v1.SandboxSpec.restart_policy:type_name -> openshell.v1.SandboxRestartPolicy - 41, // 23: openshell.v1.ResourceRequirements.gpu:type_name -> openshell.v1.GpuResourceRequirements - 261, // 24: openshell.v1.SandboxTemplate.labels:type_name -> openshell.v1.SandboxTemplate.LabelsEntry - 262, // 25: openshell.v1.SandboxTemplate.annotations:type_name -> openshell.v1.SandboxTemplate.AnnotationsEntry - 263, // 26: openshell.v1.SandboxTemplate.environment:type_name -> openshell.v1.SandboxTemplate.EnvironmentEntry - 287, // 27: openshell.v1.SandboxTemplate.resources:type_name -> google.protobuf.Struct - 287, // 28: openshell.v1.SandboxTemplate.driver_config:type_name -> google.protobuf.Struct - 285, // 29: openshell.v1.SandboxWorkloadTemplate.metadata:type_name -> openshell.datamodel.v1.ObjectMeta - 44, // 30: openshell.v1.SandboxWorkloadTemplate.spec:type_name -> openshell.v1.SandboxWorkloadTemplateSpec - 45, // 31: openshell.v1.SandboxWorkloadTemplateSpec.workload:type_name -> openshell.v1.SandboxWorkloadConfig - 287, // 32: openshell.v1.SandboxWorkloadTemplateSpec.driver_config:type_name -> google.protobuf.Struct - 47, // 33: openshell.v1.SandboxWorkloadTemplateSpec.desired_service_level:type_name -> openshell.v1.SandboxServiceLevel - 264, // 34: openshell.v1.SandboxWorkloadConfig.environment:type_name -> openshell.v1.SandboxWorkloadConfig.EnvironmentEntry - 46, // 35: openshell.v1.SandboxWorkloadConfig.resources:type_name -> openshell.v1.SandboxResources - 41, // 36: openshell.v1.SandboxResources.gpu:type_name -> openshell.v1.GpuResourceRequirements - 48, // 37: openshell.v1.SandboxServiceLevel.startup:type_name -> openshell.v1.SandboxStartup - 288, // 38: openshell.v1.SandboxStartup.ready_within:type_name -> google.protobuf.Duration - 51, // 39: openshell.v1.SandboxStatus.conditions:type_name -> openshell.v1.SandboxCondition - 1, // 40: openshell.v1.SandboxStatus.phase:type_name -> openshell.v1.SandboxPhase - 256, // 41: openshell.v1.SandboxStatus.endpoint_statuses:type_name -> openshell.v1.EndpointStatus - 180, // 42: openshell.v1.SandboxStatus.configuration_admission:type_name -> openshell.v1.SandboxConfigurationAdmission - 257, // 43: openshell.v1.SandboxStatus.provisioning:type_name -> openshell.v1.SandboxProvisioning - 284, // 44: openshell.v1.SandboxStatus.next_restart_time:type_name -> google.protobuf.Timestamp - 284, // 45: openshell.v1.SandboxStatus.main_process_started_time:type_name -> google.protobuf.Timestamp - 254, // 46: openshell.v1.SandboxStatus.last_token_grant_failure:type_name -> openshell.v1.TokenGrantFailureObservation - 284, // 47: openshell.v1.SandboxCondition.transition_time:type_name -> google.protobuf.Timestamp - 284, // 48: openshell.v1.PlatformEvent.event_time:type_name -> google.protobuf.Timestamp - 265, // 49: openshell.v1.PlatformEvent.metadata:type_name -> openshell.v1.PlatformEvent.MetadataEntry - 289, // 50: openshell.v1.CreateSandboxRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 39, // 51: openshell.v1.CreateSandboxRequest.spec:type_name -> openshell.v1.SandboxSpec - 266, // 52: openshell.v1.CreateSandboxRequest.labels:type_name -> openshell.v1.CreateSandboxRequest.LabelsEntry - 267, // 53: openshell.v1.CreateSandboxRequest.annotations:type_name -> openshell.v1.CreateSandboxRequest.AnnotationsEntry - 258, // 54: openshell.v1.CreateSandboxRequest.service_exposures:type_name -> openshell.v1.SandboxServiceExposure - 289, // 55: openshell.v1.CreateSandboxTemplateRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 43, // 56: openshell.v1.CreateSandboxTemplateRequest.template:type_name -> openshell.v1.SandboxWorkloadTemplate - 289, // 57: openshell.v1.GetSandboxTemplateRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 289, // 58: openshell.v1.ListSandboxTemplatesRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 289, // 59: openshell.v1.DeleteSandboxTemplateRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 43, // 60: openshell.v1.SandboxTemplateResponse.template:type_name -> openshell.v1.SandboxWorkloadTemplate - 43, // 61: openshell.v1.ListSandboxTemplatesResponse.templates:type_name -> openshell.v1.SandboxWorkloadTemplate - 17, // 62: openshell.v1.DeleteSandboxTemplateResponse.outcome:type_name -> openshell.v1.DeletionOutcome - 289, // 63: openshell.v1.BeginRootfsTarStagingRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 284, // 64: openshell.v1.BeginRootfsTarStagingResponse.expiration_time:type_name -> google.protobuf.Timestamp - 289, // 65: openshell.v1.GetSandboxRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 289, // 66: openshell.v1.ListSandboxesRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 289, // 67: openshell.v1.ListSandboxProvidersRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 289, // 68: openshell.v1.AttachSandboxProviderRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 289, // 69: openshell.v1.DetachSandboxProviderRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 289, // 70: openshell.v1.DeleteSandboxRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 289, // 71: openshell.v1.StopSandboxRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 289, // 72: openshell.v1.StartSandboxRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 38, // 73: openshell.v1.SandboxResponse.sandbox:type_name -> openshell.v1.Sandbox - 268, // 74: openshell.v1.SandboxResponse.service_urls:type_name -> openshell.v1.SandboxResponse.ServiceUrlsEntry - 38, // 75: openshell.v1.ListSandboxesResponse.sandboxes:type_name -> openshell.v1.Sandbox - 290, // 76: openshell.v1.ListSandboxProvidersResponse.providers:type_name -> openshell.datamodel.v1.Provider - 38, // 77: openshell.v1.AttachSandboxProviderResponse.sandbox:type_name -> openshell.v1.Sandbox - 80, // 78: openshell.v1.AttachSandboxProviderResponse.receipt:type_name -> openshell.v1.ProviderMutationReceipt - 38, // 79: openshell.v1.DetachSandboxProviderResponse.sandbox:type_name -> openshell.v1.Sandbox - 80, // 80: openshell.v1.DetachSandboxProviderResponse.receipt:type_name -> openshell.v1.ProviderMutationReceipt - 78, // 81: openshell.v1.ConfigSnapshotRevision.sandbox_config:type_name -> openshell.v1.SandboxConfigRevision - 76, // 82: openshell.v1.ConfigSnapshotRevision.provider_target:type_name -> openshell.v1.ProviderDesiredIdentity - 291, // 83: openshell.v1.SandboxConfigRevision.policy_source:type_name -> openshell.sandbox.v1.PolicySource - 5, // 84: openshell.v1.ConfigUpdateOperation.component:type_name -> openshell.v1.ConfigComponent - 77, // 85: openshell.v1.ConfigUpdateOperation.target_revision:type_name -> openshell.v1.ConfigSnapshotRevision - 7, // 86: openshell.v1.ConfigUpdateOperation.state:type_name -> openshell.v1.ConfigUpdateOperationState - 6, // 87: openshell.v1.ConfigUpdateOperation.outcome:type_name -> openshell.v1.ConfigApplyOutcome - 284, // 88: openshell.v1.ConfigUpdateOperation.created_time:type_name -> google.protobuf.Timestamp - 284, // 89: openshell.v1.ConfigUpdateOperation.updated_time:type_name -> google.protobuf.Timestamp - 284, // 90: openshell.v1.ConfigUpdateOperation.completed_time:type_name -> google.protobuf.Timestamp - 2, // 91: openshell.v1.ProviderMutationReceipt.kind:type_name -> openshell.v1.ProviderMutationKind - 76, // 92: openshell.v1.ProviderMutationReceipt.desired:type_name -> openshell.v1.ProviderDesiredIdentity - 284, // 93: openshell.v1.ProviderMutationReceipt.persisted_time:type_name -> google.protobuf.Timestamp - 4, // 94: openshell.v1.ProviderReadinessObservation.reason:type_name -> openshell.v1.ProviderReadinessReason - 80, // 95: openshell.v1.ProviderReadinessStatus.receipt:type_name -> openshell.v1.ProviderMutationReceipt - 3, // 96: openshell.v1.ProviderReadinessStatus.state:type_name -> openshell.v1.ProviderReadinessState - 4, // 97: openshell.v1.ProviderReadinessStatus.reason:type_name -> openshell.v1.ProviderReadinessReason - 81, // 98: openshell.v1.ProviderReadinessStatus.observed:type_name -> openshell.v1.ProviderReadinessObservation - 284, // 99: openshell.v1.ProviderReadinessStatus.observed_time:type_name -> google.protobuf.Timestamp - 284, // 100: openshell.v1.ProviderReadinessStatus.evaluated_time:type_name -> google.protobuf.Timestamp - 79, // 101: openshell.v1.ProviderReadinessStatus.operation:type_name -> openshell.v1.ConfigUpdateOperation - 289, // 102: openshell.v1.GetSandboxProviderStatusRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 82, // 103: openshell.v1.GetSandboxProviderStatusResponse.status:type_name -> openshell.v1.ProviderReadinessStatus - 81, // 104: openshell.v1.ReportProviderReadinessRequest.observation:type_name -> openshell.v1.ProviderReadinessObservation - 288, // 105: openshell.v1.ReportProviderReadinessResponse.report_interval:type_name -> google.protobuf.Duration - 288, // 106: openshell.v1.ReportProviderReadinessResponse.observation_ttl:type_name -> google.protobuf.Duration - 17, // 107: openshell.v1.DeleteSandboxResponse.outcome:type_name -> openshell.v1.DeletionOutcome - 289, // 108: openshell.v1.CreateSshSessionRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 284, // 109: openshell.v1.CreateSshSessionResponse.expiration_time:type_name -> google.protobuf.Timestamp - 289, // 110: openshell.v1.ExposeServiceRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 19, // 111: openshell.v1.ExposeServiceRequest.authorization_mode:type_name -> openshell.v1.ServiceAuthorizationMode - 289, // 112: openshell.v1.GetServiceRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 289, // 113: openshell.v1.ListServicesRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 97, // 114: openshell.v1.ListServicesResponse.services:type_name -> openshell.v1.ServiceEndpointResponse - 289, // 115: openshell.v1.DeleteServiceRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 17, // 116: openshell.v1.DeleteServiceResponse.outcome:type_name -> openshell.v1.DeletionOutcome - 285, // 117: openshell.v1.ServiceEndpoint.metadata:type_name -> openshell.datamodel.v1.ObjectMeta - 19, // 118: openshell.v1.ServiceEndpoint.authorization_mode:type_name -> openshell.v1.ServiceAuthorizationMode - 96, // 119: openshell.v1.ServiceEndpointResponse.endpoint:type_name -> openshell.v1.ServiceEndpoint - 17, // 120: openshell.v1.RevokeSshSessionResponse.outcome:type_name -> openshell.v1.DeletionOutcome - 289, // 121: openshell.v1.ExecSandboxRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 269, // 122: openshell.v1.ExecSandboxRequest.environment:type_name -> openshell.v1.ExecSandboxRequest.EnvironmentEntry - 288, // 123: openshell.v1.ExecSandboxRequest.execution_timeout:type_name -> google.protobuf.Duration - 101, // 124: openshell.v1.ExecSandboxEvent.stdout:type_name -> openshell.v1.ExecSandboxStdout - 102, // 125: openshell.v1.ExecSandboxEvent.stderr:type_name -> openshell.v1.ExecSandboxStderr - 103, // 126: openshell.v1.ExecSandboxEvent.exit:type_name -> openshell.v1.ExecSandboxExit - 201, // 127: openshell.v1.TcpForwardInit.ssh:type_name -> openshell.v1.SshRelayTarget - 202, // 128: openshell.v1.TcpForwardInit.tcp:type_name -> openshell.v1.TcpRelayTarget - 105, // 129: openshell.v1.TcpForwardFrame.init:type_name -> openshell.v1.TcpForwardInit - 100, // 130: openshell.v1.ExecSandboxInput.start:type_name -> openshell.v1.ExecSandboxRequest - 108, // 131: openshell.v1.ExecSandboxInput.resize:type_name -> openshell.v1.ExecSandboxWindowResize - 285, // 132: openshell.v1.SshSession.metadata:type_name -> openshell.datamodel.v1.ObjectMeta - 284, // 133: openshell.v1.SshSession.expiration_time:type_name -> google.protobuf.Timestamp - 289, // 134: openshell.v1.WatchSandboxRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 284, // 135: openshell.v1.WatchSandboxRequest.since_time:type_name -> google.protobuf.Timestamp - 38, // 136: openshell.v1.SandboxStreamEvent.sandbox:type_name -> openshell.v1.Sandbox - 112, // 137: openshell.v1.SandboxStreamEvent.log:type_name -> openshell.v1.SandboxLogLine - 52, // 138: openshell.v1.SandboxStreamEvent.event:type_name -> openshell.v1.PlatformEvent - 113, // 139: openshell.v1.SandboxStreamEvent.warning:type_name -> openshell.v1.SandboxStreamWarning - 214, // 140: openshell.v1.SandboxStreamEvent.draft_policy_update:type_name -> openshell.v1.DraftPolicyUpdate - 284, // 141: openshell.v1.SandboxLogLine.event_time:type_name -> google.protobuf.Timestamp - 270, // 142: openshell.v1.SandboxLogLine.fields:type_name -> openshell.v1.SandboxLogLine.FieldsEntry - 289, // 143: openshell.v1.CreateProviderRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 290, // 144: openshell.v1.CreateProviderRequest.provider:type_name -> openshell.datamodel.v1.Provider - 289, // 145: openshell.v1.GetProviderRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 289, // 146: openshell.v1.GetProviderCredentialsRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 288, // 147: openshell.v1.GetProviderCredentialsRequest.minimum_remaining_lifetime:type_name -> google.protobuf.Duration - 284, // 148: openshell.v1.ProviderCredentialValue.expiration_time:type_name -> google.protobuf.Timestamp - 271, // 149: openshell.v1.GetProviderCredentialsResponse.credentials:type_name -> openshell.v1.GetProviderCredentialsResponse.CredentialsEntry - 289, // 150: openshell.v1.ListProvidersRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 289, // 151: openshell.v1.UpdateProviderRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 290, // 152: openshell.v1.UpdateProviderRequest.provider:type_name -> openshell.datamodel.v1.Provider - 272, // 153: openshell.v1.UpdateProviderRequest.credential_expiration_times:type_name -> openshell.v1.UpdateProviderRequest.CredentialExpirationTimesEntry - 289, // 154: openshell.v1.DeleteProviderRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 290, // 155: openshell.v1.ProviderResponse.provider:type_name -> openshell.datamodel.v1.Provider - 80, // 156: openshell.v1.ProviderResponse.target_receipts:type_name -> openshell.v1.ProviderMutationReceipt - 290, // 157: openshell.v1.ListProvidersResponse.providers:type_name -> openshell.datamodel.v1.Provider - 289, // 158: openshell.v1.ListProviderProfilesRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 289, // 159: openshell.v1.GetProviderProfileRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 145, // 160: openshell.v1.ProviderProfileImportItem.profile:type_name -> openshell.v1.ProviderProfile - 288, // 161: openshell.v1.ProviderCredentialTokenGrant.cache_ttl:type_name -> google.protobuf.Duration - 128, // 162: openshell.v1.ProviderCredentialTokenGrant.audience_overrides:type_name -> openshell.v1.ProviderCredentialTokenGrantAudienceOverride - 8, // 163: openshell.v1.ProviderCredentialTokenGrant.grant_type:type_name -> openshell.v1.ProviderCredentialTokenGrantType - 129, // 164: openshell.v1.ProviderCredentialTokenGrant.subject_token:type_name -> openshell.v1.ProviderCredentialTokenGrantSubjectToken - 134, // 165: openshell.v1.ProviderProfileCredential.refresh:type_name -> openshell.v1.ProviderCredentialRefresh - 130, // 166: openshell.v1.ProviderProfileCredential.token_grant:type_name -> openshell.v1.ProviderCredentialTokenGrant - 9, // 167: openshell.v1.ProviderCredentialRefresh.strategy:type_name -> openshell.v1.ProviderCredentialRefreshStrategy - 288, // 168: openshell.v1.ProviderCredentialRefresh.refresh_before:type_name -> google.protobuf.Duration - 288, // 169: openshell.v1.ProviderCredentialRefresh.max_lifetime:type_name -> google.protobuf.Duration - 132, // 170: openshell.v1.ProviderCredentialRefresh.material:type_name -> openshell.v1.ProviderCredentialRefreshMaterial - 133, // 171: openshell.v1.ProviderCredentialRefresh.additional_outputs:type_name -> openshell.v1.ProviderCredentialRefreshOutput - 9, // 172: openshell.v1.ProviderCredentialRefreshStatus.strategy:type_name -> openshell.v1.ProviderCredentialRefreshStrategy - 284, // 173: openshell.v1.ProviderCredentialRefreshStatus.expiration_time:type_name -> google.protobuf.Timestamp - 284, // 174: openshell.v1.ProviderCredentialRefreshStatus.next_refresh_time:type_name -> google.protobuf.Timestamp - 284, // 175: openshell.v1.ProviderCredentialRefreshStatus.last_refresh_time:type_name -> google.protobuf.Timestamp - 15, // 176: openshell.v1.ProviderCredentialRefreshStatus.recovery_action:type_name -> openshell.v1.ProviderCredentialRefreshRecoveryAction - 284, // 177: openshell.v1.ProviderCredentialRefreshStatus.last_error_time:type_name -> google.protobuf.Timestamp - 289, // 178: openshell.v1.GetProviderRefreshStatusRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 135, // 179: openshell.v1.GetProviderRefreshStatusResponse.credentials:type_name -> openshell.v1.ProviderCredentialRefreshStatus - 289, // 180: openshell.v1.ConfigureProviderRefreshRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 9, // 181: openshell.v1.ConfigureProviderRefreshRequest.strategy:type_name -> openshell.v1.ProviderCredentialRefreshStrategy - 273, // 182: openshell.v1.ConfigureProviderRefreshRequest.material:type_name -> openshell.v1.ConfigureProviderRefreshRequest.MaterialEntry - 284, // 183: openshell.v1.ConfigureProviderRefreshRequest.expiration_time:type_name -> google.protobuf.Timestamp - 135, // 184: openshell.v1.ConfigureProviderRefreshResponse.status:type_name -> openshell.v1.ProviderCredentialRefreshStatus - 289, // 185: openshell.v1.RotateProviderCredentialRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 135, // 186: openshell.v1.RotateProviderCredentialResponse.status:type_name -> openshell.v1.ProviderCredentialRefreshStatus - 289, // 187: openshell.v1.DeleteProviderRefreshRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 17, // 188: openshell.v1.DeleteProviderRefreshResponse.outcome:type_name -> openshell.v1.DeletionOutcome - 10, // 189: openshell.v1.ProviderProfile.category:type_name -> openshell.v1.ProviderProfileCategory - 131, // 190: openshell.v1.ProviderProfile.credentials:type_name -> openshell.v1.ProviderProfileCredential - 292, // 191: openshell.v1.ProviderProfile.endpoints:type_name -> openshell.sandbox.v1.NetworkEndpoint - 293, // 192: openshell.v1.ProviderProfile.binaries:type_name -> openshell.sandbox.v1.NetworkBinary - 136, // 193: openshell.v1.ProviderProfile.discovery:type_name -> openshell.v1.ProviderProfileDiscovery - 274, // 194: openshell.v1.ProviderProfile.annotations:type_name -> openshell.v1.ProviderProfile.AnnotationsEntry - 146, // 195: openshell.v1.ProviderProfile.files:type_name -> openshell.v1.ProviderProfileFile - 145, // 196: openshell.v1.ProviderProfileResponse.profile:type_name -> openshell.v1.ProviderProfile - 145, // 197: openshell.v1.ListProviderProfilesResponse.profiles:type_name -> openshell.v1.ProviderProfile - 289, // 198: openshell.v1.ImportProviderProfilesRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 126, // 199: openshell.v1.ImportProviderProfilesRequest.profiles:type_name -> openshell.v1.ProviderProfileImportItem - 127, // 200: openshell.v1.ImportProviderProfilesResponse.diagnostics:type_name -> openshell.v1.ProviderProfileDiagnostic - 145, // 201: openshell.v1.ImportProviderProfilesResponse.profiles:type_name -> openshell.v1.ProviderProfile - 289, // 202: openshell.v1.UpdateProviderProfilesRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 126, // 203: openshell.v1.UpdateProviderProfilesRequest.profile:type_name -> openshell.v1.ProviderProfileImportItem - 127, // 204: openshell.v1.UpdateProviderProfilesResponse.diagnostics:type_name -> openshell.v1.ProviderProfileDiagnostic - 145, // 205: openshell.v1.UpdateProviderProfilesResponse.profile:type_name -> openshell.v1.ProviderProfile - 289, // 206: openshell.v1.LintProviderProfilesRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 126, // 207: openshell.v1.LintProviderProfilesRequest.profiles:type_name -> openshell.v1.ProviderProfileImportItem - 127, // 208: openshell.v1.LintProviderProfilesResponse.diagnostics:type_name -> openshell.v1.ProviderProfileDiagnostic - 17, // 209: openshell.v1.DeleteProviderResponse.outcome:type_name -> openshell.v1.DeletionOutcome - 289, // 210: openshell.v1.DeleteProviderProfileRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 17, // 211: openshell.v1.DeleteProviderProfileResponse.outcome:type_name -> openshell.v1.DeletionOutcome - 159, // 212: openshell.v1.StaticCredentialBinding.endpoints:type_name -> openshell.v1.StaticCredentialEndpointBinding - 275, // 213: openshell.v1.GetSandboxProviderEnvironmentResponse.environment:type_name -> openshell.v1.GetSandboxProviderEnvironmentResponse.EnvironmentEntry - 276, // 214: openshell.v1.GetSandboxProviderEnvironmentResponse.credential_expiration_times:type_name -> openshell.v1.GetSandboxProviderEnvironmentResponse.CredentialExpirationTimesEntry - 277, // 215: openshell.v1.GetSandboxProviderEnvironmentResponse.dynamic_credentials:type_name -> openshell.v1.GetSandboxProviderEnvironmentResponse.DynamicCredentialsEntry - 278, // 216: openshell.v1.GetSandboxProviderEnvironmentResponse.static_credential_bindings:type_name -> openshell.v1.GetSandboxProviderEnvironmentResponse.StaticCredentialBindingsEntry - 4, // 217: openshell.v1.GetSandboxProviderEnvironmentResponse.readiness_reason:type_name -> openshell.v1.ProviderReadinessReason - 279, // 218: openshell.v1.GetSandboxProviderEnvironmentResponse.files:type_name -> openshell.v1.GetSandboxProviderEnvironmentResponse.FilesEntry - 288, // 219: openshell.v1.ExchangeProviderSubjectTokenResponse.expires_after:type_name -> google.protobuf.Duration - 289, // 220: openshell.v1.UpdateConfigRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 286, // 221: openshell.v1.UpdateConfigRequest.policy:type_name -> openshell.sandbox.v1.SandboxPolicy - 294, // 222: openshell.v1.UpdateConfigRequest.setting_value:type_name -> openshell.sandbox.v1.SettingValue - 165, // 223: openshell.v1.UpdateConfigRequest.merge_operations:type_name -> openshell.v1.PolicyMergeOperation - 280, // 224: openshell.v1.UpdateConfigRequest.annotations:type_name -> openshell.v1.UpdateConfigRequest.AnnotationsEntry - 166, // 225: openshell.v1.PolicyMergeOperation.add_rule:type_name -> openshell.v1.AddNetworkRule - 167, // 226: openshell.v1.PolicyMergeOperation.remove_endpoint:type_name -> openshell.v1.RemoveNetworkEndpoint - 168, // 227: openshell.v1.PolicyMergeOperation.remove_rule:type_name -> openshell.v1.RemoveNetworkRule - 170, // 228: openshell.v1.PolicyMergeOperation.add_deny_rules:type_name -> openshell.v1.AddDenyRules - 171, // 229: openshell.v1.PolicyMergeOperation.add_allow_rules:type_name -> openshell.v1.AddAllowRules - 172, // 230: openshell.v1.PolicyMergeOperation.remove_binary:type_name -> openshell.v1.RemoveNetworkBinary - 295, // 231: openshell.v1.AddNetworkRule.rule:type_name -> openshell.sandbox.v1.NetworkPolicyRule - 293, // 232: openshell.v1.L7RuleTarget.binaries:type_name -> openshell.sandbox.v1.NetworkBinary - 296, // 233: openshell.v1.AddDenyRules.deny_rules:type_name -> openshell.sandbox.v1.L7DenyRule - 169, // 234: openshell.v1.AddDenyRules.target:type_name -> openshell.v1.L7RuleTarget - 297, // 235: openshell.v1.AddAllowRules.rules:type_name -> openshell.sandbox.v1.L7Rule - 169, // 236: openshell.v1.AddAllowRules.target:type_name -> openshell.v1.L7RuleTarget - 281, // 237: openshell.v1.UpdateConfigResponse.annotations:type_name -> openshell.v1.UpdateConfigResponse.AnnotationsEntry - 289, // 238: openshell.v1.GetSandboxPolicyStatusRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 183, // 239: openshell.v1.GetSandboxPolicyStatusResponse.revision:type_name -> openshell.v1.SandboxPolicyRevision - 289, // 240: openshell.v1.ListSandboxPoliciesRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 183, // 241: openshell.v1.ListSandboxPoliciesResponse.revisions:type_name -> openshell.v1.SandboxPolicyRevision - 12, // 242: openshell.v1.ReportPolicyStatusRequest.status:type_name -> openshell.v1.PolicyStatus - 11, // 243: openshell.v1.SandboxConfigurationAdmission.state:type_name -> openshell.v1.ConfigurationAdmissionState - 180, // 244: openshell.v1.ReportSandboxConfigurationRequest.admission:type_name -> openshell.v1.SandboxConfigurationAdmission - 12, // 245: openshell.v1.SandboxPolicyRevision.status:type_name -> openshell.v1.PolicyStatus - 284, // 246: openshell.v1.SandboxPolicyRevision.created_time:type_name -> google.protobuf.Timestamp - 284, // 247: openshell.v1.SandboxPolicyRevision.loaded_time:type_name -> google.protobuf.Timestamp - 286, // 248: openshell.v1.SandboxPolicyRevision.policy:type_name -> openshell.sandbox.v1.SandboxPolicy - 282, // 249: openshell.v1.SandboxPolicyRevision.provenance:type_name -> openshell.v1.SandboxPolicyRevision.ProvenanceEntry - 289, // 250: openshell.v1.GetSandboxLogsRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 284, // 251: openshell.v1.GetSandboxLogsRequest.since_time:type_name -> google.protobuf.Timestamp - 112, // 252: openshell.v1.PushSandboxLogsRequest.logs:type_name -> openshell.v1.SandboxLogLine - 112, // 253: openshell.v1.GetSandboxLogsResponse.logs:type_name -> openshell.v1.SandboxLogLine - 190, // 254: openshell.v1.SupervisorMessage.hello:type_name -> openshell.v1.SupervisorHello - 194, // 255: openshell.v1.SupervisorMessage.heartbeat:type_name -> openshell.v1.SupervisorHeartbeat - 207, // 256: openshell.v1.SupervisorMessage.relay_open_result:type_name -> openshell.v1.RelayOpenResult - 208, // 257: openshell.v1.SupervisorMessage.relay_close:type_name -> openshell.v1.RelayClose - 191, // 258: openshell.v1.GatewayMessage.session_accepted:type_name -> openshell.v1.SessionAccepted - 192, // 259: openshell.v1.GatewayMessage.session_rejected:type_name -> openshell.v1.SessionRejected - 195, // 260: openshell.v1.GatewayMessage.heartbeat:type_name -> openshell.v1.GatewayHeartbeat - 200, // 261: openshell.v1.GatewayMessage.relay_open:type_name -> openshell.v1.RelayOpen - 208, // 262: openshell.v1.GatewayMessage.relay_close:type_name -> openshell.v1.RelayClose - 193, // 263: openshell.v1.GatewayMessage.session_redirect:type_name -> openshell.v1.SessionRedirect - 288, // 264: openshell.v1.SessionAccepted.heartbeat_interval:type_name -> google.protobuf.Duration - 201, // 265: openshell.v1.RelayOpen.ssh:type_name -> openshell.v1.SshRelayTarget - 202, // 266: openshell.v1.RelayOpen.tcp:type_name -> openshell.v1.TcpRelayTarget - 203, // 267: openshell.v1.RelayFrame.init:type_name -> openshell.v1.RelayInit - 200, // 268: openshell.v1.PeerRelayInit.relay_open:type_name -> openshell.v1.RelayOpen - 205, // 269: openshell.v1.PeerRelayFrame.init:type_name -> openshell.v1.PeerRelayInit - 284, // 270: openshell.v1.DenialSummary.first_seen_time:type_name -> google.protobuf.Timestamp - 284, // 271: openshell.v1.DenialSummary.last_seen_time:type_name -> google.protobuf.Timestamp - 209, // 272: openshell.v1.DenialSummary.l7_request_samples:type_name -> openshell.v1.L7RequestSample - 211, // 273: openshell.v1.NetworkActivitySummary.denials_by_group:type_name -> openshell.v1.DenialGroupCount - 295, // 274: openshell.v1.PolicyChunk.proposed_rule:type_name -> openshell.sandbox.v1.NetworkPolicyRule - 284, // 275: openshell.v1.PolicyChunk.created_time:type_name -> google.protobuf.Timestamp - 284, // 276: openshell.v1.PolicyChunk.decided_time:type_name -> google.protobuf.Timestamp - 284, // 277: openshell.v1.PolicyChunk.first_seen_time:type_name -> google.protobuf.Timestamp - 284, // 278: openshell.v1.PolicyChunk.last_seen_time:type_name -> google.protobuf.Timestamp - 286, // 279: openshell.v1.PolicyChunk.current_effective_policy:type_name -> openshell.sandbox.v1.SandboxPolicy - 286, // 280: openshell.v1.PolicyChunk.candidate_effective_policy:type_name -> openshell.sandbox.v1.SandboxPolicy - 289, // 281: openshell.v1.SubmitPolicyAnalysisRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 210, // 282: openshell.v1.SubmitPolicyAnalysisRequest.summaries:type_name -> openshell.v1.DenialSummary - 213, // 283: openshell.v1.SubmitPolicyAnalysisRequest.proposed_chunks:type_name -> openshell.v1.PolicyChunk - 212, // 284: openshell.v1.SubmitPolicyAnalysisRequest.network_activity_summaries:type_name -> openshell.v1.NetworkActivitySummary - 289, // 285: openshell.v1.GetDraftPolicyRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 213, // 286: openshell.v1.GetDraftPolicyResponse.chunks:type_name -> openshell.v1.PolicyChunk - 284, // 287: openshell.v1.GetDraftPolicyResponse.last_analyzed_time:type_name -> google.protobuf.Timestamp - 289, // 288: openshell.v1.ApproveDraftChunkRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 289, // 289: openshell.v1.RejectDraftChunkRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 289, // 290: openshell.v1.ApproveAllDraftChunksRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 223, // 291: openshell.v1.ApproveAllDraftChunksRequest.approvals:type_name -> openshell.v1.DraftChunkApproval - 289, // 292: openshell.v1.EditDraftChunkRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 295, // 293: openshell.v1.EditDraftChunkRequest.proposed_rule:type_name -> openshell.sandbox.v1.NetworkPolicyRule - 289, // 294: openshell.v1.UndoDraftChunkRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 289, // 295: openshell.v1.ClearDraftChunksRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 289, // 296: openshell.v1.GetDraftHistoryRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 284, // 297: openshell.v1.DraftHistoryEntry.event_time:type_name -> google.protobuf.Timestamp - 233, // 298: openshell.v1.GetDraftHistoryResponse.entries:type_name -> openshell.v1.DraftHistoryEntry - 283, // 299: openshell.v1.CreateWorkspaceRequest.labels:type_name -> openshell.v1.CreateWorkspaceRequest.LabelsEntry - 298, // 300: openshell.v1.CreateWorkspaceResponse.workspace:type_name -> openshell.datamodel.v1.Workspace - 298, // 301: openshell.v1.GetWorkspaceResponse.workspace:type_name -> openshell.datamodel.v1.Workspace - 298, // 302: openshell.v1.ListWorkspacesResponse.workspaces:type_name -> openshell.datamodel.v1.Workspace - 17, // 303: openshell.v1.DeleteWorkspaceResponse.outcome:type_name -> openshell.v1.DeletionOutcome - 285, // 304: openshell.v1.WorkspaceMember.metadata:type_name -> openshell.datamodel.v1.ObjectMeta - 14, // 305: openshell.v1.WorkspaceMember.role:type_name -> openshell.v1.WorkspaceRole - 289, // 306: openshell.v1.AddWorkspaceMemberRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 14, // 307: openshell.v1.AddWorkspaceMemberRequest.role:type_name -> openshell.v1.WorkspaceRole - 243, // 308: openshell.v1.AddWorkspaceMemberResponse.member:type_name -> openshell.v1.WorkspaceMember - 289, // 309: openshell.v1.RemoveWorkspaceMemberRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 17, // 310: openshell.v1.RemoveWorkspaceMemberResponse.outcome:type_name -> openshell.v1.DeletionOutcome - 289, // 311: openshell.v1.ListWorkspaceMembersRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector - 243, // 312: openshell.v1.ListWorkspaceMembersResponse.members:type_name -> openshell.v1.WorkspaceMember - 284, // 313: openshell.v1.ExtensionServiceCredential.expiration_time:type_name -> google.protobuf.Timestamp - 18, // 314: openshell.v1.EndpointObservation.result:type_name -> openshell.v1.EndpointResult - 259, // 315: openshell.v1.EndpointObservation.failure:type_name -> openshell.v1.EndpointFailure - 251, // 316: openshell.v1.ReportEndpointStatusRequest.observations:type_name -> openshell.v1.EndpointObservation - 253, // 317: openshell.v1.ReportEndpointStatusRequest.token_grant_status:type_name -> openshell.v1.TokenGrantStatus - 254, // 318: openshell.v1.TokenGrantStatus.last_failure:type_name -> openshell.v1.TokenGrantFailureObservation - 259, // 319: openshell.v1.TokenGrantFailureObservation.failure:type_name -> openshell.v1.EndpointFailure - 18, // 320: openshell.v1.EndpointStatus.last_result:type_name -> openshell.v1.EndpointResult - 284, // 321: openshell.v1.EndpointStatus.last_reported_time:type_name -> google.protobuf.Timestamp - 259, // 322: openshell.v1.EndpointStatus.last_failure:type_name -> openshell.v1.EndpointFailure - 284, // 323: openshell.v1.SandboxProvisioning.configuration_change_time:type_name -> google.protobuf.Timestamp - 284, // 324: openshell.v1.SandboxProvisioning.first_rejection_time:type_name -> google.protobuf.Timestamp - 284, // 325: openshell.v1.SandboxProvisioning.deadline:type_name -> google.protobuf.Timestamp - 284, // 326: openshell.v1.SandboxProvisioning.timeout_time:type_name -> google.protobuf.Timestamp - 284, // 327: openshell.v1.SandboxProvisioning.cleanup_completed_time:type_name -> google.protobuf.Timestamp - 284, // 328: openshell.v1.SandboxProvisioning.cleanup_retry_time:type_name -> google.protobuf.Timestamp - 284, // 329: openshell.v1.SandboxProvisioning.attachment_change_time:type_name -> google.protobuf.Timestamp - 284, // 330: openshell.v1.SandboxProvisioning.preparation_deadline:type_name -> google.protobuf.Timestamp - 284, // 331: openshell.v1.SandboxProvisioning.admission_start_time:type_name -> google.protobuf.Timestamp - 19, // 332: openshell.v1.SandboxServiceExposure.authorization_mode:type_name -> openshell.v1.ServiceAuthorizationMode - 20, // 333: openshell.v1.EndpointFailure.domain:type_name -> openshell.v1.EndpointFailureDomain - 15, // 334: openshell.v1.EndpointFailure.recovery_action:type_name -> openshell.v1.ProviderCredentialRefreshRecoveryAction - 117, // 335: openshell.v1.GetProviderCredentialsResponse.CredentialsEntry.value:type_name -> openshell.v1.ProviderCredentialValue - 284, // 336: openshell.v1.UpdateProviderRequest.CredentialExpirationTimesEntry.value:type_name -> google.protobuf.Timestamp - 284, // 337: openshell.v1.GetSandboxProviderEnvironmentResponse.CredentialExpirationTimesEntry.value:type_name -> google.protobuf.Timestamp - 131, // 338: openshell.v1.GetSandboxProviderEnvironmentResponse.DynamicCredentialsEntry.value:type_name -> openshell.v1.ProviderProfileCredential - 160, // 339: openshell.v1.GetSandboxProviderEnvironmentResponse.StaticCredentialBindingsEntry.value:type_name -> openshell.v1.StaticCredentialBinding - 25, // 340: openshell.v1.OpenShell.Health:input_type -> openshell.v1.HealthRequest - 27, // 341: openshell.v1.OpenShell.GetCurrentUser:input_type -> openshell.v1.GetCurrentUserRequest - 29, // 342: openshell.v1.OpenShell.GetGatewayInfo:input_type -> openshell.v1.GetGatewayInfoRequest - 53, // 343: openshell.v1.OpenShell.CreateSandbox:input_type -> openshell.v1.CreateSandboxRequest - 61, // 344: openshell.v1.OpenShell.BeginRootfsTarStaging:input_type -> openshell.v1.BeginRootfsTarStagingRequest - 63, // 345: openshell.v1.OpenShell.GetSandbox:input_type -> openshell.v1.GetSandboxRequest - 64, // 346: openshell.v1.OpenShell.ListSandboxes:input_type -> openshell.v1.ListSandboxesRequest - 54, // 347: openshell.v1.OpenShell.CreateSandboxTemplate:input_type -> openshell.v1.CreateSandboxTemplateRequest - 55, // 348: openshell.v1.OpenShell.GetSandboxTemplate:input_type -> openshell.v1.GetSandboxTemplateRequest - 56, // 349: openshell.v1.OpenShell.ListSandboxTemplates:input_type -> openshell.v1.ListSandboxTemplatesRequest - 57, // 350: openshell.v1.OpenShell.DeleteSandboxTemplate:input_type -> openshell.v1.DeleteSandboxTemplateRequest - 65, // 351: openshell.v1.OpenShell.ListSandboxProviders:input_type -> openshell.v1.ListSandboxProvidersRequest - 66, // 352: openshell.v1.OpenShell.AttachSandboxProvider:input_type -> openshell.v1.AttachSandboxProviderRequest - 67, // 353: openshell.v1.OpenShell.DetachSandboxProvider:input_type -> openshell.v1.DetachSandboxProviderRequest - 83, // 354: openshell.v1.OpenShell.GetSandboxProviderStatus:input_type -> openshell.v1.GetSandboxProviderStatusRequest - 68, // 355: openshell.v1.OpenShell.DeleteSandbox:input_type -> openshell.v1.DeleteSandboxRequest - 69, // 356: openshell.v1.OpenShell.StopSandbox:input_type -> openshell.v1.StopSandboxRequest - 70, // 357: openshell.v1.OpenShell.StartSandbox:input_type -> openshell.v1.StartSandboxRequest - 88, // 358: openshell.v1.OpenShell.CreateSshSession:input_type -> openshell.v1.CreateSshSessionRequest - 90, // 359: openshell.v1.OpenShell.ExposeService:input_type -> openshell.v1.ExposeServiceRequest - 91, // 360: openshell.v1.OpenShell.GetService:input_type -> openshell.v1.GetServiceRequest - 92, // 361: openshell.v1.OpenShell.ListServices:input_type -> openshell.v1.ListServicesRequest - 94, // 362: openshell.v1.OpenShell.DeleteService:input_type -> openshell.v1.DeleteServiceRequest - 98, // 363: openshell.v1.OpenShell.RevokeSshSession:input_type -> openshell.v1.RevokeSshSessionRequest - 100, // 364: openshell.v1.OpenShell.ExecSandbox:input_type -> openshell.v1.ExecSandboxRequest - 106, // 365: openshell.v1.OpenShell.ForwardTcp:input_type -> openshell.v1.TcpForwardFrame - 107, // 366: openshell.v1.OpenShell.ExecSandboxInteractive:input_type -> openshell.v1.ExecSandboxInput - 114, // 367: openshell.v1.OpenShell.CreateProvider:input_type -> openshell.v1.CreateProviderRequest - 115, // 368: openshell.v1.OpenShell.GetProvider:input_type -> openshell.v1.GetProviderRequest - 116, // 369: openshell.v1.OpenShell.GetProviderCredentials:input_type -> openshell.v1.GetProviderCredentialsRequest - 119, // 370: openshell.v1.OpenShell.ListProviders:input_type -> openshell.v1.ListProvidersRequest - 124, // 371: openshell.v1.OpenShell.ListProviderProfiles:input_type -> openshell.v1.ListProviderProfilesRequest - 125, // 372: openshell.v1.OpenShell.GetProviderProfile:input_type -> openshell.v1.GetProviderProfileRequest - 149, // 373: openshell.v1.OpenShell.ImportProviderProfiles:input_type -> openshell.v1.ImportProviderProfilesRequest - 151, // 374: openshell.v1.OpenShell.UpdateProviderProfiles:input_type -> openshell.v1.UpdateProviderProfilesRequest - 153, // 375: openshell.v1.OpenShell.LintProviderProfiles:input_type -> openshell.v1.LintProviderProfilesRequest - 120, // 376: openshell.v1.OpenShell.UpdateProvider:input_type -> openshell.v1.UpdateProviderRequest - 137, // 377: openshell.v1.OpenShell.GetProviderRefreshStatus:input_type -> openshell.v1.GetProviderRefreshStatusRequest - 139, // 378: openshell.v1.OpenShell.ConfigureProviderRefresh:input_type -> openshell.v1.ConfigureProviderRefreshRequest - 141, // 379: openshell.v1.OpenShell.RotateProviderCredential:input_type -> openshell.v1.RotateProviderCredentialRequest - 143, // 380: openshell.v1.OpenShell.DeleteProviderRefresh:input_type -> openshell.v1.DeleteProviderRefreshRequest - 121, // 381: openshell.v1.OpenShell.DeleteProvider:input_type -> openshell.v1.DeleteProviderRequest - 156, // 382: openshell.v1.OpenShell.DeleteProviderProfile:input_type -> openshell.v1.DeleteProviderProfileRequest - 299, // 383: openshell.v1.OpenShell.GetSandboxConfig:input_type -> openshell.sandbox.v1.GetSandboxConfigRequest - 300, // 384: openshell.v1.OpenShell.GetGatewayConfig:input_type -> openshell.sandbox.v1.GetGatewayConfigRequest - 164, // 385: openshell.v1.OpenShell.UpdateConfig:input_type -> openshell.v1.UpdateConfigRequest - 174, // 386: openshell.v1.OpenShell.GetSandboxPolicyStatus:input_type -> openshell.v1.GetSandboxPolicyStatusRequest - 176, // 387: openshell.v1.OpenShell.ListSandboxPolicies:input_type -> openshell.v1.ListSandboxPoliciesRequest - 178, // 388: openshell.v1.OpenShell.ReportPolicyStatus:input_type -> openshell.v1.ReportPolicyStatusRequest - 252, // 389: openshell.v1.OpenShell.ReportEndpointStatus:input_type -> openshell.v1.ReportEndpointStatusRequest - 85, // 390: openshell.v1.OpenShell.ReportProviderReadiness:input_type -> openshell.v1.ReportProviderReadinessRequest - 181, // 391: openshell.v1.OpenShell.ReportSandboxConfiguration:input_type -> openshell.v1.ReportSandboxConfigurationRequest - 158, // 392: openshell.v1.OpenShell.GetSandboxProviderEnvironment:input_type -> openshell.v1.GetSandboxProviderEnvironmentRequest - 162, // 393: openshell.v1.OpenShell.ExchangeProviderSubjectToken:input_type -> openshell.v1.ExchangeProviderSubjectTokenRequest - 184, // 394: openshell.v1.OpenShell.GetSandboxLogs:input_type -> openshell.v1.GetSandboxLogsRequest - 185, // 395: openshell.v1.OpenShell.PushSandboxLogs:input_type -> openshell.v1.PushSandboxLogsRequest - 188, // 396: openshell.v1.OpenShell.ConnectSupervisor:input_type -> openshell.v1.SupervisorMessage - 196, // 397: openshell.v1.OpenShell.ReportMainProcessExit:input_type -> openshell.v1.ReportMainProcessExitRequest - 198, // 398: openshell.v1.OpenShell.FinalizeMainProcessExit:input_type -> openshell.v1.FinalizeMainProcessExitRequest - 204, // 399: openshell.v1.OpenShell.RelayStream:input_type -> openshell.v1.RelayFrame - 206, // 400: openshell.v1.OpenShell.PeerRelay:input_type -> openshell.v1.PeerRelayFrame - 85, // 401: openshell.v1.OpenShell.PeerReportProviderReadiness:input_type -> openshell.v1.ReportProviderReadinessRequest - 252, // 402: openshell.v1.OpenShell.PeerReportEndpointStatus:input_type -> openshell.v1.ReportEndpointStatusRequest - 83, // 403: openshell.v1.OpenShell.PeerGetSandboxProviderStatus:input_type -> openshell.v1.GetSandboxProviderStatusRequest - 110, // 404: openshell.v1.OpenShell.WatchSandbox:input_type -> openshell.v1.WatchSandboxRequest - 215, // 405: openshell.v1.OpenShell.SubmitPolicyAnalysis:input_type -> openshell.v1.SubmitPolicyAnalysisRequest - 217, // 406: openshell.v1.OpenShell.GetDraftPolicy:input_type -> openshell.v1.GetDraftPolicyRequest - 219, // 407: openshell.v1.OpenShell.ApproveDraftChunk:input_type -> openshell.v1.ApproveDraftChunkRequest - 221, // 408: openshell.v1.OpenShell.RejectDraftChunk:input_type -> openshell.v1.RejectDraftChunkRequest - 224, // 409: openshell.v1.OpenShell.ApproveAllDraftChunks:input_type -> openshell.v1.ApproveAllDraftChunksRequest - 226, // 410: openshell.v1.OpenShell.EditDraftChunk:input_type -> openshell.v1.EditDraftChunkRequest - 228, // 411: openshell.v1.OpenShell.UndoDraftChunk:input_type -> openshell.v1.UndoDraftChunkRequest - 230, // 412: openshell.v1.OpenShell.ClearDraftChunks:input_type -> openshell.v1.ClearDraftChunksRequest - 232, // 413: openshell.v1.OpenShell.GetDraftHistory:input_type -> openshell.v1.GetDraftHistoryRequest - 21, // 414: openshell.v1.OpenShell.IssueSandboxToken:input_type -> openshell.v1.IssueSandboxTokenRequest - 23, // 415: openshell.v1.OpenShell.RefreshSandboxToken:input_type -> openshell.v1.RefreshSandboxTokenRequest - 235, // 416: openshell.v1.OpenShell.CreateWorkspace:input_type -> openshell.v1.CreateWorkspaceRequest - 237, // 417: openshell.v1.OpenShell.GetWorkspace:input_type -> openshell.v1.GetWorkspaceRequest - 239, // 418: openshell.v1.OpenShell.ListWorkspaces:input_type -> openshell.v1.ListWorkspacesRequest - 241, // 419: openshell.v1.OpenShell.DeleteWorkspace:input_type -> openshell.v1.DeleteWorkspaceRequest - 244, // 420: openshell.v1.OpenShell.AddWorkspaceMember:input_type -> openshell.v1.AddWorkspaceMemberRequest - 246, // 421: openshell.v1.OpenShell.RemoveWorkspaceMember:input_type -> openshell.v1.RemoveWorkspaceMemberRequest - 248, // 422: openshell.v1.OpenShell.ListWorkspaceMembers:input_type -> openshell.v1.ListWorkspaceMembersRequest - 26, // 423: openshell.v1.OpenShell.Health:output_type -> openshell.v1.HealthResponse - 28, // 424: openshell.v1.OpenShell.GetCurrentUser:output_type -> openshell.v1.GetCurrentUserResponse - 30, // 425: openshell.v1.OpenShell.GetGatewayInfo:output_type -> openshell.v1.GetGatewayInfoResponse - 71, // 426: openshell.v1.OpenShell.CreateSandbox:output_type -> openshell.v1.SandboxResponse - 62, // 427: openshell.v1.OpenShell.BeginRootfsTarStaging:output_type -> openshell.v1.BeginRootfsTarStagingResponse - 71, // 428: openshell.v1.OpenShell.GetSandbox:output_type -> openshell.v1.SandboxResponse - 72, // 429: openshell.v1.OpenShell.ListSandboxes:output_type -> openshell.v1.ListSandboxesResponse - 58, // 430: openshell.v1.OpenShell.CreateSandboxTemplate:output_type -> openshell.v1.SandboxTemplateResponse - 58, // 431: openshell.v1.OpenShell.GetSandboxTemplate:output_type -> openshell.v1.SandboxTemplateResponse - 59, // 432: openshell.v1.OpenShell.ListSandboxTemplates:output_type -> openshell.v1.ListSandboxTemplatesResponse - 60, // 433: openshell.v1.OpenShell.DeleteSandboxTemplate:output_type -> openshell.v1.DeleteSandboxTemplateResponse - 73, // 434: openshell.v1.OpenShell.ListSandboxProviders:output_type -> openshell.v1.ListSandboxProvidersResponse - 74, // 435: openshell.v1.OpenShell.AttachSandboxProvider:output_type -> openshell.v1.AttachSandboxProviderResponse - 75, // 436: openshell.v1.OpenShell.DetachSandboxProvider:output_type -> openshell.v1.DetachSandboxProviderResponse - 84, // 437: openshell.v1.OpenShell.GetSandboxProviderStatus:output_type -> openshell.v1.GetSandboxProviderStatusResponse - 87, // 438: openshell.v1.OpenShell.DeleteSandbox:output_type -> openshell.v1.DeleteSandboxResponse - 71, // 439: openshell.v1.OpenShell.StopSandbox:output_type -> openshell.v1.SandboxResponse - 71, // 440: openshell.v1.OpenShell.StartSandbox:output_type -> openshell.v1.SandboxResponse - 89, // 441: openshell.v1.OpenShell.CreateSshSession:output_type -> openshell.v1.CreateSshSessionResponse - 97, // 442: openshell.v1.OpenShell.ExposeService:output_type -> openshell.v1.ServiceEndpointResponse - 97, // 443: openshell.v1.OpenShell.GetService:output_type -> openshell.v1.ServiceEndpointResponse - 93, // 444: openshell.v1.OpenShell.ListServices:output_type -> openshell.v1.ListServicesResponse - 95, // 445: openshell.v1.OpenShell.DeleteService:output_type -> openshell.v1.DeleteServiceResponse - 99, // 446: openshell.v1.OpenShell.RevokeSshSession:output_type -> openshell.v1.RevokeSshSessionResponse - 104, // 447: openshell.v1.OpenShell.ExecSandbox:output_type -> openshell.v1.ExecSandboxEvent - 106, // 448: openshell.v1.OpenShell.ForwardTcp:output_type -> openshell.v1.TcpForwardFrame - 104, // 449: openshell.v1.OpenShell.ExecSandboxInteractive:output_type -> openshell.v1.ExecSandboxEvent - 122, // 450: openshell.v1.OpenShell.CreateProvider:output_type -> openshell.v1.ProviderResponse - 122, // 451: openshell.v1.OpenShell.GetProvider:output_type -> openshell.v1.ProviderResponse - 118, // 452: openshell.v1.OpenShell.GetProviderCredentials:output_type -> openshell.v1.GetProviderCredentialsResponse - 123, // 453: openshell.v1.OpenShell.ListProviders:output_type -> openshell.v1.ListProvidersResponse - 148, // 454: openshell.v1.OpenShell.ListProviderProfiles:output_type -> openshell.v1.ListProviderProfilesResponse - 147, // 455: openshell.v1.OpenShell.GetProviderProfile:output_type -> openshell.v1.ProviderProfileResponse - 150, // 456: openshell.v1.OpenShell.ImportProviderProfiles:output_type -> openshell.v1.ImportProviderProfilesResponse - 152, // 457: openshell.v1.OpenShell.UpdateProviderProfiles:output_type -> openshell.v1.UpdateProviderProfilesResponse - 154, // 458: openshell.v1.OpenShell.LintProviderProfiles:output_type -> openshell.v1.LintProviderProfilesResponse - 122, // 459: openshell.v1.OpenShell.UpdateProvider:output_type -> openshell.v1.ProviderResponse - 138, // 460: openshell.v1.OpenShell.GetProviderRefreshStatus:output_type -> openshell.v1.GetProviderRefreshStatusResponse - 140, // 461: openshell.v1.OpenShell.ConfigureProviderRefresh:output_type -> openshell.v1.ConfigureProviderRefreshResponse - 142, // 462: openshell.v1.OpenShell.RotateProviderCredential:output_type -> openshell.v1.RotateProviderCredentialResponse - 144, // 463: openshell.v1.OpenShell.DeleteProviderRefresh:output_type -> openshell.v1.DeleteProviderRefreshResponse - 155, // 464: openshell.v1.OpenShell.DeleteProvider:output_type -> openshell.v1.DeleteProviderResponse - 157, // 465: openshell.v1.OpenShell.DeleteProviderProfile:output_type -> openshell.v1.DeleteProviderProfileResponse - 301, // 466: openshell.v1.OpenShell.GetSandboxConfig:output_type -> openshell.sandbox.v1.GetSandboxConfigResponse - 302, // 467: openshell.v1.OpenShell.GetGatewayConfig:output_type -> openshell.sandbox.v1.GetGatewayConfigResponse - 173, // 468: openshell.v1.OpenShell.UpdateConfig:output_type -> openshell.v1.UpdateConfigResponse - 175, // 469: openshell.v1.OpenShell.GetSandboxPolicyStatus:output_type -> openshell.v1.GetSandboxPolicyStatusResponse - 177, // 470: openshell.v1.OpenShell.ListSandboxPolicies:output_type -> openshell.v1.ListSandboxPoliciesResponse - 179, // 471: openshell.v1.OpenShell.ReportPolicyStatus:output_type -> openshell.v1.ReportPolicyStatusResponse - 255, // 472: openshell.v1.OpenShell.ReportEndpointStatus:output_type -> openshell.v1.ReportEndpointStatusResponse - 86, // 473: openshell.v1.OpenShell.ReportProviderReadiness:output_type -> openshell.v1.ReportProviderReadinessResponse - 182, // 474: openshell.v1.OpenShell.ReportSandboxConfiguration:output_type -> openshell.v1.ReportSandboxConfigurationResponse - 161, // 475: openshell.v1.OpenShell.GetSandboxProviderEnvironment:output_type -> openshell.v1.GetSandboxProviderEnvironmentResponse - 163, // 476: openshell.v1.OpenShell.ExchangeProviderSubjectToken:output_type -> openshell.v1.ExchangeProviderSubjectTokenResponse - 187, // 477: openshell.v1.OpenShell.GetSandboxLogs:output_type -> openshell.v1.GetSandboxLogsResponse - 186, // 478: openshell.v1.OpenShell.PushSandboxLogs:output_type -> openshell.v1.PushSandboxLogsResponse - 189, // 479: openshell.v1.OpenShell.ConnectSupervisor:output_type -> openshell.v1.GatewayMessage - 197, // 480: openshell.v1.OpenShell.ReportMainProcessExit:output_type -> openshell.v1.ReportMainProcessExitResponse - 199, // 481: openshell.v1.OpenShell.FinalizeMainProcessExit:output_type -> openshell.v1.FinalizeMainProcessExitResponse - 204, // 482: openshell.v1.OpenShell.RelayStream:output_type -> openshell.v1.RelayFrame - 206, // 483: openshell.v1.OpenShell.PeerRelay:output_type -> openshell.v1.PeerRelayFrame - 86, // 484: openshell.v1.OpenShell.PeerReportProviderReadiness:output_type -> openshell.v1.ReportProviderReadinessResponse - 255, // 485: openshell.v1.OpenShell.PeerReportEndpointStatus:output_type -> openshell.v1.ReportEndpointStatusResponse - 84, // 486: openshell.v1.OpenShell.PeerGetSandboxProviderStatus:output_type -> openshell.v1.GetSandboxProviderStatusResponse - 111, // 487: openshell.v1.OpenShell.WatchSandbox:output_type -> openshell.v1.SandboxStreamEvent - 216, // 488: openshell.v1.OpenShell.SubmitPolicyAnalysis:output_type -> openshell.v1.SubmitPolicyAnalysisResponse - 218, // 489: openshell.v1.OpenShell.GetDraftPolicy:output_type -> openshell.v1.GetDraftPolicyResponse - 220, // 490: openshell.v1.OpenShell.ApproveDraftChunk:output_type -> openshell.v1.ApproveDraftChunkResponse - 222, // 491: openshell.v1.OpenShell.RejectDraftChunk:output_type -> openshell.v1.RejectDraftChunkResponse - 225, // 492: openshell.v1.OpenShell.ApproveAllDraftChunks:output_type -> openshell.v1.ApproveAllDraftChunksResponse - 227, // 493: openshell.v1.OpenShell.EditDraftChunk:output_type -> openshell.v1.EditDraftChunkResponse - 229, // 494: openshell.v1.OpenShell.UndoDraftChunk:output_type -> openshell.v1.UndoDraftChunkResponse - 231, // 495: openshell.v1.OpenShell.ClearDraftChunks:output_type -> openshell.v1.ClearDraftChunksResponse - 234, // 496: openshell.v1.OpenShell.GetDraftHistory:output_type -> openshell.v1.GetDraftHistoryResponse - 22, // 497: openshell.v1.OpenShell.IssueSandboxToken:output_type -> openshell.v1.IssueSandboxTokenResponse - 24, // 498: openshell.v1.OpenShell.RefreshSandboxToken:output_type -> openshell.v1.RefreshSandboxTokenResponse - 236, // 499: openshell.v1.OpenShell.CreateWorkspace:output_type -> openshell.v1.CreateWorkspaceResponse - 238, // 500: openshell.v1.OpenShell.GetWorkspace:output_type -> openshell.v1.GetWorkspaceResponse - 240, // 501: openshell.v1.OpenShell.ListWorkspaces:output_type -> openshell.v1.ListWorkspacesResponse - 242, // 502: openshell.v1.OpenShell.DeleteWorkspace:output_type -> openshell.v1.DeleteWorkspaceResponse - 245, // 503: openshell.v1.OpenShell.AddWorkspaceMember:output_type -> openshell.v1.AddWorkspaceMemberResponse - 247, // 504: openshell.v1.OpenShell.RemoveWorkspaceMember:output_type -> openshell.v1.RemoveWorkspaceMemberResponse - 249, // 505: openshell.v1.OpenShell.ListWorkspaceMembers:output_type -> openshell.v1.ListWorkspaceMembersResponse - 423, // [423:506] is the sub-list for method output_type - 340, // [340:423] is the sub-list for method input_type - 340, // [340:340] is the sub-list for extension type_name - 340, // [340:340] is the sub-list for extension extendee - 0, // [0:340] is the sub-list for field type_name + 307, // 14: openshell.v1.Sandbox.metadata:type_name -> openshell.datamodel.v1.ObjectMeta + 40, // 15: openshell.v1.Sandbox.spec:type_name -> openshell.v1.SandboxSpec + 51, // 16: openshell.v1.Sandbox.status:type_name -> openshell.v1.SandboxStatus + 50, // 17: openshell.v1.Sandbox.created_from_workload_template:type_name -> openshell.v1.SandboxWorkloadTemplateProvenance + 306, // 18: openshell.v1.SandboxDelegatedIdentity.delegation_expiration_time:type_name -> google.protobuf.Timestamp + 306, // 19: openshell.v1.SandboxDelegatedIdentity.withdrawal_time:type_name -> google.protobuf.Timestamp + 282, // 20: openshell.v1.SandboxSpec.environment:type_name -> openshell.v1.SandboxSpec.EnvironmentEntry + 43, // 21: openshell.v1.SandboxSpec.template:type_name -> openshell.v1.SandboxTemplate + 308, // 22: openshell.v1.SandboxSpec.policy:type_name -> openshell.sandbox.v1.SandboxPolicy + 41, // 23: openshell.v1.SandboxSpec.resource_requirements:type_name -> openshell.v1.ResourceRequirements + 16, // 24: openshell.v1.SandboxSpec.restart_policy:type_name -> openshell.v1.SandboxRestartPolicy + 42, // 25: openshell.v1.ResourceRequirements.gpu:type_name -> openshell.v1.GpuResourceRequirements + 283, // 26: openshell.v1.SandboxTemplate.labels:type_name -> openshell.v1.SandboxTemplate.LabelsEntry + 284, // 27: openshell.v1.SandboxTemplate.annotations:type_name -> openshell.v1.SandboxTemplate.AnnotationsEntry + 285, // 28: openshell.v1.SandboxTemplate.environment:type_name -> openshell.v1.SandboxTemplate.EnvironmentEntry + 309, // 29: openshell.v1.SandboxTemplate.resources:type_name -> google.protobuf.Struct + 309, // 30: openshell.v1.SandboxTemplate.driver_config:type_name -> google.protobuf.Struct + 307, // 31: openshell.v1.SandboxWorkloadTemplate.metadata:type_name -> openshell.datamodel.v1.ObjectMeta + 45, // 32: openshell.v1.SandboxWorkloadTemplate.spec:type_name -> openshell.v1.SandboxWorkloadTemplateSpec + 46, // 33: openshell.v1.SandboxWorkloadTemplateSpec.workload:type_name -> openshell.v1.SandboxWorkloadConfig + 309, // 34: openshell.v1.SandboxWorkloadTemplateSpec.driver_config:type_name -> google.protobuf.Struct + 48, // 35: openshell.v1.SandboxWorkloadTemplateSpec.desired_service_level:type_name -> openshell.v1.SandboxServiceLevel + 286, // 36: openshell.v1.SandboxWorkloadConfig.environment:type_name -> openshell.v1.SandboxWorkloadConfig.EnvironmentEntry + 47, // 37: openshell.v1.SandboxWorkloadConfig.resources:type_name -> openshell.v1.SandboxResources + 42, // 38: openshell.v1.SandboxResources.gpu:type_name -> openshell.v1.GpuResourceRequirements + 49, // 39: openshell.v1.SandboxServiceLevel.startup:type_name -> openshell.v1.SandboxStartup + 310, // 40: openshell.v1.SandboxStartup.ready_within:type_name -> google.protobuf.Duration + 52, // 41: openshell.v1.SandboxStatus.conditions:type_name -> openshell.v1.SandboxCondition + 1, // 42: openshell.v1.SandboxStatus.phase:type_name -> openshell.v1.SandboxPhase + 278, // 43: openshell.v1.SandboxStatus.endpoint_statuses:type_name -> openshell.v1.EndpointStatus + 202, // 44: openshell.v1.SandboxStatus.configuration_admission:type_name -> openshell.v1.SandboxConfigurationAdmission + 279, // 45: openshell.v1.SandboxStatus.provisioning:type_name -> openshell.v1.SandboxProvisioning + 306, // 46: openshell.v1.SandboxStatus.next_restart_time:type_name -> google.protobuf.Timestamp + 306, // 47: openshell.v1.SandboxStatus.main_process_started_time:type_name -> google.protobuf.Timestamp + 276, // 48: openshell.v1.SandboxStatus.last_token_grant_failure:type_name -> openshell.v1.TokenGrantFailureObservation + 306, // 49: openshell.v1.SandboxCondition.transition_time:type_name -> google.protobuf.Timestamp + 306, // 50: openshell.v1.PlatformEvent.event_time:type_name -> google.protobuf.Timestamp + 287, // 51: openshell.v1.PlatformEvent.metadata:type_name -> openshell.v1.PlatformEvent.MetadataEntry + 311, // 52: openshell.v1.CreateSandboxRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 40, // 53: openshell.v1.CreateSandboxRequest.spec:type_name -> openshell.v1.SandboxSpec + 288, // 54: openshell.v1.CreateSandboxRequest.labels:type_name -> openshell.v1.CreateSandboxRequest.LabelsEntry + 289, // 55: openshell.v1.CreateSandboxRequest.annotations:type_name -> openshell.v1.CreateSandboxRequest.AnnotationsEntry + 280, // 56: openshell.v1.CreateSandboxRequest.service_exposures:type_name -> openshell.v1.SandboxServiceExposure + 55, // 57: openshell.v1.CreateSandboxRequest.delegated_identity:type_name -> openshell.v1.DelegatedIdentityRequest + 310, // 58: openshell.v1.DelegatedIdentityRequest.duration:type_name -> google.protobuf.Duration + 150, // 59: openshell.v1.GetDelegatedIdentityAuthorizationStatusResponse.credential:type_name -> openshell.v1.DelegatedIdentityCredentialSummary + 306, // 60: openshell.v1.GetDelegatedIdentityAuthorizationStatusResponse.current_time:type_name -> google.protobuf.Timestamp + 56, // 61: openshell.v1.AuthorizeDelegatedIdentityRequest.grant:type_name -> openshell.v1.DelegatedIdentityAuthorizationGrant + 150, // 62: openshell.v1.AuthorizeDelegatedIdentityResponse.credential:type_name -> openshell.v1.DelegatedIdentityCredentialSummary + 306, // 63: openshell.v1.AuthorizeDelegatedIdentityResponse.current_time:type_name -> google.protobuf.Timestamp + 311, // 64: openshell.v1.GetSandboxDelegatedIdentityStatusRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 39, // 65: openshell.v1.GetSandboxDelegatedIdentityStatusResponse.delegated_identity:type_name -> openshell.v1.SandboxDelegatedIdentity + 306, // 66: openshell.v1.GetSandboxDelegatedIdentityStatusResponse.current_time:type_name -> google.protobuf.Timestamp + 306, // 67: openshell.v1.GetSandboxDelegatedIdentityStatusResponse.credential_revocation_time:type_name -> google.protobuf.Timestamp + 311, // 68: openshell.v1.WithdrawSandboxDelegatedIdentityRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 38, // 69: openshell.v1.WithdrawSandboxDelegatedIdentityResponse.sandbox:type_name -> openshell.v1.Sandbox + 55, // 70: openshell.v1.ExtendSandboxDelegatedIdentityRequest.delegated_identity:type_name -> openshell.v1.DelegatedIdentityRequest + 311, // 71: openshell.v1.ExtendSandboxDelegatedIdentityRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 38, // 72: openshell.v1.ExtendSandboxDelegatedIdentityResponse.sandbox:type_name -> openshell.v1.Sandbox + 311, // 73: openshell.v1.CreateSandboxTemplateRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 44, // 74: openshell.v1.CreateSandboxTemplateRequest.template:type_name -> openshell.v1.SandboxWorkloadTemplate + 311, // 75: openshell.v1.GetSandboxTemplateRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 311, // 76: openshell.v1.ListSandboxTemplatesRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 311, // 77: openshell.v1.DeleteSandboxTemplateRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 44, // 78: openshell.v1.SandboxTemplateResponse.template:type_name -> openshell.v1.SandboxWorkloadTemplate + 44, // 79: openshell.v1.ListSandboxTemplatesResponse.templates:type_name -> openshell.v1.SandboxWorkloadTemplate + 17, // 80: openshell.v1.DeleteSandboxTemplateResponse.outcome:type_name -> openshell.v1.DeletionOutcome + 311, // 81: openshell.v1.BeginRootfsTarStagingRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 306, // 82: openshell.v1.BeginRootfsTarStagingResponse.expiration_time:type_name -> google.protobuf.Timestamp + 311, // 83: openshell.v1.GetSandboxRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 311, // 84: openshell.v1.ListSandboxesRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 311, // 85: openshell.v1.ListSandboxProvidersRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 311, // 86: openshell.v1.AttachSandboxProviderRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 311, // 87: openshell.v1.DetachSandboxProviderRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 311, // 88: openshell.v1.DeleteSandboxRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 311, // 89: openshell.v1.StopSandboxRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 311, // 90: openshell.v1.StartSandboxRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 38, // 91: openshell.v1.SandboxResponse.sandbox:type_name -> openshell.v1.Sandbox + 290, // 92: openshell.v1.SandboxResponse.service_urls:type_name -> openshell.v1.SandboxResponse.ServiceUrlsEntry + 38, // 93: openshell.v1.ListSandboxesResponse.sandboxes:type_name -> openshell.v1.Sandbox + 312, // 94: openshell.v1.ListSandboxProvidersResponse.providers:type_name -> openshell.datamodel.v1.Provider + 38, // 95: openshell.v1.AttachSandboxProviderResponse.sandbox:type_name -> openshell.v1.Sandbox + 93, // 96: openshell.v1.AttachSandboxProviderResponse.receipt:type_name -> openshell.v1.ProviderMutationReceipt + 38, // 97: openshell.v1.DetachSandboxProviderResponse.sandbox:type_name -> openshell.v1.Sandbox + 93, // 98: openshell.v1.DetachSandboxProviderResponse.receipt:type_name -> openshell.v1.ProviderMutationReceipt + 91, // 99: openshell.v1.ConfigSnapshotRevision.sandbox_config:type_name -> openshell.v1.SandboxConfigRevision + 89, // 100: openshell.v1.ConfigSnapshotRevision.provider_target:type_name -> openshell.v1.ProviderDesiredIdentity + 313, // 101: openshell.v1.SandboxConfigRevision.policy_source:type_name -> openshell.sandbox.v1.PolicySource + 5, // 102: openshell.v1.ConfigUpdateOperation.component:type_name -> openshell.v1.ConfigComponent + 90, // 103: openshell.v1.ConfigUpdateOperation.target_revision:type_name -> openshell.v1.ConfigSnapshotRevision + 7, // 104: openshell.v1.ConfigUpdateOperation.state:type_name -> openshell.v1.ConfigUpdateOperationState + 6, // 105: openshell.v1.ConfigUpdateOperation.outcome:type_name -> openshell.v1.ConfigApplyOutcome + 306, // 106: openshell.v1.ConfigUpdateOperation.created_time:type_name -> google.protobuf.Timestamp + 306, // 107: openshell.v1.ConfigUpdateOperation.updated_time:type_name -> google.protobuf.Timestamp + 306, // 108: openshell.v1.ConfigUpdateOperation.completed_time:type_name -> google.protobuf.Timestamp + 2, // 109: openshell.v1.ProviderMutationReceipt.kind:type_name -> openshell.v1.ProviderMutationKind + 89, // 110: openshell.v1.ProviderMutationReceipt.desired:type_name -> openshell.v1.ProviderDesiredIdentity + 306, // 111: openshell.v1.ProviderMutationReceipt.persisted_time:type_name -> google.protobuf.Timestamp + 4, // 112: openshell.v1.ProviderReadinessObservation.reason:type_name -> openshell.v1.ProviderReadinessReason + 93, // 113: openshell.v1.ProviderReadinessStatus.receipt:type_name -> openshell.v1.ProviderMutationReceipt + 3, // 114: openshell.v1.ProviderReadinessStatus.state:type_name -> openshell.v1.ProviderReadinessState + 4, // 115: openshell.v1.ProviderReadinessStatus.reason:type_name -> openshell.v1.ProviderReadinessReason + 94, // 116: openshell.v1.ProviderReadinessStatus.observed:type_name -> openshell.v1.ProviderReadinessObservation + 306, // 117: openshell.v1.ProviderReadinessStatus.observed_time:type_name -> google.protobuf.Timestamp + 306, // 118: openshell.v1.ProviderReadinessStatus.evaluated_time:type_name -> google.protobuf.Timestamp + 92, // 119: openshell.v1.ProviderReadinessStatus.operation:type_name -> openshell.v1.ConfigUpdateOperation + 311, // 120: openshell.v1.GetSandboxProviderStatusRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 95, // 121: openshell.v1.GetSandboxProviderStatusResponse.status:type_name -> openshell.v1.ProviderReadinessStatus + 94, // 122: openshell.v1.ReportProviderReadinessRequest.observation:type_name -> openshell.v1.ProviderReadinessObservation + 310, // 123: openshell.v1.ReportProviderReadinessResponse.report_interval:type_name -> google.protobuf.Duration + 310, // 124: openshell.v1.ReportProviderReadinessResponse.observation_ttl:type_name -> google.protobuf.Duration + 17, // 125: openshell.v1.DeleteSandboxResponse.outcome:type_name -> openshell.v1.DeletionOutcome + 311, // 126: openshell.v1.CreateSshSessionRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 306, // 127: openshell.v1.CreateSshSessionResponse.expiration_time:type_name -> google.protobuf.Timestamp + 311, // 128: openshell.v1.ExposeServiceRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 19, // 129: openshell.v1.ExposeServiceRequest.authorization_mode:type_name -> openshell.v1.ServiceAuthorizationMode + 311, // 130: openshell.v1.GetServiceRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 311, // 131: openshell.v1.ListServicesRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 110, // 132: openshell.v1.ListServicesResponse.services:type_name -> openshell.v1.ServiceEndpointResponse + 311, // 133: openshell.v1.DeleteServiceRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 17, // 134: openshell.v1.DeleteServiceResponse.outcome:type_name -> openshell.v1.DeletionOutcome + 307, // 135: openshell.v1.ServiceEndpoint.metadata:type_name -> openshell.datamodel.v1.ObjectMeta + 19, // 136: openshell.v1.ServiceEndpoint.authorization_mode:type_name -> openshell.v1.ServiceAuthorizationMode + 109, // 137: openshell.v1.ServiceEndpointResponse.endpoint:type_name -> openshell.v1.ServiceEndpoint + 17, // 138: openshell.v1.RevokeSshSessionResponse.outcome:type_name -> openshell.v1.DeletionOutcome + 311, // 139: openshell.v1.ExecSandboxRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 291, // 140: openshell.v1.ExecSandboxRequest.environment:type_name -> openshell.v1.ExecSandboxRequest.EnvironmentEntry + 310, // 141: openshell.v1.ExecSandboxRequest.execution_timeout:type_name -> google.protobuf.Duration + 114, // 142: openshell.v1.ExecSandboxEvent.stdout:type_name -> openshell.v1.ExecSandboxStdout + 115, // 143: openshell.v1.ExecSandboxEvent.stderr:type_name -> openshell.v1.ExecSandboxStderr + 116, // 144: openshell.v1.ExecSandboxEvent.exit:type_name -> openshell.v1.ExecSandboxExit + 223, // 145: openshell.v1.TcpForwardInit.ssh:type_name -> openshell.v1.SshRelayTarget + 224, // 146: openshell.v1.TcpForwardInit.tcp:type_name -> openshell.v1.TcpRelayTarget + 118, // 147: openshell.v1.TcpForwardFrame.init:type_name -> openshell.v1.TcpForwardInit + 113, // 148: openshell.v1.ExecSandboxInput.start:type_name -> openshell.v1.ExecSandboxRequest + 121, // 149: openshell.v1.ExecSandboxInput.resize:type_name -> openshell.v1.ExecSandboxWindowResize + 307, // 150: openshell.v1.SshSession.metadata:type_name -> openshell.datamodel.v1.ObjectMeta + 306, // 151: openshell.v1.SshSession.expiration_time:type_name -> google.protobuf.Timestamp + 311, // 152: openshell.v1.WatchSandboxRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 306, // 153: openshell.v1.WatchSandboxRequest.since_time:type_name -> google.protobuf.Timestamp + 38, // 154: openshell.v1.SandboxStreamEvent.sandbox:type_name -> openshell.v1.Sandbox + 125, // 155: openshell.v1.SandboxStreamEvent.log:type_name -> openshell.v1.SandboxLogLine + 53, // 156: openshell.v1.SandboxStreamEvent.event:type_name -> openshell.v1.PlatformEvent + 126, // 157: openshell.v1.SandboxStreamEvent.warning:type_name -> openshell.v1.SandboxStreamWarning + 236, // 158: openshell.v1.SandboxStreamEvent.draft_policy_update:type_name -> openshell.v1.DraftPolicyUpdate + 306, // 159: openshell.v1.SandboxLogLine.event_time:type_name -> google.protobuf.Timestamp + 292, // 160: openshell.v1.SandboxLogLine.fields:type_name -> openshell.v1.SandboxLogLine.FieldsEntry + 311, // 161: openshell.v1.CreateProviderRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 312, // 162: openshell.v1.CreateProviderRequest.provider:type_name -> openshell.datamodel.v1.Provider + 311, // 163: openshell.v1.GetProviderRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 311, // 164: openshell.v1.GetProviderCredentialsRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 310, // 165: openshell.v1.GetProviderCredentialsRequest.minimum_remaining_lifetime:type_name -> google.protobuf.Duration + 306, // 166: openshell.v1.ProviderCredentialValue.expiration_time:type_name -> google.protobuf.Timestamp + 293, // 167: openshell.v1.GetProviderCredentialsResponse.credentials:type_name -> openshell.v1.GetProviderCredentialsResponse.CredentialsEntry + 311, // 168: openshell.v1.ListProvidersRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 311, // 169: openshell.v1.UpdateProviderRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 312, // 170: openshell.v1.UpdateProviderRequest.provider:type_name -> openshell.datamodel.v1.Provider + 294, // 171: openshell.v1.UpdateProviderRequest.credential_expiration_times:type_name -> openshell.v1.UpdateProviderRequest.CredentialExpirationTimesEntry + 311, // 172: openshell.v1.DeleteProviderRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 312, // 173: openshell.v1.ProviderResponse.provider:type_name -> openshell.datamodel.v1.Provider + 93, // 174: openshell.v1.ProviderResponse.target_receipts:type_name -> openshell.v1.ProviderMutationReceipt + 312, // 175: openshell.v1.ListProvidersResponse.providers:type_name -> openshell.datamodel.v1.Provider + 311, // 176: openshell.v1.ListProviderProfilesRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 311, // 177: openshell.v1.GetProviderProfileRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 167, // 178: openshell.v1.ProviderProfileImportItem.profile:type_name -> openshell.v1.ProviderProfile + 310, // 179: openshell.v1.ProviderCredentialTokenGrant.cache_ttl:type_name -> google.protobuf.Duration + 141, // 180: openshell.v1.ProviderCredentialTokenGrant.audience_overrides:type_name -> openshell.v1.ProviderCredentialTokenGrantAudienceOverride + 8, // 181: openshell.v1.ProviderCredentialTokenGrant.grant_type:type_name -> openshell.v1.ProviderCredentialTokenGrantType + 142, // 182: openshell.v1.ProviderCredentialTokenGrant.subject_token:type_name -> openshell.v1.ProviderCredentialTokenGrantSubjectToken + 147, // 183: openshell.v1.ProviderProfileCredential.refresh:type_name -> openshell.v1.ProviderCredentialRefresh + 143, // 184: openshell.v1.ProviderProfileCredential.token_grant:type_name -> openshell.v1.ProviderCredentialTokenGrant + 9, // 185: openshell.v1.ProviderCredentialRefresh.strategy:type_name -> openshell.v1.ProviderCredentialRefreshStrategy + 310, // 186: openshell.v1.ProviderCredentialRefresh.refresh_before:type_name -> google.protobuf.Duration + 310, // 187: openshell.v1.ProviderCredentialRefresh.max_lifetime:type_name -> google.protobuf.Duration + 145, // 188: openshell.v1.ProviderCredentialRefresh.material:type_name -> openshell.v1.ProviderCredentialRefreshMaterial + 146, // 189: openshell.v1.ProviderCredentialRefresh.additional_outputs:type_name -> openshell.v1.ProviderCredentialRefreshOutput + 9, // 190: openshell.v1.ProviderCredentialRefreshStatus.strategy:type_name -> openshell.v1.ProviderCredentialRefreshStrategy + 306, // 191: openshell.v1.ProviderCredentialRefreshStatus.expiration_time:type_name -> google.protobuf.Timestamp + 306, // 192: openshell.v1.ProviderCredentialRefreshStatus.next_refresh_time:type_name -> google.protobuf.Timestamp + 306, // 193: openshell.v1.ProviderCredentialRefreshStatus.last_refresh_time:type_name -> google.protobuf.Timestamp + 15, // 194: openshell.v1.ProviderCredentialRefreshStatus.recovery_action:type_name -> openshell.v1.ProviderCredentialRefreshRecoveryAction + 306, // 195: openshell.v1.ProviderCredentialRefreshStatus.last_error_time:type_name -> google.protobuf.Timestamp + 307, // 196: openshell.v1.DelegatedIdentityCredentialSummary.metadata:type_name -> openshell.datamodel.v1.ObjectMeta + 306, // 197: openshell.v1.DelegatedIdentityCredentialSummary.access_token_expiration_time:type_name -> google.protobuf.Timestamp + 306, // 198: openshell.v1.DelegatedIdentityCredentialSummary.last_refresh_time:type_name -> google.protobuf.Timestamp + 306, // 199: openshell.v1.DelegatedIdentityCredentialSummary.revocation_time:type_name -> google.protobuf.Timestamp + 150, // 200: openshell.v1.ListDelegatedIdentityCredentialsResponse.credentials:type_name -> openshell.v1.DelegatedIdentityCredentialSummary + 150, // 201: openshell.v1.GetDelegatedIdentityCredentialStatusResponse.credential:type_name -> openshell.v1.DelegatedIdentityCredentialSummary + 306, // 202: openshell.v1.GetDelegatedIdentityCredentialStatusResponse.current_time:type_name -> google.protobuf.Timestamp + 306, // 203: openshell.v1.RevokeDelegatedIdentityCredentialResponse.revocation_time:type_name -> google.protobuf.Timestamp + 311, // 204: openshell.v1.GetProviderRefreshStatusRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 148, // 205: openshell.v1.GetProviderRefreshStatusResponse.credentials:type_name -> openshell.v1.ProviderCredentialRefreshStatus + 311, // 206: openshell.v1.ConfigureProviderRefreshRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 9, // 207: openshell.v1.ConfigureProviderRefreshRequest.strategy:type_name -> openshell.v1.ProviderCredentialRefreshStrategy + 295, // 208: openshell.v1.ConfigureProviderRefreshRequest.material:type_name -> openshell.v1.ConfigureProviderRefreshRequest.MaterialEntry + 306, // 209: openshell.v1.ConfigureProviderRefreshRequest.expiration_time:type_name -> google.protobuf.Timestamp + 148, // 210: openshell.v1.ConfigureProviderRefreshResponse.status:type_name -> openshell.v1.ProviderCredentialRefreshStatus + 311, // 211: openshell.v1.RotateProviderCredentialRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 148, // 212: openshell.v1.RotateProviderCredentialResponse.status:type_name -> openshell.v1.ProviderCredentialRefreshStatus + 311, // 213: openshell.v1.DeleteProviderRefreshRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 17, // 214: openshell.v1.DeleteProviderRefreshResponse.outcome:type_name -> openshell.v1.DeletionOutcome + 10, // 215: openshell.v1.ProviderProfile.category:type_name -> openshell.v1.ProviderProfileCategory + 144, // 216: openshell.v1.ProviderProfile.credentials:type_name -> openshell.v1.ProviderProfileCredential + 314, // 217: openshell.v1.ProviderProfile.endpoints:type_name -> openshell.sandbox.v1.NetworkEndpoint + 315, // 218: openshell.v1.ProviderProfile.binaries:type_name -> openshell.sandbox.v1.NetworkBinary + 149, // 219: openshell.v1.ProviderProfile.discovery:type_name -> openshell.v1.ProviderProfileDiscovery + 296, // 220: openshell.v1.ProviderProfile.annotations:type_name -> openshell.v1.ProviderProfile.AnnotationsEntry + 168, // 221: openshell.v1.ProviderProfile.files:type_name -> openshell.v1.ProviderProfileFile + 167, // 222: openshell.v1.ProviderProfileResponse.profile:type_name -> openshell.v1.ProviderProfile + 167, // 223: openshell.v1.ListProviderProfilesResponse.profiles:type_name -> openshell.v1.ProviderProfile + 311, // 224: openshell.v1.ImportProviderProfilesRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 139, // 225: openshell.v1.ImportProviderProfilesRequest.profiles:type_name -> openshell.v1.ProviderProfileImportItem + 140, // 226: openshell.v1.ImportProviderProfilesResponse.diagnostics:type_name -> openshell.v1.ProviderProfileDiagnostic + 167, // 227: openshell.v1.ImportProviderProfilesResponse.profiles:type_name -> openshell.v1.ProviderProfile + 311, // 228: openshell.v1.UpdateProviderProfilesRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 139, // 229: openshell.v1.UpdateProviderProfilesRequest.profile:type_name -> openshell.v1.ProviderProfileImportItem + 140, // 230: openshell.v1.UpdateProviderProfilesResponse.diagnostics:type_name -> openshell.v1.ProviderProfileDiagnostic + 167, // 231: openshell.v1.UpdateProviderProfilesResponse.profile:type_name -> openshell.v1.ProviderProfile + 311, // 232: openshell.v1.LintProviderProfilesRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 139, // 233: openshell.v1.LintProviderProfilesRequest.profiles:type_name -> openshell.v1.ProviderProfileImportItem + 140, // 234: openshell.v1.LintProviderProfilesResponse.diagnostics:type_name -> openshell.v1.ProviderProfileDiagnostic + 17, // 235: openshell.v1.DeleteProviderResponse.outcome:type_name -> openshell.v1.DeletionOutcome + 311, // 236: openshell.v1.DeleteProviderProfileRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 17, // 237: openshell.v1.DeleteProviderProfileResponse.outcome:type_name -> openshell.v1.DeletionOutcome + 181, // 238: openshell.v1.StaticCredentialBinding.endpoints:type_name -> openshell.v1.StaticCredentialEndpointBinding + 297, // 239: openshell.v1.GetSandboxProviderEnvironmentResponse.environment:type_name -> openshell.v1.GetSandboxProviderEnvironmentResponse.EnvironmentEntry + 298, // 240: openshell.v1.GetSandboxProviderEnvironmentResponse.credential_expiration_times:type_name -> openshell.v1.GetSandboxProviderEnvironmentResponse.CredentialExpirationTimesEntry + 299, // 241: openshell.v1.GetSandboxProviderEnvironmentResponse.dynamic_credentials:type_name -> openshell.v1.GetSandboxProviderEnvironmentResponse.DynamicCredentialsEntry + 300, // 242: openshell.v1.GetSandboxProviderEnvironmentResponse.static_credential_bindings:type_name -> openshell.v1.GetSandboxProviderEnvironmentResponse.StaticCredentialBindingsEntry + 4, // 243: openshell.v1.GetSandboxProviderEnvironmentResponse.readiness_reason:type_name -> openshell.v1.ProviderReadinessReason + 301, // 244: openshell.v1.GetSandboxProviderEnvironmentResponse.files:type_name -> openshell.v1.GetSandboxProviderEnvironmentResponse.FilesEntry + 310, // 245: openshell.v1.ExchangeProviderSubjectTokenResponse.expires_after:type_name -> google.protobuf.Duration + 311, // 246: openshell.v1.UpdateConfigRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 308, // 247: openshell.v1.UpdateConfigRequest.policy:type_name -> openshell.sandbox.v1.SandboxPolicy + 316, // 248: openshell.v1.UpdateConfigRequest.setting_value:type_name -> openshell.sandbox.v1.SettingValue + 187, // 249: openshell.v1.UpdateConfigRequest.merge_operations:type_name -> openshell.v1.PolicyMergeOperation + 302, // 250: openshell.v1.UpdateConfigRequest.annotations:type_name -> openshell.v1.UpdateConfigRequest.AnnotationsEntry + 188, // 251: openshell.v1.PolicyMergeOperation.add_rule:type_name -> openshell.v1.AddNetworkRule + 189, // 252: openshell.v1.PolicyMergeOperation.remove_endpoint:type_name -> openshell.v1.RemoveNetworkEndpoint + 190, // 253: openshell.v1.PolicyMergeOperation.remove_rule:type_name -> openshell.v1.RemoveNetworkRule + 192, // 254: openshell.v1.PolicyMergeOperation.add_deny_rules:type_name -> openshell.v1.AddDenyRules + 193, // 255: openshell.v1.PolicyMergeOperation.add_allow_rules:type_name -> openshell.v1.AddAllowRules + 194, // 256: openshell.v1.PolicyMergeOperation.remove_binary:type_name -> openshell.v1.RemoveNetworkBinary + 317, // 257: openshell.v1.AddNetworkRule.rule:type_name -> openshell.sandbox.v1.NetworkPolicyRule + 315, // 258: openshell.v1.L7RuleTarget.binaries:type_name -> openshell.sandbox.v1.NetworkBinary + 318, // 259: openshell.v1.AddDenyRules.deny_rules:type_name -> openshell.sandbox.v1.L7DenyRule + 191, // 260: openshell.v1.AddDenyRules.target:type_name -> openshell.v1.L7RuleTarget + 319, // 261: openshell.v1.AddAllowRules.rules:type_name -> openshell.sandbox.v1.L7Rule + 191, // 262: openshell.v1.AddAllowRules.target:type_name -> openshell.v1.L7RuleTarget + 303, // 263: openshell.v1.UpdateConfigResponse.annotations:type_name -> openshell.v1.UpdateConfigResponse.AnnotationsEntry + 311, // 264: openshell.v1.GetSandboxPolicyStatusRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 205, // 265: openshell.v1.GetSandboxPolicyStatusResponse.revision:type_name -> openshell.v1.SandboxPolicyRevision + 311, // 266: openshell.v1.ListSandboxPoliciesRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 205, // 267: openshell.v1.ListSandboxPoliciesResponse.revisions:type_name -> openshell.v1.SandboxPolicyRevision + 12, // 268: openshell.v1.ReportPolicyStatusRequest.status:type_name -> openshell.v1.PolicyStatus + 11, // 269: openshell.v1.SandboxConfigurationAdmission.state:type_name -> openshell.v1.ConfigurationAdmissionState + 202, // 270: openshell.v1.ReportSandboxConfigurationRequest.admission:type_name -> openshell.v1.SandboxConfigurationAdmission + 12, // 271: openshell.v1.SandboxPolicyRevision.status:type_name -> openshell.v1.PolicyStatus + 306, // 272: openshell.v1.SandboxPolicyRevision.created_time:type_name -> google.protobuf.Timestamp + 306, // 273: openshell.v1.SandboxPolicyRevision.loaded_time:type_name -> google.protobuf.Timestamp + 308, // 274: openshell.v1.SandboxPolicyRevision.policy:type_name -> openshell.sandbox.v1.SandboxPolicy + 304, // 275: openshell.v1.SandboxPolicyRevision.provenance:type_name -> openshell.v1.SandboxPolicyRevision.ProvenanceEntry + 311, // 276: openshell.v1.GetSandboxLogsRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 306, // 277: openshell.v1.GetSandboxLogsRequest.since_time:type_name -> google.protobuf.Timestamp + 125, // 278: openshell.v1.PushSandboxLogsRequest.logs:type_name -> openshell.v1.SandboxLogLine + 125, // 279: openshell.v1.GetSandboxLogsResponse.logs:type_name -> openshell.v1.SandboxLogLine + 212, // 280: openshell.v1.SupervisorMessage.hello:type_name -> openshell.v1.SupervisorHello + 216, // 281: openshell.v1.SupervisorMessage.heartbeat:type_name -> openshell.v1.SupervisorHeartbeat + 229, // 282: openshell.v1.SupervisorMessage.relay_open_result:type_name -> openshell.v1.RelayOpenResult + 230, // 283: openshell.v1.SupervisorMessage.relay_close:type_name -> openshell.v1.RelayClose + 213, // 284: openshell.v1.GatewayMessage.session_accepted:type_name -> openshell.v1.SessionAccepted + 214, // 285: openshell.v1.GatewayMessage.session_rejected:type_name -> openshell.v1.SessionRejected + 217, // 286: openshell.v1.GatewayMessage.heartbeat:type_name -> openshell.v1.GatewayHeartbeat + 222, // 287: openshell.v1.GatewayMessage.relay_open:type_name -> openshell.v1.RelayOpen + 230, // 288: openshell.v1.GatewayMessage.relay_close:type_name -> openshell.v1.RelayClose + 215, // 289: openshell.v1.GatewayMessage.session_redirect:type_name -> openshell.v1.SessionRedirect + 310, // 290: openshell.v1.SessionAccepted.heartbeat_interval:type_name -> google.protobuf.Duration + 223, // 291: openshell.v1.RelayOpen.ssh:type_name -> openshell.v1.SshRelayTarget + 224, // 292: openshell.v1.RelayOpen.tcp:type_name -> openshell.v1.TcpRelayTarget + 225, // 293: openshell.v1.RelayFrame.init:type_name -> openshell.v1.RelayInit + 222, // 294: openshell.v1.PeerRelayInit.relay_open:type_name -> openshell.v1.RelayOpen + 227, // 295: openshell.v1.PeerRelayFrame.init:type_name -> openshell.v1.PeerRelayInit + 306, // 296: openshell.v1.DenialSummary.first_seen_time:type_name -> google.protobuf.Timestamp + 306, // 297: openshell.v1.DenialSummary.last_seen_time:type_name -> google.protobuf.Timestamp + 231, // 298: openshell.v1.DenialSummary.l7_request_samples:type_name -> openshell.v1.L7RequestSample + 233, // 299: openshell.v1.NetworkActivitySummary.denials_by_group:type_name -> openshell.v1.DenialGroupCount + 317, // 300: openshell.v1.PolicyChunk.proposed_rule:type_name -> openshell.sandbox.v1.NetworkPolicyRule + 306, // 301: openshell.v1.PolicyChunk.created_time:type_name -> google.protobuf.Timestamp + 306, // 302: openshell.v1.PolicyChunk.decided_time:type_name -> google.protobuf.Timestamp + 306, // 303: openshell.v1.PolicyChunk.first_seen_time:type_name -> google.protobuf.Timestamp + 306, // 304: openshell.v1.PolicyChunk.last_seen_time:type_name -> google.protobuf.Timestamp + 308, // 305: openshell.v1.PolicyChunk.current_effective_policy:type_name -> openshell.sandbox.v1.SandboxPolicy + 308, // 306: openshell.v1.PolicyChunk.candidate_effective_policy:type_name -> openshell.sandbox.v1.SandboxPolicy + 311, // 307: openshell.v1.SubmitPolicyAnalysisRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 232, // 308: openshell.v1.SubmitPolicyAnalysisRequest.summaries:type_name -> openshell.v1.DenialSummary + 235, // 309: openshell.v1.SubmitPolicyAnalysisRequest.proposed_chunks:type_name -> openshell.v1.PolicyChunk + 234, // 310: openshell.v1.SubmitPolicyAnalysisRequest.network_activity_summaries:type_name -> openshell.v1.NetworkActivitySummary + 311, // 311: openshell.v1.GetDraftPolicyRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 235, // 312: openshell.v1.GetDraftPolicyResponse.chunks:type_name -> openshell.v1.PolicyChunk + 306, // 313: openshell.v1.GetDraftPolicyResponse.last_analyzed_time:type_name -> google.protobuf.Timestamp + 311, // 314: openshell.v1.ApproveDraftChunkRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 311, // 315: openshell.v1.RejectDraftChunkRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 311, // 316: openshell.v1.ApproveAllDraftChunksRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 245, // 317: openshell.v1.ApproveAllDraftChunksRequest.approvals:type_name -> openshell.v1.DraftChunkApproval + 311, // 318: openshell.v1.EditDraftChunkRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 317, // 319: openshell.v1.EditDraftChunkRequest.proposed_rule:type_name -> openshell.sandbox.v1.NetworkPolicyRule + 311, // 320: openshell.v1.UndoDraftChunkRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 311, // 321: openshell.v1.ClearDraftChunksRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 311, // 322: openshell.v1.GetDraftHistoryRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 306, // 323: openshell.v1.DraftHistoryEntry.event_time:type_name -> google.protobuf.Timestamp + 255, // 324: openshell.v1.GetDraftHistoryResponse.entries:type_name -> openshell.v1.DraftHistoryEntry + 305, // 325: openshell.v1.CreateWorkspaceRequest.labels:type_name -> openshell.v1.CreateWorkspaceRequest.LabelsEntry + 320, // 326: openshell.v1.CreateWorkspaceResponse.workspace:type_name -> openshell.datamodel.v1.Workspace + 320, // 327: openshell.v1.GetWorkspaceResponse.workspace:type_name -> openshell.datamodel.v1.Workspace + 320, // 328: openshell.v1.ListWorkspacesResponse.workspaces:type_name -> openshell.datamodel.v1.Workspace + 17, // 329: openshell.v1.DeleteWorkspaceResponse.outcome:type_name -> openshell.v1.DeletionOutcome + 307, // 330: openshell.v1.WorkspaceMember.metadata:type_name -> openshell.datamodel.v1.ObjectMeta + 14, // 331: openshell.v1.WorkspaceMember.role:type_name -> openshell.v1.WorkspaceRole + 311, // 332: openshell.v1.AddWorkspaceMemberRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 14, // 333: openshell.v1.AddWorkspaceMemberRequest.role:type_name -> openshell.v1.WorkspaceRole + 265, // 334: openshell.v1.AddWorkspaceMemberResponse.member:type_name -> openshell.v1.WorkspaceMember + 311, // 335: openshell.v1.RemoveWorkspaceMemberRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 17, // 336: openshell.v1.RemoveWorkspaceMemberResponse.outcome:type_name -> openshell.v1.DeletionOutcome + 311, // 337: openshell.v1.ListWorkspaceMembersRequest.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector + 265, // 338: openshell.v1.ListWorkspaceMembersResponse.members:type_name -> openshell.v1.WorkspaceMember + 306, // 339: openshell.v1.ExtensionServiceCredential.expiration_time:type_name -> google.protobuf.Timestamp + 18, // 340: openshell.v1.EndpointObservation.result:type_name -> openshell.v1.EndpointResult + 281, // 341: openshell.v1.EndpointObservation.failure:type_name -> openshell.v1.EndpointFailure + 273, // 342: openshell.v1.ReportEndpointStatusRequest.observations:type_name -> openshell.v1.EndpointObservation + 275, // 343: openshell.v1.ReportEndpointStatusRequest.token_grant_status:type_name -> openshell.v1.TokenGrantStatus + 276, // 344: openshell.v1.TokenGrantStatus.last_failure:type_name -> openshell.v1.TokenGrantFailureObservation + 281, // 345: openshell.v1.TokenGrantFailureObservation.failure:type_name -> openshell.v1.EndpointFailure + 18, // 346: openshell.v1.EndpointStatus.last_result:type_name -> openshell.v1.EndpointResult + 306, // 347: openshell.v1.EndpointStatus.last_reported_time:type_name -> google.protobuf.Timestamp + 281, // 348: openshell.v1.EndpointStatus.last_failure:type_name -> openshell.v1.EndpointFailure + 306, // 349: openshell.v1.SandboxProvisioning.configuration_change_time:type_name -> google.protobuf.Timestamp + 306, // 350: openshell.v1.SandboxProvisioning.first_rejection_time:type_name -> google.protobuf.Timestamp + 306, // 351: openshell.v1.SandboxProvisioning.deadline:type_name -> google.protobuf.Timestamp + 306, // 352: openshell.v1.SandboxProvisioning.timeout_time:type_name -> google.protobuf.Timestamp + 306, // 353: openshell.v1.SandboxProvisioning.cleanup_completed_time:type_name -> google.protobuf.Timestamp + 306, // 354: openshell.v1.SandboxProvisioning.cleanup_retry_time:type_name -> google.protobuf.Timestamp + 306, // 355: openshell.v1.SandboxProvisioning.attachment_change_time:type_name -> google.protobuf.Timestamp + 306, // 356: openshell.v1.SandboxProvisioning.preparation_deadline:type_name -> google.protobuf.Timestamp + 306, // 357: openshell.v1.SandboxProvisioning.admission_start_time:type_name -> google.protobuf.Timestamp + 19, // 358: openshell.v1.SandboxServiceExposure.authorization_mode:type_name -> openshell.v1.ServiceAuthorizationMode + 20, // 359: openshell.v1.EndpointFailure.domain:type_name -> openshell.v1.EndpointFailureDomain + 15, // 360: openshell.v1.EndpointFailure.recovery_action:type_name -> openshell.v1.ProviderCredentialRefreshRecoveryAction + 130, // 361: openshell.v1.GetProviderCredentialsResponse.CredentialsEntry.value:type_name -> openshell.v1.ProviderCredentialValue + 306, // 362: openshell.v1.UpdateProviderRequest.CredentialExpirationTimesEntry.value:type_name -> google.protobuf.Timestamp + 306, // 363: openshell.v1.GetSandboxProviderEnvironmentResponse.CredentialExpirationTimesEntry.value:type_name -> google.protobuf.Timestamp + 144, // 364: openshell.v1.GetSandboxProviderEnvironmentResponse.DynamicCredentialsEntry.value:type_name -> openshell.v1.ProviderProfileCredential + 182, // 365: openshell.v1.GetSandboxProviderEnvironmentResponse.StaticCredentialBindingsEntry.value:type_name -> openshell.v1.StaticCredentialBinding + 25, // 366: openshell.v1.OpenShell.Health:input_type -> openshell.v1.HealthRequest + 27, // 367: openshell.v1.OpenShell.GetCurrentUser:input_type -> openshell.v1.GetCurrentUserRequest + 29, // 368: openshell.v1.OpenShell.GetGatewayInfo:input_type -> openshell.v1.GetGatewayInfoRequest + 54, // 369: openshell.v1.OpenShell.CreateSandbox:input_type -> openshell.v1.CreateSandboxRequest + 74, // 370: openshell.v1.OpenShell.BeginRootfsTarStaging:input_type -> openshell.v1.BeginRootfsTarStagingRequest + 57, // 371: openshell.v1.OpenShell.GetDelegatedIdentityAuthorizationStatus:input_type -> openshell.v1.GetDelegatedIdentityAuthorizationStatusRequest + 59, // 372: openshell.v1.OpenShell.AuthorizeDelegatedIdentity:input_type -> openshell.v1.AuthorizeDelegatedIdentityRequest + 61, // 373: openshell.v1.OpenShell.GetSandboxDelegatedIdentityStatus:input_type -> openshell.v1.GetSandboxDelegatedIdentityStatusRequest + 63, // 374: openshell.v1.OpenShell.WithdrawSandboxDelegatedIdentity:input_type -> openshell.v1.WithdrawSandboxDelegatedIdentityRequest + 65, // 375: openshell.v1.OpenShell.ExtendSandboxDelegatedIdentity:input_type -> openshell.v1.ExtendSandboxDelegatedIdentityRequest + 76, // 376: openshell.v1.OpenShell.GetSandbox:input_type -> openshell.v1.GetSandboxRequest + 77, // 377: openshell.v1.OpenShell.ListSandboxes:input_type -> openshell.v1.ListSandboxesRequest + 67, // 378: openshell.v1.OpenShell.CreateSandboxTemplate:input_type -> openshell.v1.CreateSandboxTemplateRequest + 68, // 379: openshell.v1.OpenShell.GetSandboxTemplate:input_type -> openshell.v1.GetSandboxTemplateRequest + 69, // 380: openshell.v1.OpenShell.ListSandboxTemplates:input_type -> openshell.v1.ListSandboxTemplatesRequest + 70, // 381: openshell.v1.OpenShell.DeleteSandboxTemplate:input_type -> openshell.v1.DeleteSandboxTemplateRequest + 78, // 382: openshell.v1.OpenShell.ListSandboxProviders:input_type -> openshell.v1.ListSandboxProvidersRequest + 79, // 383: openshell.v1.OpenShell.AttachSandboxProvider:input_type -> openshell.v1.AttachSandboxProviderRequest + 80, // 384: openshell.v1.OpenShell.DetachSandboxProvider:input_type -> openshell.v1.DetachSandboxProviderRequest + 96, // 385: openshell.v1.OpenShell.GetSandboxProviderStatus:input_type -> openshell.v1.GetSandboxProviderStatusRequest + 81, // 386: openshell.v1.OpenShell.DeleteSandbox:input_type -> openshell.v1.DeleteSandboxRequest + 82, // 387: openshell.v1.OpenShell.StopSandbox:input_type -> openshell.v1.StopSandboxRequest + 83, // 388: openshell.v1.OpenShell.StartSandbox:input_type -> openshell.v1.StartSandboxRequest + 101, // 389: openshell.v1.OpenShell.CreateSshSession:input_type -> openshell.v1.CreateSshSessionRequest + 103, // 390: openshell.v1.OpenShell.ExposeService:input_type -> openshell.v1.ExposeServiceRequest + 104, // 391: openshell.v1.OpenShell.GetService:input_type -> openshell.v1.GetServiceRequest + 105, // 392: openshell.v1.OpenShell.ListServices:input_type -> openshell.v1.ListServicesRequest + 107, // 393: openshell.v1.OpenShell.DeleteService:input_type -> openshell.v1.DeleteServiceRequest + 111, // 394: openshell.v1.OpenShell.RevokeSshSession:input_type -> openshell.v1.RevokeSshSessionRequest + 113, // 395: openshell.v1.OpenShell.ExecSandbox:input_type -> openshell.v1.ExecSandboxRequest + 119, // 396: openshell.v1.OpenShell.ForwardTcp:input_type -> openshell.v1.TcpForwardFrame + 120, // 397: openshell.v1.OpenShell.ExecSandboxInteractive:input_type -> openshell.v1.ExecSandboxInput + 127, // 398: openshell.v1.OpenShell.CreateProvider:input_type -> openshell.v1.CreateProviderRequest + 128, // 399: openshell.v1.OpenShell.GetProvider:input_type -> openshell.v1.GetProviderRequest + 129, // 400: openshell.v1.OpenShell.GetProviderCredentials:input_type -> openshell.v1.GetProviderCredentialsRequest + 132, // 401: openshell.v1.OpenShell.ListProviders:input_type -> openshell.v1.ListProvidersRequest + 137, // 402: openshell.v1.OpenShell.ListProviderProfiles:input_type -> openshell.v1.ListProviderProfilesRequest + 138, // 403: openshell.v1.OpenShell.GetProviderProfile:input_type -> openshell.v1.GetProviderProfileRequest + 171, // 404: openshell.v1.OpenShell.ImportProviderProfiles:input_type -> openshell.v1.ImportProviderProfilesRequest + 173, // 405: openshell.v1.OpenShell.UpdateProviderProfiles:input_type -> openshell.v1.UpdateProviderProfilesRequest + 175, // 406: openshell.v1.OpenShell.LintProviderProfiles:input_type -> openshell.v1.LintProviderProfilesRequest + 133, // 407: openshell.v1.OpenShell.UpdateProvider:input_type -> openshell.v1.UpdateProviderRequest + 159, // 408: openshell.v1.OpenShell.GetProviderRefreshStatus:input_type -> openshell.v1.GetProviderRefreshStatusRequest + 161, // 409: openshell.v1.OpenShell.ConfigureProviderRefresh:input_type -> openshell.v1.ConfigureProviderRefreshRequest + 163, // 410: openshell.v1.OpenShell.RotateProviderCredential:input_type -> openshell.v1.RotateProviderCredentialRequest + 151, // 411: openshell.v1.OpenShell.ListDelegatedIdentityCredentials:input_type -> openshell.v1.ListDelegatedIdentityCredentialsRequest + 153, // 412: openshell.v1.OpenShell.GetDelegatedIdentityCredentialStatus:input_type -> openshell.v1.GetDelegatedIdentityCredentialStatusRequest + 155, // 413: openshell.v1.OpenShell.RevokeDelegatedIdentityCredential:input_type -> openshell.v1.RevokeDelegatedIdentityCredentialRequest + 157, // 414: openshell.v1.OpenShell.DeleteDelegatedIdentityCredential:input_type -> openshell.v1.DeleteDelegatedIdentityCredentialRequest + 165, // 415: openshell.v1.OpenShell.DeleteProviderRefresh:input_type -> openshell.v1.DeleteProviderRefreshRequest + 134, // 416: openshell.v1.OpenShell.DeleteProvider:input_type -> openshell.v1.DeleteProviderRequest + 178, // 417: openshell.v1.OpenShell.DeleteProviderProfile:input_type -> openshell.v1.DeleteProviderProfileRequest + 321, // 418: openshell.v1.OpenShell.GetSandboxConfig:input_type -> openshell.sandbox.v1.GetSandboxConfigRequest + 322, // 419: openshell.v1.OpenShell.GetGatewayConfig:input_type -> openshell.sandbox.v1.GetGatewayConfigRequest + 186, // 420: openshell.v1.OpenShell.UpdateConfig:input_type -> openshell.v1.UpdateConfigRequest + 196, // 421: openshell.v1.OpenShell.GetSandboxPolicyStatus:input_type -> openshell.v1.GetSandboxPolicyStatusRequest + 198, // 422: openshell.v1.OpenShell.ListSandboxPolicies:input_type -> openshell.v1.ListSandboxPoliciesRequest + 200, // 423: openshell.v1.OpenShell.ReportPolicyStatus:input_type -> openshell.v1.ReportPolicyStatusRequest + 274, // 424: openshell.v1.OpenShell.ReportEndpointStatus:input_type -> openshell.v1.ReportEndpointStatusRequest + 98, // 425: openshell.v1.OpenShell.ReportProviderReadiness:input_type -> openshell.v1.ReportProviderReadinessRequest + 203, // 426: openshell.v1.OpenShell.ReportSandboxConfiguration:input_type -> openshell.v1.ReportSandboxConfigurationRequest + 180, // 427: openshell.v1.OpenShell.GetSandboxProviderEnvironment:input_type -> openshell.v1.GetSandboxProviderEnvironmentRequest + 184, // 428: openshell.v1.OpenShell.ExchangeProviderSubjectToken:input_type -> openshell.v1.ExchangeProviderSubjectTokenRequest + 206, // 429: openshell.v1.OpenShell.GetSandboxLogs:input_type -> openshell.v1.GetSandboxLogsRequest + 207, // 430: openshell.v1.OpenShell.PushSandboxLogs:input_type -> openshell.v1.PushSandboxLogsRequest + 210, // 431: openshell.v1.OpenShell.ConnectSupervisor:input_type -> openshell.v1.SupervisorMessage + 218, // 432: openshell.v1.OpenShell.ReportMainProcessExit:input_type -> openshell.v1.ReportMainProcessExitRequest + 220, // 433: openshell.v1.OpenShell.FinalizeMainProcessExit:input_type -> openshell.v1.FinalizeMainProcessExitRequest + 226, // 434: openshell.v1.OpenShell.RelayStream:input_type -> openshell.v1.RelayFrame + 228, // 435: openshell.v1.OpenShell.PeerRelay:input_type -> openshell.v1.PeerRelayFrame + 98, // 436: openshell.v1.OpenShell.PeerReportProviderReadiness:input_type -> openshell.v1.ReportProviderReadinessRequest + 274, // 437: openshell.v1.OpenShell.PeerReportEndpointStatus:input_type -> openshell.v1.ReportEndpointStatusRequest + 96, // 438: openshell.v1.OpenShell.PeerGetSandboxProviderStatus:input_type -> openshell.v1.GetSandboxProviderStatusRequest + 123, // 439: openshell.v1.OpenShell.WatchSandbox:input_type -> openshell.v1.WatchSandboxRequest + 237, // 440: openshell.v1.OpenShell.SubmitPolicyAnalysis:input_type -> openshell.v1.SubmitPolicyAnalysisRequest + 239, // 441: openshell.v1.OpenShell.GetDraftPolicy:input_type -> openshell.v1.GetDraftPolicyRequest + 241, // 442: openshell.v1.OpenShell.ApproveDraftChunk:input_type -> openshell.v1.ApproveDraftChunkRequest + 243, // 443: openshell.v1.OpenShell.RejectDraftChunk:input_type -> openshell.v1.RejectDraftChunkRequest + 246, // 444: openshell.v1.OpenShell.ApproveAllDraftChunks:input_type -> openshell.v1.ApproveAllDraftChunksRequest + 248, // 445: openshell.v1.OpenShell.EditDraftChunk:input_type -> openshell.v1.EditDraftChunkRequest + 250, // 446: openshell.v1.OpenShell.UndoDraftChunk:input_type -> openshell.v1.UndoDraftChunkRequest + 252, // 447: openshell.v1.OpenShell.ClearDraftChunks:input_type -> openshell.v1.ClearDraftChunksRequest + 254, // 448: openshell.v1.OpenShell.GetDraftHistory:input_type -> openshell.v1.GetDraftHistoryRequest + 21, // 449: openshell.v1.OpenShell.IssueSandboxToken:input_type -> openshell.v1.IssueSandboxTokenRequest + 23, // 450: openshell.v1.OpenShell.RefreshSandboxToken:input_type -> openshell.v1.RefreshSandboxTokenRequest + 257, // 451: openshell.v1.OpenShell.CreateWorkspace:input_type -> openshell.v1.CreateWorkspaceRequest + 259, // 452: openshell.v1.OpenShell.GetWorkspace:input_type -> openshell.v1.GetWorkspaceRequest + 261, // 453: openshell.v1.OpenShell.ListWorkspaces:input_type -> openshell.v1.ListWorkspacesRequest + 263, // 454: openshell.v1.OpenShell.DeleteWorkspace:input_type -> openshell.v1.DeleteWorkspaceRequest + 266, // 455: openshell.v1.OpenShell.AddWorkspaceMember:input_type -> openshell.v1.AddWorkspaceMemberRequest + 268, // 456: openshell.v1.OpenShell.RemoveWorkspaceMember:input_type -> openshell.v1.RemoveWorkspaceMemberRequest + 270, // 457: openshell.v1.OpenShell.ListWorkspaceMembers:input_type -> openshell.v1.ListWorkspaceMembersRequest + 26, // 458: openshell.v1.OpenShell.Health:output_type -> openshell.v1.HealthResponse + 28, // 459: openshell.v1.OpenShell.GetCurrentUser:output_type -> openshell.v1.GetCurrentUserResponse + 30, // 460: openshell.v1.OpenShell.GetGatewayInfo:output_type -> openshell.v1.GetGatewayInfoResponse + 84, // 461: openshell.v1.OpenShell.CreateSandbox:output_type -> openshell.v1.SandboxResponse + 75, // 462: openshell.v1.OpenShell.BeginRootfsTarStaging:output_type -> openshell.v1.BeginRootfsTarStagingResponse + 58, // 463: openshell.v1.OpenShell.GetDelegatedIdentityAuthorizationStatus:output_type -> openshell.v1.GetDelegatedIdentityAuthorizationStatusResponse + 60, // 464: openshell.v1.OpenShell.AuthorizeDelegatedIdentity:output_type -> openshell.v1.AuthorizeDelegatedIdentityResponse + 62, // 465: openshell.v1.OpenShell.GetSandboxDelegatedIdentityStatus:output_type -> openshell.v1.GetSandboxDelegatedIdentityStatusResponse + 64, // 466: openshell.v1.OpenShell.WithdrawSandboxDelegatedIdentity:output_type -> openshell.v1.WithdrawSandboxDelegatedIdentityResponse + 66, // 467: openshell.v1.OpenShell.ExtendSandboxDelegatedIdentity:output_type -> openshell.v1.ExtendSandboxDelegatedIdentityResponse + 84, // 468: openshell.v1.OpenShell.GetSandbox:output_type -> openshell.v1.SandboxResponse + 85, // 469: openshell.v1.OpenShell.ListSandboxes:output_type -> openshell.v1.ListSandboxesResponse + 71, // 470: openshell.v1.OpenShell.CreateSandboxTemplate:output_type -> openshell.v1.SandboxTemplateResponse + 71, // 471: openshell.v1.OpenShell.GetSandboxTemplate:output_type -> openshell.v1.SandboxTemplateResponse + 72, // 472: openshell.v1.OpenShell.ListSandboxTemplates:output_type -> openshell.v1.ListSandboxTemplatesResponse + 73, // 473: openshell.v1.OpenShell.DeleteSandboxTemplate:output_type -> openshell.v1.DeleteSandboxTemplateResponse + 86, // 474: openshell.v1.OpenShell.ListSandboxProviders:output_type -> openshell.v1.ListSandboxProvidersResponse + 87, // 475: openshell.v1.OpenShell.AttachSandboxProvider:output_type -> openshell.v1.AttachSandboxProviderResponse + 88, // 476: openshell.v1.OpenShell.DetachSandboxProvider:output_type -> openshell.v1.DetachSandboxProviderResponse + 97, // 477: openshell.v1.OpenShell.GetSandboxProviderStatus:output_type -> openshell.v1.GetSandboxProviderStatusResponse + 100, // 478: openshell.v1.OpenShell.DeleteSandbox:output_type -> openshell.v1.DeleteSandboxResponse + 84, // 479: openshell.v1.OpenShell.StopSandbox:output_type -> openshell.v1.SandboxResponse + 84, // 480: openshell.v1.OpenShell.StartSandbox:output_type -> openshell.v1.SandboxResponse + 102, // 481: openshell.v1.OpenShell.CreateSshSession:output_type -> openshell.v1.CreateSshSessionResponse + 110, // 482: openshell.v1.OpenShell.ExposeService:output_type -> openshell.v1.ServiceEndpointResponse + 110, // 483: openshell.v1.OpenShell.GetService:output_type -> openshell.v1.ServiceEndpointResponse + 106, // 484: openshell.v1.OpenShell.ListServices:output_type -> openshell.v1.ListServicesResponse + 108, // 485: openshell.v1.OpenShell.DeleteService:output_type -> openshell.v1.DeleteServiceResponse + 112, // 486: openshell.v1.OpenShell.RevokeSshSession:output_type -> openshell.v1.RevokeSshSessionResponse + 117, // 487: openshell.v1.OpenShell.ExecSandbox:output_type -> openshell.v1.ExecSandboxEvent + 119, // 488: openshell.v1.OpenShell.ForwardTcp:output_type -> openshell.v1.TcpForwardFrame + 117, // 489: openshell.v1.OpenShell.ExecSandboxInteractive:output_type -> openshell.v1.ExecSandboxEvent + 135, // 490: openshell.v1.OpenShell.CreateProvider:output_type -> openshell.v1.ProviderResponse + 135, // 491: openshell.v1.OpenShell.GetProvider:output_type -> openshell.v1.ProviderResponse + 131, // 492: openshell.v1.OpenShell.GetProviderCredentials:output_type -> openshell.v1.GetProviderCredentialsResponse + 136, // 493: openshell.v1.OpenShell.ListProviders:output_type -> openshell.v1.ListProvidersResponse + 170, // 494: openshell.v1.OpenShell.ListProviderProfiles:output_type -> openshell.v1.ListProviderProfilesResponse + 169, // 495: openshell.v1.OpenShell.GetProviderProfile:output_type -> openshell.v1.ProviderProfileResponse + 172, // 496: openshell.v1.OpenShell.ImportProviderProfiles:output_type -> openshell.v1.ImportProviderProfilesResponse + 174, // 497: openshell.v1.OpenShell.UpdateProviderProfiles:output_type -> openshell.v1.UpdateProviderProfilesResponse + 176, // 498: openshell.v1.OpenShell.LintProviderProfiles:output_type -> openshell.v1.LintProviderProfilesResponse + 135, // 499: openshell.v1.OpenShell.UpdateProvider:output_type -> openshell.v1.ProviderResponse + 160, // 500: openshell.v1.OpenShell.GetProviderRefreshStatus:output_type -> openshell.v1.GetProviderRefreshStatusResponse + 162, // 501: openshell.v1.OpenShell.ConfigureProviderRefresh:output_type -> openshell.v1.ConfigureProviderRefreshResponse + 164, // 502: openshell.v1.OpenShell.RotateProviderCredential:output_type -> openshell.v1.RotateProviderCredentialResponse + 152, // 503: openshell.v1.OpenShell.ListDelegatedIdentityCredentials:output_type -> openshell.v1.ListDelegatedIdentityCredentialsResponse + 154, // 504: openshell.v1.OpenShell.GetDelegatedIdentityCredentialStatus:output_type -> openshell.v1.GetDelegatedIdentityCredentialStatusResponse + 156, // 505: openshell.v1.OpenShell.RevokeDelegatedIdentityCredential:output_type -> openshell.v1.RevokeDelegatedIdentityCredentialResponse + 158, // 506: openshell.v1.OpenShell.DeleteDelegatedIdentityCredential:output_type -> openshell.v1.DeleteDelegatedIdentityCredentialResponse + 166, // 507: openshell.v1.OpenShell.DeleteProviderRefresh:output_type -> openshell.v1.DeleteProviderRefreshResponse + 177, // 508: openshell.v1.OpenShell.DeleteProvider:output_type -> openshell.v1.DeleteProviderResponse + 179, // 509: openshell.v1.OpenShell.DeleteProviderProfile:output_type -> openshell.v1.DeleteProviderProfileResponse + 323, // 510: openshell.v1.OpenShell.GetSandboxConfig:output_type -> openshell.sandbox.v1.GetSandboxConfigResponse + 324, // 511: openshell.v1.OpenShell.GetGatewayConfig:output_type -> openshell.sandbox.v1.GetGatewayConfigResponse + 195, // 512: openshell.v1.OpenShell.UpdateConfig:output_type -> openshell.v1.UpdateConfigResponse + 197, // 513: openshell.v1.OpenShell.GetSandboxPolicyStatus:output_type -> openshell.v1.GetSandboxPolicyStatusResponse + 199, // 514: openshell.v1.OpenShell.ListSandboxPolicies:output_type -> openshell.v1.ListSandboxPoliciesResponse + 201, // 515: openshell.v1.OpenShell.ReportPolicyStatus:output_type -> openshell.v1.ReportPolicyStatusResponse + 277, // 516: openshell.v1.OpenShell.ReportEndpointStatus:output_type -> openshell.v1.ReportEndpointStatusResponse + 99, // 517: openshell.v1.OpenShell.ReportProviderReadiness:output_type -> openshell.v1.ReportProviderReadinessResponse + 204, // 518: openshell.v1.OpenShell.ReportSandboxConfiguration:output_type -> openshell.v1.ReportSandboxConfigurationResponse + 183, // 519: openshell.v1.OpenShell.GetSandboxProviderEnvironment:output_type -> openshell.v1.GetSandboxProviderEnvironmentResponse + 185, // 520: openshell.v1.OpenShell.ExchangeProviderSubjectToken:output_type -> openshell.v1.ExchangeProviderSubjectTokenResponse + 209, // 521: openshell.v1.OpenShell.GetSandboxLogs:output_type -> openshell.v1.GetSandboxLogsResponse + 208, // 522: openshell.v1.OpenShell.PushSandboxLogs:output_type -> openshell.v1.PushSandboxLogsResponse + 211, // 523: openshell.v1.OpenShell.ConnectSupervisor:output_type -> openshell.v1.GatewayMessage + 219, // 524: openshell.v1.OpenShell.ReportMainProcessExit:output_type -> openshell.v1.ReportMainProcessExitResponse + 221, // 525: openshell.v1.OpenShell.FinalizeMainProcessExit:output_type -> openshell.v1.FinalizeMainProcessExitResponse + 226, // 526: openshell.v1.OpenShell.RelayStream:output_type -> openshell.v1.RelayFrame + 228, // 527: openshell.v1.OpenShell.PeerRelay:output_type -> openshell.v1.PeerRelayFrame + 99, // 528: openshell.v1.OpenShell.PeerReportProviderReadiness:output_type -> openshell.v1.ReportProviderReadinessResponse + 277, // 529: openshell.v1.OpenShell.PeerReportEndpointStatus:output_type -> openshell.v1.ReportEndpointStatusResponse + 97, // 530: openshell.v1.OpenShell.PeerGetSandboxProviderStatus:output_type -> openshell.v1.GetSandboxProviderStatusResponse + 124, // 531: openshell.v1.OpenShell.WatchSandbox:output_type -> openshell.v1.SandboxStreamEvent + 238, // 532: openshell.v1.OpenShell.SubmitPolicyAnalysis:output_type -> openshell.v1.SubmitPolicyAnalysisResponse + 240, // 533: openshell.v1.OpenShell.GetDraftPolicy:output_type -> openshell.v1.GetDraftPolicyResponse + 242, // 534: openshell.v1.OpenShell.ApproveDraftChunk:output_type -> openshell.v1.ApproveDraftChunkResponse + 244, // 535: openshell.v1.OpenShell.RejectDraftChunk:output_type -> openshell.v1.RejectDraftChunkResponse + 247, // 536: openshell.v1.OpenShell.ApproveAllDraftChunks:output_type -> openshell.v1.ApproveAllDraftChunksResponse + 249, // 537: openshell.v1.OpenShell.EditDraftChunk:output_type -> openshell.v1.EditDraftChunkResponse + 251, // 538: openshell.v1.OpenShell.UndoDraftChunk:output_type -> openshell.v1.UndoDraftChunkResponse + 253, // 539: openshell.v1.OpenShell.ClearDraftChunks:output_type -> openshell.v1.ClearDraftChunksResponse + 256, // 540: openshell.v1.OpenShell.GetDraftHistory:output_type -> openshell.v1.GetDraftHistoryResponse + 22, // 541: openshell.v1.OpenShell.IssueSandboxToken:output_type -> openshell.v1.IssueSandboxTokenResponse + 24, // 542: openshell.v1.OpenShell.RefreshSandboxToken:output_type -> openshell.v1.RefreshSandboxTokenResponse + 258, // 543: openshell.v1.OpenShell.CreateWorkspace:output_type -> openshell.v1.CreateWorkspaceResponse + 260, // 544: openshell.v1.OpenShell.GetWorkspace:output_type -> openshell.v1.GetWorkspaceResponse + 262, // 545: openshell.v1.OpenShell.ListWorkspaces:output_type -> openshell.v1.ListWorkspacesResponse + 264, // 546: openshell.v1.OpenShell.DeleteWorkspace:output_type -> openshell.v1.DeleteWorkspaceResponse + 267, // 547: openshell.v1.OpenShell.AddWorkspaceMember:output_type -> openshell.v1.AddWorkspaceMemberResponse + 269, // 548: openshell.v1.OpenShell.RemoveWorkspaceMember:output_type -> openshell.v1.RemoveWorkspaceMemberResponse + 271, // 549: openshell.v1.OpenShell.ListWorkspaceMembers:output_type -> openshell.v1.ListWorkspaceMembersResponse + 458, // [458:550] is the sub-list for method output_type + 366, // [366:458] is the sub-list for method input_type + 366, // [366:366] is the sub-list for extension type_name + 366, // [366:366] is the sub-list for extension extendee + 0, // [0:366] is the sub-list for field type_name } func init() { file_openshell_proto_init() } @@ -21106,40 +22566,40 @@ func file_openshell_proto_init() { if File_openshell_proto != nil { return } - file_openshell_proto_msgTypes[20].OneofWrappers = []any{} file_openshell_proto_msgTypes[21].OneofWrappers = []any{} - file_openshell_proto_msgTypes[29].OneofWrappers = []any{} - file_openshell_proto_msgTypes[56].OneofWrappers = []any{ + file_openshell_proto_msgTypes[22].OneofWrappers = []any{} + file_openshell_proto_msgTypes[30].OneofWrappers = []any{} + file_openshell_proto_msgTypes[69].OneofWrappers = []any{ (*ConfigSnapshotRevision_SandboxConfig)(nil), (*ConfigSnapshotRevision_ProviderEnvironment)(nil), (*ConfigSnapshotRevision_ProviderTarget)(nil), } - file_openshell_proto_msgTypes[83].OneofWrappers = []any{ + file_openshell_proto_msgTypes[96].OneofWrappers = []any{ (*ExecSandboxEvent_Stdout)(nil), (*ExecSandboxEvent_Stderr)(nil), (*ExecSandboxEvent_Exit)(nil), } - file_openshell_proto_msgTypes[84].OneofWrappers = []any{ + file_openshell_proto_msgTypes[97].OneofWrappers = []any{ (*TcpForwardInit_Ssh)(nil), (*TcpForwardInit_Tcp)(nil), } - file_openshell_proto_msgTypes[85].OneofWrappers = []any{ + file_openshell_proto_msgTypes[98].OneofWrappers = []any{ (*TcpForwardFrame_Init)(nil), (*TcpForwardFrame_Data)(nil), } - file_openshell_proto_msgTypes[86].OneofWrappers = []any{ + file_openshell_proto_msgTypes[99].OneofWrappers = []any{ (*ExecSandboxInput_Start)(nil), (*ExecSandboxInput_Stdin)(nil), (*ExecSandboxInput_Resize)(nil), } - file_openshell_proto_msgTypes[90].OneofWrappers = []any{ + file_openshell_proto_msgTypes[103].OneofWrappers = []any{ (*SandboxStreamEvent_Sandbox)(nil), (*SandboxStreamEvent_Log)(nil), (*SandboxStreamEvent_Event)(nil), (*SandboxStreamEvent_Warning)(nil), (*SandboxStreamEvent_DraftPolicyUpdate)(nil), } - file_openshell_proto_msgTypes[144].OneofWrappers = []any{ + file_openshell_proto_msgTypes[166].OneofWrappers = []any{ (*PolicyMergeOperation_AddRule)(nil), (*PolicyMergeOperation_RemoveEndpoint)(nil), (*PolicyMergeOperation_RemoveRule)(nil), @@ -21147,14 +22607,14 @@ func file_openshell_proto_init() { (*PolicyMergeOperation_AddAllowRules)(nil), (*PolicyMergeOperation_RemoveBinary)(nil), } - file_openshell_proto_msgTypes[148].OneofWrappers = []any{} - file_openshell_proto_msgTypes[167].OneofWrappers = []any{ + file_openshell_proto_msgTypes[170].OneofWrappers = []any{} + file_openshell_proto_msgTypes[189].OneofWrappers = []any{ (*SupervisorMessage_Hello)(nil), (*SupervisorMessage_Heartbeat)(nil), (*SupervisorMessage_RelayOpenResult)(nil), (*SupervisorMessage_RelayClose)(nil), } - file_openshell_proto_msgTypes[168].OneofWrappers = []any{ + file_openshell_proto_msgTypes[190].OneofWrappers = []any{ (*GatewayMessage_SessionAccepted)(nil), (*GatewayMessage_SessionRejected)(nil), (*GatewayMessage_Heartbeat)(nil), @@ -21162,26 +22622,26 @@ func file_openshell_proto_init() { (*GatewayMessage_RelayClose)(nil), (*GatewayMessage_SessionRedirect)(nil), } - file_openshell_proto_msgTypes[179].OneofWrappers = []any{ + file_openshell_proto_msgTypes[201].OneofWrappers = []any{ (*RelayOpen_Ssh)(nil), (*RelayOpen_Tcp)(nil), } - file_openshell_proto_msgTypes[183].OneofWrappers = []any{ + file_openshell_proto_msgTypes[205].OneofWrappers = []any{ (*RelayFrame_Init)(nil), (*RelayFrame_Data)(nil), } - file_openshell_proto_msgTypes[185].OneofWrappers = []any{ + file_openshell_proto_msgTypes[207].OneofWrappers = []any{ (*PeerRelayFrame_Init)(nil), (*PeerRelayFrame_Data)(nil), } - file_openshell_proto_msgTypes[238].OneofWrappers = []any{} + file_openshell_proto_msgTypes[260].OneofWrappers = []any{} type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_openshell_proto_rawDesc), len(file_openshell_proto_rawDesc)), NumEnums: 21, - NumMessages: 263, + NumMessages: 285, NumExtensions: 0, NumServices: 1, }, diff --git a/sdk/go/proto/openshellv1/openshell_grpc.pb.go b/sdk/go/proto/openshellv1/openshell_grpc.pb.go index 5ca018daf6..a709f06b0b 100644 --- a/sdk/go/proto/openshellv1/openshell_grpc.pb.go +++ b/sdk/go/proto/openshellv1/openshell_grpc.pb.go @@ -23,89 +23,98 @@ import ( const _ = grpc.SupportPackageIsVersion9 const ( - OpenShell_Health_FullMethodName = "/openshell.v1.OpenShell/Health" - OpenShell_GetCurrentUser_FullMethodName = "/openshell.v1.OpenShell/GetCurrentUser" - OpenShell_GetGatewayInfo_FullMethodName = "/openshell.v1.OpenShell/GetGatewayInfo" - OpenShell_CreateSandbox_FullMethodName = "/openshell.v1.OpenShell/CreateSandbox" - OpenShell_BeginRootfsTarStaging_FullMethodName = "/openshell.v1.OpenShell/BeginRootfsTarStaging" - OpenShell_GetSandbox_FullMethodName = "/openshell.v1.OpenShell/GetSandbox" - OpenShell_ListSandboxes_FullMethodName = "/openshell.v1.OpenShell/ListSandboxes" - OpenShell_CreateSandboxTemplate_FullMethodName = "/openshell.v1.OpenShell/CreateSandboxTemplate" - OpenShell_GetSandboxTemplate_FullMethodName = "/openshell.v1.OpenShell/GetSandboxTemplate" - OpenShell_ListSandboxTemplates_FullMethodName = "/openshell.v1.OpenShell/ListSandboxTemplates" - OpenShell_DeleteSandboxTemplate_FullMethodName = "/openshell.v1.OpenShell/DeleteSandboxTemplate" - OpenShell_ListSandboxProviders_FullMethodName = "/openshell.v1.OpenShell/ListSandboxProviders" - OpenShell_AttachSandboxProvider_FullMethodName = "/openshell.v1.OpenShell/AttachSandboxProvider" - OpenShell_DetachSandboxProvider_FullMethodName = "/openshell.v1.OpenShell/DetachSandboxProvider" - OpenShell_GetSandboxProviderStatus_FullMethodName = "/openshell.v1.OpenShell/GetSandboxProviderStatus" - OpenShell_DeleteSandbox_FullMethodName = "/openshell.v1.OpenShell/DeleteSandbox" - OpenShell_StopSandbox_FullMethodName = "/openshell.v1.OpenShell/StopSandbox" - OpenShell_StartSandbox_FullMethodName = "/openshell.v1.OpenShell/StartSandbox" - OpenShell_CreateSshSession_FullMethodName = "/openshell.v1.OpenShell/CreateSshSession" - OpenShell_ExposeService_FullMethodName = "/openshell.v1.OpenShell/ExposeService" - OpenShell_GetService_FullMethodName = "/openshell.v1.OpenShell/GetService" - OpenShell_ListServices_FullMethodName = "/openshell.v1.OpenShell/ListServices" - OpenShell_DeleteService_FullMethodName = "/openshell.v1.OpenShell/DeleteService" - OpenShell_RevokeSshSession_FullMethodName = "/openshell.v1.OpenShell/RevokeSshSession" - OpenShell_ExecSandbox_FullMethodName = "/openshell.v1.OpenShell/ExecSandbox" - OpenShell_ForwardTcp_FullMethodName = "/openshell.v1.OpenShell/ForwardTcp" - OpenShell_ExecSandboxInteractive_FullMethodName = "/openshell.v1.OpenShell/ExecSandboxInteractive" - OpenShell_CreateProvider_FullMethodName = "/openshell.v1.OpenShell/CreateProvider" - OpenShell_GetProvider_FullMethodName = "/openshell.v1.OpenShell/GetProvider" - OpenShell_GetProviderCredentials_FullMethodName = "/openshell.v1.OpenShell/GetProviderCredentials" - OpenShell_ListProviders_FullMethodName = "/openshell.v1.OpenShell/ListProviders" - OpenShell_ListProviderProfiles_FullMethodName = "/openshell.v1.OpenShell/ListProviderProfiles" - OpenShell_GetProviderProfile_FullMethodName = "/openshell.v1.OpenShell/GetProviderProfile" - OpenShell_ImportProviderProfiles_FullMethodName = "/openshell.v1.OpenShell/ImportProviderProfiles" - OpenShell_UpdateProviderProfiles_FullMethodName = "/openshell.v1.OpenShell/UpdateProviderProfiles" - OpenShell_LintProviderProfiles_FullMethodName = "/openshell.v1.OpenShell/LintProviderProfiles" - OpenShell_UpdateProvider_FullMethodName = "/openshell.v1.OpenShell/UpdateProvider" - OpenShell_GetProviderRefreshStatus_FullMethodName = "/openshell.v1.OpenShell/GetProviderRefreshStatus" - OpenShell_ConfigureProviderRefresh_FullMethodName = "/openshell.v1.OpenShell/ConfigureProviderRefresh" - OpenShell_RotateProviderCredential_FullMethodName = "/openshell.v1.OpenShell/RotateProviderCredential" - OpenShell_DeleteProviderRefresh_FullMethodName = "/openshell.v1.OpenShell/DeleteProviderRefresh" - OpenShell_DeleteProvider_FullMethodName = "/openshell.v1.OpenShell/DeleteProvider" - OpenShell_DeleteProviderProfile_FullMethodName = "/openshell.v1.OpenShell/DeleteProviderProfile" - OpenShell_GetSandboxConfig_FullMethodName = "/openshell.v1.OpenShell/GetSandboxConfig" - OpenShell_GetGatewayConfig_FullMethodName = "/openshell.v1.OpenShell/GetGatewayConfig" - OpenShell_UpdateConfig_FullMethodName = "/openshell.v1.OpenShell/UpdateConfig" - OpenShell_GetSandboxPolicyStatus_FullMethodName = "/openshell.v1.OpenShell/GetSandboxPolicyStatus" - OpenShell_ListSandboxPolicies_FullMethodName = "/openshell.v1.OpenShell/ListSandboxPolicies" - OpenShell_ReportPolicyStatus_FullMethodName = "/openshell.v1.OpenShell/ReportPolicyStatus" - OpenShell_ReportEndpointStatus_FullMethodName = "/openshell.v1.OpenShell/ReportEndpointStatus" - OpenShell_ReportProviderReadiness_FullMethodName = "/openshell.v1.OpenShell/ReportProviderReadiness" - OpenShell_ReportSandboxConfiguration_FullMethodName = "/openshell.v1.OpenShell/ReportSandboxConfiguration" - OpenShell_GetSandboxProviderEnvironment_FullMethodName = "/openshell.v1.OpenShell/GetSandboxProviderEnvironment" - OpenShell_ExchangeProviderSubjectToken_FullMethodName = "/openshell.v1.OpenShell/ExchangeProviderSubjectToken" - OpenShell_GetSandboxLogs_FullMethodName = "/openshell.v1.OpenShell/GetSandboxLogs" - OpenShell_PushSandboxLogs_FullMethodName = "/openshell.v1.OpenShell/PushSandboxLogs" - OpenShell_ConnectSupervisor_FullMethodName = "/openshell.v1.OpenShell/ConnectSupervisor" - OpenShell_ReportMainProcessExit_FullMethodName = "/openshell.v1.OpenShell/ReportMainProcessExit" - OpenShell_FinalizeMainProcessExit_FullMethodName = "/openshell.v1.OpenShell/FinalizeMainProcessExit" - OpenShell_RelayStream_FullMethodName = "/openshell.v1.OpenShell/RelayStream" - OpenShell_PeerRelay_FullMethodName = "/openshell.v1.OpenShell/PeerRelay" - OpenShell_PeerReportProviderReadiness_FullMethodName = "/openshell.v1.OpenShell/PeerReportProviderReadiness" - OpenShell_PeerReportEndpointStatus_FullMethodName = "/openshell.v1.OpenShell/PeerReportEndpointStatus" - OpenShell_PeerGetSandboxProviderStatus_FullMethodName = "/openshell.v1.OpenShell/PeerGetSandboxProviderStatus" - OpenShell_WatchSandbox_FullMethodName = "/openshell.v1.OpenShell/WatchSandbox" - OpenShell_SubmitPolicyAnalysis_FullMethodName = "/openshell.v1.OpenShell/SubmitPolicyAnalysis" - OpenShell_GetDraftPolicy_FullMethodName = "/openshell.v1.OpenShell/GetDraftPolicy" - OpenShell_ApproveDraftChunk_FullMethodName = "/openshell.v1.OpenShell/ApproveDraftChunk" - OpenShell_RejectDraftChunk_FullMethodName = "/openshell.v1.OpenShell/RejectDraftChunk" - OpenShell_ApproveAllDraftChunks_FullMethodName = "/openshell.v1.OpenShell/ApproveAllDraftChunks" - OpenShell_EditDraftChunk_FullMethodName = "/openshell.v1.OpenShell/EditDraftChunk" - OpenShell_UndoDraftChunk_FullMethodName = "/openshell.v1.OpenShell/UndoDraftChunk" - OpenShell_ClearDraftChunks_FullMethodName = "/openshell.v1.OpenShell/ClearDraftChunks" - OpenShell_GetDraftHistory_FullMethodName = "/openshell.v1.OpenShell/GetDraftHistory" - OpenShell_IssueSandboxToken_FullMethodName = "/openshell.v1.OpenShell/IssueSandboxToken" - OpenShell_RefreshSandboxToken_FullMethodName = "/openshell.v1.OpenShell/RefreshSandboxToken" - OpenShell_CreateWorkspace_FullMethodName = "/openshell.v1.OpenShell/CreateWorkspace" - OpenShell_GetWorkspace_FullMethodName = "/openshell.v1.OpenShell/GetWorkspace" - OpenShell_ListWorkspaces_FullMethodName = "/openshell.v1.OpenShell/ListWorkspaces" - OpenShell_DeleteWorkspace_FullMethodName = "/openshell.v1.OpenShell/DeleteWorkspace" - OpenShell_AddWorkspaceMember_FullMethodName = "/openshell.v1.OpenShell/AddWorkspaceMember" - OpenShell_RemoveWorkspaceMember_FullMethodName = "/openshell.v1.OpenShell/RemoveWorkspaceMember" - OpenShell_ListWorkspaceMembers_FullMethodName = "/openshell.v1.OpenShell/ListWorkspaceMembers" + OpenShell_Health_FullMethodName = "/openshell.v1.OpenShell/Health" + OpenShell_GetCurrentUser_FullMethodName = "/openshell.v1.OpenShell/GetCurrentUser" + OpenShell_GetGatewayInfo_FullMethodName = "/openshell.v1.OpenShell/GetGatewayInfo" + OpenShell_CreateSandbox_FullMethodName = "/openshell.v1.OpenShell/CreateSandbox" + OpenShell_BeginRootfsTarStaging_FullMethodName = "/openshell.v1.OpenShell/BeginRootfsTarStaging" + OpenShell_GetDelegatedIdentityAuthorizationStatus_FullMethodName = "/openshell.v1.OpenShell/GetDelegatedIdentityAuthorizationStatus" + OpenShell_AuthorizeDelegatedIdentity_FullMethodName = "/openshell.v1.OpenShell/AuthorizeDelegatedIdentity" + OpenShell_GetSandboxDelegatedIdentityStatus_FullMethodName = "/openshell.v1.OpenShell/GetSandboxDelegatedIdentityStatus" + OpenShell_WithdrawSandboxDelegatedIdentity_FullMethodName = "/openshell.v1.OpenShell/WithdrawSandboxDelegatedIdentity" + OpenShell_ExtendSandboxDelegatedIdentity_FullMethodName = "/openshell.v1.OpenShell/ExtendSandboxDelegatedIdentity" + OpenShell_GetSandbox_FullMethodName = "/openshell.v1.OpenShell/GetSandbox" + OpenShell_ListSandboxes_FullMethodName = "/openshell.v1.OpenShell/ListSandboxes" + OpenShell_CreateSandboxTemplate_FullMethodName = "/openshell.v1.OpenShell/CreateSandboxTemplate" + OpenShell_GetSandboxTemplate_FullMethodName = "/openshell.v1.OpenShell/GetSandboxTemplate" + OpenShell_ListSandboxTemplates_FullMethodName = "/openshell.v1.OpenShell/ListSandboxTemplates" + OpenShell_DeleteSandboxTemplate_FullMethodName = "/openshell.v1.OpenShell/DeleteSandboxTemplate" + OpenShell_ListSandboxProviders_FullMethodName = "/openshell.v1.OpenShell/ListSandboxProviders" + OpenShell_AttachSandboxProvider_FullMethodName = "/openshell.v1.OpenShell/AttachSandboxProvider" + OpenShell_DetachSandboxProvider_FullMethodName = "/openshell.v1.OpenShell/DetachSandboxProvider" + OpenShell_GetSandboxProviderStatus_FullMethodName = "/openshell.v1.OpenShell/GetSandboxProviderStatus" + OpenShell_DeleteSandbox_FullMethodName = "/openshell.v1.OpenShell/DeleteSandbox" + OpenShell_StopSandbox_FullMethodName = "/openshell.v1.OpenShell/StopSandbox" + OpenShell_StartSandbox_FullMethodName = "/openshell.v1.OpenShell/StartSandbox" + OpenShell_CreateSshSession_FullMethodName = "/openshell.v1.OpenShell/CreateSshSession" + OpenShell_ExposeService_FullMethodName = "/openshell.v1.OpenShell/ExposeService" + OpenShell_GetService_FullMethodName = "/openshell.v1.OpenShell/GetService" + OpenShell_ListServices_FullMethodName = "/openshell.v1.OpenShell/ListServices" + OpenShell_DeleteService_FullMethodName = "/openshell.v1.OpenShell/DeleteService" + OpenShell_RevokeSshSession_FullMethodName = "/openshell.v1.OpenShell/RevokeSshSession" + OpenShell_ExecSandbox_FullMethodName = "/openshell.v1.OpenShell/ExecSandbox" + OpenShell_ForwardTcp_FullMethodName = "/openshell.v1.OpenShell/ForwardTcp" + OpenShell_ExecSandboxInteractive_FullMethodName = "/openshell.v1.OpenShell/ExecSandboxInteractive" + OpenShell_CreateProvider_FullMethodName = "/openshell.v1.OpenShell/CreateProvider" + OpenShell_GetProvider_FullMethodName = "/openshell.v1.OpenShell/GetProvider" + OpenShell_GetProviderCredentials_FullMethodName = "/openshell.v1.OpenShell/GetProviderCredentials" + OpenShell_ListProviders_FullMethodName = "/openshell.v1.OpenShell/ListProviders" + OpenShell_ListProviderProfiles_FullMethodName = "/openshell.v1.OpenShell/ListProviderProfiles" + OpenShell_GetProviderProfile_FullMethodName = "/openshell.v1.OpenShell/GetProviderProfile" + OpenShell_ImportProviderProfiles_FullMethodName = "/openshell.v1.OpenShell/ImportProviderProfiles" + OpenShell_UpdateProviderProfiles_FullMethodName = "/openshell.v1.OpenShell/UpdateProviderProfiles" + OpenShell_LintProviderProfiles_FullMethodName = "/openshell.v1.OpenShell/LintProviderProfiles" + OpenShell_UpdateProvider_FullMethodName = "/openshell.v1.OpenShell/UpdateProvider" + OpenShell_GetProviderRefreshStatus_FullMethodName = "/openshell.v1.OpenShell/GetProviderRefreshStatus" + OpenShell_ConfigureProviderRefresh_FullMethodName = "/openshell.v1.OpenShell/ConfigureProviderRefresh" + OpenShell_RotateProviderCredential_FullMethodName = "/openshell.v1.OpenShell/RotateProviderCredential" + OpenShell_ListDelegatedIdentityCredentials_FullMethodName = "/openshell.v1.OpenShell/ListDelegatedIdentityCredentials" + OpenShell_GetDelegatedIdentityCredentialStatus_FullMethodName = "/openshell.v1.OpenShell/GetDelegatedIdentityCredentialStatus" + OpenShell_RevokeDelegatedIdentityCredential_FullMethodName = "/openshell.v1.OpenShell/RevokeDelegatedIdentityCredential" + OpenShell_DeleteDelegatedIdentityCredential_FullMethodName = "/openshell.v1.OpenShell/DeleteDelegatedIdentityCredential" + OpenShell_DeleteProviderRefresh_FullMethodName = "/openshell.v1.OpenShell/DeleteProviderRefresh" + OpenShell_DeleteProvider_FullMethodName = "/openshell.v1.OpenShell/DeleteProvider" + OpenShell_DeleteProviderProfile_FullMethodName = "/openshell.v1.OpenShell/DeleteProviderProfile" + OpenShell_GetSandboxConfig_FullMethodName = "/openshell.v1.OpenShell/GetSandboxConfig" + OpenShell_GetGatewayConfig_FullMethodName = "/openshell.v1.OpenShell/GetGatewayConfig" + OpenShell_UpdateConfig_FullMethodName = "/openshell.v1.OpenShell/UpdateConfig" + OpenShell_GetSandboxPolicyStatus_FullMethodName = "/openshell.v1.OpenShell/GetSandboxPolicyStatus" + OpenShell_ListSandboxPolicies_FullMethodName = "/openshell.v1.OpenShell/ListSandboxPolicies" + OpenShell_ReportPolicyStatus_FullMethodName = "/openshell.v1.OpenShell/ReportPolicyStatus" + OpenShell_ReportEndpointStatus_FullMethodName = "/openshell.v1.OpenShell/ReportEndpointStatus" + OpenShell_ReportProviderReadiness_FullMethodName = "/openshell.v1.OpenShell/ReportProviderReadiness" + OpenShell_ReportSandboxConfiguration_FullMethodName = "/openshell.v1.OpenShell/ReportSandboxConfiguration" + OpenShell_GetSandboxProviderEnvironment_FullMethodName = "/openshell.v1.OpenShell/GetSandboxProviderEnvironment" + OpenShell_ExchangeProviderSubjectToken_FullMethodName = "/openshell.v1.OpenShell/ExchangeProviderSubjectToken" + OpenShell_GetSandboxLogs_FullMethodName = "/openshell.v1.OpenShell/GetSandboxLogs" + OpenShell_PushSandboxLogs_FullMethodName = "/openshell.v1.OpenShell/PushSandboxLogs" + OpenShell_ConnectSupervisor_FullMethodName = "/openshell.v1.OpenShell/ConnectSupervisor" + OpenShell_ReportMainProcessExit_FullMethodName = "/openshell.v1.OpenShell/ReportMainProcessExit" + OpenShell_FinalizeMainProcessExit_FullMethodName = "/openshell.v1.OpenShell/FinalizeMainProcessExit" + OpenShell_RelayStream_FullMethodName = "/openshell.v1.OpenShell/RelayStream" + OpenShell_PeerRelay_FullMethodName = "/openshell.v1.OpenShell/PeerRelay" + OpenShell_PeerReportProviderReadiness_FullMethodName = "/openshell.v1.OpenShell/PeerReportProviderReadiness" + OpenShell_PeerReportEndpointStatus_FullMethodName = "/openshell.v1.OpenShell/PeerReportEndpointStatus" + OpenShell_PeerGetSandboxProviderStatus_FullMethodName = "/openshell.v1.OpenShell/PeerGetSandboxProviderStatus" + OpenShell_WatchSandbox_FullMethodName = "/openshell.v1.OpenShell/WatchSandbox" + OpenShell_SubmitPolicyAnalysis_FullMethodName = "/openshell.v1.OpenShell/SubmitPolicyAnalysis" + OpenShell_GetDraftPolicy_FullMethodName = "/openshell.v1.OpenShell/GetDraftPolicy" + OpenShell_ApproveDraftChunk_FullMethodName = "/openshell.v1.OpenShell/ApproveDraftChunk" + OpenShell_RejectDraftChunk_FullMethodName = "/openshell.v1.OpenShell/RejectDraftChunk" + OpenShell_ApproveAllDraftChunks_FullMethodName = "/openshell.v1.OpenShell/ApproveAllDraftChunks" + OpenShell_EditDraftChunk_FullMethodName = "/openshell.v1.OpenShell/EditDraftChunk" + OpenShell_UndoDraftChunk_FullMethodName = "/openshell.v1.OpenShell/UndoDraftChunk" + OpenShell_ClearDraftChunks_FullMethodName = "/openshell.v1.OpenShell/ClearDraftChunks" + OpenShell_GetDraftHistory_FullMethodName = "/openshell.v1.OpenShell/GetDraftHistory" + OpenShell_IssueSandboxToken_FullMethodName = "/openshell.v1.OpenShell/IssueSandboxToken" + OpenShell_RefreshSandboxToken_FullMethodName = "/openshell.v1.OpenShell/RefreshSandboxToken" + OpenShell_CreateWorkspace_FullMethodName = "/openshell.v1.OpenShell/CreateWorkspace" + OpenShell_GetWorkspace_FullMethodName = "/openshell.v1.OpenShell/GetWorkspace" + OpenShell_ListWorkspaces_FullMethodName = "/openshell.v1.OpenShell/ListWorkspaces" + OpenShell_DeleteWorkspace_FullMethodName = "/openshell.v1.OpenShell/DeleteWorkspace" + OpenShell_AddWorkspaceMember_FullMethodName = "/openshell.v1.OpenShell/AddWorkspaceMember" + OpenShell_RemoveWorkspaceMember_FullMethodName = "/openshell.v1.OpenShell/RemoveWorkspaceMember" + OpenShell_ListWorkspaceMembers_FullMethodName = "/openshell.v1.OpenShell/ListWorkspaceMembers" ) // OpenShellClient is the client API for OpenShell service. @@ -138,6 +147,18 @@ type OpenShellClient interface { // CreateSandbox; callers never name a filesystem path themselves. Only a // client sharing the gateway's filesystem can complete the upload. BeginRootfsTarStaging(ctx context.Context, in *BeginRootfsTarStagingRequest, opts ...grpc.CallOption) (*BeginRootfsTarStagingResponse, error) + // Inspect whether the authenticated user has a usable gateway-owned + // delegated identity authorization. + GetDelegatedIdentityAuthorizationStatus(ctx context.Context, in *GetDelegatedIdentityAuthorizationStatusRequest, opts ...grpc.CallOption) (*GetDelegatedIdentityAuthorizationStatusResponse, error) + // Transfer a separate OAuth authorization grant to the gateway for use as + // the authenticated user's delegated identity. + AuthorizeDelegatedIdentity(ctx context.Context, in *AuthorizeDelegatedIdentityRequest, opts ...grpc.CallOption) (*AuthorizeDelegatedIdentityResponse, error) + // Fetch delegated identity status for one sandbox. + GetSandboxDelegatedIdentityStatus(ctx context.Context, in *GetSandboxDelegatedIdentityStatusRequest, opts ...grpc.CallOption) (*GetSandboxDelegatedIdentityStatusResponse, error) + // Withdraw delegated identity from one sandbox. + WithdrawSandboxDelegatedIdentity(ctx context.Context, in *WithdrawSandboxDelegatedIdentityRequest, opts ...grpc.CallOption) (*WithdrawSandboxDelegatedIdentityResponse, error) + // Extend delegated identity for one sandbox. + ExtendSandboxDelegatedIdentity(ctx context.Context, in *ExtendSandboxDelegatedIdentityRequest, opts ...grpc.CallOption) (*ExtendSandboxDelegatedIdentityResponse, error) // Fetch a sandbox by name. GetSandbox(ctx context.Context, in *GetSandboxRequest, opts ...grpc.CallOption) (*SandboxResponse, error) // List sandboxes. @@ -211,6 +232,14 @@ type OpenShellClient interface { ConfigureProviderRefresh(ctx context.Context, in *ConfigureProviderRefreshRequest, opts ...grpc.CallOption) (*ConfigureProviderRefreshResponse, error) // Record a gateway-owned refresh request for one provider credential. RotateProviderCredential(ctx context.Context, in *RotateProviderCredentialRequest, opts ...grpc.CallOption) (*RotateProviderCredentialResponse, error) + // List delegated identity credentials visible to the caller. + ListDelegatedIdentityCredentials(ctx context.Context, in *ListDelegatedIdentityCredentialsRequest, opts ...grpc.CallOption) (*ListDelegatedIdentityCredentialsResponse, error) + // Fetch delegated identity credential status. + GetDelegatedIdentityCredentialStatus(ctx context.Context, in *GetDelegatedIdentityCredentialStatusRequest, opts ...grpc.CallOption) (*GetDelegatedIdentityCredentialStatusResponse, error) + // Revoke a delegated identity credential. + RevokeDelegatedIdentityCredential(ctx context.Context, in *RevokeDelegatedIdentityCredentialRequest, opts ...grpc.CallOption) (*RevokeDelegatedIdentityCredentialResponse, error) + // Delete a delegated identity credential. + DeleteDelegatedIdentityCredential(ctx context.Context, in *DeleteDelegatedIdentityCredentialRequest, opts ...grpc.CallOption) (*DeleteDelegatedIdentityCredentialResponse, error) // Delete gateway-owned refresh configuration for one provider credential. DeleteProviderRefresh(ctx context.Context, in *DeleteProviderRefreshRequest, opts ...grpc.CallOption) (*DeleteProviderRefreshResponse, error) // Delete a provider by name. @@ -399,6 +428,56 @@ func (c *openShellClient) BeginRootfsTarStaging(ctx context.Context, in *BeginRo return out, nil } +func (c *openShellClient) GetDelegatedIdentityAuthorizationStatus(ctx context.Context, in *GetDelegatedIdentityAuthorizationStatusRequest, opts ...grpc.CallOption) (*GetDelegatedIdentityAuthorizationStatusResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(GetDelegatedIdentityAuthorizationStatusResponse) + err := c.cc.Invoke(ctx, OpenShell_GetDelegatedIdentityAuthorizationStatus_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *openShellClient) AuthorizeDelegatedIdentity(ctx context.Context, in *AuthorizeDelegatedIdentityRequest, opts ...grpc.CallOption) (*AuthorizeDelegatedIdentityResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(AuthorizeDelegatedIdentityResponse) + err := c.cc.Invoke(ctx, OpenShell_AuthorizeDelegatedIdentity_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *openShellClient) GetSandboxDelegatedIdentityStatus(ctx context.Context, in *GetSandboxDelegatedIdentityStatusRequest, opts ...grpc.CallOption) (*GetSandboxDelegatedIdentityStatusResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(GetSandboxDelegatedIdentityStatusResponse) + err := c.cc.Invoke(ctx, OpenShell_GetSandboxDelegatedIdentityStatus_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *openShellClient) WithdrawSandboxDelegatedIdentity(ctx context.Context, in *WithdrawSandboxDelegatedIdentityRequest, opts ...grpc.CallOption) (*WithdrawSandboxDelegatedIdentityResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(WithdrawSandboxDelegatedIdentityResponse) + err := c.cc.Invoke(ctx, OpenShell_WithdrawSandboxDelegatedIdentity_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *openShellClient) ExtendSandboxDelegatedIdentity(ctx context.Context, in *ExtendSandboxDelegatedIdentityRequest, opts ...grpc.CallOption) (*ExtendSandboxDelegatedIdentityResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ExtendSandboxDelegatedIdentityResponse) + err := c.cc.Invoke(ctx, OpenShell_ExtendSandboxDelegatedIdentity_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + func (c *openShellClient) GetSandbox(ctx context.Context, in *GetSandboxRequest, opts ...grpc.CallOption) (*SandboxResponse, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(SandboxResponse) @@ -764,6 +843,46 @@ func (c *openShellClient) RotateProviderCredential(ctx context.Context, in *Rota return out, nil } +func (c *openShellClient) ListDelegatedIdentityCredentials(ctx context.Context, in *ListDelegatedIdentityCredentialsRequest, opts ...grpc.CallOption) (*ListDelegatedIdentityCredentialsResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ListDelegatedIdentityCredentialsResponse) + err := c.cc.Invoke(ctx, OpenShell_ListDelegatedIdentityCredentials_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *openShellClient) GetDelegatedIdentityCredentialStatus(ctx context.Context, in *GetDelegatedIdentityCredentialStatusRequest, opts ...grpc.CallOption) (*GetDelegatedIdentityCredentialStatusResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(GetDelegatedIdentityCredentialStatusResponse) + err := c.cc.Invoke(ctx, OpenShell_GetDelegatedIdentityCredentialStatus_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *openShellClient) RevokeDelegatedIdentityCredential(ctx context.Context, in *RevokeDelegatedIdentityCredentialRequest, opts ...grpc.CallOption) (*RevokeDelegatedIdentityCredentialResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(RevokeDelegatedIdentityCredentialResponse) + err := c.cc.Invoke(ctx, OpenShell_RevokeDelegatedIdentityCredential_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *openShellClient) DeleteDelegatedIdentityCredential(ctx context.Context, in *DeleteDelegatedIdentityCredentialRequest, opts ...grpc.CallOption) (*DeleteDelegatedIdentityCredentialResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(DeleteDelegatedIdentityCredentialResponse) + err := c.cc.Invoke(ctx, OpenShell_DeleteDelegatedIdentityCredential_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + func (c *openShellClient) DeleteProviderRefresh(ctx context.Context, in *DeleteProviderRefreshRequest, opts ...grpc.CallOption) (*DeleteProviderRefreshResponse, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(DeleteProviderRefreshResponse) @@ -1245,6 +1364,18 @@ type OpenShellServer interface { // CreateSandbox; callers never name a filesystem path themselves. Only a // client sharing the gateway's filesystem can complete the upload. BeginRootfsTarStaging(context.Context, *BeginRootfsTarStagingRequest) (*BeginRootfsTarStagingResponse, error) + // Inspect whether the authenticated user has a usable gateway-owned + // delegated identity authorization. + GetDelegatedIdentityAuthorizationStatus(context.Context, *GetDelegatedIdentityAuthorizationStatusRequest) (*GetDelegatedIdentityAuthorizationStatusResponse, error) + // Transfer a separate OAuth authorization grant to the gateway for use as + // the authenticated user's delegated identity. + AuthorizeDelegatedIdentity(context.Context, *AuthorizeDelegatedIdentityRequest) (*AuthorizeDelegatedIdentityResponse, error) + // Fetch delegated identity status for one sandbox. + GetSandboxDelegatedIdentityStatus(context.Context, *GetSandboxDelegatedIdentityStatusRequest) (*GetSandboxDelegatedIdentityStatusResponse, error) + // Withdraw delegated identity from one sandbox. + WithdrawSandboxDelegatedIdentity(context.Context, *WithdrawSandboxDelegatedIdentityRequest) (*WithdrawSandboxDelegatedIdentityResponse, error) + // Extend delegated identity for one sandbox. + ExtendSandboxDelegatedIdentity(context.Context, *ExtendSandboxDelegatedIdentityRequest) (*ExtendSandboxDelegatedIdentityResponse, error) // Fetch a sandbox by name. GetSandbox(context.Context, *GetSandboxRequest) (*SandboxResponse, error) // List sandboxes. @@ -1318,6 +1449,14 @@ type OpenShellServer interface { ConfigureProviderRefresh(context.Context, *ConfigureProviderRefreshRequest) (*ConfigureProviderRefreshResponse, error) // Record a gateway-owned refresh request for one provider credential. RotateProviderCredential(context.Context, *RotateProviderCredentialRequest) (*RotateProviderCredentialResponse, error) + // List delegated identity credentials visible to the caller. + ListDelegatedIdentityCredentials(context.Context, *ListDelegatedIdentityCredentialsRequest) (*ListDelegatedIdentityCredentialsResponse, error) + // Fetch delegated identity credential status. + GetDelegatedIdentityCredentialStatus(context.Context, *GetDelegatedIdentityCredentialStatusRequest) (*GetDelegatedIdentityCredentialStatusResponse, error) + // Revoke a delegated identity credential. + RevokeDelegatedIdentityCredential(context.Context, *RevokeDelegatedIdentityCredentialRequest) (*RevokeDelegatedIdentityCredentialResponse, error) + // Delete a delegated identity credential. + DeleteDelegatedIdentityCredential(context.Context, *DeleteDelegatedIdentityCredentialRequest) (*DeleteDelegatedIdentityCredentialResponse, error) // Delete gateway-owned refresh configuration for one provider credential. DeleteProviderRefresh(context.Context, *DeleteProviderRefreshRequest) (*DeleteProviderRefreshResponse, error) // Delete a provider by name. @@ -1471,6 +1610,21 @@ func (UnimplementedOpenShellServer) CreateSandbox(context.Context, *CreateSandbo func (UnimplementedOpenShellServer) BeginRootfsTarStaging(context.Context, *BeginRootfsTarStagingRequest) (*BeginRootfsTarStagingResponse, error) { return nil, status.Error(codes.Unimplemented, "method BeginRootfsTarStaging not implemented") } +func (UnimplementedOpenShellServer) GetDelegatedIdentityAuthorizationStatus(context.Context, *GetDelegatedIdentityAuthorizationStatusRequest) (*GetDelegatedIdentityAuthorizationStatusResponse, error) { + return nil, status.Error(codes.Unimplemented, "method GetDelegatedIdentityAuthorizationStatus not implemented") +} +func (UnimplementedOpenShellServer) AuthorizeDelegatedIdentity(context.Context, *AuthorizeDelegatedIdentityRequest) (*AuthorizeDelegatedIdentityResponse, error) { + return nil, status.Error(codes.Unimplemented, "method AuthorizeDelegatedIdentity not implemented") +} +func (UnimplementedOpenShellServer) GetSandboxDelegatedIdentityStatus(context.Context, *GetSandboxDelegatedIdentityStatusRequest) (*GetSandboxDelegatedIdentityStatusResponse, error) { + return nil, status.Error(codes.Unimplemented, "method GetSandboxDelegatedIdentityStatus not implemented") +} +func (UnimplementedOpenShellServer) WithdrawSandboxDelegatedIdentity(context.Context, *WithdrawSandboxDelegatedIdentityRequest) (*WithdrawSandboxDelegatedIdentityResponse, error) { + return nil, status.Error(codes.Unimplemented, "method WithdrawSandboxDelegatedIdentity not implemented") +} +func (UnimplementedOpenShellServer) ExtendSandboxDelegatedIdentity(context.Context, *ExtendSandboxDelegatedIdentityRequest) (*ExtendSandboxDelegatedIdentityResponse, error) { + return nil, status.Error(codes.Unimplemented, "method ExtendSandboxDelegatedIdentity not implemented") +} func (UnimplementedOpenShellServer) GetSandbox(context.Context, *GetSandboxRequest) (*SandboxResponse, error) { return nil, status.Error(codes.Unimplemented, "method GetSandbox not implemented") } @@ -1576,6 +1730,18 @@ func (UnimplementedOpenShellServer) ConfigureProviderRefresh(context.Context, *C func (UnimplementedOpenShellServer) RotateProviderCredential(context.Context, *RotateProviderCredentialRequest) (*RotateProviderCredentialResponse, error) { return nil, status.Error(codes.Unimplemented, "method RotateProviderCredential not implemented") } +func (UnimplementedOpenShellServer) ListDelegatedIdentityCredentials(context.Context, *ListDelegatedIdentityCredentialsRequest) (*ListDelegatedIdentityCredentialsResponse, error) { + return nil, status.Error(codes.Unimplemented, "method ListDelegatedIdentityCredentials not implemented") +} +func (UnimplementedOpenShellServer) GetDelegatedIdentityCredentialStatus(context.Context, *GetDelegatedIdentityCredentialStatusRequest) (*GetDelegatedIdentityCredentialStatusResponse, error) { + return nil, status.Error(codes.Unimplemented, "method GetDelegatedIdentityCredentialStatus not implemented") +} +func (UnimplementedOpenShellServer) RevokeDelegatedIdentityCredential(context.Context, *RevokeDelegatedIdentityCredentialRequest) (*RevokeDelegatedIdentityCredentialResponse, error) { + return nil, status.Error(codes.Unimplemented, "method RevokeDelegatedIdentityCredential not implemented") +} +func (UnimplementedOpenShellServer) DeleteDelegatedIdentityCredential(context.Context, *DeleteDelegatedIdentityCredentialRequest) (*DeleteDelegatedIdentityCredentialResponse, error) { + return nil, status.Error(codes.Unimplemented, "method DeleteDelegatedIdentityCredential not implemented") +} func (UnimplementedOpenShellServer) DeleteProviderRefresh(context.Context, *DeleteProviderRefreshRequest) (*DeleteProviderRefreshResponse, error) { return nil, status.Error(codes.Unimplemented, "method DeleteProviderRefresh not implemented") } @@ -1816,6 +1982,96 @@ func _OpenShell_BeginRootfsTarStaging_Handler(srv interface{}, ctx context.Conte return interceptor(ctx, in, info, handler) } +func _OpenShell_GetDelegatedIdentityAuthorizationStatus_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetDelegatedIdentityAuthorizationStatusRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(OpenShellServer).GetDelegatedIdentityAuthorizationStatus(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: OpenShell_GetDelegatedIdentityAuthorizationStatus_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(OpenShellServer).GetDelegatedIdentityAuthorizationStatus(ctx, req.(*GetDelegatedIdentityAuthorizationStatusRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _OpenShell_AuthorizeDelegatedIdentity_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(AuthorizeDelegatedIdentityRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(OpenShellServer).AuthorizeDelegatedIdentity(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: OpenShell_AuthorizeDelegatedIdentity_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(OpenShellServer).AuthorizeDelegatedIdentity(ctx, req.(*AuthorizeDelegatedIdentityRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _OpenShell_GetSandboxDelegatedIdentityStatus_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetSandboxDelegatedIdentityStatusRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(OpenShellServer).GetSandboxDelegatedIdentityStatus(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: OpenShell_GetSandboxDelegatedIdentityStatus_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(OpenShellServer).GetSandboxDelegatedIdentityStatus(ctx, req.(*GetSandboxDelegatedIdentityStatusRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _OpenShell_WithdrawSandboxDelegatedIdentity_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(WithdrawSandboxDelegatedIdentityRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(OpenShellServer).WithdrawSandboxDelegatedIdentity(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: OpenShell_WithdrawSandboxDelegatedIdentity_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(OpenShellServer).WithdrawSandboxDelegatedIdentity(ctx, req.(*WithdrawSandboxDelegatedIdentityRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _OpenShell_ExtendSandboxDelegatedIdentity_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ExtendSandboxDelegatedIdentityRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(OpenShellServer).ExtendSandboxDelegatedIdentity(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: OpenShell_ExtendSandboxDelegatedIdentity_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(OpenShellServer).ExtendSandboxDelegatedIdentity(ctx, req.(*ExtendSandboxDelegatedIdentityRequest)) + } + return interceptor(ctx, in, info, handler) +} + func _OpenShell_GetSandbox_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(GetSandboxRequest) if err := dec(in); err != nil { @@ -2417,6 +2673,78 @@ func _OpenShell_RotateProviderCredential_Handler(srv interface{}, ctx context.Co return interceptor(ctx, in, info, handler) } +func _OpenShell_ListDelegatedIdentityCredentials_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ListDelegatedIdentityCredentialsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(OpenShellServer).ListDelegatedIdentityCredentials(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: OpenShell_ListDelegatedIdentityCredentials_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(OpenShellServer).ListDelegatedIdentityCredentials(ctx, req.(*ListDelegatedIdentityCredentialsRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _OpenShell_GetDelegatedIdentityCredentialStatus_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetDelegatedIdentityCredentialStatusRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(OpenShellServer).GetDelegatedIdentityCredentialStatus(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: OpenShell_GetDelegatedIdentityCredentialStatus_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(OpenShellServer).GetDelegatedIdentityCredentialStatus(ctx, req.(*GetDelegatedIdentityCredentialStatusRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _OpenShell_RevokeDelegatedIdentityCredential_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(RevokeDelegatedIdentityCredentialRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(OpenShellServer).RevokeDelegatedIdentityCredential(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: OpenShell_RevokeDelegatedIdentityCredential_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(OpenShellServer).RevokeDelegatedIdentityCredential(ctx, req.(*RevokeDelegatedIdentityCredentialRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _OpenShell_DeleteDelegatedIdentityCredential_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(DeleteDelegatedIdentityCredentialRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(OpenShellServer).DeleteDelegatedIdentityCredential(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: OpenShell_DeleteDelegatedIdentityCredential_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(OpenShellServer).DeleteDelegatedIdentityCredential(ctx, req.(*DeleteDelegatedIdentityCredentialRequest)) + } + return interceptor(ctx, in, info, handler) +} + func _OpenShell_DeleteProviderRefresh_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(DeleteProviderRefreshRequest) if err := dec(in); err != nil { @@ -3167,6 +3495,26 @@ var OpenShell_ServiceDesc = grpc.ServiceDesc{ MethodName: "BeginRootfsTarStaging", Handler: _OpenShell_BeginRootfsTarStaging_Handler, }, + { + MethodName: "GetDelegatedIdentityAuthorizationStatus", + Handler: _OpenShell_GetDelegatedIdentityAuthorizationStatus_Handler, + }, + { + MethodName: "AuthorizeDelegatedIdentity", + Handler: _OpenShell_AuthorizeDelegatedIdentity_Handler, + }, + { + MethodName: "GetSandboxDelegatedIdentityStatus", + Handler: _OpenShell_GetSandboxDelegatedIdentityStatus_Handler, + }, + { + MethodName: "WithdrawSandboxDelegatedIdentity", + Handler: _OpenShell_WithdrawSandboxDelegatedIdentity_Handler, + }, + { + MethodName: "ExtendSandboxDelegatedIdentity", + Handler: _OpenShell_ExtendSandboxDelegatedIdentity_Handler, + }, { MethodName: "GetSandbox", Handler: _OpenShell_GetSandbox_Handler, @@ -3295,6 +3643,22 @@ var OpenShell_ServiceDesc = grpc.ServiceDesc{ MethodName: "RotateProviderCredential", Handler: _OpenShell_RotateProviderCredential_Handler, }, + { + MethodName: "ListDelegatedIdentityCredentials", + Handler: _OpenShell_ListDelegatedIdentityCredentials_Handler, + }, + { + MethodName: "GetDelegatedIdentityCredentialStatus", + Handler: _OpenShell_GetDelegatedIdentityCredentialStatus_Handler, + }, + { + MethodName: "RevokeDelegatedIdentityCredential", + Handler: _OpenShell_RevokeDelegatedIdentityCredential_Handler, + }, + { + MethodName: "DeleteDelegatedIdentityCredential", + Handler: _OpenShell_DeleteDelegatedIdentityCredential_Handler, + }, { MethodName: "DeleteProviderRefresh", Handler: _OpenShell_DeleteProviderRefresh_Handler, diff --git a/skills/debug-openshell-cluster/SKILL.md b/skills/debug-openshell-cluster/SKILL.md index 6c91410df7..a00a57483b 100644 --- a/skills/debug-openshell-cluster/SKILL.md +++ b/skills/debug-openshell-cluster/SKILL.md @@ -391,6 +391,11 @@ Common findings: - Sandbox image missing or pull denied: verify image reference and registry credentials. - Sandbox fails before readiness with an identity-resolution error: inspect the image's OCI `USER` and matching `/etc/passwd` and `/etc/group` entries, or explicitly set both process identity fields in policy. Numeric workload identities `1` through `4294967294` are accepted; root, the invalid identity sentinel, and missing identities are rejected. - Supervisor cannot connect: check its gateway endpoint and gateway logs. +- Exec fails with a Sandbox Protocol payload digest mismatch: compare the + configured `sandbox_runtime_image` and `supervisor_image` with the artifacts + actually running. Use compatible runtime versions and immutable image + references; a locally built supervisor paired with a published sandbox + runtime can fail even after the sandbox reaches Ready. - Inspect both Podman containers for the sandbox: the `sandbox` isolation role must have network mode `none`; the `supervisor` role owns the gateway session and egress. Both run non-root with all capabilities dropped. Check the private @@ -470,6 +475,17 @@ kubectl -n openshell rollout status /openshell kubectl -n openshell logs -c openshell-gateway --tail=200 ``` +For delegated identity duration errors, compare the Helm value with the rendered +gateway configuration. `server.maxDelegatedIdentityDurationSecs` renders +`[openshell.gateway] max_delegated_identity_duration_secs` and defaults to +`86400` seconds. A negative Helm value fails rendering; a requested delegation +longer than the rendered maximum fails at the gateway: + +```bash +helm -n openshell get values openshell | rg -n 'maxDelegatedIdentityDurationSecs' +kubectl -n openshell get configmap openshell-config -o jsonpath='{.data.gateway\.toml}' | rg -n 'max_delegated_identity_duration_secs' +``` + `server.telemetryEnabled` renders `OPENSHELL_TELEMETRY_ENABLED` on the gateway pod, and the gateway propagates the effective value to sandbox supervisors. diff --git a/skills/openshell-cli/SKILL.md b/skills/openshell-cli/SKILL.md index 75d2de5057..12c56879d8 100644 --- a/skills/openshell-cli/SKILL.md +++ b/skills/openshell-cli/SKILL.md @@ -232,9 +232,10 @@ openshell provider refresh rotate my-provider --credential-key ACCESS_TOKEN Prefer `--secret-material-env KEY[=ENVVAR]` for secret refresh material. `--material KEY=VALUE` is for non-secret material; `--secret-material-key` marks supplied material keys as secret. -The gateway stores secret refresh material through its active credential driver. -With Vault selected, refresh tokens, client secrets, and private keys live in -Vault alongside injectable provider credentials; refresh state contains only +The gateway stores secret refresh material and delegated identity OAuth tokens +through its active credential driver. With Vault selected, refresh tokens, +delegated access tokens, client secrets, and private keys live in Vault +alongside injectable provider credentials; generic gateway records contain only opaque handles. A credential-backend read or write failure makes refresh fail closed rather than falling back to inline storage. Before OpenShell 0.1.0, the gateway does not migrate legacy inline refresh material or move secrets between @@ -243,6 +244,28 @@ reconfigure credentials while the original backend remains available before changing backends. Do not run mixed gateway versions against the same refresh records. +Sandbox delegated identity uses an OAuth authorization separate from the normal +CLI login. On the first `sandbox create --use-my-identity-for=`, or +after the gateway-owned grant becomes unusable, the CLI opens the browser for a +separate Authorization Code flow with PKCE for `openid offline_access` and +transfers that bundle to the gateway. It may reuse the existing browser SSO +session, but never stores the delegated bundle in the local `oidc_token.json`; +gateway RPCs continue to use the normal CLI bearer. The IdP must enable the +authorization code flow, the CLI's loopback callback URI, and offline access for +the configured client, and issue a distinct refresh-token chain for the second +authorization. A healthy gateway-owned grant is reused without prompting. + +Before helping a user delegate identity, explain that they must trust the +administrators who control attached providers and profiles with their subject +access token. Delegated-sandbox ownership restricts direct access, but mutable +exchange configuration can redirect that token. Refer to the published +[profile trust requirements](https://docs.nvidia.com/openshell/latest/providers/profiles.md). + +To repair an inactive or revoked delegated grant, retry sandbox create or run +`openshell sandbox delegated-identity extend --for=` and +complete the new browser authorization. Gateway logout/login affects only the +normal CLI login and does not repair or revoke delegated identity. + Gateway-managed refresh credentials use an identity-stable workload handle. Routine automatic refresh and `provider refresh rotate` update the access token behind that handle, so long-running processes do not need to restart. Running @@ -536,6 +559,29 @@ for verification behavior and release compatibility. For an infrastructure `Error` on a backend that supports restarting stopped sandboxes, fix the reported cause, run `sandbox stop`, and wait for it to succeed before running `sandbox start`. This retains the sandbox and persistent workspace but cannot recover deleted resources or storage. For a provisioning timeout, wait for cleanup to finish, then run `sandbox start` directly. +A sandbox created with delegated identity has a narrower authorization model. +Only its original delegator can start or interact with it. Workspace and +Platform Admins can stop it for immediate containment or delete it to release +resources, but cannot start it; other Workspace Users cannot perform those +operations. Withdrawal is not instantaneous containment. Authorization checks +reject requests when they observe delegation expiry, withdrawal, or credential +revocation, but already-authorized refreshes or exchanges may finish and populate +token caches. Each supervisor token-exchange cache entry lasts at most five +minutes from insertion, not from withdrawal. Stop or delete the sandbox when +continued execution is unacceptable, and use the IdP's revocation controls when +the upstream grant must also be revoked. +Already issued access tokens remain subject to the upstream resource server's +expiry and revocation behavior. + +Use `openshell sandbox create --use-my-identity-for=` to establish the +initial per-sandbox window. The CLI prompts for the separate delegated grant +only when the gateway does not already hold a usable one for that user. Use +`openshell sandbox delegated-identity status`, `extend`, and `withdraw` for one +sandbox. Platform admins use `openshell delegated-credential list`, `status`, +`revoke`, and `delete` for the shared gateway credential; these admin commands +cannot authorize a grant on another user's behalf. Credential listing uses +`--page-size` and `--page-token`; structured output includes the next token. + --- ## Workflow 4: Policy Iteration Loop