From bbd7377fa0accd7be06fe651cd79f5034ab92dbe Mon Sep 17 00:00:00 2001 From: Gordon Sim Date: Fri, 19 Jun 2026 18:16:47 +0100 Subject: [PATCH 1/8] feat(provider): add ability to request token exchange instead of client credentials as OAuth grant_type Signed-off-by: Gordon Sim --- .../skills/debug-openshell-cluster/SKILL.md | 12 +- Cargo.lock | 277 ++- Cargo.toml | 2 +- architecture/sandbox.md | 29 +- crates/openshell-cli/src/main.rs | 74 +- crates/openshell-cli/src/oidc_auth.rs | 22 +- crates/openshell-cli/src/run.rs | 301 ++- .../tests/ensure_providers_integration.rs | 10 +- .../openshell-cli/tests/mtls_integration.rs | 16 +- .../tests/provider_commands_integration.rs | 104 +- .../sandbox_create_lifecycle_integration.rs | 10 +- .../sandbox_name_fallback_integration.rs | 10 +- crates/openshell-core/src/grpc_client.rs | 67 +- crates/openshell-core/src/lib.rs | 1 + crates/openshell-core/src/spiffe.rs | 177 ++ crates/openshell-providers/src/profiles.rs | 356 ++- crates/openshell-sdk/tests/client_mock.rs | 7 + crates/openshell-server/Cargo.toml | 1 + crates/openshell-server/src/auth/oidc.rs | 6 + .../openshell-server/src/auth/sandbox_jwt.rs | 8 + .../src/auth/sandbox_methods.rs | 3 + crates/openshell-server/src/grpc/mod.rs | 10 +- crates/openshell-server/src/grpc/provider.rs | 786 ++++++- crates/openshell-server/src/lib.rs | 4 + .../openshell-server/src/provider_refresh.rs | 2 + crates/openshell-server/tests/common/mod.rs | 12 +- .../tests/supervisor_relay_integration.rs | 6 + .../src/l7/token_grant_injection.rs | 46 +- .../openshell-supervisor-network/src/lib.rs | 1 - .../openshell-supervisor-network/src/proxy.rs | 52 +- .../src/spiffe_endpoint.rs | 17 - .../src/token_grant.rs | 430 +++- .../openshell-supervisor-process/src/run.rs | 12 +- deploy/helm/openshell/README.md | 19 +- deploy/helm/openshell/README.md.gotmpl | 15 +- .../openshell/templates/_gateway-workload.tpl | 18 +- .../openshell/tests/gateway_config_test.yaml | 36 +- deploy/helm/openshell/values.yaml | 12 +- docs/kubernetes/access-control.mdx | 8 +- docs/reference/gateway-config.mdx | 8 + docs/sandboxes/manage-providers.mdx | 27 + docs/sandboxes/providers-v2.mdx | 59 +- proto/openshell.proto | 60 + .../v1/internal/converter/coverage_test.go | 3 + .../v1/internal/converter/profile.go | 52 + .../v1/internal/converter/profile_test.go | 26 + sdk/go/openshell/v1/types/profile.go | 19 + sdk/go/proto/openshellv1/openshell.pb.go | 2089 ++++++++++------- sdk/go/proto/openshellv1/openshell_grpc.pb.go | 42 + 49 files changed, 4111 insertions(+), 1253 deletions(-) create mode 100644 crates/openshell-core/src/spiffe.rs delete mode 100644 crates/openshell-supervisor-network/src/spiffe_endpoint.rs diff --git a/.agents/skills/debug-openshell-cluster/SKILL.md b/.agents/skills/debug-openshell-cluster/SKILL.md index 6a9bcd25b9..19bcaf2b68 100644 --- a/.agents/skills/debug-openshell-cluster/SKILL.md +++ b/.agents/skills/debug-openshell-cluster/SKILL.md @@ -354,14 +354,20 @@ kubectl -n openshell get statefulset openshell -o jsonpath='{.spec.template.spec If `server.providerTokenGrants.spiffe.enabled=true`, the gateway should still render `[openshell.gateway.gateway_jwt]` and mount the `sandbox-jwt` Secret. -SPIRE is used only by sandbox pods for dynamic provider token grants. Verify -that SPIRE is installed, the CSI driver is available, and the Kubernetes driver -config includes `provider_spiffe_workload_api_socket_path`: +SPIRE is used by both the gateway and sandbox supervisors for dynamic provider +token grants. The gateway pod must mount the `spiffe-workload-api` CSI volume +and set `OPENSHELL_GATEWAY_SPIFFE_WORKLOAD_API_SOCKET`; sandbox pods must +receive the matching Workload API socket from the Kubernetes driver config. +The gateway verifies supervisor JWT-SVIDs from JWT bundles fetched through this +Workload API socket, not from the SPIRE OIDC discovery endpoint. +Verify that SPIRE is installed, the CSI driver is available, and the Kubernetes +driver config includes `provider_spiffe_workload_api_socket_path`: ```bash helm -n openshell get values openshell | grep -E 'providerTokenGrants|workloadApiSocketPath' kubectl get pods -A | grep -E 'spire|spiffe' kubectl -n openshell get configmap openshell-config -o yaml | grep provider_spiffe_workload_api_socket_path +kubectl -n openshell get pod -l app.kubernetes.io/name=helm-chart -o jsonpath="{.items[*].spec.containers[*].env[?(@.name==\"OPENSHELL_GATEWAY_SPIFFE_WORKLOAD_API_SOCKET\")].value}{\"\n\"}" ``` Sandbox pods using provider token grants should have an diff --git a/Cargo.lock b/Cargo.lock index c30f890914..b3dd2dbc03 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -690,6 +690,12 @@ dependencies = [ "backtrace", ] +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + [[package]] name = "base16ct" version = "1.0.0" @@ -1278,6 +1284,18 @@ dependencies = [ "winapi", ] +[[package]] +name = "crypto-bigint" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +dependencies = [ + "generic-array 0.14.7", + "rand_core 0.6.4", + "subtle", + "zeroize", +] + [[package]] name = "crypto-bigint" version = "0.7.5" @@ -1322,7 +1340,7 @@ version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3633a51a39c69ebbaa4feaa694bd83d241e4093901c84a0963b19d9bb3f0cf8f" dependencies = [ - "crypto-bigint", + "crypto-bigint 0.7.5", "rand_core 0.10.1", ] @@ -1345,6 +1363,22 @@ dependencies = [ "subtle", ] +[[package]] +name = "curve25519-dalek" +version = "4.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "curve25519-dalek-derive", + "digest 0.10.7", + "fiat-crypto 0.2.9", + "rustc_version", + "subtle", + "zeroize", +] + [[package]] name = "curve25519-dalek" version = "5.0.0" @@ -1355,7 +1389,7 @@ dependencies = [ "cpufeatures 0.3.0", "curve25519-dalek-derive", "digest 0.11.2", - "fiat-crypto", + "fiat-crypto 0.3.0", "rand_core 0.10.1", "rustc_version", "subtle", @@ -1605,6 +1639,20 @@ version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" +[[package]] +name = "ecdsa" +version = "0.16.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" +dependencies = [ + "der 0.7.10", + "digest 0.10.7", + "elliptic-curve 0.13.8", + "rfc6979 0.4.0", + "signature 2.2.0", + "spki 0.7.3", +] + [[package]] name = "ecdsa" version = "0.17.0" @@ -1613,13 +1661,23 @@ checksum = "c0681a4fc24c767085329728d8dfba959af91228aa4610cca4f8ce317ba46ae0" dependencies = [ "der 0.8.0", "digest 0.11.2", - "elliptic-curve", - "rfc6979", + "elliptic-curve 0.14.1", + "rfc6979 0.6.0", "signature 3.0.0", "spki 0.8.0", "zeroize", ] +[[package]] +name = "ed25519" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" +dependencies = [ + "pkcs8 0.10.2", + "signature 2.2.0", +] + [[package]] name = "ed25519" version = "3.0.0" @@ -1630,14 +1688,28 @@ dependencies = [ "signature 3.0.0", ] +[[package]] +name = "ed25519-dalek" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9" +dependencies = [ + "curve25519-dalek 4.1.3", + "ed25519 2.2.3", + "serde", + "sha2 0.10.9", + "subtle", + "zeroize", +] + [[package]] name = "ed25519-dalek" version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ebaa1a2bf1290ab3bfe5a7b771d050ebffab2711c19a81691c683a5144a25de" dependencies = [ - "curve25519-dalek", - "ed25519", + "curve25519-dalek 5.0.0", + "ed25519 3.0.0", "rand_core 0.10.1", "serde", "sha2 0.11.0", @@ -1655,24 +1727,45 @@ dependencies = [ "serde", ] +[[package]] +name = "elliptic-curve" +version = "0.13.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" +dependencies = [ + "base16ct 0.2.0", + "crypto-bigint 0.5.5", + "digest 0.10.7", + "ff 0.13.1", + "generic-array 0.14.7", + "group 0.13.0", + "hkdf 0.12.4", + "pem-rfc7468 0.7.0", + "pkcs8 0.10.2", + "rand_core 0.6.4", + "sec1 0.7.3", + "subtle", + "zeroize", +] + [[package]] name = "elliptic-curve" version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d65aa39b3a5c1c9c1b745c9a019234bb7a21b77abcb4f4d266d706e2d577d65" dependencies = [ - "base16ct", - "crypto-bigint", + "base16ct 1.0.0", + "crypto-bigint 0.7.5", "crypto-common 0.2.2", "digest 0.11.2", - "ff", - "group", + "ff 0.14.0", + "group 0.14.0", "hkdf 0.13.0", "hybrid-array", "pem-rfc7468 1.0.0", "pkcs8 0.11.0", "rand_core 0.10.1", - "sec1", + "sec1 0.8.1", "subtle", "zeroize", ] @@ -1759,6 +1852,16 @@ version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" +[[package]] +name = "ff" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" +dependencies = [ + "rand_core 0.6.4", + "subtle", +] + [[package]] name = "ff" version = "0.14.0" @@ -1769,6 +1872,12 @@ dependencies = [ "subtle", ] +[[package]] +name = "fiat-crypto" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" + [[package]] name = "fiat-crypto" version = "0.3.0" @@ -1968,6 +2077,7 @@ checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", + "zeroize", ] [[package]] @@ -2069,13 +2179,24 @@ dependencies = [ "regex-syntax", ] +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff 0.13.1", + "rand_core 0.6.4", + "subtle", +] + [[package]] name = "group" version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7fd1a1c7a5206c5b7a3f5a0d7ccd3ff85d0c8f5133d62a02680255b0004af5f4" dependencies = [ - "ff", + "ff 0.14.0", "rand_core 0.10.1", "subtle", ] @@ -2868,11 +2989,18 @@ checksum = "0529410abe238729a60b108898784df8984c87f6054c9c4fcacc47e4803c1ce1" dependencies = [ "aws-lc-rs", "base64 0.22.1", + "ed25519-dalek 2.2.0", "getrandom 0.2.17", + "hmac 0.12.1", "js-sys", + "p256 0.13.2", + "p384 0.13.1", "pem", + "rand 0.8.6", + "rsa 0.9.10", "serde", "serde_json", + "sha2 0.10.9", "signature 2.2.0", "simple_asn1", ] @@ -4156,6 +4284,7 @@ dependencies = [ "serde_json", "sha2 0.10.9", "socket2 0.6.3", + "spiffe", "sqlx", "tempfile", "thiserror 2.0.18", @@ -4434,30 +4563,54 @@ version = "4.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d211803b9b6b570f68772237e415a029d5a50c65d382910b879fb19d3271f94d" +[[package]] +name = "p256" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" +dependencies = [ + "ecdsa 0.16.9", + "elliptic-curve 0.13.8", + "primeorder 0.13.6", + "sha2 0.10.9", +] + [[package]] name = "p256" version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2c9239b2dbc807adbbe147e8cf72ea7450c3a0aabe62cb8e75ff4ec22e1f72a" dependencies = [ - "ecdsa", - "elliptic-curve", + "ecdsa 0.17.0", + "elliptic-curve 0.14.1", "primefield", - "primeorder", + "primeorder 0.14.0", "sha2 0.11.0", ] +[[package]] +name = "p384" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe42f1670a52a47d448f14b6a5c61dd78fce51856e68edaa38f7ae3a46b8d6b6" +dependencies = [ + "ecdsa 0.16.9", + "elliptic-curve 0.13.8", + "primeorder 0.13.6", + "sha2 0.10.9", +] + [[package]] name = "p384" version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d17b851e6b3e378ab4ecb07fa2ed23f4d15f075735f8fec9fa1e7bdce5f8301f" dependencies = [ - "ecdsa", - "elliptic-curve", - "fiat-crypto", + "ecdsa 0.17.0", + "elliptic-curve 0.14.1", + "fiat-crypto 0.3.0", "primefield", - "primeorder", + "primeorder 0.14.0", "sha2 0.11.0", ] @@ -4467,11 +4620,11 @@ version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ad64cc32c2dc466317c12ee5853e61f159f9eab1fe7efade0395dc2e7b43449" dependencies = [ - "base16ct", - "ecdsa", - "elliptic-curve", + "base16ct 1.0.0", + "ecdsa 0.17.0", + "elliptic-curve 0.14.1", "primefield", - "primeorder", + "primeorder 0.14.0", "sha2 0.11.0", ] @@ -4846,21 +4999,30 @@ version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c555a6e4eb7d4e158fcb028c835c3b8642206ddc279b5c6b202ef9a8bdb592f4" dependencies = [ - "crypto-bigint", + "crypto-bigint 0.7.5", "crypto-common 0.2.2", - "ff", + "ff 0.14.0", "rand_core 0.10.1", "subtle", "zeroize", ] +[[package]] +name = "primeorder" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" +dependencies = [ + "elliptic-curve 0.13.8", +] + [[package]] name = "primeorder" version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c9f42978c78a00e3d68f69fc03e57a234debae69da4020a4fb588fcdcd07b06" dependencies = [ - "elliptic-curve", + "elliptic-curve 0.14.1", "once_cell", "primefield", "serdect", @@ -5420,13 +5582,23 @@ dependencies = [ "web-sys", ] +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac 0.12.1", + "subtle", +] + [[package]] name = "rfc6979" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4a459cddafb3fe76b31fd8f1108007566c40301feb64dc7b54656eb7388172b" dependencies = [ - "crypto-bigint", + "crypto-bigint 0.7.5", "hmac 0.13.0", ] @@ -5483,7 +5655,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "30b2aa4ba0d89f73d1e332df05be0eeab8840351c36ca5654341dfdb57bb3caf" dependencies = [ "const-oid 0.10.2", - "crypto-bigint", + "crypto-bigint 0.7.5", "crypto-primes", "digest 0.11.2", "pkcs1 0.8.0-rc.4", @@ -5509,16 +5681,16 @@ dependencies = [ "bytes", "cbc", "cipher", - "crypto-bigint", + "crypto-bigint 0.7.5", "ctr", - "curve25519-dalek", + "curve25519-dalek 5.0.0", "data-encoding", "delegate", "der 0.8.0", "digest 0.11.2", - "ecdsa", - "ed25519-dalek", - "elliptic-curve", + "ecdsa 0.17.0", + "ed25519-dalek 3.0.0", + "elliptic-curve 0.14.1", "enum_dispatch", "flate2", "futures", @@ -5535,8 +5707,8 @@ dependencies = [ "ml-kem", "module-lattice", "num-bigint", - "p256", - "p384", + "p256 0.14.0", + "p384 0.14.0", "p521", "pageant", "pbkdf2", @@ -5551,7 +5723,7 @@ dependencies = [ "russh-util", "salsa20", "scrypt", - "sec1", + "sec1 0.8.1", "sha1 0.11.0", "sha2 0.11.0", "sha3 0.12.0", @@ -5853,13 +6025,27 @@ dependencies = [ "untrusted 0.9.0", ] +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct 0.2.0", + "der 0.7.10", + "generic-array 0.14.7", + "pkcs8 0.10.2", + "subtle", + "zeroize", +] + [[package]] name = "sec1" version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d56d437c2f19203ce5f7122e507831de96f3d2d4d3be5af44a0b0a09d8a80e4d" dependencies = [ - "base16ct", + "base16ct 1.0.0", "ctutils", "der 0.8.0", "hybrid-array", @@ -6056,7 +6242,7 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9af4a3e75ebd5599b30d4de5768e00b5095d518a79fefc3ecbaf77e665d1ec06" dependencies = [ - "base16ct", + "base16ct 1.0.0", "serde", ] @@ -6274,6 +6460,7 @@ dependencies = [ "fastrand", "futures", "hyper-util", + "jsonwebtoken", "log", "prost", "prost-types", @@ -6540,7 +6727,7 @@ checksum = "7b54d0ed0498daf3f78d82e00e28c8eec9d75a067c4cfbcc7a0f7d0f4077749e" dependencies = [ "base64ct", "bytes", - "crypto-bigint", + "crypto-bigint 0.7.5", "ctutils", "digest 0.11.2", "pem-rfc7468 1.0.0", @@ -6556,15 +6743,15 @@ dependencies = [ "argon2", "bcrypt-pbkdf", "ctutils", - "ed25519-dalek", + "ed25519-dalek 3.0.0", "hex", "hmac 0.13.0", - "p256", - "p384", + "p256 0.14.0", + "p384 0.14.0", "p521", "rand_core 0.10.1", "rsa 0.10.0-rc.18", - "sec1", + "sec1 0.8.1", "sha1 0.11.0", "sha2 0.11.0", "signature 3.0.0", @@ -8338,8 +8525,8 @@ version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab12e7090f27e2ffd9322651492942d50c2926094af30601e1964337db39daf1" dependencies = [ - "ff", - "group", + "ff 0.14.0", + "group 0.14.0", "hybrid-array", ] diff --git a/Cargo.toml b/Cargo.toml index 150df10d69..c7eb364acf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -105,7 +105,7 @@ rand = "0.9" jsonwebtoken = { version = "10", features = ["aws_lc_rs"] } getrandom = "0.3" ring = "0.17" -spiffe = { version = "0.15", default-features = false, features = ["workload-api-jwt", "tracing"] } +spiffe = { version = "0.15", default-features = false, features = ["workload-api-jwt", "jwt-verify-rust-crypto", "tracing"] } # Filesystem embedding include_dir = "0.7" diff --git a/architecture/sandbox.md b/architecture/sandbox.md index e6ec528bd8..8ee43094b3 100644 --- a/architecture/sandbox.md +++ b/architecture/sandbox.md @@ -262,12 +262,29 @@ placeholders for rotating provider credentials; provider environment keys beginning with `v_` are reserved for that placeholder namespace. Provider profiles can also declare dynamic token grants. For matching HTTP -endpoints, the supervisor obtains a SPIFFE JWT-SVID from the local Workload API, -exchanges it for an OAuth2 access token, caches the token, and injects it as an -`Authorization: Bearer` header before forwarding the request. Token grant -endpoints are HTTPS-only except for loopback and Kubernetes service DNS hosts, -and returned access tokens must be bearer-compatible before they are cached or -injected. Token caching follows response-derived and profile override TTL rules. +endpoints, the supervisor obtains or exchanges OAuth2 access tokens, caches +them, and injects them before forwarding the request. `client_credentials` +grants use the supervisor SPIFFE JWT-SVID directly as the client assertion. +`token_exchange` grants ask the gateway to broker an intermediate token using a +stored provider subject credential and the gateway's own SPIFFE JWT-SVID; the +supervisor then exchanges that intermediate token for the final upstream token +using its own JWT-SVID. The gateway validates that its own JWT-SVID has the +requested audience, a SPIFFE subject, and a non-expired `exp` claim when +present. It also validates that the stored subject credential is declared by the +provider profile, and that the supervisor JWT-SVID is a well-formed +three-segment JWT with a SPIFFE subject in the same trust domain as the gateway +SVID. The gateway verifies the supervisor JWT-SVID signature with JWT bundles +fetched from its SPIFFE Workload API. Token grant endpoints are HTTPS-only +except for loopback and Kubernetes service DNS hosts, and returned access tokens +must be bearer-compatible before they are cached or injected. Token response +lifetimes are capped and cached with an expiry margin unless a profile supplies +an explicit cache TTL override. Cache entries are scoped by the sandbox provider +environment revision so provider credential updates miss the old token cache +without changing endpoint matching semantics. Gateway-brokered intermediate +tokens are cached separately by provider resource version, supervisor SPIFFE +subject, and gateway SPIFFE subject, and their cache lifetime is capped by the +intermediate token response, stored subject-token expiry, and supervisor SVID +expiry. For AWS endpoints that require request-level signing, the proxy supports SigV4 re-signing. When `credential_signing: sigv4` is set on an L7 endpoint, the proxy diff --git a/crates/openshell-cli/src/main.rs b/crates/openshell-cli/src/main.rs index 7cefd3669a..5fceabf08a 100644 --- a/crates/openshell-cli/src/main.rs +++ b/crates/openshell-cli/src/main.rs @@ -811,7 +811,7 @@ impl From for openshell_cli::ssh::Editor { #[derive(Subcommand, Debug)] enum ProviderCommands { /// Create a provider config. - #[command(group = clap::ArgGroup::new("cred_source").required(true).args(["from_existing", "credentials", "from_gcloud_adc", "runtime_credentials"]), help_template = LEAF_HELP_TEMPLATE, next_help_heading = "FLAGS")] + #[command(group = clap::ArgGroup::new("cred_source").required(true).multiple(true).args(["from_existing", "credentials", "from_gcloud_adc", "runtime_credentials", "from_oidc_token"]), help_template = LEAF_HELP_TEMPLATE, next_help_heading = "FLAGS")] Create { /// Provider name. #[arg(long)] @@ -822,7 +822,7 @@ enum ProviderCommands { provider_type: String, /// Load provider credentials/config from existing local state. - #[arg(long, conflicts_with_all = ["credentials", "from_gcloud_adc", "runtime_credentials"])] + #[arg(long, conflicts_with_all = ["credentials", "from_gcloud_adc", "runtime_credentials", "from_oidc_token"])] from_existing: bool, /// Provider credential pair (`KEY=VALUE`) or env lookup key (`KEY`). @@ -836,11 +836,15 @@ enum ProviderCommands { /// Configure credentials from gcloud Application Default Credentials /// (`~/.config/gcloud/application_default_credentials.json`). /// Valid for providers whose profile declares an ADC-compatible credential. - #[arg(long, group = "cred_source", conflicts_with_all = ["from_existing", "credentials", "runtime_credentials"])] + #[arg(long, group = "cred_source", conflicts_with_all = ["from_existing", "credentials", "runtime_credentials", "from_oidc_token"])] from_gcloud_adc: bool, + /// Store the active gateway OIDC access token as the named provider credential. + #[arg(long, group = "cred_source", conflicts_with_all = ["from_existing", "from_gcloud_adc", "runtime_credentials"])] + from_oidc_token: bool, + /// Create a provider whose required credentials are resolved at runtime by the gateway/sandbox. - #[arg(long, conflicts_with_all = ["from_existing", "credentials", "from_gcloud_adc"])] + #[arg(long, conflicts_with_all = ["from_existing", "credentials", "from_gcloud_adc", "from_oidc_token"])] runtime_credentials: bool, /// Provider config key/value pair. @@ -913,9 +917,13 @@ enum ProviderCommands { name: String, /// Re-discover credentials from existing local state (e.g. env vars, config files). - #[arg(long, conflicts_with = "credentials")] + #[arg(long, conflicts_with_all = ["credentials", "from_oidc_token"])] from_existing: bool, + /// Store the active gateway OIDC access token as the named provider credential. + #[arg(long, conflicts_with = "from_existing")] + from_oidc_token: bool, + /// Provider credential pair (`KEY=VALUE`) or env lookup key (`KEY`). #[arg( long = "credential", @@ -3352,24 +3360,34 @@ async fn run_async() -> Result<()> { from_existing, credentials, from_gcloud_adc, + from_oidc_token, runtime_credentials, config, global_profile, } => { let profile_ws = if global_profile { "" } else { &cli.workspace }; - run::provider_create_with_options( - endpoint, - &name, - provider_type.as_str(), - from_existing, - &credentials, - from_gcloud_adc, - runtime_credentials, - &config, - &cli.workspace, - profile_ws, - &tls, - ) + let credential_source = if from_existing { + run::ProviderCreateCredentialSource::Existing + } else if from_gcloud_adc { + run::ProviderCreateCredentialSource::GcloudAdc + } else if from_oidc_token { + run::ProviderCreateCredentialSource::OidcToken + } else if runtime_credentials { + run::ProviderCreateCredentialSource::Runtime + } else { + run::ProviderCreateCredentialSource::ExplicitCredentials + }; + run::provider_create_with_options(run::ProviderCreateOptions { + server: endpoint, + name: &name, + provider_type: provider_type.as_str(), + credentials: &credentials, + credential_source, + config: &config, + workspace: &cli.workspace, + profile_workspace: profile_ws, + tls: &tls, + }) .await?; } ProviderCommands::Refresh(command) => match command { @@ -3522,20 +3540,22 @@ async fn run_async() -> Result<()> { ProviderCommands::Update { name, from_existing, + from_oidc_token, credentials, config, credential_expires_at, } => { - run::provider_update( - endpoint, - &name, + run::provider_update(run::ProviderUpdateOptions { + server: endpoint, + name: &name, from_existing, - &credentials, - &config, - &credential_expires_at, - &cli.workspace, - &tls, - ) + from_oidc_token, + credentials: &credentials, + config: &config, + credential_expires_at: &credential_expires_at, + workspace: &cli.workspace, + tls: &tls, + }) .await?; } ProviderCommands::Delete { names } => { diff --git a/crates/openshell-cli/src/oidc_auth.rs b/crates/openshell-cli/src/oidc_auth.rs index 2aacdb0c9c..291108f311 100644 --- a/crates/openshell-cli/src/oidc_auth.rs +++ b/crates/openshell-cli/src/oidc_auth.rs @@ -277,10 +277,11 @@ fn bundle_from_refresh_output( } } -/// Ensure we have a valid OIDC token for the given gateway, refreshing if needed. -/// -/// Returns the access token string. -pub async fn ensure_valid_oidc_token(gateway_name: &str, insecure: bool) -> Result { +/// Ensure we have a valid OIDC token bundle for the given gateway, refreshing if needed. +pub async fn ensure_valid_oidc_token_bundle( + gateway_name: &str, + insecure: bool, +) -> Result { let bundle = openshell_bootstrap::oidc_token::load_oidc_token(gateway_name).ok_or_else(|| { miette::miette!( @@ -290,7 +291,7 @@ pub async fn ensure_valid_oidc_token(gateway_name: &str, insecure: bool) -> Resu })?; if !openshell_bootstrap::oidc_token::is_token_expired(&bundle) { - return Ok(bundle.access_token); + return Ok(bundle); } debug!( @@ -302,7 +303,16 @@ pub async fn ensure_valid_oidc_token(gateway_name: &str, insecure: bool) -> Resu .and_then(|metadata| metadata.oidc_scopes); let refreshed = oidc_refresh_token(&bundle, scopes.as_deref(), insecure).await?; openshell_bootstrap::oidc_token::store_oidc_token(gateway_name, &refreshed)?; - Ok(refreshed.access_token) + Ok(refreshed) +} + +/// Ensure we have a valid OIDC token for the given gateway, refreshing if needed. +/// +/// Returns the access token string. +pub async fn ensure_valid_oidc_token(gateway_name: &str, insecure: bool) -> Result { + Ok(ensure_valid_oidc_token_bundle(gateway_name, insecure) + .await? + .access_token) } // ── Helpers ────────────────────────────────────────────────────────── diff --git a/crates/openshell-cli/src/run.rs b/crates/openshell-cli/src/run.rs index e376d08dc5..4e8de99862 100644 --- a/crates/openshell-cli/src/run.rs +++ b/crates/openshell-cli/src/run.rs @@ -47,13 +47,14 @@ use openshell_core::proto::{ LintProviderProfilesRequest, ListProviderProfilesRequest, ListProvidersRequest, ListSandboxPoliciesRequest, ListSandboxProvidersRequest, ListSandboxesRequest, ListServicesRequest, PolicySource, PolicyStatus, Provider, ProviderCredentialRefreshStatus, - ProviderCredentialRefreshStrategy, ProviderProfile, ProviderProfileDiagnostic, - ProviderProfileImportItem, RejectDraftChunkRequest, ResourceRequirements, - RevokeSshSessionRequest, RotateProviderCredentialRequest, Sandbox, SandboxPhase, SandboxPolicy, - SandboxSpec, SandboxTemplate, ServiceEndpointResponse, SetInferenceRouteRequest, SettingScope, - StartSandboxRequest, StopSandboxRequest, TcpForwardFrame, TcpForwardInit, TcpRelayTarget, - UpdateConfigRequest, UpdateProviderProfilesRequest, UpdateProviderRequest, WatchSandboxRequest, - exec_sandbox_event, setting_value, tcp_forward_init, + ProviderCredentialRefreshStrategy, ProviderCredentialTokenGrantType, ProviderProfile, + ProviderProfileDiagnostic, ProviderProfileImportItem, RejectDraftChunkRequest, + ResourceRequirements, RevokeSshSessionRequest, RotateProviderCredentialRequest, Sandbox, + SandboxPhase, SandboxPolicy, SandboxSpec, SandboxTemplate, ServiceEndpointResponse, + SetInferenceRouteRequest, SettingScope, StartSandboxRequest, StopSandboxRequest, + TcpForwardFrame, TcpForwardInit, TcpRelayTarget, UpdateConfigRequest, + UpdateProviderProfilesRequest, UpdateProviderRequest, WatchSandboxRequest, exec_sandbox_event, + setting_value, tcp_forward_init, }; use openshell_core::settings; use openshell_core::{ObjectId, ObjectName, ObjectWorkspace}; @@ -3380,6 +3381,126 @@ fn missing_credentials_error(provider_type: &str) -> miette::Report { ) } +async fn provider_credential_from_oidc_token( + credentials: &[String], + profile: Option<&ProviderProfile>, + tls: &TlsOptions, +) -> Result<(HashMap, HashMap)> { + let credential_key = oidc_subject_credential_key(credentials, profile)?; + + let gateway_name = tls.gateway_name().ok_or_else(|| { + miette::miette!("--from-oidc-token requires an active named OIDC gateway") + })?; + let bundle = + crate::oidc_auth::ensure_valid_oidc_token_bundle(gateway_name, tls.gateway_insecure) + .await + .map_err(|err| { + miette::miette!( + "failed to load or refresh OIDC token for gateway '{gateway_name}' while preparing provider credential: {err}" + ) + })?; + + let mut credential_map = HashMap::new(); + credential_map.insert(credential_key.clone(), bundle.access_token); + + let mut credential_expires_at_ms = HashMap::new(); + if let Some(expires_at) = bundle.expires_at { + let expires_at_ms = i64::try_from(expires_at) + .unwrap_or(i64::MAX / 1000) + .saturating_mul(1000); + credential_expires_at_ms.insert(credential_key, expires_at_ms); + } + + Ok((credential_map, credential_expires_at_ms)) +} + +fn oidc_subject_credential_key( + credentials: &[String], + profile: Option<&ProviderProfile>, +) -> Result { + if credentials.len() > 1 { + return Err(miette::miette!( + "--from-oidc-token accepts at most one --credential KEY destination" + )); + } + + if let Some(credential) = credentials.first() { + let credential = credential.trim(); + if credential.is_empty() || credential.contains('=') { + return Err(miette::miette!( + "--from-oidc-token requires --credential KEY without an inline value" + )); + } + if let Some(profile) = profile { + ensure_profile_declares_subject_credential(profile, credential)?; + } + return Ok(credential.to_string()); + } + + let Some(profile) = profile else { + return Err(miette::miette!( + "--from-oidc-token requires --credential KEY when the provider profile is unavailable" + )); + }; + + infer_oidc_subject_credential_from_profile(profile) +} + +fn ensure_profile_declares_subject_credential( + profile: &ProviderProfile, + credential: &str, +) -> Result<()> { + let matches = token_exchange_subject_credentials(profile); + if matches.iter().any(|candidate| candidate == credential) { + return Ok(()); + } + Err(miette::miette!( + "credential '{credential}' is not declared as a token-exchange subject credential in provider profile '{}'; expected one of: {}", + profile.id, + matches.join(", ") + )) +} + +fn infer_oidc_subject_credential_from_profile(profile: &ProviderProfile) -> Result { + let matches = token_exchange_subject_credentials(profile); + match matches.as_slice() { + [credential] => Ok(credential.clone()), + [] => Err(miette::miette!( + "provider profile '{}' does not declare a token-exchange subject credential; pass --credential KEY", + profile.id + )), + _ => Err(miette::miette!( + "provider profile '{}' declares multiple token-exchange subject credentials ({}); pass --credential KEY", + profile.id, + matches.join(", ") + )), + } +} + +fn token_exchange_subject_credentials(profile: &ProviderProfile) -> Vec { + let mut matches = Vec::new(); + for credential in &profile.credentials { + let Some(token_grant) = credential.token_grant.as_ref() else { + continue; + }; + if ProviderCredentialTokenGrantType::try_from(token_grant.grant_type).ok() + != Some(ProviderCredentialTokenGrantType::TokenExchange) + { + continue; + } + let Some(subject_token) = token_grant.subject_token.as_ref() else { + continue; + }; + if subject_token.source != "provider_credential" || subject_token.credential.is_empty() { + continue; + } + if !matches.contains(&subject_token.credential) { + matches.push(subject_token.credential.clone()); + } + } + matches +} + #[allow(clippy::too_many_arguments)] pub async fn provider_create( server: &str, @@ -3392,44 +3513,77 @@ pub async fn provider_create( workspace: &str, tls: &TlsOptions, ) -> Result<()> { - provider_create_with_options( + let credential_source = match (from_existing, from_gcloud_adc) { + (true, true) => { + return Err(miette::miette!( + "--from-gcloud-adc cannot be combined with --from-existing, --from-oidc-token, or --credential; it also cannot be combined with --runtime-credentials" + )); + } + (true, false) => ProviderCreateCredentialSource::Existing, + (false, true) => ProviderCreateCredentialSource::GcloudAdc, + (false, false) => ProviderCreateCredentialSource::ExplicitCredentials, + }; + provider_create_with_options(ProviderCreateOptions { server, name, provider_type, - from_existing, credentials, - from_gcloud_adc, - false, + credential_source, config, workspace, - workspace, + profile_workspace: workspace, tls, - ) + }) .await } -#[allow(clippy::too_many_arguments)] -pub async fn provider_create_with_options( - server: &str, - name: &str, - provider_type: &str, - from_existing: bool, - credentials: &[String], - from_gcloud_adc: bool, - runtime_credentials: bool, - config: &[String], - workspace: &str, - profile_workspace: &str, - tls: &TlsOptions, -) -> Result<()> { - if from_gcloud_adc && (from_existing || !credentials.is_empty() || runtime_credentials) { +pub struct ProviderCreateOptions<'a> { + pub server: &'a str, + pub name: &'a str, + pub provider_type: &'a str, + pub credentials: &'a [String], + pub credential_source: ProviderCreateCredentialSource, + pub config: &'a [String], + pub workspace: &'a str, + pub profile_workspace: &'a str, + pub tls: &'a TlsOptions, +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum ProviderCreateCredentialSource { + ExplicitCredentials, + Existing, + GcloudAdc, + OidcToken, + Runtime, +} + +pub async fn provider_create_with_options(options: ProviderCreateOptions<'_>) -> Result<()> { + let ProviderCreateOptions { + server, + name, + provider_type, + credentials, + credential_source, + config, + workspace, + profile_workspace, + tls, + } = options; + + let from_existing = credential_source == ProviderCreateCredentialSource::Existing; + let from_gcloud_adc = credential_source == ProviderCreateCredentialSource::GcloudAdc; + let from_oidc_token = credential_source == ProviderCreateCredentialSource::OidcToken; + let runtime_credentials = credential_source == ProviderCreateCredentialSource::Runtime; + + if from_gcloud_adc && !credentials.is_empty() { return Err(miette::miette!( - "--from-gcloud-adc cannot be combined with --from-existing, --credential, or --runtime-credentials" + "--from-gcloud-adc cannot be combined with --from-existing, --from-oidc-token, or --credential; it also cannot be combined with --runtime-credentials" )); } - if from_existing && (!credentials.is_empty() || runtime_credentials) { + if from_existing && !credentials.is_empty() { return Err(miette::miette!( - "--from-existing cannot be combined with --credential or --runtime-credentials" + "--from-existing cannot be combined with --credential" )); } if runtime_credentials && !credentials.is_empty() { @@ -3499,7 +3653,17 @@ pub async fn provider_create_with_options( None }; - let mut credential_map = parse_credential_pairs(credentials)?; + let oidc_profile = if from_oidc_token { + Some(fetch_provider_profile(&mut client, &provider_type, profile_workspace).await?) + } else { + None + }; + + let (mut credential_map, oidc_credential_expires_at_ms) = if from_oidc_token { + provider_credential_from_oidc_token(credentials, oidc_profile.as_ref(), tls).await? + } else { + (parse_credential_pairs(credentials)?, HashMap::new()) + }; let mut config_map = parse_key_value_pairs(config, "--config")?; if from_existing { @@ -3573,7 +3737,7 @@ pub async fn provider_create_with_options( r#type: provider_type.clone(), credentials: credential_map, config: config_map, - credential_expires_at_ms: HashMap::new(), + credential_expires_at_ms: oidc_credential_expires_at_ms, profile_workspace: profile_workspace.to_string(), credential_handles: HashMap::new(), }), @@ -4601,28 +4765,73 @@ fn print_provider_type_row( ); } -#[allow(clippy::too_many_arguments)] -pub async fn provider_update( - server: &str, - name: &str, - from_existing: bool, - credentials: &[String], - config: &[String], - credential_expires_at: &[String], - workspace: &str, - tls: &TlsOptions, -) -> Result<()> { +pub struct ProviderUpdateOptions<'a> { + pub server: &'a str, + pub name: &'a str, + pub from_existing: bool, + pub from_oidc_token: bool, + pub credentials: &'a [String], + pub config: &'a [String], + pub credential_expires_at: &'a [String], + pub workspace: &'a str, + pub tls: &'a TlsOptions, +} + +pub async fn provider_update(options: ProviderUpdateOptions<'_>) -> Result<()> { + let ProviderUpdateOptions { + server, + name, + from_existing, + from_oidc_token, + credentials, + config, + credential_expires_at, + workspace, + tls, + } = options; + if from_existing && !credentials.is_empty() { return Err(miette::miette!( "--from-existing cannot be combined with --credential" )); } + if from_existing && from_oidc_token { + return Err(miette::miette!( + "--from-existing cannot be combined with --from-oidc-token" + )); + } let mut client = grpc_client(server, tls).await?; - let mut credential_map = parse_credential_pairs(credentials)?; + let oidc_profile = if from_oidc_token { + let existing = client + .get_provider(GetProviderRequest { + name: name.to_string(), + workspace: workspace.to_string(), + }) + .await + .into_diagnostic()? + .into_inner() + .provider + .ok_or_else(|| miette::miette!("provider '{name}' not found"))?; + let profile_workspace = if existing.profile_workspace.is_empty() { + workspace + } else { + &existing.profile_workspace + }; + Some(fetch_provider_profile(&mut client, &existing.r#type, profile_workspace).await?) + } else { + None + }; + + let (mut credential_map, oidc_credential_expires_at_ms) = if from_oidc_token { + provider_credential_from_oidc_token(credentials, oidc_profile.as_ref(), tls).await? + } else { + (parse_credential_pairs(credentials)?, HashMap::new()) + }; let mut config_map = parse_key_value_pairs(config, "--config")?; - let credential_expires_at_ms = parse_credential_expiry_pairs(credential_expires_at)?; + let mut credential_expires_at_ms = parse_credential_expiry_pairs(credential_expires_at)?; + credential_expires_at_ms.extend(oidc_credential_expires_at_ms); if from_existing { // Fetch the existing provider to discover its type for credential lookup. diff --git a/crates/openshell-cli/tests/ensure_providers_integration.rs b/crates/openshell-cli/tests/ensure_providers_integration.rs index 3d628f2c10..1b26be46f3 100644 --- a/crates/openshell-cli/tests/ensure_providers_integration.rs +++ b/crates/openshell-cli/tests/ensure_providers_integration.rs @@ -17,7 +17,8 @@ use openshell_core::proto::{ AttachSandboxProviderRequest, AttachSandboxProviderResponse, CreateProviderRequest, CreateSandboxRequest, CreateSshSessionRequest, CreateSshSessionResponse, DeleteProviderRequest, DeleteProviderResponse, DeleteSandboxRequest, DeleteSandboxResponse, - DetachSandboxProviderRequest, DetachSandboxProviderResponse, ExecSandboxEvent, + DetachSandboxProviderRequest, DetachSandboxProviderResponse, + ExchangeProviderSubjectTokenRequest, ExchangeProviderSubjectTokenResponse, ExecSandboxEvent, ExecSandboxInput, ExecSandboxRequest, GatewayMessage, GetGatewayConfigRequest, GetGatewayConfigResponse, GetProviderRequest, GetSandboxConfigRequest, GetSandboxConfigResponse, GetSandboxProviderEnvironmentRequest, @@ -234,6 +235,13 @@ impl OpenShell for TestOpenShell { Ok(Response::new(RevokeSshSessionResponse::default())) } + async fn exchange_provider_subject_token( + &self, + _request: tonic::Request, + ) -> Result, Status> { + Err(Status::unimplemented("unused")) + } + async fn create_provider( &self, request: tonic::Request, diff --git a/crates/openshell-cli/tests/mtls_integration.rs b/crates/openshell-cli/tests/mtls_integration.rs index 60ffbd61f8..7bf45c1ba7 100644 --- a/crates/openshell-cli/tests/mtls_integration.rs +++ b/crates/openshell-cli/tests/mtls_integration.rs @@ -13,10 +13,11 @@ use openshell_cli::{ }; use openshell_core::proto::{ CreateProviderRequest, CreateSshSessionRequest, CreateSshSessionResponse, - DeleteProviderRequest, DeleteProviderResponse, ExecSandboxEvent, ExecSandboxInput, - ExecSandboxRequest, GetProviderRequest, HealthRequest, HealthResponse, ListProvidersRequest, - ListProvidersResponse, ProviderResponse, RevokeSshSessionRequest, RevokeSshSessionResponse, - ServiceStatus, UpdateProviderRequest, + DeleteProviderRequest, DeleteProviderResponse, ExchangeProviderSubjectTokenRequest, + ExchangeProviderSubjectTokenResponse, ExecSandboxEvent, ExecSandboxInput, ExecSandboxRequest, + GetProviderRequest, HealthRequest, HealthResponse, ListProvidersRequest, ListProvidersResponse, + ProviderResponse, RevokeSshSessionRequest, RevokeSshSessionResponse, ServiceStatus, + UpdateProviderRequest, open_shell_server::{OpenShell, OpenShellServer}, }; use tempfile::tempdir; @@ -206,6 +207,13 @@ impl OpenShell for TestOpenShell { Ok(Response::new(RevokeSshSessionResponse::default())) } + async fn exchange_provider_subject_token( + &self, + _request: tonic::Request, + ) -> Result, Status> { + Err(Status::unimplemented("unused")) + } + async fn create_provider( &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 a87ff0a6d8..9c5597076c 100644 --- a/crates/openshell-cli/tests/provider_commands_integration.rs +++ b/crates/openshell-cli/tests/provider_commands_integration.rs @@ -14,7 +14,8 @@ use openshell_core::proto::{ CreateSandboxRequest, CreateSshSessionRequest, CreateSshSessionResponse, DeleteProviderRefreshRequest, DeleteProviderRefreshResponse, DeleteProviderRequest, DeleteProviderResponse, DeleteSandboxRequest, DeleteSandboxResponse, - DetachSandboxProviderRequest, DetachSandboxProviderResponse, ExecSandboxEvent, + DetachSandboxProviderRequest, DetachSandboxProviderResponse, + ExchangeProviderSubjectTokenRequest, ExchangeProviderSubjectTokenResponse, ExecSandboxEvent, ExecSandboxInput, ExecSandboxRequest, GatewayMessage, GetGatewayConfigRequest, GetGatewayConfigResponse, GetProviderRefreshStatusRequest, GetProviderRefreshStatusResponse, GetProviderRequest, GetSandboxConfigRequest, GetSandboxConfigResponse, @@ -365,6 +366,13 @@ impl OpenShell for TestOpenShell { Ok(Response::new(RevokeSshSessionResponse::default())) } + async fn exchange_provider_subject_token( + &self, + _request: tonic::Request, + ) -> Result, Status> { + Err(Status::unimplemented("unused")) + } + async fn create_provider( &self, request: tonic::Request, @@ -1179,16 +1187,17 @@ async fn provider_cli_run_functions_support_full_crud_flow() { .await .expect("provider list"); - run::provider_update( - &ts.endpoint, - "my-claude", - false, - &["API_KEY=rotated".to_string()], - &["profile=prod".to_string()], - &[], - "default", - &ts.tls, - ) + run::provider_update(run::ProviderUpdateOptions { + server: &ts.endpoint, + name: "my-claude", + from_existing: false, + from_oidc_token: false, + credentials: &["API_KEY=rotated".to_string()], + config: &["profile=prod".to_string()], + credential_expires_at: &[], + workspace: "default", + tls: &ts.tls, + }) .await .expect("provider update"); @@ -1537,19 +1546,17 @@ async fn provider_create_allows_empty_credentials_for_gateway_refresh_profiles() }, ); - run::provider_create_with_options( - &ts.endpoint, - "custom-refresh-provider", - "custom-refresh", - false, - &[], - false, - true, - &[], - "default", - "default", - &ts.tls, - ) + run::provider_create_with_options(run::ProviderCreateOptions { + server: &ts.endpoint, + name: "custom-refresh-provider", + provider_type: "custom-refresh", + credentials: &[], + credential_source: run::ProviderCreateCredentialSource::Runtime, + config: &[], + workspace: "default", + profile_workspace: "default", + tls: &ts.tls, + }) .await .expect("provider create"); @@ -2093,16 +2100,17 @@ async fn provider_update_from_existing_uses_profile_discovery_when_v2_enabled() ); let _env = EnvVarGuard::set(&[("CUSTOM_UPDATE_DISCOVERY_API_KEY", "updated-profile-secret")]); - run::provider_update( - &ts.endpoint, - "custom-update", - true, - &[], - &[], - &[], - "default", - &ts.tls, - ) + run::provider_update(run::ProviderUpdateOptions { + server: &ts.endpoint, + name: "custom-update", + from_existing: true, + from_oidc_token: false, + credentials: &[], + config: &[], + credential_expires_at: &[], + workspace: "default", + tls: &ts.tls, + }) .await .expect("profile-backed provider update --from-existing"); @@ -2357,19 +2365,17 @@ async fn provider_create_supports_generic_type_and_env_lookup_credentials() { async fn provider_create_sends_inline_credentials() { let ts = run_server().await; - run::provider_create_with_options( - &ts.endpoint, - "openai-inline", - "openai", - false, - &["OPENAI_API_KEY=sk-test".to_string()], - false, - false, - &[], - "default", - "default", - &ts.tls, - ) + run::provider_create_with_options(run::ProviderCreateOptions { + server: &ts.endpoint, + name: "openai-inline", + provider_type: "openai", + credentials: &["OPENAI_API_KEY=sk-test".to_string()], + credential_source: run::ProviderCreateCredentialSource::ExplicitCredentials, + config: &[], + workspace: "default", + profile_workspace: "default", + tls: &ts.tls, + }) .await .expect("provider create with inline credential"); @@ -2435,7 +2441,7 @@ async fn provider_create_rejects_combined_from_gcloud_adc_and_from_existing() { assert!( err.to_string() - .contains("--from-gcloud-adc cannot be combined with --from-existing, --credential"), + .contains("--from-gcloud-adc cannot be combined with --from-existing, --from-oidc-token, or --credential"), "unexpected error: {err}" ); assert!(ts.state.providers.lock().await.is_empty()); @@ -2461,7 +2467,7 @@ async fn provider_create_rejects_combined_from_gcloud_adc_and_credentials() { assert!( err.to_string() - .contains("--from-gcloud-adc cannot be combined with --from-existing, --credential"), + .contains("--from-gcloud-adc cannot be combined with --from-existing, --from-oidc-token, or --credential"), "unexpected error: {err}" ); assert!(ts.state.providers.lock().await.is_empty()); diff --git a/crates/openshell-cli/tests/sandbox_create_lifecycle_integration.rs b/crates/openshell-cli/tests/sandbox_create_lifecycle_integration.rs index 102cde3714..c263aa6640 100644 --- a/crates/openshell-cli/tests/sandbox_create_lifecycle_integration.rs +++ b/crates/openshell-cli/tests/sandbox_create_lifecycle_integration.rs @@ -16,7 +16,8 @@ use openshell_core::proto::{ AttachSandboxProviderRequest, AttachSandboxProviderResponse, CreateProviderRequest, CreateSandboxRequest, CreateSshSessionRequest, CreateSshSessionResponse, DeleteProviderRequest, DeleteProviderResponse, DeleteSandboxRequest, DeleteSandboxResponse, - DetachSandboxProviderRequest, DetachSandboxProviderResponse, ExecSandboxEvent, + DetachSandboxProviderRequest, DetachSandboxProviderResponse, + ExchangeProviderSubjectTokenRequest, ExchangeProviderSubjectTokenResponse, ExecSandboxEvent, ExecSandboxInput, ExecSandboxRequest, GatewayMessage, GetGatewayConfigRequest, GetGatewayConfigResponse, GetProviderRequest, GetSandboxConfigRequest, GetSandboxConfigResponse, GetSandboxProviderEnvironmentRequest, @@ -276,6 +277,13 @@ impl OpenShell for TestOpenShell { Ok(Response::new(RevokeSshSessionResponse::default())) } + async fn exchange_provider_subject_token( + &self, + _request: tonic::Request, + ) -> Result, Status> { + Err(Status::unimplemented("unused")) + } + async fn create_provider( &self, _request: tonic::Request, diff --git a/crates/openshell-cli/tests/sandbox_name_fallback_integration.rs b/crates/openshell-cli/tests/sandbox_name_fallback_integration.rs index 41b93bab82..09975cd52f 100644 --- a/crates/openshell-cli/tests/sandbox_name_fallback_integration.rs +++ b/crates/openshell-cli/tests/sandbox_name_fallback_integration.rs @@ -14,7 +14,8 @@ use openshell_core::proto::{ AttachSandboxProviderRequest, AttachSandboxProviderResponse, CreateProviderRequest, CreateSandboxRequest, CreateSshSessionRequest, CreateSshSessionResponse, DeleteProviderRequest, DeleteProviderResponse, DeleteSandboxRequest, DeleteSandboxResponse, - DetachSandboxProviderRequest, DetachSandboxProviderResponse, ExecSandboxEvent, + DetachSandboxProviderRequest, DetachSandboxProviderResponse, + ExchangeProviderSubjectTokenRequest, ExchangeProviderSubjectTokenResponse, ExecSandboxEvent, ExecSandboxInput, ExecSandboxRequest, GatewayMessage, GetGatewayConfigRequest, GetGatewayConfigResponse, GetProviderRequest, GetSandboxConfigRequest, GetSandboxConfigResponse, GetSandboxPolicyStatusRequest, GetSandboxPolicyStatusResponse, @@ -269,6 +270,13 @@ impl OpenShell for TestOpenShell { )) } + async fn exchange_provider_subject_token( + &self, + _request: tonic::Request, + ) -> Result, Status> { + Err(Status::unimplemented("unused")) + } + async fn create_provider( &self, _request: tonic::Request, diff --git a/crates/openshell-core/src/grpc_client.rs b/crates/openshell-core/src/grpc_client.rs index e5f246e4c0..54f0db6902 100644 --- a/crates/openshell-core/src/grpc_client.rs +++ b/crates/openshell-core/src/grpc_client.rs @@ -23,12 +23,12 @@ use std::sync::{Arc, OnceLock, RwLock}; use std::time::{Duration, SystemTime, UNIX_EPOCH}; use crate::proto::{ - DenialSummary, GetDraftPolicyRequest, GetInferenceBundleRequest, GetInferenceBundleResponse, - GetSandboxConfigRequest, GetSandboxProviderEnvironmentRequest, IssueSandboxTokenRequest, - NetworkActivitySummary, PolicyChunk, PolicySource, PolicyStatus, RefreshSandboxTokenRequest, - ReportPolicyStatusRequest, SandboxPolicy as ProtoSandboxPolicy, SubmitPolicyAnalysisRequest, - SubmitPolicyAnalysisResponse, UpdateConfigRequest, inference_client::InferenceClient, - open_shell_client::OpenShellClient, + DenialSummary, ExchangeProviderSubjectTokenRequest, GetDraftPolicyRequest, + GetInferenceBundleRequest, GetInferenceBundleResponse, GetSandboxConfigRequest, + GetSandboxProviderEnvironmentRequest, IssueSandboxTokenRequest, NetworkActivitySummary, + PolicyChunk, PolicySource, PolicyStatus, RefreshSandboxTokenRequest, ReportPolicyStatusRequest, + SandboxPolicy as ProtoSandboxPolicy, SubmitPolicyAnalysisRequest, SubmitPolicyAnalysisResponse, + UpdateConfigRequest, inference_client::InferenceClient, open_shell_client::OpenShellClient, }; use crate::sandbox_env; use miette::{IntoDiagnostic, Result, WrapErr}; @@ -860,6 +860,55 @@ pub async fn fetch_provider_environment( }) } +pub async fn exchange_provider_subject_token( + endpoint: &str, + sandbox_id: &str, + provider: &str, + credential_key: &str, + supervisor_jwt_svid: &str, +) -> Result { + debug!( + endpoint = %endpoint, + sandbox_id = %sandbox_id, + provider = %provider, + credential_key = %credential_key, + "Exchanging provider subject token through gateway" + ); + + let mut client = connect(endpoint).await?; + let response = client + .exchange_provider_subject_token(ExchangeProviderSubjectTokenRequest { + sandbox_id: sandbox_id.to_string(), + provider: provider.to_string(), + credential_key: credential_key.to_string(), + supervisor_jwt_svid: supervisor_jwt_svid.to_string(), + }) + .await + .map_err(provider_subject_token_exchange_status)?; + let inner = response.into_inner(); + Ok(ProviderSubjectTokenExchangeResult { + access_token: inner.access_token, + expires_in: inner.expires_in, + token_type: inner.token_type, + }) +} + +fn provider_subject_token_exchange_status(status: Status) -> miette::Report { + let message = status.message(); + if message.is_empty() { + miette::miette!( + "gateway ExchangeProviderSubjectToken failed with status {}", + status.code() + ) + } else { + miette::miette!( + "gateway ExchangeProviderSubjectToken failed with status {}: {}", + status.code(), + message + ) + } +} + /// A reusable gRPC client for the `OpenShell` service. /// /// Wraps a tonic channel connected once and reused for policy polling @@ -969,6 +1018,12 @@ pub struct ProviderEnvironmentResult { pub non_secret_environment_keys: Vec, } +pub struct ProviderSubjectTokenExchangeResult { + pub access_token: String, + pub expires_in: i64, + pub token_type: String, +} + impl CachedOpenShellClient { pub async fn connect(endpoint: &str) -> Result { Self::connect_with_credentials(endpoint, ExtensionCredentialStore::new()).await diff --git a/crates/openshell-core/src/lib.rs b/crates/openshell-core/src/lib.rs index d373d656ed..32f2041832 100644 --- a/crates/openshell-core/src/lib.rs +++ b/crates/openshell-core/src/lib.rs @@ -39,6 +39,7 @@ pub mod provider_credentials; pub mod sandbox_env; pub mod secrets; pub mod settings; +pub mod spiffe; pub mod telemetry; pub mod time; pub mod transport_errors; diff --git a/crates/openshell-core/src/spiffe.rs b/crates/openshell-core/src/spiffe.rs new file mode 100644 index 0000000000..c288dfd81a --- /dev/null +++ b/crates/openshell-core/src/spiffe.rs @@ -0,0 +1,177 @@ +// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +//! Shared SPIFFE helpers used by the gateway and sandbox supervisor. + +use std::path::Path; + +use base64::Engine as _; +use serde::Deserialize; + +/// SPIFFE JWT-SVID claims used by `OpenShell` token exchange flows. +#[derive(Debug, Clone, Deserialize)] +pub struct SpiffeJwtClaims { + pub iss: String, + pub sub: String, + pub aud: AudienceClaim, + #[serde(default)] + pub exp: i64, +} + +/// JWT `aud` claim representation accepted by SPIFFE JWT-SVIDs. +#[derive(Debug, Clone, Deserialize)] +#[serde(untagged)] +pub enum AudienceClaim { + One(String), + Many(Vec), +} + +impl AudienceClaim { + pub fn contains(&self, expected: &str) -> bool { + match self { + Self::One(value) => value == expected, + Self::Many(values) => values.iter().any(|value| value == expected), + } + } +} + +#[derive(Debug, thiserror::Error)] +pub enum JwtSvidParseError { + #[error("invalid JWT-SVID format")] + Format, + #[error("invalid JWT-SVID payload encoding")] + PayloadEncoding, + #[error("invalid JWT-SVID payload")] + Payload, +} + +/// Convert a path to a SPIFFE Workload API endpoint URL. +/// +/// If the path already has a scheme (`unix:` or `tcp:`), use it as-is. +/// Otherwise, assume it is a Unix socket path and prepend `unix:`. +pub fn workload_api_endpoint(path: &Path) -> String { + let path = path.to_string_lossy(); + if path.starts_with("unix:") || path.starts_with("tcp:") { + path.into_owned() + } else { + format!("unix:{path}") + } +} + +/// # Security +/// +/// This function decodes the JWT payload without verifying the signature. +/// Callers must separately verify the JWT signature before trusting the claims. +pub fn parse_unverified_jwt_svid_claims(token: &str) -> Result { + let segments = token.split('.').collect::>(); + if segments.len() != 3 || segments.iter().any(|segment| segment.is_empty()) { + return Err(JwtSvidParseError::Format); + } + let decoded = base64::engine::general_purpose::URL_SAFE_NO_PAD + .decode(segments[1]) + .map_err(|_| JwtSvidParseError::PayloadEncoding)?; + serde_json::from_slice::(&decoded).map_err(|_| JwtSvidParseError::Payload) +} + +pub fn trust_domain(subject: &str) -> Option<&str> { + let rest = subject.strip_prefix("spiffe://")?; + let (trust_domain, _) = rest.split_once('/').unwrap_or((rest, "")); + (!trust_domain.is_empty()).then_some(trust_domain) +} + +#[cfg(test)] +mod tests { + use super::*; + + fn unsigned_svid_fixture(issuer: &str, subject: &str, audience: serde_json::Value) -> String { + let header = serde_json::json!({ "alg": "RS256", "kid": "test-key" }); + let payload = serde_json::json!({ + "iss": issuer, + "sub": subject, + "aud": audience, + "exp": 4_102_444_800_i64 + }); + let encoded_header = base64::engine::general_purpose::URL_SAFE_NO_PAD + .encode(serde_json::to_vec(&header).expect("serialize header")); + let encoded_payload = base64::engine::general_purpose::URL_SAFE_NO_PAD + .encode(serde_json::to_vec(&payload).expect("serialize payload")); + format!("{encoded_header}.{encoded_payload}.signature") + } + + #[test] + fn workload_api_endpoint_preserves_explicit_scheme() { + assert_eq!( + workload_api_endpoint(Path::new("unix:/run/spire/agent.sock")), + "unix:/run/spire/agent.sock" + ); + assert_eq!( + workload_api_endpoint(Path::new("tcp:127.0.0.1:8081")), + "tcp:127.0.0.1:8081" + ); + } + + #[test] + fn workload_api_endpoint_defaults_to_unix_socket() { + assert_eq!( + workload_api_endpoint(Path::new("/run/spire/agent.sock")), + "unix:/run/spire/agent.sock" + ); + } + + #[test] + fn parse_unverified_jwt_svid_claims_accepts_string_audience() { + let token = unsigned_svid_fixture( + "https://spiffe.example.test", + "spiffe://openshell/openshell/sandbox/sb-a", + serde_json::json!("https://auth.example.com"), + ); + + let claims = parse_unverified_jwt_svid_claims(&token).expect("valid claims"); + + assert_eq!(claims.iss, "https://spiffe.example.test"); + assert_eq!(claims.sub, "spiffe://openshell/openshell/sandbox/sb-a"); + assert!(claims.aud.contains("https://auth.example.com")); + assert!(!claims.aud.contains("https://other.example.com")); + } + + #[test] + fn parse_unverified_jwt_svid_claims_accepts_array_audience() { + let token = unsigned_svid_fixture( + "https://spiffe.example.test", + "spiffe://openshell/openshell/sandbox/sb-a", + serde_json::json!(["https://auth.example.com", "https://other.example.com"]), + ); + + let claims = parse_unverified_jwt_svid_claims(&token).expect("valid claims"); + + assert!(claims.aud.contains("https://auth.example.com")); + assert!(claims.aud.contains("https://other.example.com")); + } + + #[test] + fn parse_unverified_jwt_svid_claims_rejects_truncated_jwt() { + assert!(matches!( + parse_unverified_jwt_svid_claims("header.payload"), + Err(JwtSvidParseError::Format) + )); + } + + #[test] + fn parse_unverified_jwt_svid_claims_rejects_empty_jwt_segments() { + assert!(matches!( + parse_unverified_jwt_svid_claims("header..signature"), + Err(JwtSvidParseError::Format) + )); + } + + #[test] + fn trust_domain_extracts_domain_from_spiffe_id() { + assert_eq!( + trust_domain("spiffe://openshell/openshell/sandbox/sb-a"), + Some("openshell") + ); + assert_eq!(trust_domain("spiffe://openshell"), Some("openshell")); + assert_eq!(trust_domain("not-a-spiffe-id"), None); + assert_eq!(trust_domain("spiffe:///empty"), None); + } +} diff --git a/crates/openshell-providers/src/profiles.rs b/crates/openshell-providers/src/profiles.rs index 42c82c0c2a..cf7c2faa35 100644 --- a/crates/openshell-providers/src/profiles.rs +++ b/crates/openshell-providers/src/profiles.rs @@ -9,7 +9,8 @@ use openshell_core::proto::{ GraphqlOperation, L7Allow, L7DenyRule, L7QueryMatcher, L7Rule, McpOptions, NetworkBinary, NetworkEndpoint, NetworkPolicyRule, ProviderCredentialRefresh, ProviderCredentialRefreshMaterial, ProviderCredentialRefreshOutput, - ProviderCredentialRefreshStrategy, ProviderProfile, ProviderProfileCategory, + ProviderCredentialRefreshStrategy, ProviderCredentialTokenGrantSubjectToken, + ProviderCredentialTokenGrantType, ProviderProfile, ProviderProfileCategory, ProviderProfileCredential, ProviderProfileDiscovery, }; use openshell_core::secrets::uses_reserved_revision_namespace; @@ -111,6 +112,13 @@ pub struct CredentialProfile { #[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)] pub struct TokenGrantProfile { + #[serde( + default = "default_token_grant_type", + deserialize_with = "deserialize_token_grant_type", + serialize_with = "serialize_token_grant_type", + skip_serializing_if = "is_client_credentials_grant" + )] + pub grant_type: ProviderCredentialTokenGrantType, pub token_endpoint: String, #[serde(default, skip_serializing_if = "String::is_empty")] pub audience: String, @@ -124,6 +132,18 @@ pub struct TokenGrantProfile { pub cache_ttl_seconds: i64, #[serde(default, skip_serializing_if = "Vec::is_empty")] pub audience_overrides: Vec, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub subject_token: Option, + #[serde(default, skip_serializing_if = "String::is_empty")] + pub requested_token_type: String, +} + +#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)] +pub struct TokenGrantSubjectTokenProfile { + pub source: String, + pub credential: String, + #[serde(default, skip_serializing_if = "String::is_empty")] + pub subject_token_type: String, } #[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)] @@ -816,6 +836,26 @@ fn default_refresh_strategy() -> ProviderCredentialRefreshStrategy { ProviderCredentialRefreshStrategy::Unspecified } +fn default_token_grant_type() -> ProviderCredentialTokenGrantType { + ProviderCredentialTokenGrantType::ClientCredentials +} + +fn effective_token_grant_type( + grant_type: ProviderCredentialTokenGrantType, +) -> ProviderCredentialTokenGrantType { + match grant_type { + ProviderCredentialTokenGrantType::Unspecified => { + ProviderCredentialTokenGrantType::ClientCredentials + } + other => other, + } +} + +#[allow(clippy::trivially_copy_pass_by_ref)] +fn is_client_credentials_grant(value: &ProviderCredentialTokenGrantType) -> bool { + effective_token_grant_type(*value) == ProviderCredentialTokenGrantType::ClientCredentials +} + fn deserialize_category<'de, D>(deserializer: D) -> Result where D: Deserializer<'de>, @@ -858,6 +898,28 @@ where serializer.serialize_str(provider_refresh_strategy_to_yaml(*strategy)) } +fn deserialize_token_grant_type<'de, D>( + deserializer: D, +) -> Result +where + D: Deserializer<'de>, +{ + let raw = String::deserialize(deserializer)?; + provider_token_grant_type_from_yaml(&raw) + .ok_or_else(|| de::Error::custom(format!("unsupported provider token grant type: {raw}"))) +} + +#[allow(clippy::trivially_copy_pass_by_ref)] +fn serialize_token_grant_type( + grant_type: &ProviderCredentialTokenGrantType, + serializer: S, +) -> Result +where + S: Serializer, +{ + serializer.serialize_str(provider_token_grant_type_to_yaml(*grant_type)) +} + #[must_use] pub fn provider_profile_category_from_yaml(raw: &str) -> Option { match raw.trim().to_ascii_lowercase().replace('-', "_").as_str() { @@ -918,6 +980,26 @@ pub fn provider_refresh_strategy_to_yaml( } } +#[must_use] +pub fn provider_token_grant_type_from_yaml(raw: &str) -> Option { + match raw.trim().to_ascii_lowercase().replace('-', "_").as_str() { + "" | "client_credentials" => Some(ProviderCredentialTokenGrantType::ClientCredentials), + "token_exchange" => Some(ProviderCredentialTokenGrantType::TokenExchange), + _ => None, + } +} + +#[must_use] +pub fn provider_token_grant_type_to_yaml( + grant_type: ProviderCredentialTokenGrantType, +) -> &'static str { + match grant_type { + ProviderCredentialTokenGrantType::TokenExchange => "token_exchange", + ProviderCredentialTokenGrantType::ClientCredentials + | ProviderCredentialTokenGrantType::Unspecified => "client_credentials", + } +} + fn credential_refresh_from_proto(refresh: &ProviderCredentialRefresh) -> CredentialRefreshProfile { CredentialRefreshProfile { strategy: ProviderCredentialRefreshStrategy::try_from(refresh.strategy) @@ -979,6 +1061,10 @@ fn token_grant_from_proto( token_grant: &openshell_core::proto::ProviderCredentialTokenGrant, ) -> TokenGrantProfile { TokenGrantProfile { + grant_type: effective_token_grant_type( + ProviderCredentialTokenGrantType::try_from(token_grant.grant_type) + .unwrap_or(ProviderCredentialTokenGrantType::ClientCredentials), + ), token_endpoint: token_grant.token_endpoint.clone(), audience: token_grant.audience.clone(), jwt_svid_audience: token_grant.jwt_svid_audience.clone(), @@ -990,6 +1076,11 @@ fn token_grant_from_proto( .iter() .map(token_grant_audience_override_from_proto) .collect(), + subject_token: token_grant + .subject_token + .as_ref() + .map(token_grant_subject_token_from_proto), + requested_token_type: token_grant.requested_token_type.clone(), } } @@ -997,6 +1088,7 @@ fn token_grant_to_proto( token_grant: &TokenGrantProfile, ) -> openshell_core::proto::ProviderCredentialTokenGrant { openshell_core::proto::ProviderCredentialTokenGrant { + grant_type: token_grant.grant_type as i32, token_endpoint: token_grant.token_endpoint.clone(), audience: token_grant.audience.clone(), jwt_svid_audience: token_grant.jwt_svid_audience.clone(), @@ -1008,6 +1100,31 @@ fn token_grant_to_proto( .iter() .map(token_grant_audience_override_to_proto) .collect(), + subject_token: token_grant + .subject_token + .as_ref() + .map(token_grant_subject_token_to_proto), + requested_token_type: token_grant.requested_token_type.clone(), + } +} + +fn token_grant_subject_token_from_proto( + subject_token: &ProviderCredentialTokenGrantSubjectToken, +) -> TokenGrantSubjectTokenProfile { + TokenGrantSubjectTokenProfile { + source: subject_token.source.clone(), + credential: subject_token.credential.clone(), + subject_token_type: subject_token.subject_token_type.clone(), + } +} + +fn token_grant_subject_token_to_proto( + subject_token: &TokenGrantSubjectTokenProfile, +) -> ProviderCredentialTokenGrantSubjectToken { + ProviderCredentialTokenGrantSubjectToken { + source: subject_token.source.clone(), + credential: subject_token.credential.clone(), + subject_token_type: subject_token.subject_token_type.clone(), } } @@ -1816,6 +1933,12 @@ pub fn validate_profile_set( message, )); } + diagnostics.extend(validate_token_grant_subject_token( + source, + profile_id, + credential, + &credential_names, + )); diagnostics.extend(validate_token_grant_audience_overrides( source, profile_id, @@ -2227,6 +2350,75 @@ struct TokenGrantOverrideBinding { score: u32, } +fn validate_token_grant_subject_token( + source: &str, + profile_id: &str, + credential: &CredentialProfile, + credential_names: &HashSet, +) -> Vec { + let Some(token_grant) = credential.token_grant.as_ref() else { + return Vec::new(); + }; + let grant_type = effective_token_grant_type(token_grant.grant_type); + let mut diagnostics = Vec::new(); + + match grant_type { + ProviderCredentialTokenGrantType::ClientCredentials => { + if token_grant.subject_token.is_some() { + diagnostics.push(ProfileValidationDiagnostic::error( + source, + profile_id, + "credentials.token_grant.subject_token", + "subject_token is only valid for token_exchange grants", + )); + } + } + ProviderCredentialTokenGrantType::TokenExchange => { + let Some(subject_token) = token_grant.subject_token.as_ref() else { + diagnostics.push(ProfileValidationDiagnostic::error( + source, + profile_id, + "credentials.token_grant.subject_token", + "token_exchange grants require 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}"), + )); + } + } + ProviderCredentialTokenGrantType::Unspecified => { + unreachable!("effective_token_grant_type must normalize unspecified token grant type") + } + } + + diagnostics +} + fn validate_token_grant_audience_overrides( source: &str, profile_id: &str, @@ -2556,7 +2748,7 @@ pub fn builtin_profiles() -> &'static [ProviderTypeProfile] { mod tests { use std::collections::HashMap; - use openshell_core::proto::ProviderProfileCategory; + use openshell_core::proto::{ProviderCredentialTokenGrantType, ProviderProfileCategory}; use super::{ DiscoveryProfile, L7AllowProfile, L7QueryMatcherProfile, ProfileError, ProviderTypeProfile, @@ -3140,6 +3332,166 @@ credentials: ); } + #[test] + fn token_exchange_grant_round_trips_through_proto_and_yaml() { + let profile = parse_profile_yaml( + r" +id: keycloak-token-exchange +display_name: Keycloak 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: provider_credential + credential: USER_OIDC_TOKEN + subject_token_type: urn:ietf:params:oauth:token-type:access_token + jwt_svid_audience: https://keycloak.example.com/realms/openshell + client_assertion_type: urn:ietf:params:oauth:client-assertion-type:jwt-bearer + audience: https://graph.example.com + scopes: [graph.read] + requested_token_type: urn:ietf:params:oauth:token-type:access_token +", + ) + .expect("profile should parse"); + + let diagnostics = + validate_profile_set(&[("keycloak-token-exchange.yaml".to_string(), profile.clone())]); + assert!( + diagnostics.is_empty(), + "unexpected diagnostics: {diagnostics:?}" + ); + + let token_grant = profile.credentials[1] + .token_grant + .as_ref() + .expect("token grant should parse"); + assert_eq!( + token_grant.grant_type, + ProviderCredentialTokenGrantType::TokenExchange + ); + assert_eq!( + token_grant + .subject_token + .as_ref() + .map(|subject| subject.credential.as_str()), + Some("USER_OIDC_TOKEN") + ); + + let from_proto = ProviderTypeProfile::from_proto(&profile.to_proto()); + assert_eq!( + from_proto.credentials[1].token_grant, + profile.credentials[1].token_grant + ); + + let exported = profile_to_yaml(&from_proto).expect("yaml"); + assert!(exported.contains("grant_type: token_exchange")); + assert!(exported.contains("subject_token:")); + let reparsed = parse_profile_yaml(&exported).expect("re-parse"); + assert_eq!( + reparsed.credentials[1].token_grant, + profile.credentials[1].token_grant + ); + } + + #[test] + fn validate_profile_set_rejects_token_exchange_without_subject_token() { + let profile = parse_profile_yaml( + r" +id: missing-subject-token +display_name: Missing Subject Token +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 +", + ) + .expect("profile should parse"); + + let diagnostics = validate_profile_set(&[("missing.yaml".to_string(), profile)]); + let diagnostic = diagnostics + .iter() + .find(|diagnostic| diagnostic.field == "credentials.token_grant.subject_token") + .expect("expected subject_token diagnostic"); + assert_eq!( + diagnostic.message, + "token_exchange grants require subject_token" + ); + } + + #[test] + fn validate_profile_set_rejects_token_exchange_unknown_subject_credential() { + let profile = parse_profile_yaml( + r" +id: unknown-subject-token +display_name: Unknown Subject Token +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: provider_credential + credential: USER_OIDC_TOKEN +", + ) + .expect("profile should parse"); + + let diagnostics = validate_profile_set(&[("unknown.yaml".to_string(), profile)]); + let diagnostic = diagnostics + .iter() + .find(|diagnostic| { + diagnostic.field == "credentials.token_grant.subject_token.credential" + }) + .expect("expected subject token credential diagnostic"); + assert!( + diagnostic + .message + .contains("unknown subject token credential: USER_OIDC_TOKEN") + ); + } + + #[test] + fn validate_profile_set_rejects_subject_token_on_client_credentials_grant() { + let profile = parse_profile_yaml( + r" +id: misplaced-subject-token +display_name: Misplaced Subject Token +credentials: + - name: USER_OIDC_TOKEN + - name: access_token + auth_style: bearer + header_name: Authorization + token_grant: + token_endpoint: https://keycloak.example.com/realms/openshell/protocol/openid-connect/token + subject_token: + source: provider_credential + credential: USER_OIDC_TOKEN +", + ) + .expect("profile should parse"); + + let diagnostics = validate_profile_set(&[("misplaced.yaml".to_string(), profile)]); + let diagnostic = diagnostics + .iter() + .find(|diagnostic| diagnostic.field == "credentials.token_grant.subject_token") + .expect("expected subject_token diagnostic"); + assert_eq!( + diagnostic.message, + "subject_token is only valid for token_exchange grants" + ); + } + #[test] fn validate_profile_set_rejects_plain_http_token_endpoint() { for token_endpoint in [ diff --git a/crates/openshell-sdk/tests/client_mock.rs b/crates/openshell-sdk/tests/client_mock.rs index 09e91330ce..90375a2b9d 100644 --- a/crates/openshell-sdk/tests/client_mock.rs +++ b/crates/openshell-sdk/tests/client_mock.rs @@ -492,6 +492,13 @@ impl OpenShell for TestOpenShell { Ok(Response::new(proto::GetGatewayConfigResponse::default())) } + async fn exchange_provider_subject_token( + &self, + _: tonic::Request, + ) -> Result, Status> { + Err(Status::unimplemented("unused")) + } + async fn update_config( &self, _: tonic::Request, diff --git a/crates/openshell-server/Cargo.toml b/crates/openshell-server/Cargo.toml index 772590d1b0..102f602fdb 100644 --- a/crates/openshell-server/Cargo.toml +++ b/crates/openshell-server/Cargo.toml @@ -100,6 +100,7 @@ uuid = { workspace = true } hmac = "0.12" sha2 = { workspace = true } jsonwebtoken = { workspace = true } +spiffe = { workspace = true } async-trait = "0.1" url = { workspace = true } glob = { workspace = true } diff --git a/crates/openshell-server/src/auth/oidc.rs b/crates/openshell-server/src/auth/oidc.rs index fd599b501a..475c52ebec 100644 --- a/crates/openshell-server/src/auth/oidc.rs +++ b/crates/openshell-server/src/auth/oidc.rs @@ -240,6 +240,7 @@ impl JwksCache { let Some(ref kid) = key.kid else { continue; }; + crate::install_jsonwebtoken_crypto_provider(); match DecodingKey::from_rsa_components(&key.n, &key.e) { Ok(dk) => { new_keys.insert(kid.clone(), dk); @@ -286,6 +287,8 @@ 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 { + crate::install_jsonwebtoken_crypto_provider(); + self.refresh_if_stale().await.map_err(|e| { warn!(error = %e, "JWKS refresh failed"); Status::internal("OIDC key refresh failed") @@ -568,6 +571,8 @@ mod tests { /// Sign `claims` with the test key, tagging the header with `kid`. fn mint_rs256(claims: &serde_json::Value, kid: &str) -> String { + crate::install_jsonwebtoken_crypto_provider(); + let mut header = jsonwebtoken::Header::new(Algorithm::RS256); header.kid = Some(kid.to_owned()); let key = jsonwebtoken::EncodingKey::from_rsa_pem(TEST_RSA_KEY.private_pem.as_bytes()) @@ -682,6 +687,7 @@ mod tests { let cache = cache_with_mock_issuer(&server).await; let other = TestRsaKey::generate(); + crate::install_jsonwebtoken_crypto_provider(); let mut header = jsonwebtoken::Header::new(Algorithm::RS256); header.kid = Some(TEST_KID.to_owned()); let token = jsonwebtoken::encode( diff --git a/crates/openshell-server/src/auth/sandbox_jwt.rs b/crates/openshell-server/src/auth/sandbox_jwt.rs index 3d66824f20..9dc10b8401 100644 --- a/crates/openshell-server/src/auth/sandbox_jwt.rs +++ b/crates/openshell-server/src/auth/sandbox_jwt.rs @@ -112,6 +112,8 @@ impl SandboxJwtIssuer { gateway_id: &str, ttl: Duration, ) -> Result { + crate::install_jsonwebtoken_crypto_provider(); + let encoding_key = EncodingKey::from_ed_pem(signing_key_pem) .map_err(|e| format!("failed to parse Ed25519 signing key PEM: {e}"))?; let identity = format!("openshell-gateway:{gateway_id}"); @@ -127,6 +129,8 @@ impl SandboxJwtIssuer { /// Mint a fresh token for `sandbox_id`. #[allow(clippy::result_large_err)] // `tonic::Status` is the natural error here pub fn mint(&self, sandbox_id: &str) -> Result { + crate::install_jsonwebtoken_crypto_provider(); + let now = now_secs(); let exp = if self.ttl.is_zero() { 0 @@ -248,6 +252,8 @@ impl std::fmt::Debug for SandboxJwtAuthenticator { impl SandboxJwtAuthenticator { pub fn from_pem(public_key_pem: &[u8], kid: String, gateway_id: &str) -> Result { + crate::install_jsonwebtoken_crypto_provider(); + let decoding_key = DecodingKey::from_ed_pem(public_key_pem) .map_err(|e| format!("failed to parse Ed25519 public key PEM: {e}"))?; let jwks = GatewayJwks::from_public_key_pem(public_key_pem, kid.clone())?; @@ -276,6 +282,8 @@ impl SandboxJwtAuthenticator { #[allow(clippy::result_large_err)] fn validate_bearer(&self, token: &str) -> Result, Status> { + crate::install_jsonwebtoken_crypto_provider(); + let header = decode_header(token).map_err(|e| { debug!(error = %e, "sandbox JWT header decode failed"); Status::unauthenticated("invalid token") diff --git a/crates/openshell-server/src/auth/sandbox_methods.rs b/crates/openshell-server/src/auth/sandbox_methods.rs index 5cc9e3693a..89f34d1253 100644 --- a/crates/openshell-server/src/auth/sandbox_methods.rs +++ b/crates/openshell-server/src/auth/sandbox_methods.rs @@ -32,6 +32,9 @@ mod tests { assert!(is_sandbox_callable( "/openshell.inference.v1.Inference/GetInferenceBundle" )); + assert!(is_sandbox_callable( + "/openshell.v1.OpenShell/ExchangeProviderSubjectToken" + )); } #[test] diff --git a/crates/openshell-server/src/grpc/mod.rs b/crates/openshell-server/src/grpc/mod.rs index 2c52acbe12..76eafcf5d1 100644 --- a/crates/openshell-server/src/grpc/mod.rs +++ b/crates/openshell-server/src/grpc/mod.rs @@ -23,7 +23,8 @@ use openshell_core::proto::{ DeleteProviderRequest, DeleteProviderResponse, DeleteSandboxRequest, DeleteSandboxResponse, DeleteServiceRequest, DeleteServiceResponse, DeleteWorkspaceRequest, DeleteWorkspaceResponse, DetachSandboxProviderRequest, DetachSandboxProviderResponse, EditDraftChunkRequest, - EditDraftChunkResponse, ExecSandboxEvent, ExecSandboxInput, ExecSandboxRequest, + EditDraftChunkResponse, ExchangeProviderSubjectTokenRequest, + ExchangeProviderSubjectTokenResponse, ExecSandboxEvent, ExecSandboxInput, ExecSandboxRequest, ExposeServiceRequest, GatewayMessage, GetCurrentUserRequest, GetCurrentUserResponse, GetDraftHistoryRequest, GetDraftHistoryResponse, GetDraftPolicyRequest, GetDraftPolicyResponse, GetGatewayConfigRequest, GetGatewayConfigResponse, GetGatewayInfoRequest, @@ -542,6 +543,13 @@ impl OpenShell for OpenShellService { policy::handle_get_sandbox_provider_environment(&self.state, request).await } + async fn exchange_provider_subject_token( + &self, + request: Request, + ) -> Result, Status> { + provider::handle_exchange_provider_subject_token(&self.state, request).await + } + async fn update_config( &self, request: Request, diff --git a/crates/openshell-server/src/grpc/provider.rs b/crates/openshell-server/src/grpc/provider.rs index 4d67eee018..3d8fe393c9 100644 --- a/crates/openshell-server/src/grpc/provider.rs +++ b/crates/openshell-server/src/grpc/provider.rs @@ -23,6 +23,7 @@ use openshell_core::telemetry::{ use openshell_policy::ProviderPolicyLayer; use prost::Message; use std::collections::{HashMap, HashSet}; +use std::error::Error as StdError; use tonic::Status; use tracing::warn; @@ -31,6 +32,8 @@ use super::{ MAX_MAP_KEY_LEN, MAX_MAP_VALUE_LEN, MAX_PAGE_SIZE, MAX_PROVIDER_CONFIG_ENTRIES, clamp_limit, }; +const GATEWAY_SPIFFE_WORKLOAD_API_SOCKET: &str = "OPENSHELL_GATEWAY_SPIFFE_WORKLOAD_API_SOCKET"; + // --------------------------------------------------------------------------- // CRUD helpers // --------------------------------------------------------------------------- @@ -2004,26 +2007,131 @@ use openshell_core::proto::{ ConfigureProviderRefreshRequest, ConfigureProviderRefreshResponse, CreateProviderRequest, DeleteProviderProfileRequest, DeleteProviderProfileResponse, DeleteProviderRefreshRequest, DeleteProviderRefreshResponse, DeleteProviderRequest, DeleteProviderResponse, + ExchangeProviderSubjectTokenRequest, ExchangeProviderSubjectTokenResponse, GetProviderProfileRequest, GetProviderRefreshStatusRequest, GetProviderRefreshStatusResponse, GetProviderRequest, ImportProviderProfilesRequest, ImportProviderProfilesResponse, LintProviderProfilesRequest, LintProviderProfilesResponse, ListProviderProfilesRequest, ListProviderProfilesResponse, ListProvidersRequest, ListProvidersResponse, - ProviderCredentialRefreshStrategy, ProviderProfileDiagnostic, ProviderProfileImportItem, - ProviderProfileResponse, ProviderResponse, RotateProviderCredentialRequest, - RotateProviderCredentialResponse, StoredProviderProfile, UpdateProviderProfilesRequest, - UpdateProviderProfilesResponse, UpdateProviderRequest, + ProviderCredentialRefreshStrategy, ProviderCredentialTokenGrantType, ProviderProfileDiagnostic, + ProviderProfileImportItem, ProviderProfileResponse, ProviderResponse, + RotateProviderCredentialRequest, RotateProviderCredentialResponse, StoredProviderProfile, + UpdateProviderProfilesRequest, UpdateProviderProfilesResponse, UpdateProviderRequest, +}; +use openshell_core::spiffe::{ + JwtSvidParseError, SpiffeJwtClaims, parse_unverified_jwt_svid_claims, + trust_domain as spiffe_trust_domain, workload_api_endpoint, }; use openshell_providers::{ CredentialRefreshProfile, ProfileValidationDiagnostic, ProviderTypeProfile, normalize_profile_id, normalize_provider_type, strategy_output_env_key, strategy_output_spec, strategy_primary_env_key, validate_profile_set, }; -use std::sync::Arc; +use serde::Deserialize; +use std::sync::{Arc, LazyLock, RwLock}; use tonic::{Request, Response}; use crate::auth::principal::Principal; use crate::auth::workspace_authz::{MinWorkspaceRole, authorize_workspace, require_platform_admin}; +const TOKEN_EXCHANGE_GRANT_TYPE: &str = "urn:ietf:params:oauth:grant-type:token-exchange"; +const DEFAULT_CLIENT_ASSERTION_TYPE: &str = + "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"; +const DEFAULT_TOKEN_TYPE: &str = "urn:ietf:params:oauth:token-type:access_token"; +const MAX_OAUTH_ERROR_FIELD_LEN: usize = 256; +const DEFAULT_INTERMEDIATE_TOKEN_CACHE_TTL_SECONDS: i64 = 300; +const MAX_INTERMEDIATE_TOKEN_CACHE_TTL_SECONDS: i64 = 3600; +const INTERMEDIATE_TOKEN_CACHE_EXPIRY_SKEW_SECONDS: i64 = 30; +const MAX_INTERMEDIATE_TOKEN_CACHE_ENTRIES: usize = 1024; + +static TOKEN_EXCHANGE_HTTP_CLIENT: LazyLock> = + LazyLock::new(|| { + reqwest::Client::builder() + .timeout(std::time::Duration::from_secs(30)) + .connect_timeout(std::time::Duration::from_secs(30)) + .no_proxy() + .redirect(reqwest::redirect::Policy::none()) + .build() + .map_err(|err| { + format!("provider token exchange HTTP client configuration failed: {err}") + }) + }); +static INTERMEDIATE_TOKEN_CACHE: LazyLock = + LazyLock::new(IntermediateTokenCache::new); + +fn token_exchange_http_client() -> Result<&'static reqwest::Client, Status> { + TOKEN_EXCHANGE_HTTP_CLIENT + .as_ref() + .map_err(|err| Status::internal(err.clone())) +} + +#[derive(Clone)] +struct CachedIntermediateToken { + access_token: String, + token_type: String, + expires_at_ms: i64, +} + +struct IntermediateTokenCache { + tokens: Arc>>, +} + +impl IntermediateTokenCache { + fn new() -> Self { + Self { + tokens: Arc::new(RwLock::new(HashMap::new())), + } + } + + fn get(&self, key: &str) -> Option { + let now_ms = crate::persistence::current_time_ms(); + let tokens = self.tokens.read().ok()?; + let cached = tokens.get(key)?; + if cached.expires_at_ms <= now_ms { + return None; + } + Some(TokenExchangeResponseBody { + access_token: cached.access_token.clone(), + expires_in: cached.expires_at_ms.saturating_sub(now_ms) / 1000, + token_type: cached.token_type.clone(), + }) + } + + fn set(&self, key: String, token: &TokenExchangeResponseBody, expires_at_ms: i64) { + if let Ok(mut tokens) = self.tokens.write() { + let now_ms = crate::persistence::current_time_ms(); + tokens.retain(|_, cached| cached.expires_at_ms > now_ms); + if tokens.len() >= MAX_INTERMEDIATE_TOKEN_CACHE_ENTRIES + && let Some(evict_key) = tokens.keys().next().cloned() + { + tokens.remove(&evict_key); + } + tokens.insert( + key, + CachedIntermediateToken { + access_token: token.access_token.clone(), + token_type: token.token_type.clone(), + expires_at_ms, + }, + ); + } + } +} + +#[derive(Debug, Deserialize)] +struct TokenExchangeResponseBody { + access_token: String, + #[serde(default)] + expires_in: i64, + #[serde(default)] + token_type: String, +} + +#[derive(Debug, Deserialize)] +struct OAuthErrorResponse { + error: Option, + error_description: Option, +} + async fn authorize_and_resolve_profile_workspace( state: &Arc, principal: &Principal, @@ -2048,7 +2156,6 @@ async fn authorize_and_resolve_profile_workspace( super::workspace::resolve_workspace(state.store.as_ref(), &authz.workspace).await } } - pub(super) async fn handle_create_provider( state: &Arc, request: Request, @@ -3273,6 +3380,640 @@ pub(super) async fn handle_update_provider( } } +pub(super) async fn handle_exchange_provider_subject_token( + state: &Arc, + request: Request, +) -> Result, Status> { + let req = request.get_ref().clone(); + let principal = crate::auth::guard::enforce_sandbox_scope(&request, &req.sandbox_id)?; + crate::auth::guard::ensure_sandbox_principal_scope(&principal, &req.sandbox_id)?; + drop(request); + + if req.provider.trim().is_empty() { + return Err(Status::invalid_argument("provider is required")); + } + if req.credential_key.trim().is_empty() { + return Err(Status::invalid_argument("credential_key is required")); + } + if req.supervisor_jwt_svid.trim().is_empty() { + return Err(Status::invalid_argument("supervisor_jwt_svid is required")); + } + + let sandbox = state + .store + .get_message::(&req.sandbox_id) + .await + .map_err(|e| Status::internal(format!("fetch sandbox failed: {e}")))? + .ok_or_else(|| Status::not_found("sandbox not found"))?; + let workspace = sandbox.object_workspace().to_string(); + let spec = sandbox + .spec + .as_ref() + .ok_or_else(|| Status::internal("sandbox has no spec"))?; + if !spec + .providers + .iter() + .any(|provider| provider == &req.provider) + { + return Err(Status::permission_denied( + "provider is not attached to this sandbox", + )); + } + + let provider = state + .store + .get_message_by_name::(&workspace, &req.provider) + .await + .map_err(|e| Status::internal(format!("fetch provider failed: {e}")))? + .ok_or_else(|| Status::not_found("provider not found"))?; + let profile_id = normalize_provider_type(&provider.r#type).unwrap_or(provider.r#type.as_str()); + let catalog = state + .provider_profile_sources + .snapshot_catalog(state.store.as_ref(), &workspace) + .await?; + let profile = + get_provider_type_profile_for_scope(&catalog, profile_id, &provider.profile_workspace) + .ok_or_else(|| Status::failed_precondition("provider profile not found"))?; + let profile_proto = profile.to_proto(); + let credential = profile_proto + .credentials + .iter() + .find(|credential| credential.name == req.credential_key) + .ok_or_else(|| { + Status::failed_precondition("credential not declared by provider profile") + })?; + let token_grant = credential + .token_grant + .as_ref() + .ok_or_else(|| Status::failed_precondition("credential does not declare token_grant"))?; + let grant_type = ProviderCredentialTokenGrantType::try_from(token_grant.grant_type) + .unwrap_or(ProviderCredentialTokenGrantType::ClientCredentials); + if grant_type != ProviderCredentialTokenGrantType::TokenExchange { + return Err(Status::failed_precondition( + "credential token_grant is not token_exchange", + )); + } + let subject_token = token_grant + .subject_token + .as_ref() + .ok_or_else(|| Status::failed_precondition("token_exchange subject_token is missing"))?; + if subject_token.source != "provider_credential" { + return Err(Status::failed_precondition( + "unsupported subject_token source", + )); + } + if !profile_proto + .credentials + .iter() + .any(|credential| credential.name == subject_token.credential) + { + return Err(Status::failed_precondition( + "subject token credential not declared by provider profile", + )); + } + let stored_subject_token = + resolve_subject_token_credential(&state.credentials, &provider, &subject_token.credential) + .await?; + + let jwt_svid_audience = + effective_jwt_svid_audience(&token_grant.token_endpoint, &token_grant.jwt_svid_audience); + let gateway_jwt_svid = fetch_gateway_jwt_svid(&jwt_svid_audience).await?; + let gateway_claims = parse_unverified_spiffe_claims(&gateway_jwt_svid)?; + validate_gateway_jwt_svid_claims(&gateway_claims, &jwt_svid_audience)?; + let supervisor_claims = validate_supervisor_jwt_svid( + &req.supervisor_jwt_svid, + &gateway_claims, + &jwt_svid_audience, + ) + .await?; + + let intermediate_cache_key = intermediate_token_cache_key(IntermediateTokenCacheKeyInput { + provider: &provider, + dynamic_credential: &req.credential_key, + subject_credential: &subject_token.credential, + 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), + audience: &supervisor_claims.sub, + requested_token_type: effective_token_type(&token_grant.requested_token_type), + supervisor_subject: &supervisor_claims.sub, + gateway_subject: &gateway_claims.sub, + }); + if let Some(cached) = INTERMEDIATE_TOKEN_CACHE.get(&intermediate_cache_key) { + return Ok(Response::new(ExchangeProviderSubjectTokenResponse { + access_token: cached.access_token, + expires_in: cached.expires_in, + token_type: cached.token_type, + })); + } + + let token_response = perform_intermediate_token_exchange( + &token_grant.token_endpoint, + &gateway_jwt_svid, + &token_grant.client_assertion_type, + &stored_subject_token, + &subject_token.subject_token_type, + &supervisor_claims.sub, + &token_grant.requested_token_type, + ) + .await + .inspect_err(|status| { + warn!( + sandbox_id = %req.sandbox_id, + provider = %req.provider, + credential_key = %req.credential_key, + subject_credential = %subject_token.credential, + 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, + token_grant.cache_ttl_seconds, + provider_credential_expires_at_ms(&provider, &subject_token.credential), + supervisor_claims.exp, + ); + if cache_expires_at_ms > crate::persistence::current_time_ms() { + INTERMEDIATE_TOKEN_CACHE.set(intermediate_cache_key, &token_response, cache_expires_at_ms); + } + + Ok(Response::new(ExchangeProviderSubjectTokenResponse { + access_token: token_response.access_token, + expires_in: token_response.expires_in, + token_type: token_response.token_type, + })) +} + +async fn resolve_subject_token_credential( + credentials: &crate::credentials::CredentialRuntime, + provider: &Provider, + credential_key: &str, +) -> Result { + if let Some(value) = provider + .credentials + .get(credential_key) + .filter(|value| !value.is_empty()) + { + ensure_subject_token_credential_not_expired(provider, credential_key)?; + return Ok(value.clone()); + } + + if provider.credential_handles.contains_key(credential_key) { + let resolved = credentials + .resolve_provider_handles(provider, crate::persistence::current_time_ms()) + .await?; + if let Some(value) = resolved + .values + .get(credential_key) + .filter(|value| !value.is_empty()) + { + return Ok(value.clone()); + } + } + + Err(Status::failed_precondition( + "subject token credential is not configured", + )) +} + +fn ensure_subject_token_credential_not_expired( + provider: &Provider, + credential_key: &str, +) -> Result<(), Status> { + let expires_at_ms = provider_credential_expires_at_ms(provider, credential_key); + if expires_at_ms > 0 && expires_at_ms <= crate::persistence::current_time_ms() { + return Err(Status::failed_precondition( + "subject token credential has expired", + )); + } + Ok(()) +} + +fn provider_credential_expires_at_ms(provider: &Provider, credential_key: &str) -> i64 { + provider + .credential_expires_at_ms + .get(credential_key) + .copied() + .unwrap_or_default() +} + +struct IntermediateTokenCacheKeyInput<'a> { + provider: &'a Provider, + dynamic_credential: &'a str, + subject_credential: &'a str, + token_endpoint: &'a str, + client_assertion_type: &'a str, + subject_token_type: &'a str, + audience: &'a str, + requested_token_type: &'a str, + supervisor_subject: &'a str, + gateway_subject: &'a str, +} + +fn intermediate_token_cache_key(input: IntermediateTokenCacheKeyInput<'_>) -> String { + let provider_id = input + .provider + .metadata + .as_ref() + .map(|metadata| metadata.id.as_str()) + .filter(|id| !id.is_empty()) + .unwrap_or_else(|| input.provider.object_name()); + let provider_resource_version = input + .provider + .metadata + .as_ref() + .map_or(0, |metadata| metadata.resource_version); + format!( + "{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}", + provider_id, + provider_resource_version, + input.dynamic_credential, + input.subject_credential, + input.token_endpoint, + input.client_assertion_type, + input.subject_token_type, + input.audience, + input.requested_token_type, + input.supervisor_subject, + input.gateway_subject + ) +} + +fn intermediate_token_cache_expires_at_ms( + token: &TokenExchangeResponseBody, + cache_ttl_seconds: i64, + subject_token_expires_at_ms: i64, + supervisor_svid_exp_seconds: i64, +) -> i64 { + let now_ms = crate::persistence::current_time_ms(); + let mut ttl_seconds = if token.expires_in > 0 { + token + .expires_in + .min(MAX_INTERMEDIATE_TOKEN_CACHE_TTL_SECONDS) + } else { + DEFAULT_INTERMEDIATE_TOKEN_CACHE_TTL_SECONDS + }; + if cache_ttl_seconds > 0 { + ttl_seconds = ttl_seconds.min(cache_ttl_seconds); + } + ttl_seconds = ttl_seconds + .saturating_sub(INTERMEDIATE_TOKEN_CACHE_EXPIRY_SKEW_SECONDS) + .max(1); + let mut expires_at_ms = now_ms.saturating_add(ttl_seconds.saturating_mul(1000)); + expires_at_ms = cap_cache_expiry_ms(expires_at_ms, jwt_exp_ms(&token.access_token)); + expires_at_ms = cap_cache_expiry_ms(expires_at_ms, Some(subject_token_expires_at_ms)); + expires_at_ms = cap_cache_expiry_ms( + expires_at_ms, + (supervisor_svid_exp_seconds > 0).then(|| supervisor_svid_exp_seconds.saturating_mul(1000)), + ); + expires_at_ms +} + +fn cap_cache_expiry_ms(current_expires_at_ms: i64, cap_expires_at_ms: Option) -> i64 { + let Some(cap_expires_at_ms) = cap_expires_at_ms.filter(|value| *value > 0) else { + return current_expires_at_ms; + }; + current_expires_at_ms.min( + cap_expires_at_ms + .saturating_sub(INTERMEDIATE_TOKEN_CACHE_EXPIRY_SKEW_SECONDS.saturating_mul(1000)), + ) +} + +fn jwt_exp_ms(token: &str) -> Option { + use base64::Engine as _; + let payload = token.split('.').nth(1)?; + let decoded = base64::engine::general_purpose::URL_SAFE_NO_PAD + .decode(payload) + .ok()?; + let claims = serde_json::from_slice::(&decoded).ok()?; + claims + .get("exp")? + .as_i64() + .map(|exp| exp.saturating_mul(1000)) +} + +async fn fetch_gateway_jwt_svid(audience: &str) -> Result { + let socket_path = std::env::var(GATEWAY_SPIFFE_WORKLOAD_API_SOCKET) + .ok() + .filter(|value| !value.trim().is_empty()) + .ok_or_else(|| { + Status::failed_precondition(format!( + "{GATEWAY_SPIFFE_WORKLOAD_API_SOCKET} is required for provider token exchange" + )) + })?; + let endpoint = workload_api_endpoint(std::path::Path::new(&socket_path)); + let client = spiffe::WorkloadApiClient::connect_to(&endpoint) + .await + .map_err(|e| { + Status::failed_precondition(format!("SPIFFE Workload API unavailable: {e}")) + })?; + client + .fetch_jwt_token([audience], None) + .await + .map_err(|e| Status::failed_precondition(format!("failed to fetch gateway JWT-SVID: {e}"))) +} + +fn validate_gateway_jwt_svid_claims( + claims: &SpiffeJwtClaims, + expected_audience: &str, +) -> Result<(), Status> { + if !claims.aud.contains(expected_audience) { + return Err(Status::failed_precondition( + "gateway SVID audience does not match token grant audience", + )); + } + if spiffe_trust_domain(&claims.sub).is_none() { + return Err(Status::failed_precondition( + "gateway SVID subject is not a SPIFFE ID", + )); + } + if claims.exp > 0 && claims.exp.saturating_mul(1000) <= crate::persistence::current_time_ms() { + return Err(Status::failed_precondition("gateway SVID has expired")); + } + Ok(()) +} + +async fn validate_supervisor_jwt_svid( + token: &str, + gateway_claims: &SpiffeJwtClaims, + expected_audience: &str, +) -> Result { + let unverified = parse_unverified_spiffe_claims(token)?; + if unverified.iss != gateway_claims.iss { + return Err(Status::permission_denied( + "supervisor SVID issuer does not match gateway SVID issuer", + )); + } + if !unverified.aud.contains(expected_audience) { + return Err(Status::permission_denied( + "supervisor SVID audience does not match token grant audience", + )); + } + let supervisor_trust_domain = spiffe_trust_domain(&unverified.sub) + .ok_or_else(|| Status::permission_denied("supervisor SVID subject is not a SPIFFE ID"))?; + let gateway_trust_domain = spiffe_trust_domain(&gateway_claims.sub) + .ok_or_else(|| Status::failed_precondition("gateway SVID subject is not a SPIFFE ID"))?; + if supervisor_trust_domain != gateway_trust_domain { + return Err(Status::permission_denied( + "supervisor SVID trust domain does not match gateway SVID trust domain", + )); + } + + let socket_path = std::env::var(GATEWAY_SPIFFE_WORKLOAD_API_SOCKET) + .ok() + .filter(|value| !value.trim().is_empty()) + .ok_or_else(|| { + Status::failed_precondition(format!( + "{GATEWAY_SPIFFE_WORKLOAD_API_SOCKET} is required for supervisor JWT-SVID validation" + )) + })?; + let endpoint = workload_api_endpoint(std::path::Path::new(&socket_path)); + let client = spiffe::WorkloadApiClient::connect_to(&endpoint) + .await + .map_err(|e| { + Status::failed_precondition(format!("SPIFFE Workload API unavailable: {e}")) + })?; + let bundles = client + .fetch_jwt_bundles() + .await + .map_err(|e| Status::internal(format!("SPIFFE JWT bundle fetch failed: {e}")))?; + spiffe::JwtSvid::parse_and_validate(token, &bundles, &[expected_audience]) + .map_err(|e| Status::permission_denied(format!("invalid supervisor JWT-SVID: {e}")))?; + Ok(unverified) +} + +fn format_error_chain(prefix: &str, error: &dyn StdError) -> String { + let mut message = format!("{prefix}: {error}"); + let mut source = error.source(); + while let Some(err) = source { + message.push_str(": "); + message.push_str(&err.to_string()); + source = err.source(); + } + message +} + +fn parse_unverified_spiffe_claims(token: &str) -> Result { + parse_unverified_jwt_svid_claims(token).map_err(jwt_svid_parse_error_status) +} + +fn jwt_svid_parse_error_status(error: JwtSvidParseError) -> Status { + Status::permission_denied(error.to_string()) +} + +async fn perform_intermediate_token_exchange( + token_endpoint: &str, + gateway_jwt_svid: &str, + client_assertion_type: &str, + subject_token: &str, + subject_token_type: &str, + audience: &str, + requested_token_type: &str, +) -> Result { + let token_endpoint_url = parse_token_endpoint_url(token_endpoint)?; + let client_assertion_type = effective_client_assertion_type(client_assertion_type); + let subject_token_type = effective_token_type(subject_token_type); + let requested_token_type = effective_token_type(requested_token_type); + let form_params = [ + ("grant_type", TOKEN_EXCHANGE_GRANT_TYPE), + ("client_assertion_type", client_assertion_type), + ("client_assertion", gateway_jwt_svid), + ("subject_token", subject_token), + ("subject_token_type", subject_token_type), + ("audience", audience), + ("requested_token_type", requested_token_type), + ]; + + let response = token_exchange_http_client()? + .post(token_endpoint_url) + .form(&form_params) + .send() + .await + .map_err(|e| { + Status::internal(format_error_chain( + "provider token exchange request failed", + &e, + )) + })?; + if !response.status().is_success() { + let status = response.status(); + let body = response + .text() + .await + .unwrap_or_else(|_| "".to_string()); + return Err(Status::failed_precondition(token_exchange_failure_message( + status, &body, + ))); + } + let body = response + .json::() + .await + .map_err(|e| { + Status::internal(format!( + "provider token exchange response parse failed: {e}" + )) + })?; + validate_oauth_access_token(&body.access_token)?; + Ok(body) +} + +fn parse_token_endpoint_url(token_endpoint: &str) -> Result { + let url = reqwest::Url::parse(token_endpoint) + .map_err(|_| Status::invalid_argument("token_endpoint must be an absolute URL"))?; + if token_endpoint_transport_allowed(&url) { + return Ok(url); + } + Err(Status::invalid_argument( + "token_endpoint must use https, except http for loopback or in-cluster service hosts", + )) +} + +fn token_endpoint_transport_allowed(url: &reqwest::Url) -> bool { + match url.scheme() { + "https" => true, + "http" => url + .host_str() + .is_some_and(|host| is_loopback_host(host) || is_kubernetes_service_host(host)), + _ => false, + } +} + +fn is_loopback_host(host: &str) -> bool { + let host = host.trim_matches(['[', ']']); + if host.eq_ignore_ascii_case("localhost") { + return true; + } + match host.parse::() { + Ok(std::net::IpAddr::V4(v4)) => v4.is_loopback(), + Ok(std::net::IpAddr::V6(v6)) => { + v6.is_loopback() || v6.to_ipv4_mapped().is_some_and(|v4| v4.is_loopback()) + } + Err(_) => false, + } +} + +fn is_kubernetes_service_host(host: &str) -> bool { + let host = host.trim_end_matches('.').to_ascii_lowercase(); + let labels = host.split('.').collect::>(); + let is_service_name = labels.len() == 3 && labels[2] == "svc"; + let is_cluster_local_service = + labels.len() == 5 && labels[2] == "svc" && labels[3] == "cluster" && labels[4] == "local"; + (is_service_name || is_cluster_local_service) && labels.iter().all(|label| !label.is_empty()) +} + +fn effective_client_assertion_type(client_assertion_type: &str) -> &str { + if client_assertion_type.trim().is_empty() { + DEFAULT_CLIENT_ASSERTION_TYPE + } else { + client_assertion_type + } +} + +fn effective_token_type(token_type: &str) -> &str { + if token_type.trim().is_empty() { + DEFAULT_TOKEN_TYPE + } else { + token_type + } +} + +fn effective_jwt_svid_audience(token_endpoint: &str, jwt_svid_audience: &str) -> String { + if !jwt_svid_audience.trim().is_empty() { + return jwt_svid_audience.to_string(); + } + derive_issuer_from_token_endpoint(token_endpoint) +} + +fn derive_issuer_from_token_endpoint(token_endpoint: &str) -> String { + if let Some(realms_idx) = token_endpoint.find("/realms/") { + let after_realms = &token_endpoint[realms_idx + "/realms/".len()..]; + if let Some(slash_idx) = after_realms.find('/') { + let realm_end = realms_idx + "/realms/".len() + slash_idx; + return token_endpoint[..realm_end].to_string(); + } + } + token_endpoint.to_string() +} + +fn validate_oauth_access_token(token: &str) -> Result<(), Status> { + if token.is_empty() || !is_token68(token) { + return Err(Status::internal( + "provider token exchange returned a malformed access token", + )); + } + Ok(()) +} + +fn is_token68(token: &str) -> bool { + let mut padding_started = false; + let mut saw_value = false; + for byte in token.bytes() { + if byte == b'=' { + padding_started = true; + continue; + } + if padding_started || !is_token68_value_byte(byte) { + return false; + } + saw_value = true; + } + saw_value +} + +fn is_token68_value_byte(byte: u8) -> bool { + byte.is_ascii_alphanumeric() || matches!(byte, b'-' | b'.' | b'_' | b'~' | b'+' | b'/') +} + +fn token_exchange_failure_message(status: reqwest::StatusCode, body: &str) -> String { + let Ok(error_response) = serde_json::from_str::(body) else { + return format!("provider token exchange failed with status {status}"); + }; + let error = error_response + .error + .as_deref() + .map(sanitize_oauth_error_field) + .filter(|value| !value.is_empty()); + let description = error_response + .error_description + .as_deref() + .map(sanitize_oauth_error_field) + .filter(|value| !value.is_empty()); + match (error, description) { + (Some(error), Some(description)) => { + format!( + "provider token exchange failed with status {status}: error={error}; error_description={description}" + ) + } + (Some(error), None) => { + format!("provider token exchange failed with status {status}: error={error}") + } + (None, Some(description)) => { + format!( + "provider token exchange failed with status {status}: error_description={description}" + ) + } + (None, None) => format!("provider token exchange failed with status {status}"), + } +} + +fn sanitize_oauth_error_field(value: &str) -> String { + value + .chars() + .map(|ch| if ch.is_control() { ' ' } else { ch }) + .take(MAX_OAUTH_ERROR_FIELD_LEN) + .collect::() + .trim() + .to_string() +} + pub(super) async fn handle_get_provider_refresh_status( state: &Arc, request: Request, @@ -3969,13 +4710,16 @@ mod tests { refresh: None, path_template: String::new(), token_grant: Some(ProviderCredentialTokenGrant { + grant_type: ProviderCredentialTokenGrantType::ClientCredentials as i32, token_endpoint: "http://keycloak.default.svc.cluster.local/realms/openshell/protocol/openid-connect/token".to_string(), audience: "api://default".to_string(), jwt_svid_audience: "http://keycloak.default.svc.cluster.local/realms/openshell" .to_string(), client_assertion_type: "urn:ietf:params:oauth:client-assertion-type:jwt-bearer".to_string(), + subject_token: None, scopes: vec!["openid".to_string()], + requested_token_type: String::new(), cache_ttl_seconds: 300, audience_overrides: service_audiences .iter() @@ -4745,12 +5489,15 @@ mod tests { refresh: None, path_template: String::new(), token_grant: Some(ProviderCredentialTokenGrant { + grant_type: ProviderCredentialTokenGrantType::ClientCredentials as i32, token_endpoint: "https://auth.example.com/token".to_string(), audience: "api://default".to_string(), jwt_svid_audience: "https://auth.example.com".to_string(), client_assertion_type: "urn:ietf:params:oauth:client-assertion-type:jwt-bearer" .to_string(), + subject_token: None, scopes: vec!["read".to_string()], + requested_token_type: String::new(), cache_ttl_seconds: 300, audience_overrides: Vec::new(), }), @@ -6681,6 +7428,33 @@ mod tests { ); } + #[tokio::test] + async fn token_exchange_subject_token_resolves_credential_handle() { + let config = openshell_core::Config::new(None).with_credential_drivers(["test-static"]); + let credentials = crate::credentials::CredentialRuntime::from_config(&config).unwrap(); + let mut provider = provider_with_values("exchange-local", "custom"); + provider.credentials.clear(); + provider.metadata.as_mut().expect("provider metadata").id = "provider-id".to_string(); + let handles = credentials + .store_provider_credentials( + provider.object_name(), + provider.object_workspace(), + provider.object_id(), + &HashMap::from([("subject_token".to_string(), "user-token".to_string())]), + &HashMap::new(), + ) + .await + .unwrap(); + provider.credential_handles = handles; + + let subject_token = + resolve_subject_token_credential(&credentials, &provider, "subject_token") + .await + .unwrap(); + + assert_eq!(subject_token, "user-token"); + } + #[tokio::test] async fn update_provider_record_overwrites_credentials_with_runtime() { let store = test_store().await; diff --git a/crates/openshell-server/src/lib.rs b/crates/openshell-server/src/lib.rs index a6031a9bc6..107e79e36a 100644 --- a/crates/openshell-server/src/lib.rs +++ b/crates/openshell-server/src/lib.rs @@ -82,6 +82,10 @@ pub(crate) static TEST_ENV_LOCK: LazyLock> = LazyLock::new(|| Mutex::n #[cfg(test)] pub(crate) static TEST_TRACING_LOCK: LazyLock> = LazyLock::new(|| Mutex::new(())); +pub(crate) fn install_jsonwebtoken_crypto_provider() { + let _ = jsonwebtoken::crypto::aws_lc::DEFAULT_PROVIDER.install_default(); +} + use compute::ComputeRuntime; use gateway_listener::{BoundGatewayListener, GatewayListenerScope, bind_gateway_listeners}; pub use grpc::OpenShellService; diff --git a/crates/openshell-server/src/provider_refresh.rs b/crates/openshell-server/src/provider_refresh.rs index dc039265f6..54daf1727c 100644 --- a/crates/openshell-server/src/provider_refresh.rs +++ b/crates/openshell-server/src/provider_refresh.rs @@ -787,6 +787,8 @@ async fn mint_oauth2_client_credentials( async fn mint_google_service_account_jwt( state: &StoredProviderCredentialRefreshState, ) -> Result { + crate::install_jsonwebtoken_crypto_provider(); + let token_url = google_token_url(state); let client_email = required_material(&state.material, "client_email")?; let private_key = required_material(&state.material, "private_key")?; diff --git a/crates/openshell-server/tests/common/mod.rs b/crates/openshell-server/tests/common/mod.rs index a2df4755f0..e5377e3d1f 100644 --- a/crates/openshell-server/tests/common/mod.rs +++ b/crates/openshell-server/tests/common/mod.rs @@ -16,8 +16,9 @@ use hyper_util::{ use openshell_core::proto::{ CreateProviderRequest, CreateSandboxRequest, CreateSshSessionRequest, CreateSshSessionResponse, DeleteProviderRequest, DeleteProviderResponse, DeleteSandboxRequest, DeleteSandboxResponse, - ExecSandboxEvent, ExecSandboxInput, ExecSandboxRequest, GatewayMessage, - GetGatewayConfigRequest, GetGatewayConfigResponse, GetProviderRequest, GetSandboxConfigRequest, + ExchangeProviderSubjectTokenRequest, ExchangeProviderSubjectTokenResponse, ExecSandboxEvent, + ExecSandboxInput, ExecSandboxRequest, GatewayMessage, GetGatewayConfigRequest, + GetGatewayConfigResponse, GetProviderRequest, GetSandboxConfigRequest, GetSandboxConfigResponse, GetSandboxProviderEnvironmentRequest, GetSandboxProviderEnvironmentResponse, GetSandboxRequest, HealthRequest, HealthResponse, IssueSandboxTokenRequest, IssueSandboxTokenResponse, ListProvidersRequest, @@ -212,6 +213,13 @@ impl OpenShell for TestOpenShell { Ok(Response::new(RevokeSshSessionResponse::default())) } + async fn exchange_provider_subject_token( + &self, + _request: tonic::Request, + ) -> Result, Status> { + Err(Status::unimplemented("unused")) + } + async fn create_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 86c7354647..0a7d9fe28d 100644 --- a/crates/openshell-server/tests/supervisor_relay_integration.rs +++ b/crates/openshell-server/tests/supervisor_relay_integration.rs @@ -236,6 +236,12 @@ impl OpenShell for RelayGateway { ) -> Result, Status> { Err(Status::unimplemented("unused")) } + async fn exchange_provider_subject_token( + &self, + _: tonic::Request, + ) -> Result, Status> { + Err(Status::unimplemented("unused")) + } async fn create_provider( &self, _: tonic::Request, diff --git a/crates/openshell-supervisor-network/src/l7/token_grant_injection.rs b/crates/openshell-supervisor-network/src/l7/token_grant_injection.rs index ce6c581bc8..969060bc96 100644 --- a/crates/openshell-supervisor-network/src/l7/token_grant_injection.rs +++ b/crates/openshell-supervisor-network/src/l7/token_grant_injection.rs @@ -26,6 +26,8 @@ pub struct TokenGrantRequest<'a> { pub audience: &'a str, pub scopes: &'a [String], pub cache_ttl_seconds: i64, + pub grant_type: i32, + pub requested_token_type: &'a str, } pub trait TokenGrantResolver: Send + Sync { @@ -45,13 +47,17 @@ impl TokenGrantResolver for SpiffeTokenGrantResolver { ) -> Pin> + Send + 'a>> { Box::pin(async move { crate::token_grant::obtain_provider_token( - request.provider_key, - request.token_endpoint, - request.jwt_svid_audience, - request.client_assertion_type, - request.audience, - request.scopes, - request.cache_ttl_seconds, + crate::token_grant::ObtainProviderTokenRequest { + provider_name: request.provider_key, + token_endpoint: request.token_endpoint, + jwt_svid_audience: request.jwt_svid_audience, + client_assertion_type: request.client_assertion_type, + audience: request.audience, + scopes: request.scopes, + cache_ttl_override: request.cache_ttl_seconds, + grant_type: request.grant_type, + requested_token_type: request.requested_token_type, + }, ) .await }) @@ -127,7 +133,7 @@ pub async fn inject_if_needed(req: L7Request, ctx: &L7EvalContext) -> Result( audience: &token_grant.audience, scopes: &token_grant.scopes, cache_ttl_seconds: token_grant.cache_ttl_seconds, + grant_type: token_grant.grant_type, + requested_token_type: &token_grant.requested_token_type, } } @@ -348,7 +356,9 @@ fn inject_header(raw_header: &[u8], header_name: &str, header_value: &str) -> Re #[cfg(test)] pub mod test_support { use super::*; - use openshell_core::proto::{ProviderCredentialTokenGrant, ProviderProfileCredential}; + use openshell_core::proto::{ + ProviderCredentialTokenGrant, ProviderCredentialTokenGrantType, ProviderProfileCredential, + }; use std::collections::HashMap; use std::sync::{Arc, Mutex}; @@ -366,6 +376,8 @@ pub mod test_support { audience: String, scopes: Vec, cache_ttl_seconds: i64, + grant_type: i32, + requested_token_type: String, } pub struct TokenGrantTestFixture { @@ -445,6 +457,11 @@ pub mod test_support { assert_eq!(request.audience, "api://example"); assert_eq!(request.scopes, ["read"]); assert_eq!(request.cache_ttl_seconds, 300); + assert_eq!( + request.grant_type, + ProviderCredentialTokenGrantType::ClientCredentials as i32 + ); + assert!(request.requested_token_type.is_empty()); } } @@ -458,6 +475,9 @@ pub mod test_support { scopes: vec!["read".to_string()], cache_ttl_seconds: 300, audience_overrides: Vec::new(), + grant_type: ProviderCredentialTokenGrantType::ClientCredentials as i32, + subject_token: None, + requested_token_type: String::new(), } } @@ -474,6 +494,8 @@ pub mod test_support { audience: request.audience.to_string(), scopes: request.scopes.to_vec(), cache_ttl_seconds: request.cache_ttl_seconds, + grant_type: request.grant_type, + requested_token_type: request.requested_token_type.to_string(), }; Box::pin(async move { self.requests @@ -510,6 +532,12 @@ mod tests { 443, "/repos/owner/repo" )); + assert!(dynamic_credential_key_matches( + "api.example.com\t443\t/repos/**\trev:42\tgithub:access_token", + "api.example.com", + 443, + "/repos/owner/repo" + )); assert!(!dynamic_credential_key_matches( key, "uploads.example.com", diff --git a/crates/openshell-supervisor-network/src/lib.rs b/crates/openshell-supervisor-network/src/lib.rs index f5d0205e3a..2f4c9e2638 100644 --- a/crates/openshell-supervisor-network/src/lib.rs +++ b/crates/openshell-supervisor-network/src/lib.rs @@ -17,7 +17,6 @@ pub mod procfs; pub mod proxy; pub mod run; pub mod sigv4; -mod spiffe_endpoint; mod token_grant; pub mod upstream_proxy; diff --git a/crates/openshell-supervisor-network/src/proxy.rs b/crates/openshell-supervisor-network/src/proxy.rs index b4da286bf8..385627296f 100644 --- a/crates/openshell-supervisor-network/src/proxy.rs +++ b/crates/openshell-supervisor-network/src/proxy.rs @@ -20,7 +20,7 @@ use openshell_core::net::{ set_tcp_nodelay_best_effort, }; use openshell_core::policy::ProxyPolicy; -use openshell_core::provider_credentials::ProviderCredentialState; +use openshell_core::provider_credentials::{ProviderCredentialSnapshot, ProviderCredentialState}; use openshell_core::secrets::{self, SecretResolver, rewrite_header_line_checked}; use openshell_ocsf::{ ActionId, ActivityId, DispositionId, Endpoint, HttpActivityBuilder, HttpRequest, @@ -96,6 +96,27 @@ const HOST_GATEWAY_ALIASES: &[&str] = &[ "host.docker.internal", ]; +fn revision_scoped_dynamic_credentials( + snapshot: &ProviderCredentialSnapshot, +) -> std::collections::HashMap { + snapshot + .dynamic_credentials + .iter() + .map(|(key, credential)| { + let scoped_key = key.rsplit_once('\t').map_or_else( + || format!("rev:{}\t{key}", snapshot.revision), + |(endpoint_selector, provider_credential)| { + format!( + "{endpoint_selector}\trev:{}\t{provider_credential}", + snapshot.revision + ) + }, + ); + (scoped_key, credential.clone()) + }) + .collect() +} + /// Cloud instance metadata IPs that are NEVER exempted from SSRF blocking, /// even when they coincidentally match a host-gateway alias resolution. /// This list covers the well-known IMDS endpoints across major cloud providers. @@ -356,9 +377,9 @@ impl ProxyHandle { .as_ref() .and_then(ProviderCredentialState::resolver); let dynamic_credentials = provider_credentials.as_ref().map(|state| { - Arc::new(std::sync::RwLock::new( - state.snapshot().dynamic_credentials.clone(), - )) + Arc::new(std::sync::RwLock::new(revision_scoped_dynamic_credentials( + &state.snapshot(), + ))) }); let dtx = denial_tx.clone(); let atx = activity_tx.clone(); @@ -6429,6 +6450,29 @@ network_policies: ); } + #[test] + fn revision_scoped_dynamic_credentials_preserves_endpoint_selector_and_adds_revision() { + let mut dynamic_credentials = std::collections::HashMap::new(); + dynamic_credentials.insert( + "api.example.test\t443\t/v1/**\tprovider:access_token".to_string(), + openshell_core::proto::ProviderProfileCredential { + name: "access_token".to_string(), + ..Default::default() + }, + ); + let snapshot = ProviderCredentialSnapshot { + revision: 42, + child_env: std::collections::HashMap::new(), + dynamic_credentials, + }; + + let scoped = revision_scoped_dynamic_credentials(&snapshot); + + assert!( + scoped.contains_key("api.example.test\t443\t/v1/**\trev:42\tprovider:access_token") + ); + } + #[test] fn connect_activity_is_skipped_when_l7_will_count_the_request() { let (tx, mut rx) = mpsc::channel(4); diff --git a/crates/openshell-supervisor-network/src/spiffe_endpoint.rs b/crates/openshell-supervisor-network/src/spiffe_endpoint.rs deleted file mode 100644 index 4494626275..0000000000 --- a/crates/openshell-supervisor-network/src/spiffe_endpoint.rs +++ /dev/null @@ -1,17 +0,0 @@ -// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -use std::path::Path; - -/// Convert a path to a SPIFFE Workload API endpoint URL. -/// -/// If the path already has a scheme (`unix:` or `tcp:`), use it as-is. -/// Otherwise, assume it is a Unix socket path and prepend `unix:`. -pub fn workload_api_endpoint(path: &Path) -> String { - let path = path.to_string_lossy(); - if path.starts_with("unix:") || path.starts_with("tcp:") { - path.into_owned() - } else { - format!("unix:{path}") - } -} diff --git a/crates/openshell-supervisor-network/src/token_grant.rs b/crates/openshell-supervisor-network/src/token_grant.rs index 03e9bfb398..23a45ab14b 100644 --- a/crates/openshell-supervisor-network/src/token_grant.rs +++ b/crates/openshell-supervisor-network/src/token_grant.rs @@ -39,6 +39,7 @@ use std::sync::{Arc, LazyLock, RwLock}; use std::time::{Duration, SystemTime, UNIX_EPOCH}; use miette::{IntoDiagnostic, Result, WrapErr}; +use openshell_core::proto::ProviderCredentialTokenGrantType; use openshell_core::sandbox_env; use serde::Deserialize; use spiffe::WorkloadApiClient; @@ -60,6 +61,7 @@ const TOKEN_CACHE_EXPIRY_SKEW_SECONDS: i64 = 30; const MAX_TOKEN_EXPIRES_IN_SECONDS: i64 = 3600; const DEFAULT_CLIENT_ASSERTION_TYPE: &str = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"; +const DEFAULT_TOKEN_TYPE: &str = "urn:ietf:params:oauth:token-type:access_token"; /// `OAuth2` token response from the authorization server. #[derive(Debug, Clone, Deserialize)] @@ -153,41 +155,98 @@ impl TokenCache { /// - JWT-SVID fetch fails /// - Token service request fails /// - Token response is invalid -pub async fn obtain_provider_token( - provider_name: &str, - token_endpoint: &str, - jwt_svid_audience: &str, - client_assertion_type: &str, - audience: &str, - scopes: &[String], - cache_ttl_override: i64, -) -> Result { +pub struct ObtainProviderTokenRequest<'a> { + pub provider_name: &'a str, + pub token_endpoint: &'a str, + pub jwt_svid_audience: &'a str, + pub client_assertion_type: &'a str, + pub audience: &'a str, + pub scopes: &'a [String], + pub cache_ttl_override: i64, + pub grant_type: i32, + pub requested_token_type: &'a str, +} + +pub async fn obtain_provider_token(request: ObtainProviderTokenRequest<'_>) -> Result { + let grant_type = + ProviderCredentialTokenGrantType::try_from(request.grant_type).map_err(|_| { + tracing::error!( + grant_type = request.grant_type, + provider = request.provider_name, + "unrecognised token grant_type" + ); + miette::miette!( + "unrecognised token grant_type {} for provider credential", + request.grant_type + ) + })?; obtain_provider_token_with_grant( ObtainProviderTokenInput { cache: &TOKEN_CACHE, - provider_name, - token_endpoint, - jwt_svid_audience, - client_assertion_type, - audience, - scopes, - cache_ttl_override, + provider_name: request.provider_name, + token_endpoint: request.token_endpoint, + jwt_svid_audience: request.jwt_svid_audience, + client_assertion_type: request.client_assertion_type, + audience: request.audience, + scopes: request.scopes, + cache_ttl_override: request.cache_ttl_override, + grant_type, + requested_token_type: request.requested_token_type, }, |jwt_audience| async move { // Fetch JWT-SVID with authorization server as audience // For RFC 7523, the JWT assertion's aud claim identifies the issuer/realm let jwt_svid = fetch_jwt_svid_for_token_grant(&jwt_audience).await?; - // Perform OAuth2 JWT client assertion grant - // The audience parameter in the token request specifies the resource server - perform_token_grant( - token_endpoint, - &jwt_svid, - client_assertion_type, - audience, - scopes, - ) - .await + match grant_type { + ProviderCredentialTokenGrantType::ClientCredentials + | ProviderCredentialTokenGrantType::Unspecified => { + // Perform OAuth2 JWT client assertion grant. The audience + // parameter in the token request specifies the resource server. + perform_token_grant( + request.token_endpoint, + &jwt_svid, + request.client_assertion_type, + request.audience, + request.scopes, + ) + .await + } + ProviderCredentialTokenGrantType::TokenExchange => { + let (provider, credential_key) = + parse_provider_credential_key(request.provider_name)?; + let endpoint = supervisor_gateway_endpoint_from_env()?; + let sandbox_id = supervisor_sandbox_id_from_env()?; + let intermediate = + openshell_core::grpc_client::exchange_provider_subject_token( + &endpoint, + &sandbox_id, + provider, + credential_key, + &jwt_svid, + ) + .await + .map_err(|err| { + miette::miette!( + "gateway intermediate provider token exchange failed: {err}" + ) + })?; + validate_access_token(&intermediate.access_token)?; + let intermediate_subject_token_type = + final_exchange_subject_token_type(request.requested_token_type); + perform_token_exchange( + request.token_endpoint, + &jwt_svid, + request.client_assertion_type, + &intermediate.access_token, + intermediate_subject_token_type, + request.audience, + request.scopes, + request.requested_token_type, + ) + .await + } + } }, ) .await @@ -202,6 +261,8 @@ struct ObtainProviderTokenInput<'a> { audience: &'a str, scopes: &'a [String], cache_ttl_override: i64, + grant_type: ProviderCredentialTokenGrantType, + requested_token_type: &'a str, } async fn obtain_provider_token_with_grant( @@ -216,14 +277,16 @@ where // For Keycloak: https://auth.example.com/realms/openshell/protocol/openid-connect/token // -> https://auth.example.com/realms/openshell let jwt_audience = effective_jwt_svid_audience(input.token_endpoint, input.jwt_svid_audience); - let cache_key = token_cache_key( - input.provider_name, - input.token_endpoint, - &jwt_audience, - effective_client_assertion_type(input.client_assertion_type), - input.audience, - input.scopes, - ); + let cache_key = token_cache_key(TokenCacheKeyInput { + provider_name: input.provider_name, + token_endpoint: input.token_endpoint, + jwt_svid_audience: &jwt_audience, + client_assertion_type: effective_client_assertion_type(input.client_assertion_type), + audience: input.audience, + scopes: input.scopes, + grant_type: input.grant_type, + requested_token_type: effective_token_type(input.requested_token_type), + }); // Check cache first if let Some(cached) = input.cache.get(&cache_key) { @@ -256,7 +319,7 @@ async fn fetch_jwt_svid_for_token_grant(audience: &str) -> Result { let socket_path = provider_spiffe_workload_api_socket_from_env()?; let endpoint = - crate::spiffe_endpoint::workload_api_endpoint(std::path::Path::new(&socket_path)); + openshell_core::spiffe::workload_api_endpoint(std::path::Path::new(&socket_path)); // Connect to SPIRE agent let client = WorkloadApiClient::connect_to(&endpoint) @@ -359,6 +422,74 @@ async fn perform_token_grant( Ok(token_response) } +#[allow(clippy::too_many_arguments)] +async fn perform_token_exchange( + token_endpoint: &str, + jwt_svid: &str, + client_assertion_type: &str, + subject_token: &str, + subject_token_type: &str, + audience: &str, + scopes: &[String], + requested_token_type: &str, +) -> Result { + let token_endpoint_url = parse_token_endpoint_url(token_endpoint)?; + let client_assertion_type = effective_client_assertion_type(client_assertion_type); + let subject_token_type = effective_token_type(subject_token_type); + let requested_token_type = effective_token_type(requested_token_type); + let mut form_params = vec![ + ( + "grant_type", + "urn:ietf:params:oauth:grant-type:token-exchange", + ), + ("client_assertion_type", client_assertion_type), + ("client_assertion", jwt_svid), + ("subject_token", subject_token), + ("subject_token_type", subject_token_type), + ("requested_token_type", requested_token_type), + ]; + + let audience_param; + if !audience.is_empty() { + audience_param = audience.to_string(); + form_params.push(("audience", &audience_param)); + } + + let scope_param; + if !scopes.is_empty() { + scope_param = scopes.join(" "); + form_params.push(("scope", &scope_param)); + } + + let response = TOKEN_GRANT_HTTP_CLIENT + .post(token_endpoint_url) + .form(&form_params) + .send() + .await + .into_diagnostic() + .wrap_err_with(|| format!("failed to POST token exchange to {token_endpoint}"))?; + + if !response.status().is_success() { + let status = response.status(); + let body = response + .text() + .await + .unwrap_or_else(|_| "".to_string()); + return Err(miette::miette!( + "{}", + token_grant_failure_message(status, &body) + )); + } + + let token_response = response + .json::() + .await + .into_diagnostic() + .wrap_err("failed to parse token exchange response as JSON")?; + validate_access_token(&token_response.access_token)?; + Ok(token_response) +} + fn parse_token_endpoint_url(token_endpoint: &str) -> Result { let url = reqwest::Url::parse(token_endpoint) .into_diagnostic() @@ -491,22 +622,63 @@ fn effective_client_assertion_type(client_assertion_type: &str) -> &str { } } -fn token_cache_key( - provider_name: &str, - token_endpoint: &str, - jwt_svid_audience: &str, - client_assertion_type: &str, - audience: &str, - scopes: &[String], -) -> String { +fn effective_token_type(token_type: &str) -> &str { + if token_type.trim().is_empty() { + DEFAULT_TOKEN_TYPE + } else { + token_type + } +} + +fn final_exchange_subject_token_type(requested_token_type: &str) -> &str { + effective_token_type(requested_token_type) +} + +fn supervisor_gateway_endpoint_from_env() -> Result { + std::env::var(sandbox_env::ENDPOINT) + .ok() + .filter(|value| !value.trim().is_empty()) + .ok_or_else(|| miette::miette!("{} not set", sandbox_env::ENDPOINT)) +} + +fn supervisor_sandbox_id_from_env() -> Result { + std::env::var(sandbox_env::SANDBOX_ID) + .ok() + .filter(|value| !value.trim().is_empty()) + .ok_or_else(|| miette::miette!("{} not set", sandbox_env::SANDBOX_ID)) +} + +fn parse_provider_credential_key(key: &str) -> Result<(&str, &str)> { + let provider_and_credential = key + .rsplit_once('\t') + .map_or(key, |(_, provider_and_credential)| provider_and_credential); + provider_and_credential.split_once(':').ok_or_else(|| { + miette::miette!("dynamic token grant key is missing provider credential identity") + }) +} + +struct TokenCacheKeyInput<'a> { + provider_name: &'a str, + token_endpoint: &'a str, + jwt_svid_audience: &'a str, + client_assertion_type: &'a str, + audience: &'a str, + scopes: &'a [String], + grant_type: ProviderCredentialTokenGrantType, + requested_token_type: &'a str, +} + +fn token_cache_key(input: TokenCacheKeyInput<'_>) -> String { format!( - "{}\t{}\t{}\t{}\t{}\t{}", - provider_name, - token_endpoint, - jwt_svid_audience, - client_assertion_type, - audience, - scopes.join(" ") + "{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}", + input.provider_name, + input.token_endpoint, + input.jwt_svid_audience, + input.client_assertion_type, + input.audience, + input.scopes.join(" "), + input.grant_type as i32, + input.requested_token_type ) } @@ -778,6 +950,8 @@ mod tests { audience: input.audience, scopes: input.scopes, cache_ttl_override: input.cache_ttl_override, + grant_type: ProviderCredentialTokenGrantType::ClientCredentials, + requested_token_type: "", }, move |_| { let grant_calls = input.grant_calls.clone(); @@ -814,6 +988,8 @@ mod tests { audience, scopes, cache_ttl_override, + grant_type: ProviderCredentialTokenGrantType::ClientCredentials, + requested_token_type: "", }, |_| async { Err(miette::miette!("grant should not be called on cache hit")) }, ) @@ -854,6 +1030,19 @@ mod tests { assert_eq!(audience, "spiffe://custom-audience"); } + #[test] + fn final_exchange_subject_token_type_uses_intermediate_requested_token_type() { + let stored_subject_token_type = "urn:ietf:params:oauth:token-type:id_token"; + let requested_token_type = "urn:ietf:params:oauth:token-type:access_token"; + + assert_ne!(stored_subject_token_type, requested_token_type); + assert_eq!( + final_exchange_subject_token_type(requested_token_type), + requested_token_type + ); + assert_eq!(final_exchange_subject_token_type(""), DEFAULT_TOKEN_TYPE); + } + #[test] fn validate_access_token_accepts_token68_values() { for token in [ @@ -916,44 +1105,99 @@ mod tests { #[test] fn token_cache_key_varies_by_resource_audience_and_scopes() { - let base = token_cache_key( - "alpha.default.svc.cluster.local\t80\t\tprovider:access_token", - "https://auth.example.com/realms/openshell/protocol/openid-connect/token", - "https://auth.example.com/realms/openshell", - DEFAULT_CLIENT_ASSERTION_TYPE, - "alpha", - &["alpha".to_string()], - ); - let different_audience = token_cache_key( - "alpha.default.svc.cluster.local\t80\t\tprovider:access_token", - "https://auth.example.com/realms/openshell/protocol/openid-connect/token", - "https://auth.example.com/realms/openshell", - DEFAULT_CLIENT_ASSERTION_TYPE, - "delta", - &["alpha".to_string()], - ); - let different_scopes = token_cache_key( - "alpha.default.svc.cluster.local\t80\t\tprovider:access_token", - "https://auth.example.com/realms/openshell/protocol/openid-connect/token", - "https://auth.example.com/realms/openshell", - DEFAULT_CLIENT_ASSERTION_TYPE, - "alpha", - &["delta".to_string()], - ); - let different_assertion_type = token_cache_key( - "alpha.default.svc.cluster.local\t80\t\tprovider:access_token", - "https://auth.example.com/realms/openshell/protocol/openid-connect/token", - "https://auth.example.com/realms/openshell", - "urn:ietf:params:oauth:client-assertion-type:jwt-spiffe", - "alpha", - &["alpha".to_string()], - ); + let provider_name = "alpha.default.svc.cluster.local\t80\t\tprovider:access_token"; + let token_endpoint = + "https://auth.example.com/realms/openshell/protocol/openid-connect/token"; + let jwt_svid_audience = "https://auth.example.com/realms/openshell"; + let alpha_scopes = ["alpha".to_string()]; + let delta_scopes = ["delta".to_string()]; + let base = token_cache_key(TokenCacheKeyInput { + provider_name, + token_endpoint, + jwt_svid_audience, + client_assertion_type: DEFAULT_CLIENT_ASSERTION_TYPE, + audience: "alpha", + scopes: &alpha_scopes, + grant_type: ProviderCredentialTokenGrantType::ClientCredentials, + requested_token_type: DEFAULT_TOKEN_TYPE, + }); + let different_audience = token_cache_key(TokenCacheKeyInput { + provider_name, + token_endpoint, + jwt_svid_audience, + client_assertion_type: DEFAULT_CLIENT_ASSERTION_TYPE, + audience: "delta", + scopes: &alpha_scopes, + grant_type: ProviderCredentialTokenGrantType::ClientCredentials, + requested_token_type: DEFAULT_TOKEN_TYPE, + }); + let different_scopes = token_cache_key(TokenCacheKeyInput { + provider_name, + token_endpoint, + jwt_svid_audience, + client_assertion_type: DEFAULT_CLIENT_ASSERTION_TYPE, + audience: "alpha", + scopes: &delta_scopes, + grant_type: ProviderCredentialTokenGrantType::ClientCredentials, + requested_token_type: DEFAULT_TOKEN_TYPE, + }); + let different_assertion_type = token_cache_key(TokenCacheKeyInput { + provider_name, + token_endpoint, + jwt_svid_audience, + client_assertion_type: "urn:ietf:params:oauth:client-assertion-type:jwt-spiffe", + audience: "alpha", + scopes: &alpha_scopes, + grant_type: ProviderCredentialTokenGrantType::ClientCredentials, + requested_token_type: DEFAULT_TOKEN_TYPE, + }); assert_ne!(base, different_audience); assert_ne!(base, different_scopes); assert_ne!(base, different_assertion_type); } + #[test] + fn token_cache_key_varies_by_provider_env_revision_prefix() { + let token_endpoint = + "https://auth.example.com/realms/openshell/protocol/openid-connect/token"; + let jwt_svid_audience = "https://auth.example.com/realms/openshell"; + let scopes = ["alpha".to_string()]; + let revision_one = token_cache_key(TokenCacheKeyInput { + provider_name: "api.example.test\t443\t/v1/**\trev:1\tprovider:access_token", + token_endpoint, + jwt_svid_audience, + client_assertion_type: DEFAULT_CLIENT_ASSERTION_TYPE, + audience: "alpha", + scopes: &scopes, + grant_type: ProviderCredentialTokenGrantType::TokenExchange, + requested_token_type: DEFAULT_TOKEN_TYPE, + }); + let revision_two = token_cache_key(TokenCacheKeyInput { + provider_name: "api.example.test\t443\t/v1/**\trev:2\tprovider:access_token", + token_endpoint, + jwt_svid_audience, + client_assertion_type: DEFAULT_CLIENT_ASSERTION_TYPE, + audience: "alpha", + scopes: &scopes, + grant_type: ProviderCredentialTokenGrantType::TokenExchange, + requested_token_type: DEFAULT_TOKEN_TYPE, + }); + + assert_ne!(revision_one, revision_two); + } + + #[test] + fn provider_credential_key_parser_ignores_revision_segment() { + assert_eq!( + parse_provider_credential_key( + "api.example.test\t443\t/v1/**\trev:42\tprovider:access_token" + ) + .expect("parse provider credential key"), + ("provider", "access_token") + ); + } + #[test] fn token_cache_ttl_uses_override_without_endpoint_skew() { assert_eq!(token_cache_ttl_seconds(120, 10), 120); @@ -1077,14 +1321,16 @@ mod tests { let jwt_svid_audience = "https://auth.example.com"; let audience = "api://resource"; - let cache_key = token_cache_key( + let cache_key = token_cache_key(TokenCacheKeyInput { provider_name, token_endpoint, jwt_svid_audience, - DEFAULT_CLIENT_ASSERTION_TYPE, + client_assertion_type: DEFAULT_CLIENT_ASSERTION_TYPE, audience, - &scopes, - ); + scopes: &scopes, + grant_type: ProviderCredentialTokenGrantType::ClientCredentials, + requested_token_type: DEFAULT_TOKEN_TYPE, + }); cache.set( cache_key, "expired-token".to_string(), @@ -1128,6 +1374,8 @@ mod tests { audience, scopes: &scopes, cache_ttl_override: 0, + grant_type: ProviderCredentialTokenGrantType::ClientCredentials, + requested_token_type: "", }, |_| async { Ok(TokenResponse { @@ -1141,14 +1389,16 @@ mod tests { .await .expect_err("malformed access token should fail before caching"); - let cache_key = token_cache_key( + let cache_key = token_cache_key(TokenCacheKeyInput { provider_name, token_endpoint, jwt_svid_audience, - DEFAULT_CLIENT_ASSERTION_TYPE, + client_assertion_type: DEFAULT_CLIENT_ASSERTION_TYPE, audience, - &scopes, - ); + scopes: &scopes, + grant_type: ProviderCredentialTokenGrantType::ClientCredentials, + requested_token_type: DEFAULT_TOKEN_TYPE, + }); assert_eq!( err.to_string(), diff --git a/crates/openshell-supervisor-process/src/run.rs b/crates/openshell-supervisor-process/src/run.rs index 91e56b7ec8..3ea2cbce89 100644 --- a/crates/openshell-supervisor-process/src/run.rs +++ b/crates/openshell-supervisor-process/src/run.rs @@ -110,10 +110,18 @@ pub async fn run_process( // the flag stays at its default (false) and no skill is installed. install_initial_agent_skill(sandbox_id, openshell_endpoint, &agent_proposals).await; + // Provider token grants may mount supervisor-only identity sockets such as + // the SPIFFE Workload API. Prepare the child mount namespace that hides + // those mounts before supervisor seccomp hardening removes the needed + // namespace syscalls. + #[cfg(target_os = "linux")] + crate::process::prepare_supervisor_identity_mount_namespace_from_env()?; + // Install the supervisor seccomp prelude before spawning any workload-side // tasks. By this point the orchestrator has finished privileged startup - // helpers (network namespace setup, nftables probes via run_networking), - // and the SSH listener and entrypoint child have not been exposed yet. + // helpers (network namespace setup, identity mount namespace setup, + // nftables probes via run_networking), and the SSH listener and entrypoint + // child have not been exposed yet. crate::sandbox::apply_supervisor_startup_hardening()?; // Spawn the bypass detection monitor. It tails dmesg for nftables LOG diff --git a/deploy/helm/openshell/README.md b/deploy/helm/openshell/README.md index 93dab354b6..6bd909fc2b 100644 --- a/deploy/helm/openshell/README.md +++ b/deploy/helm/openshell/README.md @@ -139,15 +139,20 @@ JWT signing Secret. ## SPIFFE/SPIRE provider token grants -Set `server.providerTokenGrants.spiffe.enabled=true` to let sandbox supervisors -use SPIFFE JWT-SVIDs for dynamic provider token grants. The chart keeps -supervisor-to-gateway authentication on gateway-minted sandbox JWTs and passes -the SPIFFE Workload API socket path to the Kubernetes driver so sandbox pods can -mount the SPIFFE CSI socket. +Set `server.providerTokenGrants.spiffe.enabled=true` to let the gateway and +sandbox supervisors use SPIFFE JWT-SVIDs for dynamic provider token grants. The +chart keeps supervisor-to-gateway authentication on gateway-minted sandbox JWTs, +mounts the SPIFFE CSI socket into the gateway pod, exports +`OPENSHELL_GATEWAY_SPIFFE_WORKLOAD_API_SOCKET`, and passes the socket path to +the Kubernetes driver so sandbox pods can mount the same socket. For local development, uncomment the SPIRE Helm releases in `skaffold.yaml` and add `ci/values-spire.yaml` to the OpenShell release values files. +The gateway verifies supervisor JWT-SVIDs with JWT bundles fetched from the +SPIFFE Workload API, so this path does not require access to the SPIRE OIDC +discovery endpoint or its TLS CA. + ## Values | Key | Type | Default | Description | @@ -249,8 +254,8 @@ add `ci/values-spire.yaml` to the OpenShell release values files. | server.oidc.scopesClaim | string | `""` | Dot-separated path to the scopes array in the JWT claims. | | server.oidc.userRole | string | `""` | Role name for standard user access. | | server.policyValidationFailureMode | string | `"fail_closed"` | Posture when a candidate sandbox policy fails validation. `fail_closed` deactivates the previous policy; `retain_last_valid` keeps it active. | -| server.providerTokenGrants.spiffe.enabled | bool | `false` | Mount the SPIFFE Workload API socket into sandbox pods for dynamic provider token grants. | -| server.providerTokenGrants.spiffe.workloadApiSocketPath | string | `"/spiffe-workload-api/spire-agent.sock"` | Path to the SPIFFE Workload API socket mounted into sandbox pods. | +| server.providerTokenGrants.spiffe.enabled | bool | `false` | Mount the SPIFFE Workload API socket into gateway and sandbox pods for dynamic provider token grants. | +| server.providerTokenGrants.spiffe.workloadApiSocketPath | string | `"/spiffe-workload-api/spire-agent.sock"` | Path to the SPIFFE Workload API socket mounted into gateway and sandbox pods. | | server.sandboxImage | string | `"ghcr.io/nvidia/openshell-community/sandboxes/base:latest"` | Default sandbox image used when requests do not specify one. | | server.sandboxImagePullPolicy | string | `""` | Kubernetes imagePullPolicy for sandbox pods. Empty = Kubernetes default (Always for :latest, IfNotPresent otherwise). Set to "Always" for dev clusters so new images are picked up without manual eviction. | | server.sandboxImagePullSecrets | list | `[]` | Image pull secrets attached to sandbox pods. Referenced Secrets must exist in the sandbox namespace. | diff --git a/deploy/helm/openshell/README.md.gotmpl b/deploy/helm/openshell/README.md.gotmpl index 0242d8118c..3757abc795 100644 --- a/deploy/helm/openshell/README.md.gotmpl +++ b/deploy/helm/openshell/README.md.gotmpl @@ -139,14 +139,19 @@ JWT signing Secret. ## SPIFFE/SPIRE provider token grants -Set `server.providerTokenGrants.spiffe.enabled=true` to let sandbox supervisors -use SPIFFE JWT-SVIDs for dynamic provider token grants. The chart keeps -supervisor-to-gateway authentication on gateway-minted sandbox JWTs and passes -the SPIFFE Workload API socket path to the Kubernetes driver so sandbox pods can -mount the SPIFFE CSI socket. +Set `server.providerTokenGrants.spiffe.enabled=true` to let the gateway and +sandbox supervisors use SPIFFE JWT-SVIDs for dynamic provider token grants. The +chart keeps supervisor-to-gateway authentication on gateway-minted sandbox JWTs, +mounts the SPIFFE CSI socket into the gateway pod, exports +`OPENSHELL_GATEWAY_SPIFFE_WORKLOAD_API_SOCKET`, and passes the socket path to +the Kubernetes driver so sandbox pods can mount the same socket. For local development, uncomment the SPIRE Helm releases in `skaffold.yaml` and add `ci/values-spire.yaml` to the OpenShell release values files. +The gateway verifies supervisor JWT-SVIDs with JWT bundles fetched from the +SPIFFE Workload API, so this path does not require access to the SPIRE OIDC +discovery endpoint or its TLS CA. + {{ template "chart.valuesSection" . }} {{ template "helm-docs.versionFooter" . }} diff --git a/deploy/helm/openshell/templates/_gateway-workload.tpl b/deploy/helm/openshell/templates/_gateway-workload.tpl index 5ff608ae59..a73acc9810 100644 --- a/deploy/helm/openshell/templates/_gateway-workload.tpl +++ b/deploy/helm/openshell/templates/_gateway-workload.tpl @@ -66,7 +66,8 @@ spec: {{- end }} # Most gateway settings live in the ConfigMap-backed TOML file # mounted at /etc/openshell/gateway.toml. Secret-bearing settings use - # env vars that the TOML references by name. + # env vars that the TOML references by name. Some process-level + # settings consumed by libraries outside gateway code also remain here. {{- if and .Values.server.oidc.issuer .Values.server.oidc.caConfigMapName }} # OIDC issuer custom-CA: rustls/reqwest read SSL_CERT_FILE for # outbound TLS verification. This is a process-level env var @@ -77,6 +78,10 @@ spec: {{- end }} - name: OPENSHELL_TELEMETRY_ENABLED value: {{ .Values.server.telemetryEnabled | quote }} + {{- if .Values.server.providerTokenGrants.spiffe.enabled }} + - name: OPENSHELL_GATEWAY_SPIFFE_WORKLOAD_API_SOCKET + value: {{ .Values.server.providerTokenGrants.spiffe.workloadApiSocketPath | quote }} + {{- end }} volumeMounts: {{- if eq (include "openshell.workloadKind" .) "statefulset" }} - name: openshell-data @@ -108,6 +113,11 @@ spec: mountPath: /etc/openshell-tls/oidc-ca readOnly: true {{- end }} + {{- if .Values.server.providerTokenGrants.spiffe.enabled }} + - name: spiffe-workload-api + mountPath: {{ dir .Values.server.providerTokenGrants.spiffe.workloadApiSocketPath | quote }} + readOnly: true + {{- end }} ports: - name: grpc containerPort: {{ .Values.service.port }} @@ -180,6 +190,12 @@ spec: configMap: name: {{ .Values.server.oidc.caConfigMapName }} {{- end }} + {{- if .Values.server.providerTokenGrants.spiffe.enabled }} + - name: spiffe-workload-api + csi: + driver: csi.spiffe.io + readOnly: true + {{- end }} {{- with .Values.nodeSelector }} nodeSelector: {{- toYaml . | nindent 4 }} diff --git a/deploy/helm/openshell/tests/gateway_config_test.yaml b/deploy/helm/openshell/tests/gateway_config_test.yaml index afacd01eb4..eaa2140862 100644 --- a/deploy/helm/openshell/tests/gateway_config_test.yaml +++ b/deploy/helm/openshell/tests/gateway_config_test.yaml @@ -549,14 +549,42 @@ tests: path: data["gateway.toml"] pattern: '\[openshell\.gateway\.spiffe\]' - - it: keeps the gateway sandbox JWT secret mounted when provider SPIFFE grants are enabled + - it: mounts the gateway SPIFFE socket while keeping sandbox JWT auth set: server.providerTokenGrants.spiffe.enabled: true template: templates/statefulset.yaml asserts: - - matchRegex: - path: spec.template.spec.volumes[1].name - pattern: '^sandbox-jwt$' + - contains: + path: spec.template.spec.containers[0].env + content: + name: OPENSHELL_GATEWAY_SPIFFE_WORKLOAD_API_SOCKET + value: /spiffe-workload-api/spire-agent.sock + - contains: + path: spec.template.spec.containers[0].volumeMounts + content: + name: sandbox-jwt + mountPath: /etc/openshell-jwt + readOnly: true + - contains: + path: spec.template.spec.containers[0].volumeMounts + content: + name: spiffe-workload-api + mountPath: /spiffe-workload-api + readOnly: true + - contains: + path: spec.template.spec.volumes + content: + name: sandbox-jwt + secret: + defaultMode: 256 + secretName: openshell-jwt-keys + - contains: + path: spec.template.spec.volumes + content: + name: spiffe-workload-api + csi: + driver: csi.spiffe.io + readOnly: true - it: fails when serverIssuerRef is set but certManager is disabled template: templates/statefulset.yaml diff --git a/deploy/helm/openshell/values.yaml b/deploy/helm/openshell/values.yaml index 33337c768e..b9d7387088 100644 --- a/deploy/helm/openshell/values.yaml +++ b/deploy/helm/openshell/values.yaml @@ -366,15 +366,15 @@ server: # (owner-read only). Override to 0440 or 0444 if the container UID # does not match the volume file owner. secretDefaultMode: "" - # Dynamic provider token grants. When SPIFFE is enabled here, sandbox - # supervisors mount the SPIFFE Workload API socket so provider profiles can - # exchange JWT-SVIDs for upstream access tokens. Supervisor-to-gateway - # authentication still uses gateway-minted sandbox JWTs. + # Dynamic provider token grants. When SPIFFE is enabled here, both the + # gateway and sandbox supervisors mount the SPIFFE Workload API socket so + # token-exchange profiles can use gateway- and sandbox-scoped JWT-SVIDs. + # Supervisor-to-gateway authentication still uses gateway-minted sandbox JWTs. providerTokenGrants: spiffe: - # -- Mount the SPIFFE Workload API socket into sandbox pods for dynamic provider token grants. + # -- Mount the SPIFFE Workload API socket into gateway and sandbox pods for dynamic provider token grants. enabled: false - # -- Path to the SPIFFE Workload API socket mounted into sandbox pods. + # -- Path to the SPIFFE Workload API socket mounted into gateway and sandbox pods. workloadApiSocketPath: /spiffe-workload-api/spire-agent.sock # OIDC (OpenID Connect) configuration for JWT-based authentication. # When issuer is set, the server validates Bearer tokens on gRPC requests. diff --git a/docs/kubernetes/access-control.mdx b/docs/kubernetes/access-control.mdx index 5409a4b11d..e3addad032 100644 --- a/docs/kubernetes/access-control.mdx +++ b/docs/kubernetes/access-control.mdx @@ -23,9 +23,11 @@ For how the CLI resolves gateways and stores credentials, refer to [Gateway Auth Kubernetes sandbox supervisors authenticate back to the gateway as sandbox workloads. By default, the gateway mints its own sandbox JWTs and Kubernetes sandboxes bootstrap them with a projected ServiceAccount token. -Dynamic provider token grants can use SPIFFE without changing supervisor-to-gateway authentication. Set `server.providerTokenGrants.spiffe.enabled=true` to mount the SPIFFE CSI Workload API socket into sandbox pods while keeping the projected ServiceAccount token bootstrap and gateway-minted sandbox JWT path. +Dynamic provider token grants can use SPIFFE without changing supervisor-to-gateway authentication. Set `server.providerTokenGrants.spiffe.enabled=true` to mount the SPIFFE CSI Workload API socket into gateway and sandbox pods while keeping the projected ServiceAccount token bootstrap and gateway-minted sandbox JWT path. -Provider token grants require a SPIFFE implementation such as SPIRE and a `ClusterSPIFFEID` that assigns per-sandbox IDs from the pod's `openshell.io/sandbox-id` annotation. Provider profiles with `token_grant` metadata cause the sandbox supervisor to request JWT-SVIDs and exchange them for upstream OAuth2 access tokens. +Provider token grants require a SPIFFE implementation such as SPIRE and identities for the gateway and sandbox pods. The repository's local SPIRE overlay assigns sandbox IDs from the pod's `openshell.io/sandbox-id` annotation, but the gateway validation path only requires the supervisor SVID to be valid and in the same SPIFFE trust domain as the gateway SVID. Provider profiles with `token_grant` metadata cause the sandbox supervisor to request JWT-SVIDs and exchange them for upstream OAuth2 access tokens. Token-exchange profiles also require a gateway SPIFFE identity because the gateway brokers the intermediate token exchange with its own JWT-SVID. + +The gateway verifies supervisor JWT-SVIDs with JWT bundles fetched from the SPIFFE Workload API, so intermediate token exchange does not require gateway access to the SPIRE OIDC discovery endpoint or its TLS CA. ## OIDC User Authentication @@ -74,6 +76,8 @@ helm upgrade openshell \ Both `adminRole` and `userRole` must be set, or both must be empty. Setting only one is not supported. +OIDC RBAC is method-level authorization. It controls which API operations a caller can perform, but provider and sandbox records are not owned by individual OIDC subjects. In shared clusters, treat provider credentials as gateway-wide resources and use separate gateways or external tenancy controls when users must not see or attach each other's providers and sandboxes. + ### Provider-specific rolesClaim paths | Provider | rolesClaim value | diff --git a/docs/reference/gateway-config.mdx b/docs/reference/gateway-config.mdx index 8d74b3d44f..9c9e7f8d36 100644 --- a/docs/reference/gateway-config.mdx +++ b/docs/reference/gateway-config.mdx @@ -551,6 +551,14 @@ In managed workspace mode, the Kubernetes driver copies each explicitly named namespace on sandbox creation. Shared and operator modes require the Secret to already exist in the sandbox namespace. +For token-exchange provider profiles, the gateway also needs access to its own +SPIFFE Workload API socket. In Helm deployments, set +`server.providerTokenGrants.spiffe.enabled=true`; the chart mounts the socket +into the gateway pod and sets `OPENSHELL_GATEWAY_SPIFFE_WORKLOAD_API_SOCKET`. +The gateway verifies supervisor JWT-SVIDs with JWT bundles fetched from the +SPIFFE Workload API, so this validation path does not require gateway access to +the SPIRE OIDC discovery endpoint or its TLS CA. + ### Docker Sandboxes run as containers on a local bridge network. The supervisor binary is bind-mounted from the host (no in-cluster image pull required); guest mTLS material is supplied as host paths. diff --git a/docs/sandboxes/manage-providers.mdx b/docs/sandboxes/manage-providers.mdx index 8cf1e3715c..efe498da70 100644 --- a/docs/sandboxes/manage-providers.mdx +++ b/docs/sandboxes/manage-providers.mdx @@ -60,6 +60,33 @@ openshell provider create --name my-nvidia --type nvidia --credential NVIDIA_API This looks up the current value of `$NVIDIA_API_KEY` in your shell and stores it. +### From the Current OIDC Login + +Profile-backed token-exchange providers can store the current gateway OIDC +access token as their subject credential: + +```shell +openshell provider create \ + --name custom-api \ + --type custom-api \ + --from-oidc-token +``` + +OpenShell infers the destination credential from the provider profile when the +profile has exactly one `token_grant.subject_token.credential`. If the profile +has more than one, pass `--credential `. Refresh the stored subject token +later with: + +```shell +openshell provider update custom-api --from-oidc-token +``` + +This copies the current OIDC access token and expiry from the active gateway +login. This requires an active named gateway that was registered for OIDC. If +the stored gateway access token is expired and a refresh token is available, the +CLI refreshes it before storing the provider credential. It does not store the +OIDC refresh token in the provider. + Provider profile metadata is available for known provider types. Provider profile network policy is gateway opt-in: diff --git a/docs/sandboxes/providers-v2.mdx b/docs/sandboxes/providers-v2.mdx index f867b84bd5..c01b9963b8 100644 --- a/docs/sandboxes/providers-v2.mdx +++ b/docs/sandboxes/providers-v2.mdx @@ -312,6 +312,12 @@ category: data inference_capable: false credentials: + - name: user_oidc_token + description: User OIDC token used as a token-exchange subject token + env_vars: [CUSTOM_API_USER_OIDC_TOKEN] + required: true + auth_style: bearer + - name: api_token description: API access token env_vars: [CUSTOM_API_TOKEN] @@ -344,17 +350,23 @@ credentials: required: true secret: true - # Optional dynamic credential. The sandbox supervisor requests a - # SPIFFE JWT-SVID, exchanges it at token_endpoint, caches the returned - # access token, and injects it according to auth_style/header_name for - # matching endpoint traffic. + # Optional dynamic credential. The sandbox supervisor resolves this on + # demand for matching endpoint traffic, caches the returned access token, + # and injects it according to auth_style/header_name. token_grant: + # Accepted values: client_credentials, token_exchange. + grant_type: token_exchange token_endpoint: https://login.example.com/realms/custom/protocol/openid-connect/token audience: api://custom-api jwt_svid_audience: https://login.example.com/realms/custom - client_assertion_type: urn:ietf:params:oauth:client-assertion-type:jwt-bearer + client_assertion_type: urn:ietf:params:oauth:client-assertion-type:jwt-spiffe scopes: [api.read, api.write] cache_ttl_seconds: 300 + requested_token_type: urn:ietf:params:oauth:token-type:access_token + subject_token: + source: provider_credential + credential: user_oidc_token + subject_token_type: urn:ietf:params:oauth:token-type:access_token audience_overrides: - host: api.example.com port: 443 @@ -486,7 +498,14 @@ The refresh attaches to the primary credential (`access_key_id`). Each reference ### Dynamic Token Grants -`token_grant` belongs to one credential declaration. When a sandbox with the provider attached sends HTTP traffic to a matching profile endpoint, the supervisor requests a SPIFFE JWT-SVID from the local Workload API, exchanges it at `token_endpoint`, caches the returned access token, and injects it before forwarding the request upstream. Use `auth_style: bearer` to inject `Authorization: Bearer `, or `auth_style: header` with `header_name` to inject the raw access token into a custom header. Token grants do not support `query` or `path` placement. +`token_grant` belongs to one credential declaration. When a sandbox with the provider attached sends HTTP traffic to a matching profile endpoint, the supervisor resolves the dynamic credential, caches the returned access token, and injects it before forwarding the request upstream. Use `auth_style: bearer` to inject `Authorization: Bearer `, or `auth_style: header` with `header_name` to inject the raw access token into a custom header. Token grants do not support `query` or `path` placement. + +OpenShell supports two dynamic grant types: + +| Grant type | Behavior | +|---|---| +| `client_credentials` | The supervisor requests a SPIFFE JWT-SVID from the local Workload API and sends it directly to `token_endpoint` as the OAuth2 client assertion. This is the default when `grant_type` is omitted. | +| `token_exchange` | The supervisor first asks the gateway for an intermediate token. The request includes the supervisor JWT-SVID; the gateway verifies it, uses the SVID subject as the intermediate token audience, and exchanges the stored subject credential at the same `token_endpoint` using the gateway's own JWT-SVID as the client assertion. The supervisor then exchanges that intermediate token for the final upstream token using its own JWT-SVID as the client assertion. | Create provider instances for token-grant-only profiles with `--runtime-credentials`. This records an empty provider instance and makes the runtime-resolved credential source explicit: @@ -497,19 +516,39 @@ openshell provider create \ --runtime-credentials ``` +For `token_exchange` profiles, the provider also stores the user subject token referenced by `token_grant.subject_token.credential`. 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 \ + --name custom-api \ + --type custom-api \ + --from-oidc-token + +openshell provider update custom-api \ + --from-oidc-token +``` + +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. + Token grant fields: | Field | Required | Behavior | |---|---|---| +| `grant_type` | No | `client_credentials` or `token_exchange`. Defaults to `client_credentials` for backward compatibility. | | `token_endpoint` | Yes | OAuth2 token endpoint that accepts a SPIFFE JWT-SVID client assertion. Use `https://` unless the endpoint is loopback or a Kubernetes service DNS name such as `token-issuer.default.svc.cluster.local`. | -| `audience` | No | Resource audience requested from the token service. | +| `audience` | No | Resource audience requested from the token service. For `token_exchange`, this is the final exchange audience; the gateway intermediate exchange always uses the verified supervisor SVID subject as its audience. | | `jwt_svid_audience` | No | Audience used when requesting the JWT-SVID. When omitted, OpenShell derives an issuer-style audience from Keycloak token endpoint paths or falls back to the full token endpoint URL. | -| `client_assertion_type` | No | OAuth2 `client_assertion_type` form value. Defaults to RFC 7523 `urn:ietf:params:oauth:client-assertion-type:jwt-bearer`. Set a provider-specific value, such as `urn:ietf:params:oauth:client-assertion-type:jwt-spiffe`, only when the token issuer explicitly requires it. | +| `client_assertion_type` | No | OAuth2 `client_assertion_type` form value. Defaults to RFC 7523 `urn:ietf:params:oauth:client-assertion-type:jwt-bearer`. Set `urn:ietf:params:oauth:client-assertion-type:jwt-spiffe` when the token issuer expects the SPIFFE assertion type. | | `scopes` | No | OAuth2 scopes sent as a space-separated `scope` parameter. | | `cache_ttl_seconds` | No | Token cache TTL override. When omitted or `0`, OpenShell uses the token response `expires_in` with a 30-second safety margin and one-hour cap, or five minutes minus the margin if the response does not include an expiry. | -| `audience_overrides` | No | Endpoint-specific `audience` and `scopes` overrides selected by host, port, and path. | +| `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. | +| `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. | + +Token grants require the sandbox supervisor to have access to a SPIFFE Workload API socket. `token_exchange` also requires the gateway to have its own Workload API socket so it can present a gateway JWT-SVID during the intermediate exchange. They apply to HTTP traffic that the proxy can inspect. Endpoints with `tls: skip` bypass TLS termination and cannot receive dynamic token grant injection for HTTPS traffic. The token service must return a token value that is safe for HTTP header placement; malformed values are rejected before caching or header injection. -Token grants require the sandbox supervisor to have access to a SPIFFE Workload API socket. They apply to HTTP traffic that the proxy can inspect. Endpoints with `tls: skip` bypass TLS termination and cannot receive dynamic token grant injection for HTTPS traffic. The token service must return a token value that is safe for HTTP header placement; malformed values are rejected before caching or header injection. +The gateway only brokers an intermediate token for a sandbox principal, and only when the requested provider is attached to that sandbox. It verifies the supervisor JWT-SVID issuer, audience, signature, and SPIFFE trust domain against the gateway's own JWT-SVID, then uses the verified supervisor SVID subject as the intermediate-token audience. ## Provider Instances diff --git a/proto/openshell.proto b/proto/openshell.proto index a30852664c..655e08e802 100644 --- a/proto/openshell.proto +++ b/proto/openshell.proto @@ -426,6 +426,15 @@ service OpenShell { }; } + // Exchange a stored provider subject token for an intermediate token scoped + // to the calling supervisor's SPIFFE identity. + rpc ExchangeProviderSubjectToken(ExchangeProviderSubjectTokenRequest) + returns (ExchangeProviderSubjectTokenResponse) { + option (openshell.options.v1.authorization) = { + auth_mode: "sandbox" + }; + } + // Fetch recent sandbox logs (one-shot). rpc GetSandboxLogs(GetSandboxLogsRequest) returns (GetSandboxLogsResponse) { option (openshell.options.v1.authorization) = { @@ -1478,6 +1487,25 @@ message ProviderCredentialTokenGrantAudienceOverride { // Provider credential token grant configuration. // When present, the credential is obtained dynamically via OAuth2 grant when needed. +enum ProviderCredentialTokenGrantType { + PROVIDER_CREDENTIAL_TOKEN_GRANT_TYPE_UNSPECIFIED = 0; + PROVIDER_CREDENTIAL_TOKEN_GRANT_TYPE_CLIENT_CREDENTIALS = 1; + PROVIDER_CREDENTIAL_TOKEN_GRANT_TYPE_TOKEN_EXCHANGE = 2; +} + +message ProviderCredentialTokenGrantSubjectToken { + // Source for the token exchange subject token. Phase one supports + // "provider_credential". + string source = 1; + + // Provider credential key that stores the subject token. + string credential = 2; + + // OAuth2 subject_token_type. If omitted, OpenShell uses + // urn:ietf:params:oauth:token-type:access_token. + string subject_token_type = 3; +} + message ProviderCredentialTokenGrant { // OAuth2 token endpoint URL (e.g., https://keycloak.example.com/realms/my-realm/protocol/openid-connect/token) string token_endpoint = 1; @@ -1502,6 +1530,17 @@ message ProviderCredentialTokenGrant { // Optional: OAuth2 client_assertion_type value. If omitted, OpenShell uses // urn:ietf:params:oauth:client-assertion-type:jwt-bearer. string client_assertion_type = 7; + + // Grant type. If omitted/unspecified, OpenShell treats this as client_credentials + // for backwards compatibility. + ProviderCredentialTokenGrantType grant_type = 8; + + // Subject token metadata for token_exchange grants. + ProviderCredentialTokenGrantSubjectToken subject_token = 9; + + // OAuth2 requested_token_type. If omitted for token_exchange, OpenShell uses + // urn:ietf:params:oauth:token-type:access_token. + string requested_token_type = 10; } // Provider credential declaration. @@ -1814,6 +1853,27 @@ message GetSandboxProviderEnvironmentResponse { repeated string non_secret_environment_keys = 6; } +message ExchangeProviderSubjectTokenRequest { + // The sandbox ID. Must match the authenticated sandbox principal. + string sandbox_id = 1; + + // Attached provider record holding the configured subject token credential. + string provider = 2; + + // Provider profile credential that declares the token_exchange grant. + string credential_key = 3; + + // Supervisor JWT-SVID. The gateway verifies this and uses its `sub` claim + // as the requested audience for the intermediate token. + string supervisor_jwt_svid = 4 [(openshell.options.v1.secret) = true]; +} + +message ExchangeProviderSubjectTokenResponse { + string access_token = 1 [(openshell.options.v1.secret) = true]; + int64 expires_in = 2; + string token_type = 3; +} + // --------------------------------------------------------------------------- // Policy update messages // --------------------------------------------------------------------------- diff --git a/sdk/go/openshell/v1/internal/converter/coverage_test.go b/sdk/go/openshell/v1/internal/converter/coverage_test.go index 34cdc0e05e..703992d645 100644 --- a/sdk/go/openshell/v1/internal/converter/coverage_test.go +++ b/sdk/go/openshell/v1/internal/converter/coverage_test.go @@ -257,6 +257,9 @@ func TestConverterCoversAllProtoFields_ProviderCredentialTokenGrant(t *testing.T "cache_ttl_seconds": true, "audience_overrides": true, "client_assertion_type": true, + "grant_type": true, + "subject_token": true, + "requested_token_type": true, } assertAllFieldsCovered(t, (&pb.ProviderCredentialTokenGrant{}).ProtoReflect().Descriptor(), handled, nil) diff --git a/sdk/go/openshell/v1/internal/converter/profile.go b/sdk/go/openshell/v1/internal/converter/profile.go index b0edaf6445..8bbb346829 100644 --- a/sdk/go/openshell/v1/internal/converter/profile.go +++ b/sdk/go/openshell/v1/internal/converter/profile.go @@ -55,6 +55,30 @@ func ProfileCategoryToProto(c types.ProfileCategory) pb.ProviderProfileCategory } } +// CredentialTokenGrantTypeFromProto converts a proto token grant type to an SDK token grant type. +func CredentialTokenGrantTypeFromProto(t pb.ProviderCredentialTokenGrantType) types.CredentialTokenGrantType { + switch t { + case pb.ProviderCredentialTokenGrantType_PROVIDER_CREDENTIAL_TOKEN_GRANT_TYPE_CLIENT_CREDENTIALS: + return types.CredentialTokenGrantTypeClientCredentials + case pb.ProviderCredentialTokenGrantType_PROVIDER_CREDENTIAL_TOKEN_GRANT_TYPE_TOKEN_EXCHANGE: + return types.CredentialTokenGrantTypeTokenExchange + default: + return types.CredentialTokenGrantType("") + } +} + +// CredentialTokenGrantTypeToProto converts an SDK token grant type to a proto token grant type. +func CredentialTokenGrantTypeToProto(t types.CredentialTokenGrantType) pb.ProviderCredentialTokenGrantType { + switch t { + case types.CredentialTokenGrantTypeClientCredentials: + return pb.ProviderCredentialTokenGrantType_PROVIDER_CREDENTIAL_TOKEN_GRANT_TYPE_CLIENT_CREDENTIALS + case types.CredentialTokenGrantTypeTokenExchange: + return pb.ProviderCredentialTokenGrantType_PROVIDER_CREDENTIAL_TOKEN_GRANT_TYPE_TOKEN_EXCHANGE + default: + return pb.ProviderCredentialTokenGrantType_PROVIDER_CREDENTIAL_TOKEN_GRANT_TYPE_UNSPECIFIED + } +} + // --- NetworkEndpoint --- // NetworkEndpointFromProto converts a proto NetworkEndpoint to an SDK NetworkEndpoint. @@ -193,6 +217,9 @@ func tokenGrantFromProto(tg *pb.ProviderCredentialTokenGrant) *types.CredentialT Scopes: CopyStringSlice(tg.GetScopes()), CacheTTLSeconds: tg.GetCacheTtlSeconds(), ClientAssertionType: tg.GetClientAssertionType(), + GrantType: CredentialTokenGrantTypeFromProto(tg.GetGrantType()), + SubjectToken: subjectTokenFromProto(tg.GetSubjectToken()), + RequestedTokenType: tg.GetRequestedTokenType(), } if overrides := tg.GetAudienceOverrides(); len(overrides) > 0 { result.AudienceOverrides = make([]types.TokenGrantAudienceOverride, len(overrides)) @@ -214,6 +241,9 @@ func tokenGrantToProto(tg *types.CredentialTokenGrant) *pb.ProviderCredentialTok Scopes: CopyStringSlice(tg.Scopes), CacheTtlSeconds: tg.CacheTTLSeconds, ClientAssertionType: tg.ClientAssertionType, + GrantType: CredentialTokenGrantTypeToProto(tg.GrantType), + SubjectToken: subjectTokenToProto(tg.SubjectToken), + RequestedTokenType: tg.RequestedTokenType, } if len(tg.AudienceOverrides) > 0 { result.AudienceOverrides = make([]*pb.ProviderCredentialTokenGrantAudienceOverride, len(tg.AudienceOverrides)) @@ -224,6 +254,28 @@ func tokenGrantToProto(tg *types.CredentialTokenGrant) *pb.ProviderCredentialTok return result } +func subjectTokenFromProto(st *pb.ProviderCredentialTokenGrantSubjectToken) *types.TokenGrantSubjectToken { + if st == nil { + return nil + } + return &types.TokenGrantSubjectToken{ + Source: st.GetSource(), + Credential: st.GetCredential(), + SubjectTokenType: st.GetSubjectTokenType(), + } +} + +func subjectTokenToProto(st *types.TokenGrantSubjectToken) *pb.ProviderCredentialTokenGrantSubjectToken { + if st == nil { + return nil + } + return &pb.ProviderCredentialTokenGrantSubjectToken{ + Source: st.Source, + Credential: st.Credential, + SubjectTokenType: st.SubjectTokenType, + } +} + func audienceOverrideFromProto(o *pb.ProviderCredentialTokenGrantAudienceOverride) types.TokenGrantAudienceOverride { if o == nil { return types.TokenGrantAudienceOverride{} diff --git a/sdk/go/openshell/v1/internal/converter/profile_test.go b/sdk/go/openshell/v1/internal/converter/profile_test.go index 8efe8d294b..5d52c0a9ee 100644 --- a/sdk/go/openshell/v1/internal/converter/profile_test.go +++ b/sdk/go/openshell/v1/internal/converter/profile_test.go @@ -156,6 +156,13 @@ func TestProfileCredentialFromProto(t *testing.T) { Scopes: []string{"read", "write"}, CacheTtlSeconds: 300, ClientAssertionType: "urn:ietf:params:oauth:client-assertion-type:jwt-bearer", + GrantType: pb.ProviderCredentialTokenGrantType_PROVIDER_CREDENTIAL_TOKEN_GRANT_TYPE_TOKEN_EXCHANGE, + SubjectToken: &pb.ProviderCredentialTokenGrantSubjectToken{ + Source: "provider_credential", + Credential: "UPSTREAM_TOKEN", + SubjectTokenType: "urn:ietf:params:oauth:token-type:access_token", + }, + RequestedTokenType: "urn:ietf:params:oauth:token-type:refresh_token", AudienceOverrides: []*pb.ProviderCredentialTokenGrantAudienceOverride{ {Host: "special.example.com", Port: 8443, Path: "/api", Audience: "https://special.example.com", Scopes: []string{"admin"}}, }, @@ -182,6 +189,12 @@ func TestProfileCredentialFromProto(t *testing.T) { assert.Equal(t, []string{"read", "write"}, cred.TokenGrant.Scopes) assert.Equal(t, int64(300), cred.TokenGrant.CacheTTLSeconds) assert.Equal(t, "urn:ietf:params:oauth:client-assertion-type:jwt-bearer", cred.TokenGrant.ClientAssertionType) + assert.Equal(t, v1.CredentialTokenGrantTypeTokenExchange, cred.TokenGrant.GrantType) + require.NotNil(t, cred.TokenGrant.SubjectToken) + assert.Equal(t, "provider_credential", cred.TokenGrant.SubjectToken.Source) + assert.Equal(t, "UPSTREAM_TOKEN", cred.TokenGrant.SubjectToken.Credential) + assert.Equal(t, "urn:ietf:params:oauth:token-type:access_token", cred.TokenGrant.SubjectToken.SubjectTokenType) + assert.Equal(t, "urn:ietf:params:oauth:token-type:refresh_token", cred.TokenGrant.RequestedTokenType) require.Len(t, cred.TokenGrant.AudienceOverrides, 1) assert.Equal(t, "special.example.com", cred.TokenGrant.AudienceOverrides[0].Host) assert.Equal(t, uint32(8443), cred.TokenGrant.AudienceOverrides[0].Port) @@ -261,6 +274,13 @@ func TestProfileCredentialToProto(t *testing.T) { Scopes: []string{"read"}, CacheTTLSeconds: 300, ClientAssertionType: "urn:custom", + GrantType: v1.CredentialTokenGrantTypeTokenExchange, + SubjectToken: &v1.TokenGrantSubjectToken{ + Source: "provider_credential", + Credential: "UPSTREAM_TOKEN", + SubjectTokenType: "urn:ietf:params:oauth:token-type:access_token", + }, + RequestedTokenType: "urn:ietf:params:oauth:token-type:refresh_token", AudienceOverrides: []v1.TokenGrantAudienceOverride{ {Host: "h", Port: 443, Path: "/p", Audience: "aud", Scopes: []string{"s"}}, }, @@ -292,6 +312,12 @@ func TestProfileCredentialToProto(t *testing.T) { assert.Equal(t, []string{"read"}, proto.TokenGrant.Scopes) assert.Equal(t, int64(300), proto.TokenGrant.CacheTtlSeconds) assert.Equal(t, "urn:custom", proto.TokenGrant.ClientAssertionType) + assert.Equal(t, pb.ProviderCredentialTokenGrantType_PROVIDER_CREDENTIAL_TOKEN_GRANT_TYPE_TOKEN_EXCHANGE, proto.TokenGrant.GrantType) + require.NotNil(t, proto.TokenGrant.SubjectToken) + assert.Equal(t, "provider_credential", proto.TokenGrant.SubjectToken.Source) + assert.Equal(t, "UPSTREAM_TOKEN", proto.TokenGrant.SubjectToken.Credential) + assert.Equal(t, "urn:ietf:params:oauth:token-type:access_token", proto.TokenGrant.SubjectToken.SubjectTokenType) + assert.Equal(t, "urn:ietf:params:oauth:token-type:refresh_token", proto.TokenGrant.RequestedTokenType) require.Len(t, proto.TokenGrant.AudienceOverrides, 1) assert.Equal(t, "h", proto.TokenGrant.AudienceOverrides[0].Host) } diff --git a/sdk/go/openshell/v1/types/profile.go b/sdk/go/openshell/v1/types/profile.go index 2ca7da18ff..e519df634d 100644 --- a/sdk/go/openshell/v1/types/profile.go +++ b/sdk/go/openshell/v1/types/profile.go @@ -75,6 +75,15 @@ type ProfileCredentialRefreshOutput struct { Credential string } +// CredentialTokenGrantType describes which OAuth2 grant type to use for dynamic credentials. +type CredentialTokenGrantType string + +// CredentialTokenGrantType values. +const ( + CredentialTokenGrantTypeClientCredentials CredentialTokenGrantType = "ClientCredentials" + CredentialTokenGrantTypeTokenExchange CredentialTokenGrantType = "TokenExchange" +) + // CredentialTokenGrant configures dynamic credential acquisition via OAuth2 grant. type CredentialTokenGrant struct { TokenEndpoint string @@ -84,6 +93,16 @@ type CredentialTokenGrant struct { CacheTTLSeconds int64 AudienceOverrides []TokenGrantAudienceOverride ClientAssertionType string + GrantType CredentialTokenGrantType + SubjectToken *TokenGrantSubjectToken + RequestedTokenType string +} + +// TokenGrantSubjectToken configures the subject token for token exchange grants. +type TokenGrantSubjectToken struct { + Source string + Credential string + SubjectTokenType string } // TokenGrantAudienceOverride selects an endpoint-specific resource audience. diff --git a/sdk/go/proto/openshellv1/openshell.pb.go b/sdk/go/proto/openshellv1/openshell.pb.go index ecff3a1e17..7335718fd5 100644 --- a/sdk/go/proto/openshellv1/openshell.pb.go +++ b/sdk/go/proto/openshellv1/openshell.pb.go @@ -99,6 +99,57 @@ func (SandboxPhase) EnumDescriptor() ([]byte, []int) { return file_openshell_proto_rawDescGZIP(), []int{0} } +// Provider credential token grant configuration. +// When present, the credential is obtained dynamically via OAuth2 grant when needed. +type ProviderCredentialTokenGrantType int32 + +const ( + ProviderCredentialTokenGrantType_PROVIDER_CREDENTIAL_TOKEN_GRANT_TYPE_UNSPECIFIED ProviderCredentialTokenGrantType = 0 + ProviderCredentialTokenGrantType_PROVIDER_CREDENTIAL_TOKEN_GRANT_TYPE_CLIENT_CREDENTIALS ProviderCredentialTokenGrantType = 1 + ProviderCredentialTokenGrantType_PROVIDER_CREDENTIAL_TOKEN_GRANT_TYPE_TOKEN_EXCHANGE ProviderCredentialTokenGrantType = 2 +) + +// Enum value maps for ProviderCredentialTokenGrantType. +var ( + ProviderCredentialTokenGrantType_name = map[int32]string{ + 0: "PROVIDER_CREDENTIAL_TOKEN_GRANT_TYPE_UNSPECIFIED", + 1: "PROVIDER_CREDENTIAL_TOKEN_GRANT_TYPE_CLIENT_CREDENTIALS", + 2: "PROVIDER_CREDENTIAL_TOKEN_GRANT_TYPE_TOKEN_EXCHANGE", + } + ProviderCredentialTokenGrantType_value = map[string]int32{ + "PROVIDER_CREDENTIAL_TOKEN_GRANT_TYPE_UNSPECIFIED": 0, + "PROVIDER_CREDENTIAL_TOKEN_GRANT_TYPE_CLIENT_CREDENTIALS": 1, + "PROVIDER_CREDENTIAL_TOKEN_GRANT_TYPE_TOKEN_EXCHANGE": 2, + } +) + +func (x ProviderCredentialTokenGrantType) Enum() *ProviderCredentialTokenGrantType { + p := new(ProviderCredentialTokenGrantType) + *p = x + return p +} + +func (x ProviderCredentialTokenGrantType) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (ProviderCredentialTokenGrantType) Descriptor() protoreflect.EnumDescriptor { + return file_openshell_proto_enumTypes[1].Descriptor() +} + +func (ProviderCredentialTokenGrantType) Type() protoreflect.EnumType { + return &file_openshell_proto_enumTypes[1] +} + +func (x ProviderCredentialTokenGrantType) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use ProviderCredentialTokenGrantType.Descriptor instead. +func (ProviderCredentialTokenGrantType) EnumDescriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{1} +} + type ProviderCredentialRefreshStrategy int32 const ( @@ -144,11 +195,11 @@ func (x ProviderCredentialRefreshStrategy) String() string { } func (ProviderCredentialRefreshStrategy) Descriptor() protoreflect.EnumDescriptor { - return file_openshell_proto_enumTypes[1].Descriptor() + return file_openshell_proto_enumTypes[2].Descriptor() } func (ProviderCredentialRefreshStrategy) Type() protoreflect.EnumType { - return &file_openshell_proto_enumTypes[1] + return &file_openshell_proto_enumTypes[2] } func (x ProviderCredentialRefreshStrategy) Number() protoreflect.EnumNumber { @@ -157,7 +208,7 @@ func (x ProviderCredentialRefreshStrategy) Number() protoreflect.EnumNumber { // Deprecated: Use ProviderCredentialRefreshStrategy.Descriptor instead. func (ProviderCredentialRefreshStrategy) EnumDescriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{1} + return file_openshell_proto_rawDescGZIP(), []int{2} } // Stable provider profile categories used by clients for grouping and filtering. @@ -209,11 +260,11 @@ func (x ProviderProfileCategory) String() string { } func (ProviderProfileCategory) Descriptor() protoreflect.EnumDescriptor { - return file_openshell_proto_enumTypes[2].Descriptor() + return file_openshell_proto_enumTypes[3].Descriptor() } func (ProviderProfileCategory) Type() protoreflect.EnumType { - return &file_openshell_proto_enumTypes[2] + return &file_openshell_proto_enumTypes[3] } func (x ProviderProfileCategory) Number() protoreflect.EnumNumber { @@ -222,7 +273,7 @@ func (x ProviderProfileCategory) Number() protoreflect.EnumNumber { // Deprecated: Use ProviderProfileCategory.Descriptor instead. func (ProviderProfileCategory) EnumDescriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{2} + return file_openshell_proto_rawDescGZIP(), []int{3} } // Policy load status. @@ -269,11 +320,11 @@ func (x PolicyStatus) String() string { } func (PolicyStatus) Descriptor() protoreflect.EnumDescriptor { - return file_openshell_proto_enumTypes[3].Descriptor() + return file_openshell_proto_enumTypes[4].Descriptor() } func (PolicyStatus) Type() protoreflect.EnumType { - return &file_openshell_proto_enumTypes[3] + return &file_openshell_proto_enumTypes[4] } func (x PolicyStatus) Number() protoreflect.EnumNumber { @@ -282,7 +333,7 @@ func (x PolicyStatus) Number() protoreflect.EnumNumber { // Deprecated: Use PolicyStatus.Descriptor instead. func (PolicyStatus) EnumDescriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{3} + return file_openshell_proto_rawDescGZIP(), []int{4} } // Service status enum. @@ -322,11 +373,11 @@ func (x ServiceStatus) String() string { } func (ServiceStatus) Descriptor() protoreflect.EnumDescriptor { - return file_openshell_proto_enumTypes[4].Descriptor() + return file_openshell_proto_enumTypes[5].Descriptor() } func (ServiceStatus) Type() protoreflect.EnumType { - return &file_openshell_proto_enumTypes[4] + return &file_openshell_proto_enumTypes[5] } func (x ServiceStatus) Number() protoreflect.EnumNumber { @@ -335,7 +386,7 @@ func (x ServiceStatus) Number() protoreflect.EnumNumber { // Deprecated: Use ServiceStatus.Descriptor instead. func (ServiceStatus) EnumDescriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{4} + return file_openshell_proto_rawDescGZIP(), []int{5} } // Workspace-scoped role for members. @@ -372,11 +423,11 @@ func (x WorkspaceRole) String() string { } func (WorkspaceRole) Descriptor() protoreflect.EnumDescriptor { - return file_openshell_proto_enumTypes[5].Descriptor() + return file_openshell_proto_enumTypes[6].Descriptor() } func (WorkspaceRole) Type() protoreflect.EnumType { - return &file_openshell_proto_enumTypes[5] + return &file_openshell_proto_enumTypes[6] } func (x WorkspaceRole) Number() protoreflect.EnumNumber { @@ -385,7 +436,7 @@ func (x WorkspaceRole) Number() protoreflect.EnumNumber { // Deprecated: Use WorkspaceRole.Descriptor instead. func (WorkspaceRole) EnumDescriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{5} + return file_openshell_proto_rawDescGZIP(), []int{6} } // IssueSandboxToken request. Empty body; identity is established by the @@ -5240,8 +5291,71 @@ func (x *ProviderCredentialTokenGrantAudienceOverride) GetScopes() []string { return nil } -// Provider credential token grant configuration. -// When present, the credential is obtained dynamically via OAuth2 grant when needed. +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 *ProviderCredentialTokenGrantSubjectToken) Reset() { + *x = ProviderCredentialTokenGrantSubjectToken{} + mi := &file_openshell_proto_msgTypes[73] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ProviderCredentialTokenGrantSubjectToken) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ProviderCredentialTokenGrantSubjectToken) ProtoMessage() {} + +func (x *ProviderCredentialTokenGrantSubjectToken) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[73] + 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 ProviderCredentialTokenGrantSubjectToken.ProtoReflect.Descriptor instead. +func (*ProviderCredentialTokenGrantSubjectToken) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{73} +} + +func (x *ProviderCredentialTokenGrantSubjectToken) GetSource() string { + if x != nil { + return x.Source + } + return "" +} + +func (x *ProviderCredentialTokenGrantSubjectToken) GetCredential() string { + if x != nil { + return x.Credential + } + return "" +} + +func (x *ProviderCredentialTokenGrantSubjectToken) GetSubjectTokenType() string { + if x != nil { + return x.SubjectTokenType + } + return "" +} + 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) @@ -5261,13 +5375,21 @@ type ProviderCredentialTokenGrant struct { // 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"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + // 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 *ProviderCredentialTokenGrant) Reset() { *x = ProviderCredentialTokenGrant{} - mi := &file_openshell_proto_msgTypes[73] + mi := &file_openshell_proto_msgTypes[74] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5279,7 +5401,7 @@ func (x *ProviderCredentialTokenGrant) String() string { func (*ProviderCredentialTokenGrant) ProtoMessage() {} func (x *ProviderCredentialTokenGrant) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[73] + mi := &file_openshell_proto_msgTypes[74] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5292,7 +5414,7 @@ func (x *ProviderCredentialTokenGrant) ProtoReflect() protoreflect.Message { // Deprecated: Use ProviderCredentialTokenGrant.ProtoReflect.Descriptor instead. func (*ProviderCredentialTokenGrant) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{73} + return file_openshell_proto_rawDescGZIP(), []int{74} } func (x *ProviderCredentialTokenGrant) GetTokenEndpoint() string { @@ -5344,6 +5466,27 @@ func (x *ProviderCredentialTokenGrant) GetClientAssertionType() string { return "" } +func (x *ProviderCredentialTokenGrant) GetGrantType() ProviderCredentialTokenGrantType { + if x != nil { + return x.GrantType + } + return ProviderCredentialTokenGrantType_PROVIDER_CREDENTIAL_TOKEN_GRANT_TYPE_UNSPECIFIED +} + +func (x *ProviderCredentialTokenGrant) GetSubjectToken() *ProviderCredentialTokenGrantSubjectToken { + if x != nil { + return x.SubjectToken + } + return nil +} + +func (x *ProviderCredentialTokenGrant) GetRequestedTokenType() string { + if x != nil { + return x.RequestedTokenType + } + return "" +} + // Provider credential declaration. type ProviderProfileCredential struct { state protoimpl.MessageState `protogen:"open.v1"` @@ -5363,7 +5506,7 @@ type ProviderProfileCredential struct { func (x *ProviderProfileCredential) Reset() { *x = ProviderProfileCredential{} - mi := &file_openshell_proto_msgTypes[74] + mi := &file_openshell_proto_msgTypes[75] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5375,7 +5518,7 @@ func (x *ProviderProfileCredential) String() string { func (*ProviderProfileCredential) ProtoMessage() {} func (x *ProviderProfileCredential) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[74] + mi := &file_openshell_proto_msgTypes[75] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5388,7 +5531,7 @@ func (x *ProviderProfileCredential) ProtoReflect() protoreflect.Message { // Deprecated: Use ProviderProfileCredential.ProtoReflect.Descriptor instead. func (*ProviderProfileCredential) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{74} + return file_openshell_proto_rawDescGZIP(), []int{75} } func (x *ProviderProfileCredential) GetName() string { @@ -5473,7 +5616,7 @@ type ProviderCredentialRefreshMaterial struct { func (x *ProviderCredentialRefreshMaterial) Reset() { *x = ProviderCredentialRefreshMaterial{} - mi := &file_openshell_proto_msgTypes[75] + mi := &file_openshell_proto_msgTypes[76] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5485,7 +5628,7 @@ func (x *ProviderCredentialRefreshMaterial) String() string { func (*ProviderCredentialRefreshMaterial) ProtoMessage() {} func (x *ProviderCredentialRefreshMaterial) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[75] + mi := &file_openshell_proto_msgTypes[76] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5498,7 +5641,7 @@ func (x *ProviderCredentialRefreshMaterial) ProtoReflect() protoreflect.Message // Deprecated: Use ProviderCredentialRefreshMaterial.ProtoReflect.Descriptor instead. func (*ProviderCredentialRefreshMaterial) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{75} + return file_openshell_proto_rawDescGZIP(), []int{76} } func (x *ProviderCredentialRefreshMaterial) GetName() string { @@ -5543,7 +5686,7 @@ type ProviderCredentialRefreshOutput struct { func (x *ProviderCredentialRefreshOutput) Reset() { *x = ProviderCredentialRefreshOutput{} - mi := &file_openshell_proto_msgTypes[76] + mi := &file_openshell_proto_msgTypes[77] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5555,7 +5698,7 @@ func (x *ProviderCredentialRefreshOutput) String() string { func (*ProviderCredentialRefreshOutput) ProtoMessage() {} func (x *ProviderCredentialRefreshOutput) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[76] + mi := &file_openshell_proto_msgTypes[77] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5568,7 +5711,7 @@ func (x *ProviderCredentialRefreshOutput) ProtoReflect() protoreflect.Message { // Deprecated: Use ProviderCredentialRefreshOutput.ProtoReflect.Descriptor instead. func (*ProviderCredentialRefreshOutput) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{76} + return file_openshell_proto_rawDescGZIP(), []int{77} } func (x *ProviderCredentialRefreshOutput) GetOutput() string { @@ -5600,7 +5743,7 @@ type ProviderCredentialRefresh struct { func (x *ProviderCredentialRefresh) Reset() { *x = ProviderCredentialRefresh{} - mi := &file_openshell_proto_msgTypes[77] + mi := &file_openshell_proto_msgTypes[78] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5612,7 +5755,7 @@ func (x *ProviderCredentialRefresh) String() string { func (*ProviderCredentialRefresh) ProtoMessage() {} func (x *ProviderCredentialRefresh) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[77] + mi := &file_openshell_proto_msgTypes[78] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5625,7 +5768,7 @@ func (x *ProviderCredentialRefresh) ProtoReflect() protoreflect.Message { // Deprecated: Use ProviderCredentialRefresh.ProtoReflect.Descriptor instead. func (*ProviderCredentialRefresh) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{77} + return file_openshell_proto_rawDescGZIP(), []int{78} } func (x *ProviderCredentialRefresh) GetStrategy() ProviderCredentialRefreshStrategy { @@ -5694,7 +5837,7 @@ type ProviderCredentialRefreshStatus struct { func (x *ProviderCredentialRefreshStatus) Reset() { *x = ProviderCredentialRefreshStatus{} - mi := &file_openshell_proto_msgTypes[78] + mi := &file_openshell_proto_msgTypes[79] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5706,7 +5849,7 @@ func (x *ProviderCredentialRefreshStatus) String() string { func (*ProviderCredentialRefreshStatus) ProtoMessage() {} func (x *ProviderCredentialRefreshStatus) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[78] + mi := &file_openshell_proto_msgTypes[79] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5719,7 +5862,7 @@ func (x *ProviderCredentialRefreshStatus) ProtoReflect() protoreflect.Message { // Deprecated: Use ProviderCredentialRefreshStatus.ProtoReflect.Descriptor instead. func (*ProviderCredentialRefreshStatus) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{78} + return file_openshell_proto_rawDescGZIP(), []int{79} } func (x *ProviderCredentialRefreshStatus) GetProviderName() string { @@ -5796,7 +5939,7 @@ type ProviderProfileDiscovery struct { func (x *ProviderProfileDiscovery) Reset() { *x = ProviderProfileDiscovery{} - mi := &file_openshell_proto_msgTypes[79] + mi := &file_openshell_proto_msgTypes[80] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5808,7 +5951,7 @@ func (x *ProviderProfileDiscovery) String() string { func (*ProviderProfileDiscovery) ProtoMessage() {} func (x *ProviderProfileDiscovery) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[79] + mi := &file_openshell_proto_msgTypes[80] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5821,7 +5964,7 @@ func (x *ProviderProfileDiscovery) ProtoReflect() protoreflect.Message { // Deprecated: Use ProviderProfileDiscovery.ProtoReflect.Descriptor instead. func (*ProviderProfileDiscovery) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{79} + return file_openshell_proto_rawDescGZIP(), []int{80} } func (x *ProviderProfileDiscovery) GetCredentials() []string { @@ -5860,7 +6003,7 @@ type StoredProviderCredentialRefreshState struct { func (x *StoredProviderCredentialRefreshState) Reset() { *x = StoredProviderCredentialRefreshState{} - mi := &file_openshell_proto_msgTypes[80] + mi := &file_openshell_proto_msgTypes[81] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5872,7 +6015,7 @@ func (x *StoredProviderCredentialRefreshState) String() string { func (*StoredProviderCredentialRefreshState) ProtoMessage() {} func (x *StoredProviderCredentialRefreshState) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[80] + mi := &file_openshell_proto_msgTypes[81] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5885,7 +6028,7 @@ func (x *StoredProviderCredentialRefreshState) ProtoReflect() protoreflect.Messa // Deprecated: Use StoredProviderCredentialRefreshState.ProtoReflect.Descriptor instead. func (*StoredProviderCredentialRefreshState) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{80} + return file_openshell_proto_rawDescGZIP(), []int{81} } func (x *StoredProviderCredentialRefreshState) GetMetadata() *datamodelv1.ObjectMeta { @@ -6019,7 +6162,7 @@ type GetProviderRefreshStatusRequest struct { func (x *GetProviderRefreshStatusRequest) Reset() { *x = GetProviderRefreshStatusRequest{} - mi := &file_openshell_proto_msgTypes[81] + mi := &file_openshell_proto_msgTypes[82] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6031,7 +6174,7 @@ func (x *GetProviderRefreshStatusRequest) String() string { func (*GetProviderRefreshStatusRequest) ProtoMessage() {} func (x *GetProviderRefreshStatusRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[81] + mi := &file_openshell_proto_msgTypes[82] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6044,7 +6187,7 @@ func (x *GetProviderRefreshStatusRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetProviderRefreshStatusRequest.ProtoReflect.Descriptor instead. func (*GetProviderRefreshStatusRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{81} + return file_openshell_proto_rawDescGZIP(), []int{82} } func (x *GetProviderRefreshStatusRequest) GetProvider() string { @@ -6077,7 +6220,7 @@ type GetProviderRefreshStatusResponse struct { func (x *GetProviderRefreshStatusResponse) Reset() { *x = GetProviderRefreshStatusResponse{} - mi := &file_openshell_proto_msgTypes[82] + mi := &file_openshell_proto_msgTypes[83] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6089,7 +6232,7 @@ func (x *GetProviderRefreshStatusResponse) String() string { func (*GetProviderRefreshStatusResponse) ProtoMessage() {} func (x *GetProviderRefreshStatusResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[82] + mi := &file_openshell_proto_msgTypes[83] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6102,7 +6245,7 @@ func (x *GetProviderRefreshStatusResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetProviderRefreshStatusResponse.ProtoReflect.Descriptor instead. func (*GetProviderRefreshStatusResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{82} + return file_openshell_proto_rawDescGZIP(), []int{83} } func (x *GetProviderRefreshStatusResponse) GetCredentials() []*ProviderCredentialRefreshStatus { @@ -6128,7 +6271,7 @@ type ConfigureProviderRefreshRequest struct { func (x *ConfigureProviderRefreshRequest) Reset() { *x = ConfigureProviderRefreshRequest{} - mi := &file_openshell_proto_msgTypes[83] + mi := &file_openshell_proto_msgTypes[84] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6140,7 +6283,7 @@ func (x *ConfigureProviderRefreshRequest) String() string { func (*ConfigureProviderRefreshRequest) ProtoMessage() {} func (x *ConfigureProviderRefreshRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[83] + mi := &file_openshell_proto_msgTypes[84] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6153,7 +6296,7 @@ func (x *ConfigureProviderRefreshRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ConfigureProviderRefreshRequest.ProtoReflect.Descriptor instead. func (*ConfigureProviderRefreshRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{83} + return file_openshell_proto_rawDescGZIP(), []int{84} } func (x *ConfigureProviderRefreshRequest) GetProvider() string { @@ -6214,7 +6357,7 @@ type ConfigureProviderRefreshResponse struct { func (x *ConfigureProviderRefreshResponse) Reset() { *x = ConfigureProviderRefreshResponse{} - mi := &file_openshell_proto_msgTypes[84] + mi := &file_openshell_proto_msgTypes[85] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6226,7 +6369,7 @@ func (x *ConfigureProviderRefreshResponse) String() string { func (*ConfigureProviderRefreshResponse) ProtoMessage() {} func (x *ConfigureProviderRefreshResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[84] + mi := &file_openshell_proto_msgTypes[85] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6239,7 +6382,7 @@ func (x *ConfigureProviderRefreshResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ConfigureProviderRefreshResponse.ProtoReflect.Descriptor instead. func (*ConfigureProviderRefreshResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{84} + return file_openshell_proto_rawDescGZIP(), []int{85} } func (x *ConfigureProviderRefreshResponse) GetStatus() *ProviderCredentialRefreshStatus { @@ -6261,7 +6404,7 @@ type RotateProviderCredentialRequest struct { func (x *RotateProviderCredentialRequest) Reset() { *x = RotateProviderCredentialRequest{} - mi := &file_openshell_proto_msgTypes[85] + mi := &file_openshell_proto_msgTypes[86] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6273,7 +6416,7 @@ func (x *RotateProviderCredentialRequest) String() string { func (*RotateProviderCredentialRequest) ProtoMessage() {} func (x *RotateProviderCredentialRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[85] + mi := &file_openshell_proto_msgTypes[86] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6286,7 +6429,7 @@ func (x *RotateProviderCredentialRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use RotateProviderCredentialRequest.ProtoReflect.Descriptor instead. func (*RotateProviderCredentialRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{85} + return file_openshell_proto_rawDescGZIP(), []int{86} } func (x *RotateProviderCredentialRequest) GetProvider() string { @@ -6319,7 +6462,7 @@ type RotateProviderCredentialResponse struct { func (x *RotateProviderCredentialResponse) Reset() { *x = RotateProviderCredentialResponse{} - mi := &file_openshell_proto_msgTypes[86] + mi := &file_openshell_proto_msgTypes[87] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6331,7 +6474,7 @@ func (x *RotateProviderCredentialResponse) String() string { func (*RotateProviderCredentialResponse) ProtoMessage() {} func (x *RotateProviderCredentialResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[86] + mi := &file_openshell_proto_msgTypes[87] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6344,7 +6487,7 @@ func (x *RotateProviderCredentialResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use RotateProviderCredentialResponse.ProtoReflect.Descriptor instead. func (*RotateProviderCredentialResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{86} + return file_openshell_proto_rawDescGZIP(), []int{87} } func (x *RotateProviderCredentialResponse) GetStatus() *ProviderCredentialRefreshStatus { @@ -6366,7 +6509,7 @@ type DeleteProviderRefreshRequest struct { func (x *DeleteProviderRefreshRequest) Reset() { *x = DeleteProviderRefreshRequest{} - mi := &file_openshell_proto_msgTypes[87] + mi := &file_openshell_proto_msgTypes[88] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6378,7 +6521,7 @@ func (x *DeleteProviderRefreshRequest) String() string { func (*DeleteProviderRefreshRequest) ProtoMessage() {} func (x *DeleteProviderRefreshRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[87] + mi := &file_openshell_proto_msgTypes[88] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6391,7 +6534,7 @@ func (x *DeleteProviderRefreshRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteProviderRefreshRequest.ProtoReflect.Descriptor instead. func (*DeleteProviderRefreshRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{87} + return file_openshell_proto_rawDescGZIP(), []int{88} } func (x *DeleteProviderRefreshRequest) GetProvider() string { @@ -6424,7 +6567,7 @@ type DeleteProviderRefreshResponse struct { func (x *DeleteProviderRefreshResponse) Reset() { *x = DeleteProviderRefreshResponse{} - mi := &file_openshell_proto_msgTypes[88] + mi := &file_openshell_proto_msgTypes[89] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6436,7 +6579,7 @@ func (x *DeleteProviderRefreshResponse) String() string { func (*DeleteProviderRefreshResponse) ProtoMessage() {} func (x *DeleteProviderRefreshResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[88] + mi := &file_openshell_proto_msgTypes[89] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6449,7 +6592,7 @@ func (x *DeleteProviderRefreshResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteProviderRefreshResponse.ProtoReflect.Descriptor instead. func (*DeleteProviderRefreshResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{88} + return file_openshell_proto_rawDescGZIP(), []int{89} } func (x *DeleteProviderRefreshResponse) GetDeleted() bool { @@ -6489,7 +6632,7 @@ type ProviderProfile struct { func (x *ProviderProfile) Reset() { *x = ProviderProfile{} - mi := &file_openshell_proto_msgTypes[89] + mi := &file_openshell_proto_msgTypes[90] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6501,7 +6644,7 @@ func (x *ProviderProfile) String() string { func (*ProviderProfile) ProtoMessage() {} func (x *ProviderProfile) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[89] + mi := &file_openshell_proto_msgTypes[90] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6514,7 +6657,7 @@ func (x *ProviderProfile) ProtoReflect() protoreflect.Message { // Deprecated: Use ProviderProfile.ProtoReflect.Descriptor instead. func (*ProviderProfile) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{89} + return file_openshell_proto_rawDescGZIP(), []int{90} } func (x *ProviderProfile) GetId() string { @@ -6619,7 +6762,7 @@ type StoredProviderProfile struct { func (x *StoredProviderProfile) Reset() { *x = StoredProviderProfile{} - mi := &file_openshell_proto_msgTypes[90] + mi := &file_openshell_proto_msgTypes[91] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6631,7 +6774,7 @@ func (x *StoredProviderProfile) String() string { func (*StoredProviderProfile) ProtoMessage() {} func (x *StoredProviderProfile) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[90] + mi := &file_openshell_proto_msgTypes[91] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6644,7 +6787,7 @@ func (x *StoredProviderProfile) ProtoReflect() protoreflect.Message { // Deprecated: Use StoredProviderProfile.ProtoReflect.Descriptor instead. func (*StoredProviderProfile) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{90} + return file_openshell_proto_rawDescGZIP(), []int{91} } func (x *StoredProviderProfile) GetMetadata() *datamodelv1.ObjectMeta { @@ -6671,7 +6814,7 @@ type ProviderProfileResponse struct { func (x *ProviderProfileResponse) Reset() { *x = ProviderProfileResponse{} - mi := &file_openshell_proto_msgTypes[91] + mi := &file_openshell_proto_msgTypes[92] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6683,7 +6826,7 @@ func (x *ProviderProfileResponse) String() string { func (*ProviderProfileResponse) ProtoMessage() {} func (x *ProviderProfileResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[91] + mi := &file_openshell_proto_msgTypes[92] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6696,7 +6839,7 @@ func (x *ProviderProfileResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ProviderProfileResponse.ProtoReflect.Descriptor instead. func (*ProviderProfileResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{91} + return file_openshell_proto_rawDescGZIP(), []int{92} } func (x *ProviderProfileResponse) GetProfile() *ProviderProfile { @@ -6716,7 +6859,7 @@ type ListProviderProfilesResponse struct { func (x *ListProviderProfilesResponse) Reset() { *x = ListProviderProfilesResponse{} - mi := &file_openshell_proto_msgTypes[92] + mi := &file_openshell_proto_msgTypes[93] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6728,7 +6871,7 @@ func (x *ListProviderProfilesResponse) String() string { func (*ListProviderProfilesResponse) ProtoMessage() {} func (x *ListProviderProfilesResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[92] + mi := &file_openshell_proto_msgTypes[93] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6741,7 +6884,7 @@ func (x *ListProviderProfilesResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListProviderProfilesResponse.ProtoReflect.Descriptor instead. func (*ListProviderProfilesResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{92} + return file_openshell_proto_rawDescGZIP(), []int{93} } func (x *ListProviderProfilesResponse) GetProfiles() []*ProviderProfile { @@ -6764,7 +6907,7 @@ type ImportProviderProfilesRequest struct { func (x *ImportProviderProfilesRequest) Reset() { *x = ImportProviderProfilesRequest{} - mi := &file_openshell_proto_msgTypes[93] + mi := &file_openshell_proto_msgTypes[94] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6776,7 +6919,7 @@ func (x *ImportProviderProfilesRequest) String() string { func (*ImportProviderProfilesRequest) ProtoMessage() {} func (x *ImportProviderProfilesRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[93] + mi := &file_openshell_proto_msgTypes[94] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6789,7 +6932,7 @@ func (x *ImportProviderProfilesRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ImportProviderProfilesRequest.ProtoReflect.Descriptor instead. func (*ImportProviderProfilesRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{93} + return file_openshell_proto_rawDescGZIP(), []int{94} } func (x *ImportProviderProfilesRequest) GetProfiles() []*ProviderProfileImportItem { @@ -6818,7 +6961,7 @@ type ImportProviderProfilesResponse struct { func (x *ImportProviderProfilesResponse) Reset() { *x = ImportProviderProfilesResponse{} - mi := &file_openshell_proto_msgTypes[94] + mi := &file_openshell_proto_msgTypes[95] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6830,7 +6973,7 @@ func (x *ImportProviderProfilesResponse) String() string { func (*ImportProviderProfilesResponse) ProtoMessage() {} func (x *ImportProviderProfilesResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[94] + mi := &file_openshell_proto_msgTypes[95] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6843,7 +6986,7 @@ func (x *ImportProviderProfilesResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ImportProviderProfilesResponse.ProtoReflect.Descriptor instead. func (*ImportProviderProfilesResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{94} + return file_openshell_proto_rawDescGZIP(), []int{95} } func (x *ImportProviderProfilesResponse) GetDiagnostics() []*ProviderProfileDiagnostic { @@ -6887,7 +7030,7 @@ type UpdateProviderProfilesRequest struct { func (x *UpdateProviderProfilesRequest) Reset() { *x = UpdateProviderProfilesRequest{} - mi := &file_openshell_proto_msgTypes[95] + mi := &file_openshell_proto_msgTypes[96] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6899,7 +7042,7 @@ func (x *UpdateProviderProfilesRequest) String() string { func (*UpdateProviderProfilesRequest) ProtoMessage() {} func (x *UpdateProviderProfilesRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[95] + mi := &file_openshell_proto_msgTypes[96] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6912,7 +7055,7 @@ func (x *UpdateProviderProfilesRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateProviderProfilesRequest.ProtoReflect.Descriptor instead. func (*UpdateProviderProfilesRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{95} + return file_openshell_proto_rawDescGZIP(), []int{96} } func (x *UpdateProviderProfilesRequest) GetProfile() *ProviderProfileImportItem { @@ -6955,7 +7098,7 @@ type UpdateProviderProfilesResponse struct { func (x *UpdateProviderProfilesResponse) Reset() { *x = UpdateProviderProfilesResponse{} - mi := &file_openshell_proto_msgTypes[96] + mi := &file_openshell_proto_msgTypes[97] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6967,7 +7110,7 @@ func (x *UpdateProviderProfilesResponse) String() string { func (*UpdateProviderProfilesResponse) ProtoMessage() {} func (x *UpdateProviderProfilesResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[96] + mi := &file_openshell_proto_msgTypes[97] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6980,7 +7123,7 @@ func (x *UpdateProviderProfilesResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateProviderProfilesResponse.ProtoReflect.Descriptor instead. func (*UpdateProviderProfilesResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{96} + return file_openshell_proto_rawDescGZIP(), []int{97} } func (x *UpdateProviderProfilesResponse) GetDiagnostics() []*ProviderProfileDiagnostic { @@ -7017,7 +7160,7 @@ type LintProviderProfilesRequest struct { func (x *LintProviderProfilesRequest) Reset() { *x = LintProviderProfilesRequest{} - mi := &file_openshell_proto_msgTypes[97] + mi := &file_openshell_proto_msgTypes[98] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7029,7 +7172,7 @@ func (x *LintProviderProfilesRequest) String() string { func (*LintProviderProfilesRequest) ProtoMessage() {} func (x *LintProviderProfilesRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[97] + mi := &file_openshell_proto_msgTypes[98] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7042,7 +7185,7 @@ func (x *LintProviderProfilesRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use LintProviderProfilesRequest.ProtoReflect.Descriptor instead. func (*LintProviderProfilesRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{97} + return file_openshell_proto_rawDescGZIP(), []int{98} } func (x *LintProviderProfilesRequest) GetProfiles() []*ProviderProfileImportItem { @@ -7070,7 +7213,7 @@ type LintProviderProfilesResponse struct { func (x *LintProviderProfilesResponse) Reset() { *x = LintProviderProfilesResponse{} - mi := &file_openshell_proto_msgTypes[98] + mi := &file_openshell_proto_msgTypes[99] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7082,7 +7225,7 @@ func (x *LintProviderProfilesResponse) String() string { func (*LintProviderProfilesResponse) ProtoMessage() {} func (x *LintProviderProfilesResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[98] + mi := &file_openshell_proto_msgTypes[99] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7095,7 +7238,7 @@ func (x *LintProviderProfilesResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use LintProviderProfilesResponse.ProtoReflect.Descriptor instead. func (*LintProviderProfilesResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{98} + return file_openshell_proto_rawDescGZIP(), []int{99} } func (x *LintProviderProfilesResponse) GetDiagnostics() []*ProviderProfileDiagnostic { @@ -7122,7 +7265,7 @@ type DeleteProviderResponse struct { func (x *DeleteProviderResponse) Reset() { *x = DeleteProviderResponse{} - mi := &file_openshell_proto_msgTypes[99] + mi := &file_openshell_proto_msgTypes[100] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7134,7 +7277,7 @@ func (x *DeleteProviderResponse) String() string { func (*DeleteProviderResponse) ProtoMessage() {} func (x *DeleteProviderResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[99] + mi := &file_openshell_proto_msgTypes[100] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7147,7 +7290,7 @@ func (x *DeleteProviderResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteProviderResponse.ProtoReflect.Descriptor instead. func (*DeleteProviderResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{99} + return file_openshell_proto_rawDescGZIP(), []int{100} } func (x *DeleteProviderResponse) GetDeleted() bool { @@ -7170,7 +7313,7 @@ type DeleteProviderProfileRequest struct { func (x *DeleteProviderProfileRequest) Reset() { *x = DeleteProviderProfileRequest{} - mi := &file_openshell_proto_msgTypes[100] + mi := &file_openshell_proto_msgTypes[101] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7182,7 +7325,7 @@ func (x *DeleteProviderProfileRequest) String() string { func (*DeleteProviderProfileRequest) ProtoMessage() {} func (x *DeleteProviderProfileRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[100] + mi := &file_openshell_proto_msgTypes[101] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7195,7 +7338,7 @@ func (x *DeleteProviderProfileRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteProviderProfileRequest.ProtoReflect.Descriptor instead. func (*DeleteProviderProfileRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{100} + return file_openshell_proto_rawDescGZIP(), []int{101} } func (x *DeleteProviderProfileRequest) GetId() string { @@ -7222,7 +7365,7 @@ type DeleteProviderProfileResponse struct { func (x *DeleteProviderProfileResponse) Reset() { *x = DeleteProviderProfileResponse{} - mi := &file_openshell_proto_msgTypes[101] + mi := &file_openshell_proto_msgTypes[102] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7234,7 +7377,7 @@ func (x *DeleteProviderProfileResponse) String() string { func (*DeleteProviderProfileResponse) ProtoMessage() {} func (x *DeleteProviderProfileResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[101] + mi := &file_openshell_proto_msgTypes[102] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7247,7 +7390,7 @@ func (x *DeleteProviderProfileResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteProviderProfileResponse.ProtoReflect.Descriptor instead. func (*DeleteProviderProfileResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{101} + return file_openshell_proto_rawDescGZIP(), []int{102} } func (x *DeleteProviderProfileResponse) GetDeleted() bool { @@ -7272,7 +7415,7 @@ type GetSandboxProviderEnvironmentRequest struct { func (x *GetSandboxProviderEnvironmentRequest) Reset() { *x = GetSandboxProviderEnvironmentRequest{} - mi := &file_openshell_proto_msgTypes[102] + mi := &file_openshell_proto_msgTypes[103] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7284,7 +7427,7 @@ func (x *GetSandboxProviderEnvironmentRequest) String() string { func (*GetSandboxProviderEnvironmentRequest) ProtoMessage() {} func (x *GetSandboxProviderEnvironmentRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[102] + mi := &file_openshell_proto_msgTypes[103] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7297,7 +7440,7 @@ func (x *GetSandboxProviderEnvironmentRequest) ProtoReflect() protoreflect.Messa // Deprecated: Use GetSandboxProviderEnvironmentRequest.ProtoReflect.Descriptor instead. func (*GetSandboxProviderEnvironmentRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{102} + return file_openshell_proto_rawDescGZIP(), []int{103} } func (x *GetSandboxProviderEnvironmentRequest) GetSandboxId() string { @@ -7326,7 +7469,7 @@ type StaticCredentialEndpointBinding struct { func (x *StaticCredentialEndpointBinding) Reset() { *x = StaticCredentialEndpointBinding{} - mi := &file_openshell_proto_msgTypes[103] + mi := &file_openshell_proto_msgTypes[104] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7338,7 +7481,7 @@ func (x *StaticCredentialEndpointBinding) String() string { func (*StaticCredentialEndpointBinding) ProtoMessage() {} func (x *StaticCredentialEndpointBinding) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[103] + mi := &file_openshell_proto_msgTypes[104] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7351,7 +7494,7 @@ func (x *StaticCredentialEndpointBinding) ProtoReflect() protoreflect.Message { // Deprecated: Use StaticCredentialEndpointBinding.ProtoReflect.Descriptor instead. func (*StaticCredentialEndpointBinding) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{103} + return file_openshell_proto_rawDescGZIP(), []int{104} } func (x *StaticCredentialEndpointBinding) GetHost() string { @@ -7389,7 +7532,7 @@ type StaticCredentialBinding struct { func (x *StaticCredentialBinding) Reset() { *x = StaticCredentialBinding{} - mi := &file_openshell_proto_msgTypes[104] + mi := &file_openshell_proto_msgTypes[105] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7401,7 +7544,7 @@ func (x *StaticCredentialBinding) String() string { func (*StaticCredentialBinding) ProtoMessage() {} func (x *StaticCredentialBinding) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[104] + mi := &file_openshell_proto_msgTypes[105] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7414,7 +7557,7 @@ func (x *StaticCredentialBinding) ProtoReflect() protoreflect.Message { // Deprecated: Use StaticCredentialBinding.ProtoReflect.Descriptor instead. func (*StaticCredentialBinding) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{104} + return file_openshell_proto_rawDescGZIP(), []int{105} } func (x *StaticCredentialBinding) GetEndpoints() []*StaticCredentialEndpointBinding { @@ -7458,7 +7601,7 @@ type GetSandboxProviderEnvironmentResponse struct { func (x *GetSandboxProviderEnvironmentResponse) Reset() { *x = GetSandboxProviderEnvironmentResponse{} - mi := &file_openshell_proto_msgTypes[105] + mi := &file_openshell_proto_msgTypes[106] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7470,7 +7613,7 @@ func (x *GetSandboxProviderEnvironmentResponse) String() string { func (*GetSandboxProviderEnvironmentResponse) ProtoMessage() {} func (x *GetSandboxProviderEnvironmentResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[105] + mi := &file_openshell_proto_msgTypes[106] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7483,7 +7626,7 @@ func (x *GetSandboxProviderEnvironmentResponse) ProtoReflect() protoreflect.Mess // Deprecated: Use GetSandboxProviderEnvironmentResponse.ProtoReflect.Descriptor instead. func (*GetSandboxProviderEnvironmentResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{105} + return file_openshell_proto_rawDescGZIP(), []int{106} } func (x *GetSandboxProviderEnvironmentResponse) GetEnvironment() map[string]string { @@ -7528,6 +7671,139 @@ func (x *GetSandboxProviderEnvironmentResponse) GetNonSecretEnvironmentKeys() [] return nil } +type ExchangeProviderSubjectTokenRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The sandbox ID. Must match the authenticated sandbox principal. + SandboxId string `protobuf:"bytes,1,opt,name=sandbox_id,json=sandboxId,proto3" json:"sandbox_id,omitempty"` + // Attached provider record holding the configured subject token credential. + Provider string `protobuf:"bytes,2,opt,name=provider,proto3" json:"provider,omitempty"` + // Provider profile credential that declares the token_exchange grant. + CredentialKey string `protobuf:"bytes,3,opt,name=credential_key,json=credentialKey,proto3" json:"credential_key,omitempty"` + // Supervisor JWT-SVID. The gateway verifies this and uses its `sub` claim + // as the requested audience for the intermediate token. + SupervisorJwtSvid string `protobuf:"bytes,4,opt,name=supervisor_jwt_svid,json=supervisorJwtSvid,proto3" json:"supervisor_jwt_svid,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ExchangeProviderSubjectTokenRequest) Reset() { + *x = ExchangeProviderSubjectTokenRequest{} + mi := &file_openshell_proto_msgTypes[107] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ExchangeProviderSubjectTokenRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ExchangeProviderSubjectTokenRequest) ProtoMessage() {} + +func (x *ExchangeProviderSubjectTokenRequest) 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 ExchangeProviderSubjectTokenRequest.ProtoReflect.Descriptor instead. +func (*ExchangeProviderSubjectTokenRequest) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{107} +} + +func (x *ExchangeProviderSubjectTokenRequest) GetSandboxId() string { + if x != nil { + return x.SandboxId + } + return "" +} + +func (x *ExchangeProviderSubjectTokenRequest) GetProvider() string { + if x != nil { + return x.Provider + } + return "" +} + +func (x *ExchangeProviderSubjectTokenRequest) GetCredentialKey() string { + if x != nil { + return x.CredentialKey + } + return "" +} + +func (x *ExchangeProviderSubjectTokenRequest) GetSupervisorJwtSvid() string { + if x != nil { + return x.SupervisorJwtSvid + } + return "" +} + +type ExchangeProviderSubjectTokenResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + AccessToken string `protobuf:"bytes,1,opt,name=access_token,json=accessToken,proto3" json:"access_token,omitempty"` + ExpiresIn int64 `protobuf:"varint,2,opt,name=expires_in,json=expiresIn,proto3" json:"expires_in,omitempty"` + TokenType string `protobuf:"bytes,3,opt,name=token_type,json=tokenType,proto3" json:"token_type,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ExchangeProviderSubjectTokenResponse) Reset() { + *x = ExchangeProviderSubjectTokenResponse{} + mi := &file_openshell_proto_msgTypes[108] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ExchangeProviderSubjectTokenResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ExchangeProviderSubjectTokenResponse) ProtoMessage() {} + +func (x *ExchangeProviderSubjectTokenResponse) 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 ExchangeProviderSubjectTokenResponse.ProtoReflect.Descriptor instead. +func (*ExchangeProviderSubjectTokenResponse) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{108} +} + +func (x *ExchangeProviderSubjectTokenResponse) GetAccessToken() string { + if x != nil { + return x.AccessToken + } + return "" +} + +func (x *ExchangeProviderSubjectTokenResponse) GetExpiresIn() int64 { + if x != nil { + return x.ExpiresIn + } + return 0 +} + +func (x *ExchangeProviderSubjectTokenResponse) GetTokenType() string { + if x != nil { + return x.TokenType + } + return "" +} + // Update sandbox policy request. type UpdateConfigRequest struct { state protoimpl.MessageState `protogen:"open.v1"` @@ -7575,7 +7851,7 @@ type UpdateConfigRequest struct { func (x *UpdateConfigRequest) Reset() { *x = UpdateConfigRequest{} - mi := &file_openshell_proto_msgTypes[106] + mi := &file_openshell_proto_msgTypes[109] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7587,7 +7863,7 @@ func (x *UpdateConfigRequest) String() string { func (*UpdateConfigRequest) ProtoMessage() {} func (x *UpdateConfigRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[106] + mi := &file_openshell_proto_msgTypes[109] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7600,7 +7876,7 @@ func (x *UpdateConfigRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateConfigRequest.ProtoReflect.Descriptor instead. func (*UpdateConfigRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{106} + return file_openshell_proto_rawDescGZIP(), []int{109} } func (x *UpdateConfigRequest) GetName() string { @@ -7690,7 +7966,7 @@ type PolicyMergeOperation struct { func (x *PolicyMergeOperation) Reset() { *x = PolicyMergeOperation{} - mi := &file_openshell_proto_msgTypes[107] + mi := &file_openshell_proto_msgTypes[110] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7702,7 +7978,7 @@ func (x *PolicyMergeOperation) String() string { func (*PolicyMergeOperation) ProtoMessage() {} func (x *PolicyMergeOperation) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[107] + mi := &file_openshell_proto_msgTypes[110] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7715,7 +7991,7 @@ func (x *PolicyMergeOperation) ProtoReflect() protoreflect.Message { // Deprecated: Use PolicyMergeOperation.ProtoReflect.Descriptor instead. func (*PolicyMergeOperation) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{107} + return file_openshell_proto_rawDescGZIP(), []int{110} } func (x *PolicyMergeOperation) GetOperation() isPolicyMergeOperation_Operation { @@ -7829,7 +8105,7 @@ type AddNetworkRule struct { func (x *AddNetworkRule) Reset() { *x = AddNetworkRule{} - mi := &file_openshell_proto_msgTypes[108] + mi := &file_openshell_proto_msgTypes[111] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7841,7 +8117,7 @@ func (x *AddNetworkRule) String() string { func (*AddNetworkRule) ProtoMessage() {} func (x *AddNetworkRule) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[108] + mi := &file_openshell_proto_msgTypes[111] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7854,7 +8130,7 @@ func (x *AddNetworkRule) ProtoReflect() protoreflect.Message { // Deprecated: Use AddNetworkRule.ProtoReflect.Descriptor instead. func (*AddNetworkRule) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{108} + return file_openshell_proto_rawDescGZIP(), []int{111} } func (x *AddNetworkRule) GetRuleName() string { @@ -7882,7 +8158,7 @@ type RemoveNetworkEndpoint struct { func (x *RemoveNetworkEndpoint) Reset() { *x = RemoveNetworkEndpoint{} - mi := &file_openshell_proto_msgTypes[109] + mi := &file_openshell_proto_msgTypes[112] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7894,7 +8170,7 @@ func (x *RemoveNetworkEndpoint) String() string { func (*RemoveNetworkEndpoint) ProtoMessage() {} func (x *RemoveNetworkEndpoint) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[109] + mi := &file_openshell_proto_msgTypes[112] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7907,7 +8183,7 @@ func (x *RemoveNetworkEndpoint) ProtoReflect() protoreflect.Message { // Deprecated: Use RemoveNetworkEndpoint.ProtoReflect.Descriptor instead. func (*RemoveNetworkEndpoint) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{109} + return file_openshell_proto_rawDescGZIP(), []int{112} } func (x *RemoveNetworkEndpoint) GetRuleName() string { @@ -7940,7 +8216,7 @@ type RemoveNetworkRule struct { func (x *RemoveNetworkRule) Reset() { *x = RemoveNetworkRule{} - mi := &file_openshell_proto_msgTypes[110] + mi := &file_openshell_proto_msgTypes[113] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7952,7 +8228,7 @@ func (x *RemoveNetworkRule) String() string { func (*RemoveNetworkRule) ProtoMessage() {} func (x *RemoveNetworkRule) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[110] + mi := &file_openshell_proto_msgTypes[113] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7965,7 +8241,7 @@ func (x *RemoveNetworkRule) ProtoReflect() protoreflect.Message { // Deprecated: Use RemoveNetworkRule.ProtoReflect.Descriptor instead. func (*RemoveNetworkRule) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{110} + return file_openshell_proto_rawDescGZIP(), []int{113} } func (x *RemoveNetworkRule) GetRuleName() string { @@ -7986,7 +8262,7 @@ type AddDenyRules struct { func (x *AddDenyRules) Reset() { *x = AddDenyRules{} - mi := &file_openshell_proto_msgTypes[111] + mi := &file_openshell_proto_msgTypes[114] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7998,7 +8274,7 @@ func (x *AddDenyRules) String() string { func (*AddDenyRules) ProtoMessage() {} func (x *AddDenyRules) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[111] + mi := &file_openshell_proto_msgTypes[114] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8011,7 +8287,7 @@ func (x *AddDenyRules) ProtoReflect() protoreflect.Message { // Deprecated: Use AddDenyRules.ProtoReflect.Descriptor instead. func (*AddDenyRules) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{111} + return file_openshell_proto_rawDescGZIP(), []int{114} } func (x *AddDenyRules) GetHost() string { @@ -8046,7 +8322,7 @@ type AddAllowRules struct { func (x *AddAllowRules) Reset() { *x = AddAllowRules{} - mi := &file_openshell_proto_msgTypes[112] + mi := &file_openshell_proto_msgTypes[115] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8058,7 +8334,7 @@ func (x *AddAllowRules) String() string { func (*AddAllowRules) ProtoMessage() {} func (x *AddAllowRules) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[112] + mi := &file_openshell_proto_msgTypes[115] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8071,7 +8347,7 @@ func (x *AddAllowRules) ProtoReflect() protoreflect.Message { // Deprecated: Use AddAllowRules.ProtoReflect.Descriptor instead. func (*AddAllowRules) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{112} + return file_openshell_proto_rawDescGZIP(), []int{115} } func (x *AddAllowRules) GetHost() string { @@ -8105,7 +8381,7 @@ type RemoveNetworkBinary struct { func (x *RemoveNetworkBinary) Reset() { *x = RemoveNetworkBinary{} - mi := &file_openshell_proto_msgTypes[113] + mi := &file_openshell_proto_msgTypes[116] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8117,7 +8393,7 @@ func (x *RemoveNetworkBinary) String() string { func (*RemoveNetworkBinary) ProtoMessage() {} func (x *RemoveNetworkBinary) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[113] + mi := &file_openshell_proto_msgTypes[116] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8130,7 +8406,7 @@ func (x *RemoveNetworkBinary) ProtoReflect() protoreflect.Message { // Deprecated: Use RemoveNetworkBinary.ProtoReflect.Descriptor instead. func (*RemoveNetworkBinary) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{113} + return file_openshell_proto_rawDescGZIP(), []int{116} } func (x *RemoveNetworkBinary) GetRuleName() string { @@ -8166,7 +8442,7 @@ type UpdateConfigResponse struct { func (x *UpdateConfigResponse) Reset() { *x = UpdateConfigResponse{} - mi := &file_openshell_proto_msgTypes[114] + mi := &file_openshell_proto_msgTypes[117] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8178,7 +8454,7 @@ func (x *UpdateConfigResponse) String() string { func (*UpdateConfigResponse) ProtoMessage() {} func (x *UpdateConfigResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[114] + mi := &file_openshell_proto_msgTypes[117] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8191,7 +8467,7 @@ func (x *UpdateConfigResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateConfigResponse.ProtoReflect.Descriptor instead. func (*UpdateConfigResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{114} + return file_openshell_proto_rawDescGZIP(), []int{117} } func (x *UpdateConfigResponse) GetVersion() uint32 { @@ -8246,7 +8522,7 @@ type GetSandboxPolicyStatusRequest struct { func (x *GetSandboxPolicyStatusRequest) Reset() { *x = GetSandboxPolicyStatusRequest{} - mi := &file_openshell_proto_msgTypes[115] + mi := &file_openshell_proto_msgTypes[118] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8258,7 +8534,7 @@ func (x *GetSandboxPolicyStatusRequest) String() string { func (*GetSandboxPolicyStatusRequest) ProtoMessage() {} func (x *GetSandboxPolicyStatusRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[115] + mi := &file_openshell_proto_msgTypes[118] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8271,7 +8547,7 @@ func (x *GetSandboxPolicyStatusRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetSandboxPolicyStatusRequest.ProtoReflect.Descriptor instead. func (*GetSandboxPolicyStatusRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{115} + return file_openshell_proto_rawDescGZIP(), []int{118} } func (x *GetSandboxPolicyStatusRequest) GetName() string { @@ -8315,7 +8591,7 @@ type GetSandboxPolicyStatusResponse struct { func (x *GetSandboxPolicyStatusResponse) Reset() { *x = GetSandboxPolicyStatusResponse{} - mi := &file_openshell_proto_msgTypes[116] + mi := &file_openshell_proto_msgTypes[119] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8327,7 +8603,7 @@ func (x *GetSandboxPolicyStatusResponse) String() string { func (*GetSandboxPolicyStatusResponse) ProtoMessage() {} func (x *GetSandboxPolicyStatusResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[116] + mi := &file_openshell_proto_msgTypes[119] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8340,7 +8616,7 @@ func (x *GetSandboxPolicyStatusResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetSandboxPolicyStatusResponse.ProtoReflect.Descriptor instead. func (*GetSandboxPolicyStatusResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{116} + return file_openshell_proto_rawDescGZIP(), []int{119} } func (x *GetSandboxPolicyStatusResponse) GetRevision() *SandboxPolicyRevision { @@ -8374,7 +8650,7 @@ type ListSandboxPoliciesRequest struct { func (x *ListSandboxPoliciesRequest) Reset() { *x = ListSandboxPoliciesRequest{} - mi := &file_openshell_proto_msgTypes[117] + mi := &file_openshell_proto_msgTypes[120] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8386,7 +8662,7 @@ func (x *ListSandboxPoliciesRequest) String() string { func (*ListSandboxPoliciesRequest) ProtoMessage() {} func (x *ListSandboxPoliciesRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[117] + mi := &file_openshell_proto_msgTypes[120] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8399,7 +8675,7 @@ func (x *ListSandboxPoliciesRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListSandboxPoliciesRequest.ProtoReflect.Descriptor instead. func (*ListSandboxPoliciesRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{117} + return file_openshell_proto_rawDescGZIP(), []int{120} } func (x *ListSandboxPoliciesRequest) GetName() string { @@ -8447,7 +8723,7 @@ type ListSandboxPoliciesResponse struct { func (x *ListSandboxPoliciesResponse) Reset() { *x = ListSandboxPoliciesResponse{} - mi := &file_openshell_proto_msgTypes[118] + mi := &file_openshell_proto_msgTypes[121] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8459,7 +8735,7 @@ func (x *ListSandboxPoliciesResponse) String() string { func (*ListSandboxPoliciesResponse) ProtoMessage() {} func (x *ListSandboxPoliciesResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[118] + mi := &file_openshell_proto_msgTypes[121] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8472,7 +8748,7 @@ func (x *ListSandboxPoliciesResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListSandboxPoliciesResponse.ProtoReflect.Descriptor instead. func (*ListSandboxPoliciesResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{118} + return file_openshell_proto_rawDescGZIP(), []int{121} } func (x *ListSandboxPoliciesResponse) GetRevisions() []*SandboxPolicyRevision { @@ -8499,7 +8775,7 @@ type ReportPolicyStatusRequest struct { func (x *ReportPolicyStatusRequest) Reset() { *x = ReportPolicyStatusRequest{} - mi := &file_openshell_proto_msgTypes[119] + mi := &file_openshell_proto_msgTypes[122] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8511,7 +8787,7 @@ func (x *ReportPolicyStatusRequest) String() string { func (*ReportPolicyStatusRequest) ProtoMessage() {} func (x *ReportPolicyStatusRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[119] + mi := &file_openshell_proto_msgTypes[122] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8524,7 +8800,7 @@ func (x *ReportPolicyStatusRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ReportPolicyStatusRequest.ProtoReflect.Descriptor instead. func (*ReportPolicyStatusRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{119} + return file_openshell_proto_rawDescGZIP(), []int{122} } func (x *ReportPolicyStatusRequest) GetSandboxId() string { @@ -8564,7 +8840,7 @@ type ReportPolicyStatusResponse struct { func (x *ReportPolicyStatusResponse) Reset() { *x = ReportPolicyStatusResponse{} - mi := &file_openshell_proto_msgTypes[120] + mi := &file_openshell_proto_msgTypes[123] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8576,7 +8852,7 @@ func (x *ReportPolicyStatusResponse) String() string { func (*ReportPolicyStatusResponse) ProtoMessage() {} func (x *ReportPolicyStatusResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[120] + mi := &file_openshell_proto_msgTypes[123] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8589,7 +8865,7 @@ func (x *ReportPolicyStatusResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ReportPolicyStatusResponse.ProtoReflect.Descriptor instead. func (*ReportPolicyStatusResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{120} + return file_openshell_proto_rawDescGZIP(), []int{123} } // A versioned policy revision with metadata. @@ -8617,7 +8893,7 @@ type SandboxPolicyRevision struct { func (x *SandboxPolicyRevision) Reset() { *x = SandboxPolicyRevision{} - mi := &file_openshell_proto_msgTypes[121] + mi := &file_openshell_proto_msgTypes[124] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8629,7 +8905,7 @@ func (x *SandboxPolicyRevision) String() string { func (*SandboxPolicyRevision) ProtoMessage() {} func (x *SandboxPolicyRevision) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[121] + mi := &file_openshell_proto_msgTypes[124] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8642,7 +8918,7 @@ func (x *SandboxPolicyRevision) ProtoReflect() protoreflect.Message { // Deprecated: Use SandboxPolicyRevision.ProtoReflect.Descriptor instead. func (*SandboxPolicyRevision) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{121} + return file_openshell_proto_rawDescGZIP(), []int{124} } func (x *SandboxPolicyRevision) GetVersion() uint32 { @@ -8722,7 +8998,7 @@ type GetSandboxLogsRequest struct { func (x *GetSandboxLogsRequest) Reset() { *x = GetSandboxLogsRequest{} - mi := &file_openshell_proto_msgTypes[122] + mi := &file_openshell_proto_msgTypes[125] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8734,7 +9010,7 @@ func (x *GetSandboxLogsRequest) String() string { func (*GetSandboxLogsRequest) ProtoMessage() {} func (x *GetSandboxLogsRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[122] + mi := &file_openshell_proto_msgTypes[125] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8747,7 +9023,7 @@ func (x *GetSandboxLogsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetSandboxLogsRequest.ProtoReflect.Descriptor instead. func (*GetSandboxLogsRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{122} + return file_openshell_proto_rawDescGZIP(), []int{125} } func (x *GetSandboxLogsRequest) GetSandboxId() string { @@ -8805,7 +9081,7 @@ type PushSandboxLogsRequest struct { func (x *PushSandboxLogsRequest) Reset() { *x = PushSandboxLogsRequest{} - mi := &file_openshell_proto_msgTypes[123] + mi := &file_openshell_proto_msgTypes[126] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8817,7 +9093,7 @@ func (x *PushSandboxLogsRequest) String() string { func (*PushSandboxLogsRequest) ProtoMessage() {} func (x *PushSandboxLogsRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[123] + mi := &file_openshell_proto_msgTypes[126] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8830,7 +9106,7 @@ func (x *PushSandboxLogsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use PushSandboxLogsRequest.ProtoReflect.Descriptor instead. func (*PushSandboxLogsRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{123} + return file_openshell_proto_rawDescGZIP(), []int{126} } func (x *PushSandboxLogsRequest) GetSandboxId() string { @@ -8856,7 +9132,7 @@ type PushSandboxLogsResponse struct { func (x *PushSandboxLogsResponse) Reset() { *x = PushSandboxLogsResponse{} - mi := &file_openshell_proto_msgTypes[124] + mi := &file_openshell_proto_msgTypes[127] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8868,7 +9144,7 @@ func (x *PushSandboxLogsResponse) String() string { func (*PushSandboxLogsResponse) ProtoMessage() {} func (x *PushSandboxLogsResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[124] + mi := &file_openshell_proto_msgTypes[127] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8881,7 +9157,7 @@ func (x *PushSandboxLogsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use PushSandboxLogsResponse.ProtoReflect.Descriptor instead. func (*PushSandboxLogsResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{124} + return file_openshell_proto_rawDescGZIP(), []int{127} } // Get sandbox logs response. @@ -8897,7 +9173,7 @@ type GetSandboxLogsResponse struct { func (x *GetSandboxLogsResponse) Reset() { *x = GetSandboxLogsResponse{} - mi := &file_openshell_proto_msgTypes[125] + mi := &file_openshell_proto_msgTypes[128] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8909,7 +9185,7 @@ func (x *GetSandboxLogsResponse) String() string { func (*GetSandboxLogsResponse) ProtoMessage() {} func (x *GetSandboxLogsResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[125] + mi := &file_openshell_proto_msgTypes[128] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8922,7 +9198,7 @@ func (x *GetSandboxLogsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetSandboxLogsResponse.ProtoReflect.Descriptor instead. func (*GetSandboxLogsResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{125} + return file_openshell_proto_rawDescGZIP(), []int{128} } func (x *GetSandboxLogsResponse) GetLogs() []*SandboxLogLine { @@ -8955,7 +9231,7 @@ type SupervisorMessage struct { func (x *SupervisorMessage) Reset() { *x = SupervisorMessage{} - mi := &file_openshell_proto_msgTypes[126] + mi := &file_openshell_proto_msgTypes[129] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8967,7 +9243,7 @@ func (x *SupervisorMessage) String() string { func (*SupervisorMessage) ProtoMessage() {} func (x *SupervisorMessage) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[126] + mi := &file_openshell_proto_msgTypes[129] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8980,7 +9256,7 @@ func (x *SupervisorMessage) ProtoReflect() protoreflect.Message { // Deprecated: Use SupervisorMessage.ProtoReflect.Descriptor instead. func (*SupervisorMessage) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{126} + return file_openshell_proto_rawDescGZIP(), []int{129} } func (x *SupervisorMessage) GetPayload() isSupervisorMessage_Payload { @@ -9071,7 +9347,7 @@ type GatewayMessage struct { func (x *GatewayMessage) Reset() { *x = GatewayMessage{} - mi := &file_openshell_proto_msgTypes[127] + mi := &file_openshell_proto_msgTypes[130] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9083,7 +9359,7 @@ func (x *GatewayMessage) String() string { func (*GatewayMessage) ProtoMessage() {} func (x *GatewayMessage) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[127] + mi := &file_openshell_proto_msgTypes[130] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9096,7 +9372,7 @@ func (x *GatewayMessage) ProtoReflect() protoreflect.Message { // Deprecated: Use GatewayMessage.ProtoReflect.Descriptor instead. func (*GatewayMessage) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{127} + return file_openshell_proto_rawDescGZIP(), []int{130} } func (x *GatewayMessage) GetPayload() isGatewayMessage_Payload { @@ -9198,7 +9474,7 @@ type SupervisorHello struct { func (x *SupervisorHello) Reset() { *x = SupervisorHello{} - mi := &file_openshell_proto_msgTypes[128] + mi := &file_openshell_proto_msgTypes[131] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9210,7 +9486,7 @@ func (x *SupervisorHello) String() string { func (*SupervisorHello) ProtoMessage() {} func (x *SupervisorHello) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[128] + mi := &file_openshell_proto_msgTypes[131] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9223,7 +9499,7 @@ func (x *SupervisorHello) ProtoReflect() protoreflect.Message { // Deprecated: Use SupervisorHello.ProtoReflect.Descriptor instead. func (*SupervisorHello) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{128} + return file_openshell_proto_rawDescGZIP(), []int{131} } func (x *SupervisorHello) GetSandboxId() string { @@ -9253,7 +9529,7 @@ type SessionAccepted struct { func (x *SessionAccepted) Reset() { *x = SessionAccepted{} - mi := &file_openshell_proto_msgTypes[129] + mi := &file_openshell_proto_msgTypes[132] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9265,7 +9541,7 @@ func (x *SessionAccepted) String() string { func (*SessionAccepted) ProtoMessage() {} func (x *SessionAccepted) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[129] + mi := &file_openshell_proto_msgTypes[132] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9278,7 +9554,7 @@ func (x *SessionAccepted) ProtoReflect() protoreflect.Message { // Deprecated: Use SessionAccepted.ProtoReflect.Descriptor instead. func (*SessionAccepted) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{129} + return file_openshell_proto_rawDescGZIP(), []int{132} } func (x *SessionAccepted) GetSessionId() string { @@ -9306,7 +9582,7 @@ type SessionRejected struct { func (x *SessionRejected) Reset() { *x = SessionRejected{} - mi := &file_openshell_proto_msgTypes[130] + mi := &file_openshell_proto_msgTypes[133] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9318,7 +9594,7 @@ func (x *SessionRejected) String() string { func (*SessionRejected) ProtoMessage() {} func (x *SessionRejected) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[130] + mi := &file_openshell_proto_msgTypes[133] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9331,7 +9607,7 @@ func (x *SessionRejected) ProtoReflect() protoreflect.Message { // Deprecated: Use SessionRejected.ProtoReflect.Descriptor instead. func (*SessionRejected) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{130} + return file_openshell_proto_rawDescGZIP(), []int{133} } func (x *SessionRejected) GetReason() string { @@ -9350,7 +9626,7 @@ type SupervisorHeartbeat struct { func (x *SupervisorHeartbeat) Reset() { *x = SupervisorHeartbeat{} - mi := &file_openshell_proto_msgTypes[131] + mi := &file_openshell_proto_msgTypes[134] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9362,7 +9638,7 @@ func (x *SupervisorHeartbeat) String() string { func (*SupervisorHeartbeat) ProtoMessage() {} func (x *SupervisorHeartbeat) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[131] + mi := &file_openshell_proto_msgTypes[134] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9375,7 +9651,7 @@ func (x *SupervisorHeartbeat) ProtoReflect() protoreflect.Message { // Deprecated: Use SupervisorHeartbeat.ProtoReflect.Descriptor instead. func (*SupervisorHeartbeat) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{131} + return file_openshell_proto_rawDescGZIP(), []int{134} } // Gateway heartbeat. @@ -9387,7 +9663,7 @@ type GatewayHeartbeat struct { func (x *GatewayHeartbeat) Reset() { *x = GatewayHeartbeat{} - mi := &file_openshell_proto_msgTypes[132] + mi := &file_openshell_proto_msgTypes[135] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9399,7 +9675,7 @@ func (x *GatewayHeartbeat) String() string { func (*GatewayHeartbeat) ProtoMessage() {} func (x *GatewayHeartbeat) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[132] + mi := &file_openshell_proto_msgTypes[135] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9412,7 +9688,7 @@ func (x *GatewayHeartbeat) ProtoReflect() protoreflect.Message { // Deprecated: Use GatewayHeartbeat.ProtoReflect.Descriptor instead. func (*GatewayHeartbeat) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{132} + return file_openshell_proto_rawDescGZIP(), []int{135} } // Gateway requests the supervisor to open a relay channel. @@ -9441,7 +9717,7 @@ type RelayOpen struct { func (x *RelayOpen) Reset() { *x = RelayOpen{} - mi := &file_openshell_proto_msgTypes[133] + mi := &file_openshell_proto_msgTypes[136] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9453,7 +9729,7 @@ func (x *RelayOpen) String() string { func (*RelayOpen) ProtoMessage() {} func (x *RelayOpen) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[133] + mi := &file_openshell_proto_msgTypes[136] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9466,7 +9742,7 @@ func (x *RelayOpen) ProtoReflect() protoreflect.Message { // Deprecated: Use RelayOpen.ProtoReflect.Descriptor instead. func (*RelayOpen) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{133} + return file_openshell_proto_rawDescGZIP(), []int{136} } func (x *RelayOpen) GetChannelId() string { @@ -9533,7 +9809,7 @@ type SshRelayTarget struct { func (x *SshRelayTarget) Reset() { *x = SshRelayTarget{} - mi := &file_openshell_proto_msgTypes[134] + mi := &file_openshell_proto_msgTypes[137] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9545,7 +9821,7 @@ func (x *SshRelayTarget) String() string { func (*SshRelayTarget) ProtoMessage() {} func (x *SshRelayTarget) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[134] + mi := &file_openshell_proto_msgTypes[137] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9558,7 +9834,7 @@ func (x *SshRelayTarget) ProtoReflect() protoreflect.Message { // Deprecated: Use SshRelayTarget.ProtoReflect.Descriptor instead. func (*SshRelayTarget) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{134} + return file_openshell_proto_rawDescGZIP(), []int{137} } // TCP target dialed by the supervisor from inside the sandbox. @@ -9574,7 +9850,7 @@ type TcpRelayTarget struct { func (x *TcpRelayTarget) Reset() { *x = TcpRelayTarget{} - mi := &file_openshell_proto_msgTypes[135] + mi := &file_openshell_proto_msgTypes[138] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9586,7 +9862,7 @@ func (x *TcpRelayTarget) String() string { func (*TcpRelayTarget) ProtoMessage() {} func (x *TcpRelayTarget) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[135] + mi := &file_openshell_proto_msgTypes[138] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9599,7 +9875,7 @@ func (x *TcpRelayTarget) ProtoReflect() protoreflect.Message { // Deprecated: Use TcpRelayTarget.ProtoReflect.Descriptor instead. func (*TcpRelayTarget) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{135} + return file_openshell_proto_rawDescGZIP(), []int{138} } func (x *TcpRelayTarget) GetHost() string { @@ -9627,7 +9903,7 @@ type RelayInit struct { func (x *RelayInit) Reset() { *x = RelayInit{} - mi := &file_openshell_proto_msgTypes[136] + mi := &file_openshell_proto_msgTypes[139] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9639,7 +9915,7 @@ func (x *RelayInit) String() string { func (*RelayInit) ProtoMessage() {} func (x *RelayInit) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[136] + mi := &file_openshell_proto_msgTypes[139] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9652,7 +9928,7 @@ func (x *RelayInit) ProtoReflect() protoreflect.Message { // Deprecated: Use RelayInit.ProtoReflect.Descriptor instead. func (*RelayInit) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{136} + return file_openshell_proto_rawDescGZIP(), []int{139} } func (x *RelayInit) GetChannelId() string { @@ -9679,7 +9955,7 @@ type RelayFrame struct { func (x *RelayFrame) Reset() { *x = RelayFrame{} - mi := &file_openshell_proto_msgTypes[137] + mi := &file_openshell_proto_msgTypes[140] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9691,7 +9967,7 @@ func (x *RelayFrame) String() string { func (*RelayFrame) ProtoMessage() {} func (x *RelayFrame) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[137] + mi := &file_openshell_proto_msgTypes[140] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9704,7 +9980,7 @@ func (x *RelayFrame) ProtoReflect() protoreflect.Message { // Deprecated: Use RelayFrame.ProtoReflect.Descriptor instead. func (*RelayFrame) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{137} + return file_openshell_proto_rawDescGZIP(), []int{140} } func (x *RelayFrame) GetPayload() isRelayFrame_Payload { @@ -9763,7 +10039,7 @@ type RelayOpenResult struct { func (x *RelayOpenResult) Reset() { *x = RelayOpenResult{} - mi := &file_openshell_proto_msgTypes[138] + mi := &file_openshell_proto_msgTypes[141] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9775,7 +10051,7 @@ func (x *RelayOpenResult) String() string { func (*RelayOpenResult) ProtoMessage() {} func (x *RelayOpenResult) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[138] + mi := &file_openshell_proto_msgTypes[141] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9788,7 +10064,7 @@ func (x *RelayOpenResult) ProtoReflect() protoreflect.Message { // Deprecated: Use RelayOpenResult.ProtoReflect.Descriptor instead. func (*RelayOpenResult) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{138} + return file_openshell_proto_rawDescGZIP(), []int{141} } func (x *RelayOpenResult) GetChannelId() string { @@ -9825,7 +10101,7 @@ type RelayClose struct { func (x *RelayClose) Reset() { *x = RelayClose{} - mi := &file_openshell_proto_msgTypes[139] + mi := &file_openshell_proto_msgTypes[142] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9837,7 +10113,7 @@ func (x *RelayClose) String() string { func (*RelayClose) ProtoMessage() {} func (x *RelayClose) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[139] + mi := &file_openshell_proto_msgTypes[142] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9850,7 +10126,7 @@ func (x *RelayClose) ProtoReflect() protoreflect.Message { // Deprecated: Use RelayClose.ProtoReflect.Descriptor instead. func (*RelayClose) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{139} + return file_openshell_proto_rawDescGZIP(), []int{142} } func (x *RelayClose) GetChannelId() string { @@ -9884,7 +10160,7 @@ type L7RequestSample struct { func (x *L7RequestSample) Reset() { *x = L7RequestSample{} - mi := &file_openshell_proto_msgTypes[140] + mi := &file_openshell_proto_msgTypes[143] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9896,7 +10172,7 @@ func (x *L7RequestSample) String() string { func (*L7RequestSample) ProtoMessage() {} func (x *L7RequestSample) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[140] + mi := &file_openshell_proto_msgTypes[143] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9909,7 +10185,7 @@ func (x *L7RequestSample) ProtoReflect() protoreflect.Message { // Deprecated: Use L7RequestSample.ProtoReflect.Descriptor instead. func (*L7RequestSample) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{140} + return file_openshell_proto_rawDescGZIP(), []int{143} } func (x *L7RequestSample) GetMethod() string { @@ -9983,7 +10259,7 @@ type DenialSummary struct { func (x *DenialSummary) Reset() { *x = DenialSummary{} - mi := &file_openshell_proto_msgTypes[141] + mi := &file_openshell_proto_msgTypes[144] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9995,7 +10271,7 @@ func (x *DenialSummary) String() string { func (*DenialSummary) ProtoMessage() {} func (x *DenialSummary) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[141] + mi := &file_openshell_proto_msgTypes[144] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10008,7 +10284,7 @@ func (x *DenialSummary) ProtoReflect() protoreflect.Message { // Deprecated: Use DenialSummary.ProtoReflect.Descriptor instead. func (*DenialSummary) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{141} + return file_openshell_proto_rawDescGZIP(), []int{144} } func (x *DenialSummary) GetSandboxId() string { @@ -10143,7 +10419,7 @@ type DenialGroupCount struct { func (x *DenialGroupCount) Reset() { *x = DenialGroupCount{} - mi := &file_openshell_proto_msgTypes[142] + mi := &file_openshell_proto_msgTypes[145] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10155,7 +10431,7 @@ func (x *DenialGroupCount) String() string { func (*DenialGroupCount) ProtoMessage() {} func (x *DenialGroupCount) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[142] + mi := &file_openshell_proto_msgTypes[145] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10168,7 +10444,7 @@ func (x *DenialGroupCount) ProtoReflect() protoreflect.Message { // Deprecated: Use DenialGroupCount.ProtoReflect.Descriptor instead. func (*DenialGroupCount) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{142} + return file_openshell_proto_rawDescGZIP(), []int{145} } func (x *DenialGroupCount) GetDenyGroup() string { @@ -10201,7 +10477,7 @@ type NetworkActivitySummary struct { func (x *NetworkActivitySummary) Reset() { *x = NetworkActivitySummary{} - mi := &file_openshell_proto_msgTypes[143] + mi := &file_openshell_proto_msgTypes[146] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10213,7 +10489,7 @@ func (x *NetworkActivitySummary) String() string { func (*NetworkActivitySummary) ProtoMessage() {} func (x *NetworkActivitySummary) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[143] + mi := &file_openshell_proto_msgTypes[146] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10226,7 +10502,7 @@ func (x *NetworkActivitySummary) ProtoReflect() protoreflect.Message { // Deprecated: Use NetworkActivitySummary.ProtoReflect.Descriptor instead. func (*NetworkActivitySummary) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{143} + return file_openshell_proto_rawDescGZIP(), []int{146} } func (x *NetworkActivitySummary) GetNetworkActivityCount() uint32 { @@ -10300,7 +10576,7 @@ type PolicyChunk struct { func (x *PolicyChunk) Reset() { *x = PolicyChunk{} - mi := &file_openshell_proto_msgTypes[144] + mi := &file_openshell_proto_msgTypes[147] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10312,7 +10588,7 @@ func (x *PolicyChunk) String() string { func (*PolicyChunk) ProtoMessage() {} func (x *PolicyChunk) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[144] + mi := &file_openshell_proto_msgTypes[147] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10325,7 +10601,7 @@ func (x *PolicyChunk) ProtoReflect() protoreflect.Message { // Deprecated: Use PolicyChunk.ProtoReflect.Descriptor instead. func (*PolicyChunk) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{144} + return file_openshell_proto_rawDescGZIP(), []int{147} } func (x *PolicyChunk) GetId() string { @@ -10471,7 +10747,7 @@ type DraftPolicyUpdate struct { func (x *DraftPolicyUpdate) Reset() { *x = DraftPolicyUpdate{} - mi := &file_openshell_proto_msgTypes[145] + mi := &file_openshell_proto_msgTypes[148] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10483,7 +10759,7 @@ func (x *DraftPolicyUpdate) String() string { func (*DraftPolicyUpdate) ProtoMessage() {} func (x *DraftPolicyUpdate) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[145] + mi := &file_openshell_proto_msgTypes[148] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10496,7 +10772,7 @@ func (x *DraftPolicyUpdate) ProtoReflect() protoreflect.Message { // Deprecated: Use DraftPolicyUpdate.ProtoReflect.Descriptor instead. func (*DraftPolicyUpdate) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{145} + return file_openshell_proto_rawDescGZIP(), []int{148} } func (x *DraftPolicyUpdate) GetDraftVersion() uint64 { @@ -10554,7 +10830,7 @@ type SubmitPolicyAnalysisRequest struct { func (x *SubmitPolicyAnalysisRequest) Reset() { *x = SubmitPolicyAnalysisRequest{} - mi := &file_openshell_proto_msgTypes[146] + mi := &file_openshell_proto_msgTypes[149] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10566,7 +10842,7 @@ func (x *SubmitPolicyAnalysisRequest) String() string { func (*SubmitPolicyAnalysisRequest) ProtoMessage() {} func (x *SubmitPolicyAnalysisRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[146] + mi := &file_openshell_proto_msgTypes[149] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10579,7 +10855,7 @@ func (x *SubmitPolicyAnalysisRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use SubmitPolicyAnalysisRequest.ProtoReflect.Descriptor instead. func (*SubmitPolicyAnalysisRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{146} + return file_openshell_proto_rawDescGZIP(), []int{149} } func (x *SubmitPolicyAnalysisRequest) GetSummaries() []*DenialSummary { @@ -10642,7 +10918,7 @@ type SubmitPolicyAnalysisResponse struct { func (x *SubmitPolicyAnalysisResponse) Reset() { *x = SubmitPolicyAnalysisResponse{} - mi := &file_openshell_proto_msgTypes[147] + mi := &file_openshell_proto_msgTypes[150] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10654,7 +10930,7 @@ func (x *SubmitPolicyAnalysisResponse) String() string { func (*SubmitPolicyAnalysisResponse) ProtoMessage() {} func (x *SubmitPolicyAnalysisResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[147] + mi := &file_openshell_proto_msgTypes[150] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10667,7 +10943,7 @@ func (x *SubmitPolicyAnalysisResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use SubmitPolicyAnalysisResponse.ProtoReflect.Descriptor instead. func (*SubmitPolicyAnalysisResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{147} + return file_openshell_proto_rawDescGZIP(), []int{150} } func (x *SubmitPolicyAnalysisResponse) GetAcceptedChunks() uint32 { @@ -10713,7 +10989,7 @@ type GetDraftPolicyRequest struct { func (x *GetDraftPolicyRequest) Reset() { *x = GetDraftPolicyRequest{} - mi := &file_openshell_proto_msgTypes[148] + mi := &file_openshell_proto_msgTypes[151] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10725,7 +11001,7 @@ func (x *GetDraftPolicyRequest) String() string { func (*GetDraftPolicyRequest) ProtoMessage() {} func (x *GetDraftPolicyRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[148] + mi := &file_openshell_proto_msgTypes[151] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10738,7 +11014,7 @@ func (x *GetDraftPolicyRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetDraftPolicyRequest.ProtoReflect.Descriptor instead. func (*GetDraftPolicyRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{148} + return file_openshell_proto_rawDescGZIP(), []int{151} } func (x *GetDraftPolicyRequest) GetName() string { @@ -10778,7 +11054,7 @@ type GetDraftPolicyResponse struct { func (x *GetDraftPolicyResponse) Reset() { *x = GetDraftPolicyResponse{} - mi := &file_openshell_proto_msgTypes[149] + mi := &file_openshell_proto_msgTypes[152] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10790,7 +11066,7 @@ func (x *GetDraftPolicyResponse) String() string { func (*GetDraftPolicyResponse) ProtoMessage() {} func (x *GetDraftPolicyResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[149] + mi := &file_openshell_proto_msgTypes[152] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10803,7 +11079,7 @@ func (x *GetDraftPolicyResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetDraftPolicyResponse.ProtoReflect.Descriptor instead. func (*GetDraftPolicyResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{149} + return file_openshell_proto_rawDescGZIP(), []int{152} } func (x *GetDraftPolicyResponse) GetChunks() []*PolicyChunk { @@ -10849,7 +11125,7 @@ type ApproveDraftChunkRequest struct { func (x *ApproveDraftChunkRequest) Reset() { *x = ApproveDraftChunkRequest{} - mi := &file_openshell_proto_msgTypes[150] + mi := &file_openshell_proto_msgTypes[153] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10861,7 +11137,7 @@ func (x *ApproveDraftChunkRequest) String() string { func (*ApproveDraftChunkRequest) ProtoMessage() {} func (x *ApproveDraftChunkRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[150] + mi := &file_openshell_proto_msgTypes[153] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10874,7 +11150,7 @@ func (x *ApproveDraftChunkRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ApproveDraftChunkRequest.ProtoReflect.Descriptor instead. func (*ApproveDraftChunkRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{150} + return file_openshell_proto_rawDescGZIP(), []int{153} } func (x *ApproveDraftChunkRequest) GetName() string { @@ -10910,7 +11186,7 @@ type ApproveDraftChunkResponse struct { func (x *ApproveDraftChunkResponse) Reset() { *x = ApproveDraftChunkResponse{} - mi := &file_openshell_proto_msgTypes[151] + mi := &file_openshell_proto_msgTypes[154] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10922,7 +11198,7 @@ func (x *ApproveDraftChunkResponse) String() string { func (*ApproveDraftChunkResponse) ProtoMessage() {} func (x *ApproveDraftChunkResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[151] + mi := &file_openshell_proto_msgTypes[154] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10935,7 +11211,7 @@ func (x *ApproveDraftChunkResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ApproveDraftChunkResponse.ProtoReflect.Descriptor instead. func (*ApproveDraftChunkResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{151} + return file_openshell_proto_rawDescGZIP(), []int{154} } func (x *ApproveDraftChunkResponse) GetPolicyVersion() uint32 { @@ -10969,7 +11245,7 @@ type RejectDraftChunkRequest struct { func (x *RejectDraftChunkRequest) Reset() { *x = RejectDraftChunkRequest{} - mi := &file_openshell_proto_msgTypes[152] + mi := &file_openshell_proto_msgTypes[155] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10981,7 +11257,7 @@ func (x *RejectDraftChunkRequest) String() string { func (*RejectDraftChunkRequest) ProtoMessage() {} func (x *RejectDraftChunkRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[152] + mi := &file_openshell_proto_msgTypes[155] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10994,7 +11270,7 @@ func (x *RejectDraftChunkRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use RejectDraftChunkRequest.ProtoReflect.Descriptor instead. func (*RejectDraftChunkRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{152} + return file_openshell_proto_rawDescGZIP(), []int{155} } func (x *RejectDraftChunkRequest) GetName() string { @@ -11033,7 +11309,7 @@ type RejectDraftChunkResponse struct { func (x *RejectDraftChunkResponse) Reset() { *x = RejectDraftChunkResponse{} - mi := &file_openshell_proto_msgTypes[153] + mi := &file_openshell_proto_msgTypes[156] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11045,7 +11321,7 @@ func (x *RejectDraftChunkResponse) String() string { func (*RejectDraftChunkResponse) ProtoMessage() {} func (x *RejectDraftChunkResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[153] + mi := &file_openshell_proto_msgTypes[156] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11058,7 +11334,7 @@ func (x *RejectDraftChunkResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use RejectDraftChunkResponse.ProtoReflect.Descriptor instead. func (*RejectDraftChunkResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{153} + return file_openshell_proto_rawDescGZIP(), []int{156} } // Approve all pending chunks. @@ -11076,7 +11352,7 @@ type ApproveAllDraftChunksRequest struct { func (x *ApproveAllDraftChunksRequest) Reset() { *x = ApproveAllDraftChunksRequest{} - mi := &file_openshell_proto_msgTypes[154] + mi := &file_openshell_proto_msgTypes[157] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11088,7 +11364,7 @@ func (x *ApproveAllDraftChunksRequest) String() string { func (*ApproveAllDraftChunksRequest) ProtoMessage() {} func (x *ApproveAllDraftChunksRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[154] + mi := &file_openshell_proto_msgTypes[157] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11101,7 +11377,7 @@ func (x *ApproveAllDraftChunksRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ApproveAllDraftChunksRequest.ProtoReflect.Descriptor instead. func (*ApproveAllDraftChunksRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{154} + return file_openshell_proto_rawDescGZIP(), []int{157} } func (x *ApproveAllDraftChunksRequest) GetName() string { @@ -11141,7 +11417,7 @@ type ApproveAllDraftChunksResponse struct { func (x *ApproveAllDraftChunksResponse) Reset() { *x = ApproveAllDraftChunksResponse{} - mi := &file_openshell_proto_msgTypes[155] + mi := &file_openshell_proto_msgTypes[158] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11153,7 +11429,7 @@ func (x *ApproveAllDraftChunksResponse) String() string { func (*ApproveAllDraftChunksResponse) ProtoMessage() {} func (x *ApproveAllDraftChunksResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[155] + mi := &file_openshell_proto_msgTypes[158] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11166,7 +11442,7 @@ func (x *ApproveAllDraftChunksResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ApproveAllDraftChunksResponse.ProtoReflect.Descriptor instead. func (*ApproveAllDraftChunksResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{155} + return file_openshell_proto_rawDescGZIP(), []int{158} } func (x *ApproveAllDraftChunksResponse) GetPolicyVersion() uint32 { @@ -11214,7 +11490,7 @@ type EditDraftChunkRequest struct { func (x *EditDraftChunkRequest) Reset() { *x = EditDraftChunkRequest{} - mi := &file_openshell_proto_msgTypes[156] + mi := &file_openshell_proto_msgTypes[159] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11226,7 +11502,7 @@ func (x *EditDraftChunkRequest) String() string { func (*EditDraftChunkRequest) ProtoMessage() {} func (x *EditDraftChunkRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[156] + mi := &file_openshell_proto_msgTypes[159] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11239,7 +11515,7 @@ func (x *EditDraftChunkRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use EditDraftChunkRequest.ProtoReflect.Descriptor instead. func (*EditDraftChunkRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{156} + return file_openshell_proto_rawDescGZIP(), []int{159} } func (x *EditDraftChunkRequest) GetName() string { @@ -11278,7 +11554,7 @@ type EditDraftChunkResponse struct { func (x *EditDraftChunkResponse) Reset() { *x = EditDraftChunkResponse{} - mi := &file_openshell_proto_msgTypes[157] + mi := &file_openshell_proto_msgTypes[160] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11290,7 +11566,7 @@ func (x *EditDraftChunkResponse) String() string { func (*EditDraftChunkResponse) ProtoMessage() {} func (x *EditDraftChunkResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[157] + mi := &file_openshell_proto_msgTypes[160] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11303,7 +11579,7 @@ func (x *EditDraftChunkResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use EditDraftChunkResponse.ProtoReflect.Descriptor instead. func (*EditDraftChunkResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{157} + return file_openshell_proto_rawDescGZIP(), []int{160} } // Reverse an approval (remove merged rule from active policy). @@ -11321,7 +11597,7 @@ type UndoDraftChunkRequest struct { func (x *UndoDraftChunkRequest) Reset() { *x = UndoDraftChunkRequest{} - mi := &file_openshell_proto_msgTypes[158] + mi := &file_openshell_proto_msgTypes[161] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11333,7 +11609,7 @@ func (x *UndoDraftChunkRequest) String() string { func (*UndoDraftChunkRequest) ProtoMessage() {} func (x *UndoDraftChunkRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[158] + mi := &file_openshell_proto_msgTypes[161] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11346,7 +11622,7 @@ func (x *UndoDraftChunkRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UndoDraftChunkRequest.ProtoReflect.Descriptor instead. func (*UndoDraftChunkRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{158} + return file_openshell_proto_rawDescGZIP(), []int{161} } func (x *UndoDraftChunkRequest) GetName() string { @@ -11382,7 +11658,7 @@ type UndoDraftChunkResponse struct { func (x *UndoDraftChunkResponse) Reset() { *x = UndoDraftChunkResponse{} - mi := &file_openshell_proto_msgTypes[159] + mi := &file_openshell_proto_msgTypes[162] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11394,7 +11670,7 @@ func (x *UndoDraftChunkResponse) String() string { func (*UndoDraftChunkResponse) ProtoMessage() {} func (x *UndoDraftChunkResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[159] + mi := &file_openshell_proto_msgTypes[162] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11407,7 +11683,7 @@ func (x *UndoDraftChunkResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use UndoDraftChunkResponse.ProtoReflect.Descriptor instead. func (*UndoDraftChunkResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{159} + return file_openshell_proto_rawDescGZIP(), []int{162} } func (x *UndoDraftChunkResponse) GetPolicyVersion() uint32 { @@ -11437,7 +11713,7 @@ type ClearDraftChunksRequest struct { func (x *ClearDraftChunksRequest) Reset() { *x = ClearDraftChunksRequest{} - mi := &file_openshell_proto_msgTypes[160] + mi := &file_openshell_proto_msgTypes[163] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11449,7 +11725,7 @@ func (x *ClearDraftChunksRequest) String() string { func (*ClearDraftChunksRequest) ProtoMessage() {} func (x *ClearDraftChunksRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[160] + mi := &file_openshell_proto_msgTypes[163] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11462,7 +11738,7 @@ func (x *ClearDraftChunksRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ClearDraftChunksRequest.ProtoReflect.Descriptor instead. func (*ClearDraftChunksRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{160} + return file_openshell_proto_rawDescGZIP(), []int{163} } func (x *ClearDraftChunksRequest) GetName() string { @@ -11489,7 +11765,7 @@ type ClearDraftChunksResponse struct { func (x *ClearDraftChunksResponse) Reset() { *x = ClearDraftChunksResponse{} - mi := &file_openshell_proto_msgTypes[161] + mi := &file_openshell_proto_msgTypes[164] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11501,7 +11777,7 @@ func (x *ClearDraftChunksResponse) String() string { func (*ClearDraftChunksResponse) ProtoMessage() {} func (x *ClearDraftChunksResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[161] + mi := &file_openshell_proto_msgTypes[164] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11514,7 +11790,7 @@ func (x *ClearDraftChunksResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ClearDraftChunksResponse.ProtoReflect.Descriptor instead. func (*ClearDraftChunksResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{161} + return file_openshell_proto_rawDescGZIP(), []int{164} } func (x *ClearDraftChunksResponse) GetChunksCleared() uint32 { @@ -11537,7 +11813,7 @@ type GetDraftHistoryRequest struct { func (x *GetDraftHistoryRequest) Reset() { *x = GetDraftHistoryRequest{} - mi := &file_openshell_proto_msgTypes[162] + mi := &file_openshell_proto_msgTypes[165] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11549,7 +11825,7 @@ func (x *GetDraftHistoryRequest) String() string { func (*GetDraftHistoryRequest) ProtoMessage() {} func (x *GetDraftHistoryRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[162] + mi := &file_openshell_proto_msgTypes[165] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11562,7 +11838,7 @@ func (x *GetDraftHistoryRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetDraftHistoryRequest.ProtoReflect.Descriptor instead. func (*GetDraftHistoryRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{162} + return file_openshell_proto_rawDescGZIP(), []int{165} } func (x *GetDraftHistoryRequest) GetName() string { @@ -11596,7 +11872,7 @@ type DraftHistoryEntry struct { func (x *DraftHistoryEntry) Reset() { *x = DraftHistoryEntry{} - mi := &file_openshell_proto_msgTypes[163] + mi := &file_openshell_proto_msgTypes[166] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11608,7 +11884,7 @@ func (x *DraftHistoryEntry) String() string { func (*DraftHistoryEntry) ProtoMessage() {} func (x *DraftHistoryEntry) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[163] + mi := &file_openshell_proto_msgTypes[166] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11621,7 +11897,7 @@ func (x *DraftHistoryEntry) ProtoReflect() protoreflect.Message { // Deprecated: Use DraftHistoryEntry.ProtoReflect.Descriptor instead. func (*DraftHistoryEntry) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{163} + return file_openshell_proto_rawDescGZIP(), []int{166} } func (x *DraftHistoryEntry) GetTimestampMs() int64 { @@ -11662,7 +11938,7 @@ type GetDraftHistoryResponse struct { func (x *GetDraftHistoryResponse) Reset() { *x = GetDraftHistoryResponse{} - mi := &file_openshell_proto_msgTypes[164] + mi := &file_openshell_proto_msgTypes[167] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11674,7 +11950,7 @@ func (x *GetDraftHistoryResponse) String() string { func (*GetDraftHistoryResponse) ProtoMessage() {} func (x *GetDraftHistoryResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[164] + mi := &file_openshell_proto_msgTypes[167] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11687,7 +11963,7 @@ func (x *GetDraftHistoryResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetDraftHistoryResponse.ProtoReflect.Descriptor instead. func (*GetDraftHistoryResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{164} + return file_openshell_proto_rawDescGZIP(), []int{167} } func (x *GetDraftHistoryResponse) GetEntries() []*DraftHistoryEntry { @@ -11716,7 +11992,7 @@ type PolicyRevisionPayload struct { func (x *PolicyRevisionPayload) Reset() { *x = PolicyRevisionPayload{} - mi := &file_openshell_proto_msgTypes[165] + mi := &file_openshell_proto_msgTypes[168] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11728,7 +12004,7 @@ func (x *PolicyRevisionPayload) String() string { func (*PolicyRevisionPayload) ProtoMessage() {} func (x *PolicyRevisionPayload) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[165] + mi := &file_openshell_proto_msgTypes[168] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11741,7 +12017,7 @@ func (x *PolicyRevisionPayload) ProtoReflect() protoreflect.Message { // Deprecated: Use PolicyRevisionPayload.ProtoReflect.Descriptor instead. func (*PolicyRevisionPayload) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{165} + return file_openshell_proto_rawDescGZIP(), []int{168} } func (x *PolicyRevisionPayload) GetPolicy() *sandboxv1.SandboxPolicy { @@ -11814,7 +12090,7 @@ type DraftChunkPayload struct { func (x *DraftChunkPayload) Reset() { *x = DraftChunkPayload{} - mi := &file_openshell_proto_msgTypes[166] + mi := &file_openshell_proto_msgTypes[169] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11826,7 +12102,7 @@ func (x *DraftChunkPayload) String() string { func (*DraftChunkPayload) ProtoMessage() {} func (x *DraftChunkPayload) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[166] + mi := &file_openshell_proto_msgTypes[169] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11839,7 +12115,7 @@ func (x *DraftChunkPayload) ProtoReflect() protoreflect.Message { // Deprecated: Use DraftChunkPayload.ProtoReflect.Descriptor instead. func (*DraftChunkPayload) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{166} + return file_openshell_proto_rawDescGZIP(), []int{169} } func (x *DraftChunkPayload) GetRuleName() string { @@ -11945,7 +12221,7 @@ type StoredPolicyRevision struct { func (x *StoredPolicyRevision) Reset() { *x = StoredPolicyRevision{} - mi := &file_openshell_proto_msgTypes[167] + mi := &file_openshell_proto_msgTypes[170] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11957,7 +12233,7 @@ func (x *StoredPolicyRevision) String() string { func (*StoredPolicyRevision) ProtoMessage() {} func (x *StoredPolicyRevision) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[167] + mi := &file_openshell_proto_msgTypes[170] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11970,7 +12246,7 @@ func (x *StoredPolicyRevision) ProtoReflect() protoreflect.Message { // Deprecated: Use StoredPolicyRevision.ProtoReflect.Descriptor instead. func (*StoredPolicyRevision) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{167} + return file_openshell_proto_rawDescGZIP(), []int{170} } func (x *StoredPolicyRevision) GetId() string { @@ -12073,7 +12349,7 @@ type StoredDraftChunk struct { func (x *StoredDraftChunk) Reset() { *x = StoredDraftChunk{} - mi := &file_openshell_proto_msgTypes[168] + mi := &file_openshell_proto_msgTypes[171] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12085,7 +12361,7 @@ func (x *StoredDraftChunk) String() string { func (*StoredDraftChunk) ProtoMessage() {} func (x *StoredDraftChunk) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[168] + mi := &file_openshell_proto_msgTypes[171] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12098,7 +12374,7 @@ func (x *StoredDraftChunk) ProtoReflect() protoreflect.Message { // Deprecated: Use StoredDraftChunk.ProtoReflect.Descriptor instead. func (*StoredDraftChunk) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{168} + return file_openshell_proto_rawDescGZIP(), []int{171} } func (x *StoredDraftChunk) GetId() string { @@ -12247,7 +12523,7 @@ type CreateWorkspaceRequest struct { func (x *CreateWorkspaceRequest) Reset() { *x = CreateWorkspaceRequest{} - mi := &file_openshell_proto_msgTypes[169] + mi := &file_openshell_proto_msgTypes[172] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12259,7 +12535,7 @@ func (x *CreateWorkspaceRequest) String() string { func (*CreateWorkspaceRequest) ProtoMessage() {} func (x *CreateWorkspaceRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[169] + mi := &file_openshell_proto_msgTypes[172] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12272,7 +12548,7 @@ func (x *CreateWorkspaceRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use CreateWorkspaceRequest.ProtoReflect.Descriptor instead. func (*CreateWorkspaceRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{169} + return file_openshell_proto_rawDescGZIP(), []int{172} } func (x *CreateWorkspaceRequest) GetName() string { @@ -12299,7 +12575,7 @@ type CreateWorkspaceResponse struct { func (x *CreateWorkspaceResponse) Reset() { *x = CreateWorkspaceResponse{} - mi := &file_openshell_proto_msgTypes[170] + mi := &file_openshell_proto_msgTypes[173] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12311,7 +12587,7 @@ func (x *CreateWorkspaceResponse) String() string { func (*CreateWorkspaceResponse) ProtoMessage() {} func (x *CreateWorkspaceResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[170] + mi := &file_openshell_proto_msgTypes[173] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12324,7 +12600,7 @@ func (x *CreateWorkspaceResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use CreateWorkspaceResponse.ProtoReflect.Descriptor instead. func (*CreateWorkspaceResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{170} + return file_openshell_proto_rawDescGZIP(), []int{173} } func (x *CreateWorkspaceResponse) GetWorkspace() *datamodelv1.Workspace { @@ -12345,7 +12621,7 @@ type GetWorkspaceRequest struct { func (x *GetWorkspaceRequest) Reset() { *x = GetWorkspaceRequest{} - mi := &file_openshell_proto_msgTypes[171] + mi := &file_openshell_proto_msgTypes[174] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12357,7 +12633,7 @@ func (x *GetWorkspaceRequest) String() string { func (*GetWorkspaceRequest) ProtoMessage() {} func (x *GetWorkspaceRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[171] + mi := &file_openshell_proto_msgTypes[174] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12370,7 +12646,7 @@ func (x *GetWorkspaceRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetWorkspaceRequest.ProtoReflect.Descriptor instead. func (*GetWorkspaceRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{171} + return file_openshell_proto_rawDescGZIP(), []int{174} } func (x *GetWorkspaceRequest) GetName() string { @@ -12390,7 +12666,7 @@ type GetWorkspaceResponse struct { func (x *GetWorkspaceResponse) Reset() { *x = GetWorkspaceResponse{} - mi := &file_openshell_proto_msgTypes[172] + mi := &file_openshell_proto_msgTypes[175] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12402,7 +12678,7 @@ func (x *GetWorkspaceResponse) String() string { func (*GetWorkspaceResponse) ProtoMessage() {} func (x *GetWorkspaceResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[172] + mi := &file_openshell_proto_msgTypes[175] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12415,7 +12691,7 @@ func (x *GetWorkspaceResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetWorkspaceResponse.ProtoReflect.Descriptor instead. func (*GetWorkspaceResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{172} + return file_openshell_proto_rawDescGZIP(), []int{175} } func (x *GetWorkspaceResponse) GetWorkspace() *datamodelv1.Workspace { @@ -12438,7 +12714,7 @@ type ListWorkspacesRequest struct { func (x *ListWorkspacesRequest) Reset() { *x = ListWorkspacesRequest{} - mi := &file_openshell_proto_msgTypes[173] + mi := &file_openshell_proto_msgTypes[176] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12450,7 +12726,7 @@ func (x *ListWorkspacesRequest) String() string { func (*ListWorkspacesRequest) ProtoMessage() {} func (x *ListWorkspacesRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[173] + mi := &file_openshell_proto_msgTypes[176] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12463,7 +12739,7 @@ func (x *ListWorkspacesRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListWorkspacesRequest.ProtoReflect.Descriptor instead. func (*ListWorkspacesRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{173} + return file_openshell_proto_rawDescGZIP(), []int{176} } func (x *ListWorkspacesRequest) GetLimit() uint32 { @@ -12497,7 +12773,7 @@ type ListWorkspacesResponse struct { func (x *ListWorkspacesResponse) Reset() { *x = ListWorkspacesResponse{} - mi := &file_openshell_proto_msgTypes[174] + mi := &file_openshell_proto_msgTypes[177] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12509,7 +12785,7 @@ func (x *ListWorkspacesResponse) String() string { func (*ListWorkspacesResponse) ProtoMessage() {} func (x *ListWorkspacesResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[174] + mi := &file_openshell_proto_msgTypes[177] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12522,7 +12798,7 @@ func (x *ListWorkspacesResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListWorkspacesResponse.ProtoReflect.Descriptor instead. func (*ListWorkspacesResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{174} + return file_openshell_proto_rawDescGZIP(), []int{177} } func (x *ListWorkspacesResponse) GetWorkspaces() []*datamodelv1.Workspace { @@ -12543,7 +12819,7 @@ type DeleteWorkspaceRequest struct { func (x *DeleteWorkspaceRequest) Reset() { *x = DeleteWorkspaceRequest{} - mi := &file_openshell_proto_msgTypes[175] + mi := &file_openshell_proto_msgTypes[178] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12555,7 +12831,7 @@ func (x *DeleteWorkspaceRequest) String() string { func (*DeleteWorkspaceRequest) ProtoMessage() {} func (x *DeleteWorkspaceRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[175] + mi := &file_openshell_proto_msgTypes[178] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12568,7 +12844,7 @@ func (x *DeleteWorkspaceRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteWorkspaceRequest.ProtoReflect.Descriptor instead. func (*DeleteWorkspaceRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{175} + return file_openshell_proto_rawDescGZIP(), []int{178} } func (x *DeleteWorkspaceRequest) GetName() string { @@ -12588,7 +12864,7 @@ type DeleteWorkspaceResponse struct { func (x *DeleteWorkspaceResponse) Reset() { *x = DeleteWorkspaceResponse{} - mi := &file_openshell_proto_msgTypes[176] + mi := &file_openshell_proto_msgTypes[179] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12600,7 +12876,7 @@ func (x *DeleteWorkspaceResponse) String() string { func (*DeleteWorkspaceResponse) ProtoMessage() {} func (x *DeleteWorkspaceResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[176] + mi := &file_openshell_proto_msgTypes[179] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12613,7 +12889,7 @@ func (x *DeleteWorkspaceResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteWorkspaceResponse.ProtoReflect.Descriptor instead. func (*DeleteWorkspaceResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{176} + return file_openshell_proto_rawDescGZIP(), []int{179} } func (x *DeleteWorkspaceResponse) GetDeleted() bool { @@ -12637,7 +12913,7 @@ type WorkspaceMember struct { func (x *WorkspaceMember) Reset() { *x = WorkspaceMember{} - mi := &file_openshell_proto_msgTypes[177] + mi := &file_openshell_proto_msgTypes[180] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12649,7 +12925,7 @@ func (x *WorkspaceMember) String() string { func (*WorkspaceMember) ProtoMessage() {} func (x *WorkspaceMember) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[177] + mi := &file_openshell_proto_msgTypes[180] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12662,7 +12938,7 @@ func (x *WorkspaceMember) ProtoReflect() protoreflect.Message { // Deprecated: Use WorkspaceMember.ProtoReflect.Descriptor instead. func (*WorkspaceMember) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{177} + return file_openshell_proto_rawDescGZIP(), []int{180} } func (x *WorkspaceMember) GetMetadata() *datamodelv1.ObjectMeta { @@ -12701,7 +12977,7 @@ type AddWorkspaceMemberRequest struct { func (x *AddWorkspaceMemberRequest) Reset() { *x = AddWorkspaceMemberRequest{} - mi := &file_openshell_proto_msgTypes[178] + mi := &file_openshell_proto_msgTypes[181] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12713,7 +12989,7 @@ func (x *AddWorkspaceMemberRequest) String() string { func (*AddWorkspaceMemberRequest) ProtoMessage() {} func (x *AddWorkspaceMemberRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[178] + mi := &file_openshell_proto_msgTypes[181] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12726,7 +13002,7 @@ func (x *AddWorkspaceMemberRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use AddWorkspaceMemberRequest.ProtoReflect.Descriptor instead. func (*AddWorkspaceMemberRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{178} + return file_openshell_proto_rawDescGZIP(), []int{181} } func (x *AddWorkspaceMemberRequest) GetWorkspace() string { @@ -12760,7 +13036,7 @@ type AddWorkspaceMemberResponse struct { func (x *AddWorkspaceMemberResponse) Reset() { *x = AddWorkspaceMemberResponse{} - mi := &file_openshell_proto_msgTypes[179] + mi := &file_openshell_proto_msgTypes[182] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12772,7 +13048,7 @@ func (x *AddWorkspaceMemberResponse) String() string { func (*AddWorkspaceMemberResponse) ProtoMessage() {} func (x *AddWorkspaceMemberResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[179] + mi := &file_openshell_proto_msgTypes[182] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12785,7 +13061,7 @@ func (x *AddWorkspaceMemberResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use AddWorkspaceMemberResponse.ProtoReflect.Descriptor instead. func (*AddWorkspaceMemberResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{179} + return file_openshell_proto_rawDescGZIP(), []int{182} } func (x *AddWorkspaceMemberResponse) GetMember() *WorkspaceMember { @@ -12808,7 +13084,7 @@ type RemoveWorkspaceMemberRequest struct { func (x *RemoveWorkspaceMemberRequest) Reset() { *x = RemoveWorkspaceMemberRequest{} - mi := &file_openshell_proto_msgTypes[180] + mi := &file_openshell_proto_msgTypes[183] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12820,7 +13096,7 @@ func (x *RemoveWorkspaceMemberRequest) String() string { func (*RemoveWorkspaceMemberRequest) ProtoMessage() {} func (x *RemoveWorkspaceMemberRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[180] + mi := &file_openshell_proto_msgTypes[183] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12833,7 +13109,7 @@ func (x *RemoveWorkspaceMemberRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use RemoveWorkspaceMemberRequest.ProtoReflect.Descriptor instead. func (*RemoveWorkspaceMemberRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{180} + return file_openshell_proto_rawDescGZIP(), []int{183} } func (x *RemoveWorkspaceMemberRequest) GetWorkspace() string { @@ -12860,7 +13136,7 @@ type RemoveWorkspaceMemberResponse struct { func (x *RemoveWorkspaceMemberResponse) Reset() { *x = RemoveWorkspaceMemberResponse{} - mi := &file_openshell_proto_msgTypes[181] + mi := &file_openshell_proto_msgTypes[184] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12872,7 +13148,7 @@ func (x *RemoveWorkspaceMemberResponse) String() string { func (*RemoveWorkspaceMemberResponse) ProtoMessage() {} func (x *RemoveWorkspaceMemberResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[181] + mi := &file_openshell_proto_msgTypes[184] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12885,7 +13161,7 @@ func (x *RemoveWorkspaceMemberResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use RemoveWorkspaceMemberResponse.ProtoReflect.Descriptor instead. func (*RemoveWorkspaceMemberResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{181} + return file_openshell_proto_rawDescGZIP(), []int{184} } func (x *RemoveWorkspaceMemberResponse) GetRemoved() bool { @@ -12908,7 +13184,7 @@ type ListWorkspaceMembersRequest struct { func (x *ListWorkspaceMembersRequest) Reset() { *x = ListWorkspaceMembersRequest{} - mi := &file_openshell_proto_msgTypes[182] + mi := &file_openshell_proto_msgTypes[185] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12920,7 +13196,7 @@ func (x *ListWorkspaceMembersRequest) String() string { func (*ListWorkspaceMembersRequest) ProtoMessage() {} func (x *ListWorkspaceMembersRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[182] + mi := &file_openshell_proto_msgTypes[185] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12933,7 +13209,7 @@ func (x *ListWorkspaceMembersRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListWorkspaceMembersRequest.ProtoReflect.Descriptor instead. func (*ListWorkspaceMembersRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{182} + return file_openshell_proto_rawDescGZIP(), []int{185} } func (x *ListWorkspaceMembersRequest) GetWorkspace() string { @@ -12967,7 +13243,7 @@ type ListWorkspaceMembersResponse struct { func (x *ListWorkspaceMembersResponse) Reset() { *x = ListWorkspaceMembersResponse{} - mi := &file_openshell_proto_msgTypes[183] + mi := &file_openshell_proto_msgTypes[186] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12979,7 +13255,7 @@ func (x *ListWorkspaceMembersResponse) String() string { func (*ListWorkspaceMembersResponse) ProtoMessage() {} func (x *ListWorkspaceMembersResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[183] + mi := &file_openshell_proto_msgTypes[186] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12992,7 +13268,7 @@ func (x *ListWorkspaceMembersResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListWorkspaceMembersResponse.ProtoReflect.Descriptor instead. func (*ListWorkspaceMembersResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{183} + return file_openshell_proto_rawDescGZIP(), []int{186} } func (x *ListWorkspaceMembersResponse) GetMembers() []*WorkspaceMember { @@ -13020,7 +13296,7 @@ type ExtensionServiceCredential struct { func (x *ExtensionServiceCredential) Reset() { *x = ExtensionServiceCredential{} - mi := &file_openshell_proto_msgTypes[184] + mi := &file_openshell_proto_msgTypes[187] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13032,7 +13308,7 @@ func (x *ExtensionServiceCredential) String() string { func (*ExtensionServiceCredential) ProtoMessage() {} func (x *ExtensionServiceCredential) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[184] + mi := &file_openshell_proto_msgTypes[187] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13045,7 +13321,7 @@ func (x *ExtensionServiceCredential) ProtoReflect() protoreflect.Message { // Deprecated: Use ExtensionServiceCredential.ProtoReflect.Descriptor instead. func (*ExtensionServiceCredential) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{184} + return file_openshell_proto_rawDescGZIP(), []int{187} } func (x *ExtensionServiceCredential) GetServiceName() string { @@ -13425,7 +13701,13 @@ const file_openshell_proto_rawDesc = "" + "\x04port\x18\x02 \x01(\rR\x04port\x12\x12\n" + "\x04path\x18\x03 \x01(\tR\x04path\x12\x1a\n" + "\baudience\x18\x04 \x01(\tR\baudience\x12\x16\n" + - "\x06scopes\x18\x05 \x03(\tR\x06scopes\"\xf0\x02\n" + + "\x06scopes\x18\x05 \x03(\tR\x06scopes\"\x90\x01\n" + + "(ProviderCredentialTokenGrantSubjectToken\x12\x16\n" + + "\x06source\x18\x01 \x01(\tR\x06source\x12\x1e\n" + + "\n" + + "credential\x18\x02 \x01(\tR\n" + + "credential\x12,\n" + + "\x12subject_token_type\x18\x03 \x01(\tR\x10subjectTokenType\"\xce\x04\n" + "\x1cProviderCredentialTokenGrant\x12%\n" + "\x0etoken_endpoint\x18\x01 \x01(\tR\rtokenEndpoint\x12\x1a\n" + "\baudience\x18\x02 \x01(\tR\baudience\x12*\n" + @@ -13433,7 +13715,12 @@ const file_openshell_proto_rawDesc = "" + "\x06scopes\x18\x03 \x03(\tR\x06scopes\x12*\n" + "\x11cache_ttl_seconds\x18\x04 \x01(\x03R\x0fcacheTtlSeconds\x12i\n" + "\x12audience_overrides\x18\x05 \x03(\v2:.openshell.v1.ProviderCredentialTokenGrantAudienceOverrideR\x11audienceOverrides\x122\n" + - "\x15client_assertion_type\x18\a \x01(\tR\x13clientAssertionType\"\x9e\x03\n" + + "\x15client_assertion_type\x18\a \x01(\tR\x13clientAssertionType\x12M\n" + + "\n" + + "grant_type\x18\b \x01(\x0e2..openshell.v1.ProviderCredentialTokenGrantTypeR\tgrantType\x12[\n" + + "\rsubject_token\x18\t \x01(\v26.openshell.v1.ProviderCredentialTokenGrantSubjectTokenR\fsubjectToken\x120\n" + + "\x14requested_token_type\x18\n" + + " \x01(\tR\x12requestedTokenType\"\x9e\x03\n" + "\x19ProviderProfileCredential\x12\x12\n" + "\x04name\x18\x01 \x01(\tR\x04name\x12 \n" + "\vdescription\x18\x02 \x01(\tR\vdescription\x12\x19\n" + @@ -13624,7 +13911,19 @@ const file_openshell_proto_rawDesc = "" + "\x05value\x18\x02 \x01(\v2'.openshell.v1.ProviderProfileCredentialR\x05value:\x028\x01\x1ar\n" + "\x1dStaticCredentialBindingsEntry\x12\x10\n" + "\x03key\x18\x01 \x01(\tR\x03key\x12;\n" + - "\x05value\x18\x02 \x01(\v2%.openshell.v1.StaticCredentialBindingR\x05value:\x028\x01\"\xce\x04\n" + + "\x05value\x18\x02 \x01(\v2%.openshell.v1.StaticCredentialBindingR\x05value:\x028\x01\"\xbd\x01\n" + + "#ExchangeProviderSubjectTokenRequest\x12\x1d\n" + + "\n" + + "sandbox_id\x18\x01 \x01(\tR\tsandboxId\x12\x1a\n" + + "\bprovider\x18\x02 \x01(\tR\bprovider\x12%\n" + + "\x0ecredential_key\x18\x03 \x01(\tR\rcredentialKey\x124\n" + + "\x13supervisor_jwt_svid\x18\x04 \x01(\tB\x04\x88\xb5\x18\x01R\x11supervisorJwtSvid\"\x8d\x01\n" + + "$ExchangeProviderSubjectTokenResponse\x12'\n" + + "\faccess_token\x18\x01 \x01(\tB\x04\x88\xb5\x18\x01R\vaccessToken\x12\x1d\n" + + "\n" + + "expires_in\x18\x02 \x01(\x03R\texpiresIn\x12\x1d\n" + + "\n" + + "token_type\x18\x03 \x01(\tR\ttokenType\"\xce\x04\n" + "\x13UpdateConfigRequest\x12\x12\n" + "\x04name\x18\x01 \x01(\tR\x04name\x12;\n" + "\x06policy\x18\x02 \x01(\v2#.openshell.sandbox.v1.SandboxPolicyR\x06policy\x12\x1f\n" + @@ -14076,7 +14375,11 @@ const file_openshell_proto_rawDesc = "" + "\x15SANDBOX_PHASE_UNKNOWN\x10\x05\x12\x1a\n" + "\x16SANDBOX_PHASE_STOPPING\x10\x06\x12\x19\n" + "\x15SANDBOX_PHASE_STOPPED\x10\a\x12\x1a\n" + - "\x16SANDBOX_PHASE_STARTING\x10\b*\xc3\x03\n" + + "\x16SANDBOX_PHASE_STARTING\x10\b*\xce\x01\n" + + " ProviderCredentialTokenGrantType\x124\n" + + "0PROVIDER_CREDENTIAL_TOKEN_GRANT_TYPE_UNSPECIFIED\x10\x00\x12;\n" + + "7PROVIDER_CREDENTIAL_TOKEN_GRANT_TYPE_CLIENT_CREDENTIALS\x10\x01\x127\n" + + "3PROVIDER_CREDENTIAL_TOKEN_GRANT_TYPE_TOKEN_EXCHANGE\x10\x02*\xc3\x03\n" + "!ProviderCredentialRefreshStrategy\x124\n" + "0PROVIDER_CREDENTIAL_REFRESH_STRATEGY_UNSPECIFIED\x10\x00\x12/\n" + "+PROVIDER_CREDENTIAL_REFRESH_STRATEGY_STATIC\x10\x01\x121\n" + @@ -14108,7 +14411,7 @@ const file_openshell_proto_rawDesc = "" + "\rWorkspaceRole\x12\x1e\n" + "\x1aWORKSPACE_ROLE_UNSPECIFIED\x10\x00\x12\x17\n" + "\x13WORKSPACE_ROLE_USER\x10\x01\x12\x18\n" + - "\x14WORKSPACE_ROLE_ADMIN\x10\x022\x94D\n" + + "\x14WORKSPACE_ROLE_ADMIN\x10\x022\xabE\n" + "\tOpenShell\x12Z\n" + "\x06Health\x12\x1b.openshell.v1.HealthRequest\x1a\x1c.openshell.v1.HealthResponse\"\x15\x82\xb5\x18\x11\n" + "\x0funauthenticated\x12i\n" + @@ -14198,6 +14501,8 @@ const file_openshell_proto_rawDesc = "" + "\x12ReportPolicyStatus\x12'.openshell.v1.ReportPolicyStatusRequest\x1a(.openshell.v1.ReportPolicyStatusResponse\"\r\x82\xb5\x18\t\n" + "\asandbox\x12\x97\x01\n" + "\x1dGetSandboxProviderEnvironment\x122.openshell.v1.GetSandboxProviderEnvironmentRequest\x1a3.openshell.v1.GetSandboxProviderEnvironmentResponse\"\r\x82\xb5\x18\t\n" + + "\asandbox\x12\x94\x01\n" + + "\x1cExchangeProviderSubjectToken\x121.openshell.v1.ExchangeProviderSubjectTokenRequest\x1a2.openshell.v1.ExchangeProviderSubjectTokenResponse\"\r\x82\xb5\x18\t\n" + "\asandbox\x12}\n" + "\x0eGetSandboxLogs\x12#.openshell.v1.GetSandboxLogsRequest\x1a$.openshell.v1.GetSandboxLogsResponse\" \x82\xb5\x18\x1c\n" + "\x06bearer\x12\x04user\"\fsandbox:read\x12o\n" + @@ -14258,529 +14563,537 @@ func file_openshell_proto_rawDescGZIP() []byte { return file_openshell_proto_rawDescData } -var file_openshell_proto_enumTypes = make([]protoimpl.EnumInfo, 6) -var file_openshell_proto_msgTypes = make([]protoimpl.MessageInfo, 209) +var file_openshell_proto_enumTypes = make([]protoimpl.EnumInfo, 7) +var file_openshell_proto_msgTypes = make([]protoimpl.MessageInfo, 212) var file_openshell_proto_goTypes = []any{ (SandboxPhase)(0), // 0: openshell.v1.SandboxPhase - (ProviderCredentialRefreshStrategy)(0), // 1: openshell.v1.ProviderCredentialRefreshStrategy - (ProviderProfileCategory)(0), // 2: openshell.v1.ProviderProfileCategory - (PolicyStatus)(0), // 3: openshell.v1.PolicyStatus - (ServiceStatus)(0), // 4: openshell.v1.ServiceStatus - (WorkspaceRole)(0), // 5: openshell.v1.WorkspaceRole - (*IssueSandboxTokenRequest)(nil), // 6: openshell.v1.IssueSandboxTokenRequest - (*IssueSandboxTokenResponse)(nil), // 7: openshell.v1.IssueSandboxTokenResponse - (*RefreshSandboxTokenRequest)(nil), // 8: openshell.v1.RefreshSandboxTokenRequest - (*RefreshSandboxTokenResponse)(nil), // 9: openshell.v1.RefreshSandboxTokenResponse - (*HealthRequest)(nil), // 10: openshell.v1.HealthRequest - (*HealthResponse)(nil), // 11: openshell.v1.HealthResponse - (*GetCurrentUserRequest)(nil), // 12: openshell.v1.GetCurrentUserRequest - (*GetCurrentUserResponse)(nil), // 13: openshell.v1.GetCurrentUserResponse - (*GetGatewayInfoRequest)(nil), // 14: openshell.v1.GetGatewayInfoRequest - (*GetGatewayInfoResponse)(nil), // 15: openshell.v1.GetGatewayInfoResponse - (*ComputeDriverInfo)(nil), // 16: openshell.v1.ComputeDriverInfo - (*ComputeDriverCapabilities)(nil), // 17: openshell.v1.ComputeDriverCapabilities - (*Sandbox)(nil), // 18: openshell.v1.Sandbox - (*SandboxSpec)(nil), // 19: openshell.v1.SandboxSpec - (*ResourceRequirements)(nil), // 20: openshell.v1.ResourceRequirements - (*GpuResourceRequirements)(nil), // 21: openshell.v1.GpuResourceRequirements - (*SandboxTemplate)(nil), // 22: openshell.v1.SandboxTemplate - (*SandboxStatus)(nil), // 23: openshell.v1.SandboxStatus - (*SandboxCondition)(nil), // 24: openshell.v1.SandboxCondition - (*PlatformEvent)(nil), // 25: openshell.v1.PlatformEvent - (*CreateSandboxRequest)(nil), // 26: openshell.v1.CreateSandboxRequest - (*GetSandboxRequest)(nil), // 27: openshell.v1.GetSandboxRequest - (*ListSandboxesRequest)(nil), // 28: openshell.v1.ListSandboxesRequest - (*ListSandboxProvidersRequest)(nil), // 29: openshell.v1.ListSandboxProvidersRequest - (*AttachSandboxProviderRequest)(nil), // 30: openshell.v1.AttachSandboxProviderRequest - (*DetachSandboxProviderRequest)(nil), // 31: openshell.v1.DetachSandboxProviderRequest - (*DeleteSandboxRequest)(nil), // 32: openshell.v1.DeleteSandboxRequest - (*StopSandboxRequest)(nil), // 33: openshell.v1.StopSandboxRequest - (*StartSandboxRequest)(nil), // 34: openshell.v1.StartSandboxRequest - (*SandboxResponse)(nil), // 35: openshell.v1.SandboxResponse - (*ListSandboxesResponse)(nil), // 36: openshell.v1.ListSandboxesResponse - (*ListSandboxProvidersResponse)(nil), // 37: openshell.v1.ListSandboxProvidersResponse - (*AttachSandboxProviderResponse)(nil), // 38: openshell.v1.AttachSandboxProviderResponse - (*DetachSandboxProviderResponse)(nil), // 39: openshell.v1.DetachSandboxProviderResponse - (*DeleteSandboxResponse)(nil), // 40: openshell.v1.DeleteSandboxResponse - (*CreateSshSessionRequest)(nil), // 41: openshell.v1.CreateSshSessionRequest - (*CreateSshSessionResponse)(nil), // 42: openshell.v1.CreateSshSessionResponse - (*ExposeServiceRequest)(nil), // 43: openshell.v1.ExposeServiceRequest - (*GetServiceRequest)(nil), // 44: openshell.v1.GetServiceRequest - (*ListServicesRequest)(nil), // 45: openshell.v1.ListServicesRequest - (*ListServicesResponse)(nil), // 46: openshell.v1.ListServicesResponse - (*DeleteServiceRequest)(nil), // 47: openshell.v1.DeleteServiceRequest - (*DeleteServiceResponse)(nil), // 48: openshell.v1.DeleteServiceResponse - (*ServiceEndpoint)(nil), // 49: openshell.v1.ServiceEndpoint - (*ServiceEndpointResponse)(nil), // 50: openshell.v1.ServiceEndpointResponse - (*RevokeSshSessionRequest)(nil), // 51: openshell.v1.RevokeSshSessionRequest - (*RevokeSshSessionResponse)(nil), // 52: openshell.v1.RevokeSshSessionResponse - (*ExecSandboxRequest)(nil), // 53: openshell.v1.ExecSandboxRequest - (*ExecSandboxStdout)(nil), // 54: openshell.v1.ExecSandboxStdout - (*ExecSandboxStderr)(nil), // 55: openshell.v1.ExecSandboxStderr - (*ExecSandboxExit)(nil), // 56: openshell.v1.ExecSandboxExit - (*ExecSandboxEvent)(nil), // 57: openshell.v1.ExecSandboxEvent - (*TcpForwardInit)(nil), // 58: openshell.v1.TcpForwardInit - (*TcpForwardFrame)(nil), // 59: openshell.v1.TcpForwardFrame - (*ExecSandboxInput)(nil), // 60: openshell.v1.ExecSandboxInput - (*ExecSandboxWindowResize)(nil), // 61: openshell.v1.ExecSandboxWindowResize - (*SshSession)(nil), // 62: openshell.v1.SshSession - (*WatchSandboxRequest)(nil), // 63: openshell.v1.WatchSandboxRequest - (*SandboxStreamEvent)(nil), // 64: openshell.v1.SandboxStreamEvent - (*SandboxLogLine)(nil), // 65: openshell.v1.SandboxLogLine - (*SandboxStreamWarning)(nil), // 66: openshell.v1.SandboxStreamWarning - (*CreateProviderRequest)(nil), // 67: openshell.v1.CreateProviderRequest - (*GetProviderRequest)(nil), // 68: openshell.v1.GetProviderRequest - (*ListProvidersRequest)(nil), // 69: openshell.v1.ListProvidersRequest - (*UpdateProviderRequest)(nil), // 70: openshell.v1.UpdateProviderRequest - (*DeleteProviderRequest)(nil), // 71: openshell.v1.DeleteProviderRequest - (*ProviderResponse)(nil), // 72: openshell.v1.ProviderResponse - (*ListProvidersResponse)(nil), // 73: openshell.v1.ListProvidersResponse - (*ListProviderProfilesRequest)(nil), // 74: openshell.v1.ListProviderProfilesRequest - (*GetProviderProfileRequest)(nil), // 75: openshell.v1.GetProviderProfileRequest - (*ProviderProfileImportItem)(nil), // 76: openshell.v1.ProviderProfileImportItem - (*ProviderProfileDiagnostic)(nil), // 77: openshell.v1.ProviderProfileDiagnostic - (*ProviderCredentialTokenGrantAudienceOverride)(nil), // 78: openshell.v1.ProviderCredentialTokenGrantAudienceOverride - (*ProviderCredentialTokenGrant)(nil), // 79: openshell.v1.ProviderCredentialTokenGrant - (*ProviderProfileCredential)(nil), // 80: openshell.v1.ProviderProfileCredential - (*ProviderCredentialRefreshMaterial)(nil), // 81: openshell.v1.ProviderCredentialRefreshMaterial - (*ProviderCredentialRefreshOutput)(nil), // 82: openshell.v1.ProviderCredentialRefreshOutput - (*ProviderCredentialRefresh)(nil), // 83: openshell.v1.ProviderCredentialRefresh - (*ProviderCredentialRefreshStatus)(nil), // 84: openshell.v1.ProviderCredentialRefreshStatus - (*ProviderProfileDiscovery)(nil), // 85: openshell.v1.ProviderProfileDiscovery - (*StoredProviderCredentialRefreshState)(nil), // 86: openshell.v1.StoredProviderCredentialRefreshState - (*GetProviderRefreshStatusRequest)(nil), // 87: openshell.v1.GetProviderRefreshStatusRequest - (*GetProviderRefreshStatusResponse)(nil), // 88: openshell.v1.GetProviderRefreshStatusResponse - (*ConfigureProviderRefreshRequest)(nil), // 89: openshell.v1.ConfigureProviderRefreshRequest - (*ConfigureProviderRefreshResponse)(nil), // 90: openshell.v1.ConfigureProviderRefreshResponse - (*RotateProviderCredentialRequest)(nil), // 91: openshell.v1.RotateProviderCredentialRequest - (*RotateProviderCredentialResponse)(nil), // 92: openshell.v1.RotateProviderCredentialResponse - (*DeleteProviderRefreshRequest)(nil), // 93: openshell.v1.DeleteProviderRefreshRequest - (*DeleteProviderRefreshResponse)(nil), // 94: openshell.v1.DeleteProviderRefreshResponse - (*ProviderProfile)(nil), // 95: openshell.v1.ProviderProfile - (*StoredProviderProfile)(nil), // 96: openshell.v1.StoredProviderProfile - (*ProviderProfileResponse)(nil), // 97: openshell.v1.ProviderProfileResponse - (*ListProviderProfilesResponse)(nil), // 98: openshell.v1.ListProviderProfilesResponse - (*ImportProviderProfilesRequest)(nil), // 99: openshell.v1.ImportProviderProfilesRequest - (*ImportProviderProfilesResponse)(nil), // 100: openshell.v1.ImportProviderProfilesResponse - (*UpdateProviderProfilesRequest)(nil), // 101: openshell.v1.UpdateProviderProfilesRequest - (*UpdateProviderProfilesResponse)(nil), // 102: openshell.v1.UpdateProviderProfilesResponse - (*LintProviderProfilesRequest)(nil), // 103: openshell.v1.LintProviderProfilesRequest - (*LintProviderProfilesResponse)(nil), // 104: openshell.v1.LintProviderProfilesResponse - (*DeleteProviderResponse)(nil), // 105: openshell.v1.DeleteProviderResponse - (*DeleteProviderProfileRequest)(nil), // 106: openshell.v1.DeleteProviderProfileRequest - (*DeleteProviderProfileResponse)(nil), // 107: openshell.v1.DeleteProviderProfileResponse - (*GetSandboxProviderEnvironmentRequest)(nil), // 108: openshell.v1.GetSandboxProviderEnvironmentRequest - (*StaticCredentialEndpointBinding)(nil), // 109: openshell.v1.StaticCredentialEndpointBinding - (*StaticCredentialBinding)(nil), // 110: openshell.v1.StaticCredentialBinding - (*GetSandboxProviderEnvironmentResponse)(nil), // 111: openshell.v1.GetSandboxProviderEnvironmentResponse - (*UpdateConfigRequest)(nil), // 112: openshell.v1.UpdateConfigRequest - (*PolicyMergeOperation)(nil), // 113: openshell.v1.PolicyMergeOperation - (*AddNetworkRule)(nil), // 114: openshell.v1.AddNetworkRule - (*RemoveNetworkEndpoint)(nil), // 115: openshell.v1.RemoveNetworkEndpoint - (*RemoveNetworkRule)(nil), // 116: openshell.v1.RemoveNetworkRule - (*AddDenyRules)(nil), // 117: openshell.v1.AddDenyRules - (*AddAllowRules)(nil), // 118: openshell.v1.AddAllowRules - (*RemoveNetworkBinary)(nil), // 119: openshell.v1.RemoveNetworkBinary - (*UpdateConfigResponse)(nil), // 120: openshell.v1.UpdateConfigResponse - (*GetSandboxPolicyStatusRequest)(nil), // 121: openshell.v1.GetSandboxPolicyStatusRequest - (*GetSandboxPolicyStatusResponse)(nil), // 122: openshell.v1.GetSandboxPolicyStatusResponse - (*ListSandboxPoliciesRequest)(nil), // 123: openshell.v1.ListSandboxPoliciesRequest - (*ListSandboxPoliciesResponse)(nil), // 124: openshell.v1.ListSandboxPoliciesResponse - (*ReportPolicyStatusRequest)(nil), // 125: openshell.v1.ReportPolicyStatusRequest - (*ReportPolicyStatusResponse)(nil), // 126: openshell.v1.ReportPolicyStatusResponse - (*SandboxPolicyRevision)(nil), // 127: openshell.v1.SandboxPolicyRevision - (*GetSandboxLogsRequest)(nil), // 128: openshell.v1.GetSandboxLogsRequest - (*PushSandboxLogsRequest)(nil), // 129: openshell.v1.PushSandboxLogsRequest - (*PushSandboxLogsResponse)(nil), // 130: openshell.v1.PushSandboxLogsResponse - (*GetSandboxLogsResponse)(nil), // 131: openshell.v1.GetSandboxLogsResponse - (*SupervisorMessage)(nil), // 132: openshell.v1.SupervisorMessage - (*GatewayMessage)(nil), // 133: openshell.v1.GatewayMessage - (*SupervisorHello)(nil), // 134: openshell.v1.SupervisorHello - (*SessionAccepted)(nil), // 135: openshell.v1.SessionAccepted - (*SessionRejected)(nil), // 136: openshell.v1.SessionRejected - (*SupervisorHeartbeat)(nil), // 137: openshell.v1.SupervisorHeartbeat - (*GatewayHeartbeat)(nil), // 138: openshell.v1.GatewayHeartbeat - (*RelayOpen)(nil), // 139: openshell.v1.RelayOpen - (*SshRelayTarget)(nil), // 140: openshell.v1.SshRelayTarget - (*TcpRelayTarget)(nil), // 141: openshell.v1.TcpRelayTarget - (*RelayInit)(nil), // 142: openshell.v1.RelayInit - (*RelayFrame)(nil), // 143: openshell.v1.RelayFrame - (*RelayOpenResult)(nil), // 144: openshell.v1.RelayOpenResult - (*RelayClose)(nil), // 145: openshell.v1.RelayClose - (*L7RequestSample)(nil), // 146: openshell.v1.L7RequestSample - (*DenialSummary)(nil), // 147: openshell.v1.DenialSummary - (*DenialGroupCount)(nil), // 148: openshell.v1.DenialGroupCount - (*NetworkActivitySummary)(nil), // 149: openshell.v1.NetworkActivitySummary - (*PolicyChunk)(nil), // 150: openshell.v1.PolicyChunk - (*DraftPolicyUpdate)(nil), // 151: openshell.v1.DraftPolicyUpdate - (*SubmitPolicyAnalysisRequest)(nil), // 152: openshell.v1.SubmitPolicyAnalysisRequest - (*SubmitPolicyAnalysisResponse)(nil), // 153: openshell.v1.SubmitPolicyAnalysisResponse - (*GetDraftPolicyRequest)(nil), // 154: openshell.v1.GetDraftPolicyRequest - (*GetDraftPolicyResponse)(nil), // 155: openshell.v1.GetDraftPolicyResponse - (*ApproveDraftChunkRequest)(nil), // 156: openshell.v1.ApproveDraftChunkRequest - (*ApproveDraftChunkResponse)(nil), // 157: openshell.v1.ApproveDraftChunkResponse - (*RejectDraftChunkRequest)(nil), // 158: openshell.v1.RejectDraftChunkRequest - (*RejectDraftChunkResponse)(nil), // 159: openshell.v1.RejectDraftChunkResponse - (*ApproveAllDraftChunksRequest)(nil), // 160: openshell.v1.ApproveAllDraftChunksRequest - (*ApproveAllDraftChunksResponse)(nil), // 161: openshell.v1.ApproveAllDraftChunksResponse - (*EditDraftChunkRequest)(nil), // 162: openshell.v1.EditDraftChunkRequest - (*EditDraftChunkResponse)(nil), // 163: openshell.v1.EditDraftChunkResponse - (*UndoDraftChunkRequest)(nil), // 164: openshell.v1.UndoDraftChunkRequest - (*UndoDraftChunkResponse)(nil), // 165: openshell.v1.UndoDraftChunkResponse - (*ClearDraftChunksRequest)(nil), // 166: openshell.v1.ClearDraftChunksRequest - (*ClearDraftChunksResponse)(nil), // 167: openshell.v1.ClearDraftChunksResponse - (*GetDraftHistoryRequest)(nil), // 168: openshell.v1.GetDraftHistoryRequest - (*DraftHistoryEntry)(nil), // 169: openshell.v1.DraftHistoryEntry - (*GetDraftHistoryResponse)(nil), // 170: openshell.v1.GetDraftHistoryResponse - (*PolicyRevisionPayload)(nil), // 171: openshell.v1.PolicyRevisionPayload - (*DraftChunkPayload)(nil), // 172: openshell.v1.DraftChunkPayload - (*StoredPolicyRevision)(nil), // 173: openshell.v1.StoredPolicyRevision - (*StoredDraftChunk)(nil), // 174: openshell.v1.StoredDraftChunk - (*CreateWorkspaceRequest)(nil), // 175: openshell.v1.CreateWorkspaceRequest - (*CreateWorkspaceResponse)(nil), // 176: openshell.v1.CreateWorkspaceResponse - (*GetWorkspaceRequest)(nil), // 177: openshell.v1.GetWorkspaceRequest - (*GetWorkspaceResponse)(nil), // 178: openshell.v1.GetWorkspaceResponse - (*ListWorkspacesRequest)(nil), // 179: openshell.v1.ListWorkspacesRequest - (*ListWorkspacesResponse)(nil), // 180: openshell.v1.ListWorkspacesResponse - (*DeleteWorkspaceRequest)(nil), // 181: openshell.v1.DeleteWorkspaceRequest - (*DeleteWorkspaceResponse)(nil), // 182: openshell.v1.DeleteWorkspaceResponse - (*WorkspaceMember)(nil), // 183: openshell.v1.WorkspaceMember - (*AddWorkspaceMemberRequest)(nil), // 184: openshell.v1.AddWorkspaceMemberRequest - (*AddWorkspaceMemberResponse)(nil), // 185: openshell.v1.AddWorkspaceMemberResponse - (*RemoveWorkspaceMemberRequest)(nil), // 186: openshell.v1.RemoveWorkspaceMemberRequest - (*RemoveWorkspaceMemberResponse)(nil), // 187: openshell.v1.RemoveWorkspaceMemberResponse - (*ListWorkspaceMembersRequest)(nil), // 188: openshell.v1.ListWorkspaceMembersRequest - (*ListWorkspaceMembersResponse)(nil), // 189: openshell.v1.ListWorkspaceMembersResponse - (*ExtensionServiceCredential)(nil), // 190: openshell.v1.ExtensionServiceCredential - nil, // 191: openshell.v1.SandboxSpec.EnvironmentEntry - nil, // 192: openshell.v1.SandboxTemplate.LabelsEntry - nil, // 193: openshell.v1.SandboxTemplate.AnnotationsEntry - nil, // 194: openshell.v1.SandboxTemplate.EnvironmentEntry - nil, // 195: openshell.v1.PlatformEvent.MetadataEntry - nil, // 196: openshell.v1.CreateSandboxRequest.LabelsEntry - nil, // 197: openshell.v1.CreateSandboxRequest.AnnotationsEntry - nil, // 198: openshell.v1.ExecSandboxRequest.EnvironmentEntry - nil, // 199: openshell.v1.SandboxLogLine.FieldsEntry - nil, // 200: openshell.v1.UpdateProviderRequest.CredentialExpiresAtMsEntry - nil, // 201: openshell.v1.StoredProviderCredentialRefreshState.MaterialEntry - nil, // 202: openshell.v1.StoredProviderCredentialRefreshState.AdditionalOutputKeysEntry - nil, // 203: openshell.v1.ConfigureProviderRefreshRequest.MaterialEntry - nil, // 204: openshell.v1.ProviderProfile.AnnotationsEntry - nil, // 205: openshell.v1.GetSandboxProviderEnvironmentResponse.EnvironmentEntry - nil, // 206: openshell.v1.GetSandboxProviderEnvironmentResponse.CredentialExpiresAtMsEntry - nil, // 207: openshell.v1.GetSandboxProviderEnvironmentResponse.DynamicCredentialsEntry - nil, // 208: openshell.v1.GetSandboxProviderEnvironmentResponse.StaticCredentialBindingsEntry - nil, // 209: openshell.v1.UpdateConfigRequest.AnnotationsEntry - nil, // 210: openshell.v1.UpdateConfigResponse.AnnotationsEntry - nil, // 211: openshell.v1.SandboxPolicyRevision.ProvenanceEntry - nil, // 212: openshell.v1.PolicyRevisionPayload.ProvenanceEntry - nil, // 213: openshell.v1.StoredPolicyRevision.ProvenanceEntry - nil, // 214: openshell.v1.CreateWorkspaceRequest.LabelsEntry - (*datamodelv1.ObjectMeta)(nil), // 215: openshell.datamodel.v1.ObjectMeta - (*sandboxv1.SandboxPolicy)(nil), // 216: openshell.sandbox.v1.SandboxPolicy - (*structpb.Struct)(nil), // 217: google.protobuf.Struct - (*datamodelv1.Provider)(nil), // 218: openshell.datamodel.v1.Provider - (*sandboxv1.NetworkEndpoint)(nil), // 219: openshell.sandbox.v1.NetworkEndpoint - (*sandboxv1.NetworkBinary)(nil), // 220: openshell.sandbox.v1.NetworkBinary - (*sandboxv1.SettingValue)(nil), // 221: openshell.sandbox.v1.SettingValue - (*sandboxv1.NetworkPolicyRule)(nil), // 222: openshell.sandbox.v1.NetworkPolicyRule - (*sandboxv1.L7DenyRule)(nil), // 223: openshell.sandbox.v1.L7DenyRule - (*sandboxv1.L7Rule)(nil), // 224: openshell.sandbox.v1.L7Rule - (*datamodelv1.Workspace)(nil), // 225: openshell.datamodel.v1.Workspace - (*sandboxv1.GetSandboxConfigRequest)(nil), // 226: openshell.sandbox.v1.GetSandboxConfigRequest - (*sandboxv1.GetGatewayConfigRequest)(nil), // 227: openshell.sandbox.v1.GetGatewayConfigRequest - (*sandboxv1.GetSandboxConfigResponse)(nil), // 228: openshell.sandbox.v1.GetSandboxConfigResponse - (*sandboxv1.GetGatewayConfigResponse)(nil), // 229: openshell.sandbox.v1.GetGatewayConfigResponse + (ProviderCredentialTokenGrantType)(0), // 1: openshell.v1.ProviderCredentialTokenGrantType + (ProviderCredentialRefreshStrategy)(0), // 2: openshell.v1.ProviderCredentialRefreshStrategy + (ProviderProfileCategory)(0), // 3: openshell.v1.ProviderProfileCategory + (PolicyStatus)(0), // 4: openshell.v1.PolicyStatus + (ServiceStatus)(0), // 5: openshell.v1.ServiceStatus + (WorkspaceRole)(0), // 6: openshell.v1.WorkspaceRole + (*IssueSandboxTokenRequest)(nil), // 7: openshell.v1.IssueSandboxTokenRequest + (*IssueSandboxTokenResponse)(nil), // 8: openshell.v1.IssueSandboxTokenResponse + (*RefreshSandboxTokenRequest)(nil), // 9: openshell.v1.RefreshSandboxTokenRequest + (*RefreshSandboxTokenResponse)(nil), // 10: openshell.v1.RefreshSandboxTokenResponse + (*HealthRequest)(nil), // 11: openshell.v1.HealthRequest + (*HealthResponse)(nil), // 12: openshell.v1.HealthResponse + (*GetCurrentUserRequest)(nil), // 13: openshell.v1.GetCurrentUserRequest + (*GetCurrentUserResponse)(nil), // 14: openshell.v1.GetCurrentUserResponse + (*GetGatewayInfoRequest)(nil), // 15: openshell.v1.GetGatewayInfoRequest + (*GetGatewayInfoResponse)(nil), // 16: openshell.v1.GetGatewayInfoResponse + (*ComputeDriverInfo)(nil), // 17: openshell.v1.ComputeDriverInfo + (*ComputeDriverCapabilities)(nil), // 18: openshell.v1.ComputeDriverCapabilities + (*Sandbox)(nil), // 19: openshell.v1.Sandbox + (*SandboxSpec)(nil), // 20: openshell.v1.SandboxSpec + (*ResourceRequirements)(nil), // 21: openshell.v1.ResourceRequirements + (*GpuResourceRequirements)(nil), // 22: openshell.v1.GpuResourceRequirements + (*SandboxTemplate)(nil), // 23: openshell.v1.SandboxTemplate + (*SandboxStatus)(nil), // 24: openshell.v1.SandboxStatus + (*SandboxCondition)(nil), // 25: openshell.v1.SandboxCondition + (*PlatformEvent)(nil), // 26: openshell.v1.PlatformEvent + (*CreateSandboxRequest)(nil), // 27: openshell.v1.CreateSandboxRequest + (*GetSandboxRequest)(nil), // 28: openshell.v1.GetSandboxRequest + (*ListSandboxesRequest)(nil), // 29: openshell.v1.ListSandboxesRequest + (*ListSandboxProvidersRequest)(nil), // 30: openshell.v1.ListSandboxProvidersRequest + (*AttachSandboxProviderRequest)(nil), // 31: openshell.v1.AttachSandboxProviderRequest + (*DetachSandboxProviderRequest)(nil), // 32: openshell.v1.DetachSandboxProviderRequest + (*DeleteSandboxRequest)(nil), // 33: openshell.v1.DeleteSandboxRequest + (*StopSandboxRequest)(nil), // 34: openshell.v1.StopSandboxRequest + (*StartSandboxRequest)(nil), // 35: openshell.v1.StartSandboxRequest + (*SandboxResponse)(nil), // 36: openshell.v1.SandboxResponse + (*ListSandboxesResponse)(nil), // 37: openshell.v1.ListSandboxesResponse + (*ListSandboxProvidersResponse)(nil), // 38: openshell.v1.ListSandboxProvidersResponse + (*AttachSandboxProviderResponse)(nil), // 39: openshell.v1.AttachSandboxProviderResponse + (*DetachSandboxProviderResponse)(nil), // 40: openshell.v1.DetachSandboxProviderResponse + (*DeleteSandboxResponse)(nil), // 41: openshell.v1.DeleteSandboxResponse + (*CreateSshSessionRequest)(nil), // 42: openshell.v1.CreateSshSessionRequest + (*CreateSshSessionResponse)(nil), // 43: openshell.v1.CreateSshSessionResponse + (*ExposeServiceRequest)(nil), // 44: openshell.v1.ExposeServiceRequest + (*GetServiceRequest)(nil), // 45: openshell.v1.GetServiceRequest + (*ListServicesRequest)(nil), // 46: openshell.v1.ListServicesRequest + (*ListServicesResponse)(nil), // 47: openshell.v1.ListServicesResponse + (*DeleteServiceRequest)(nil), // 48: openshell.v1.DeleteServiceRequest + (*DeleteServiceResponse)(nil), // 49: openshell.v1.DeleteServiceResponse + (*ServiceEndpoint)(nil), // 50: openshell.v1.ServiceEndpoint + (*ServiceEndpointResponse)(nil), // 51: openshell.v1.ServiceEndpointResponse + (*RevokeSshSessionRequest)(nil), // 52: openshell.v1.RevokeSshSessionRequest + (*RevokeSshSessionResponse)(nil), // 53: openshell.v1.RevokeSshSessionResponse + (*ExecSandboxRequest)(nil), // 54: openshell.v1.ExecSandboxRequest + (*ExecSandboxStdout)(nil), // 55: openshell.v1.ExecSandboxStdout + (*ExecSandboxStderr)(nil), // 56: openshell.v1.ExecSandboxStderr + (*ExecSandboxExit)(nil), // 57: openshell.v1.ExecSandboxExit + (*ExecSandboxEvent)(nil), // 58: openshell.v1.ExecSandboxEvent + (*TcpForwardInit)(nil), // 59: openshell.v1.TcpForwardInit + (*TcpForwardFrame)(nil), // 60: openshell.v1.TcpForwardFrame + (*ExecSandboxInput)(nil), // 61: openshell.v1.ExecSandboxInput + (*ExecSandboxWindowResize)(nil), // 62: openshell.v1.ExecSandboxWindowResize + (*SshSession)(nil), // 63: openshell.v1.SshSession + (*WatchSandboxRequest)(nil), // 64: openshell.v1.WatchSandboxRequest + (*SandboxStreamEvent)(nil), // 65: openshell.v1.SandboxStreamEvent + (*SandboxLogLine)(nil), // 66: openshell.v1.SandboxLogLine + (*SandboxStreamWarning)(nil), // 67: openshell.v1.SandboxStreamWarning + (*CreateProviderRequest)(nil), // 68: openshell.v1.CreateProviderRequest + (*GetProviderRequest)(nil), // 69: openshell.v1.GetProviderRequest + (*ListProvidersRequest)(nil), // 70: openshell.v1.ListProvidersRequest + (*UpdateProviderRequest)(nil), // 71: openshell.v1.UpdateProviderRequest + (*DeleteProviderRequest)(nil), // 72: openshell.v1.DeleteProviderRequest + (*ProviderResponse)(nil), // 73: openshell.v1.ProviderResponse + (*ListProvidersResponse)(nil), // 74: openshell.v1.ListProvidersResponse + (*ListProviderProfilesRequest)(nil), // 75: openshell.v1.ListProviderProfilesRequest + (*GetProviderProfileRequest)(nil), // 76: openshell.v1.GetProviderProfileRequest + (*ProviderProfileImportItem)(nil), // 77: openshell.v1.ProviderProfileImportItem + (*ProviderProfileDiagnostic)(nil), // 78: openshell.v1.ProviderProfileDiagnostic + (*ProviderCredentialTokenGrantAudienceOverride)(nil), // 79: openshell.v1.ProviderCredentialTokenGrantAudienceOverride + (*ProviderCredentialTokenGrantSubjectToken)(nil), // 80: openshell.v1.ProviderCredentialTokenGrantSubjectToken + (*ProviderCredentialTokenGrant)(nil), // 81: openshell.v1.ProviderCredentialTokenGrant + (*ProviderProfileCredential)(nil), // 82: openshell.v1.ProviderProfileCredential + (*ProviderCredentialRefreshMaterial)(nil), // 83: openshell.v1.ProviderCredentialRefreshMaterial + (*ProviderCredentialRefreshOutput)(nil), // 84: openshell.v1.ProviderCredentialRefreshOutput + (*ProviderCredentialRefresh)(nil), // 85: openshell.v1.ProviderCredentialRefresh + (*ProviderCredentialRefreshStatus)(nil), // 86: openshell.v1.ProviderCredentialRefreshStatus + (*ProviderProfileDiscovery)(nil), // 87: openshell.v1.ProviderProfileDiscovery + (*StoredProviderCredentialRefreshState)(nil), // 88: openshell.v1.StoredProviderCredentialRefreshState + (*GetProviderRefreshStatusRequest)(nil), // 89: openshell.v1.GetProviderRefreshStatusRequest + (*GetProviderRefreshStatusResponse)(nil), // 90: openshell.v1.GetProviderRefreshStatusResponse + (*ConfigureProviderRefreshRequest)(nil), // 91: openshell.v1.ConfigureProviderRefreshRequest + (*ConfigureProviderRefreshResponse)(nil), // 92: openshell.v1.ConfigureProviderRefreshResponse + (*RotateProviderCredentialRequest)(nil), // 93: openshell.v1.RotateProviderCredentialRequest + (*RotateProviderCredentialResponse)(nil), // 94: openshell.v1.RotateProviderCredentialResponse + (*DeleteProviderRefreshRequest)(nil), // 95: openshell.v1.DeleteProviderRefreshRequest + (*DeleteProviderRefreshResponse)(nil), // 96: openshell.v1.DeleteProviderRefreshResponse + (*ProviderProfile)(nil), // 97: openshell.v1.ProviderProfile + (*StoredProviderProfile)(nil), // 98: openshell.v1.StoredProviderProfile + (*ProviderProfileResponse)(nil), // 99: openshell.v1.ProviderProfileResponse + (*ListProviderProfilesResponse)(nil), // 100: openshell.v1.ListProviderProfilesResponse + (*ImportProviderProfilesRequest)(nil), // 101: openshell.v1.ImportProviderProfilesRequest + (*ImportProviderProfilesResponse)(nil), // 102: openshell.v1.ImportProviderProfilesResponse + (*UpdateProviderProfilesRequest)(nil), // 103: openshell.v1.UpdateProviderProfilesRequest + (*UpdateProviderProfilesResponse)(nil), // 104: openshell.v1.UpdateProviderProfilesResponse + (*LintProviderProfilesRequest)(nil), // 105: openshell.v1.LintProviderProfilesRequest + (*LintProviderProfilesResponse)(nil), // 106: openshell.v1.LintProviderProfilesResponse + (*DeleteProviderResponse)(nil), // 107: openshell.v1.DeleteProviderResponse + (*DeleteProviderProfileRequest)(nil), // 108: openshell.v1.DeleteProviderProfileRequest + (*DeleteProviderProfileResponse)(nil), // 109: openshell.v1.DeleteProviderProfileResponse + (*GetSandboxProviderEnvironmentRequest)(nil), // 110: openshell.v1.GetSandboxProviderEnvironmentRequest + (*StaticCredentialEndpointBinding)(nil), // 111: openshell.v1.StaticCredentialEndpointBinding + (*StaticCredentialBinding)(nil), // 112: openshell.v1.StaticCredentialBinding + (*GetSandboxProviderEnvironmentResponse)(nil), // 113: openshell.v1.GetSandboxProviderEnvironmentResponse + (*ExchangeProviderSubjectTokenRequest)(nil), // 114: openshell.v1.ExchangeProviderSubjectTokenRequest + (*ExchangeProviderSubjectTokenResponse)(nil), // 115: openshell.v1.ExchangeProviderSubjectTokenResponse + (*UpdateConfigRequest)(nil), // 116: openshell.v1.UpdateConfigRequest + (*PolicyMergeOperation)(nil), // 117: openshell.v1.PolicyMergeOperation + (*AddNetworkRule)(nil), // 118: openshell.v1.AddNetworkRule + (*RemoveNetworkEndpoint)(nil), // 119: openshell.v1.RemoveNetworkEndpoint + (*RemoveNetworkRule)(nil), // 120: openshell.v1.RemoveNetworkRule + (*AddDenyRules)(nil), // 121: openshell.v1.AddDenyRules + (*AddAllowRules)(nil), // 122: openshell.v1.AddAllowRules + (*RemoveNetworkBinary)(nil), // 123: openshell.v1.RemoveNetworkBinary + (*UpdateConfigResponse)(nil), // 124: openshell.v1.UpdateConfigResponse + (*GetSandboxPolicyStatusRequest)(nil), // 125: openshell.v1.GetSandboxPolicyStatusRequest + (*GetSandboxPolicyStatusResponse)(nil), // 126: openshell.v1.GetSandboxPolicyStatusResponse + (*ListSandboxPoliciesRequest)(nil), // 127: openshell.v1.ListSandboxPoliciesRequest + (*ListSandboxPoliciesResponse)(nil), // 128: openshell.v1.ListSandboxPoliciesResponse + (*ReportPolicyStatusRequest)(nil), // 129: openshell.v1.ReportPolicyStatusRequest + (*ReportPolicyStatusResponse)(nil), // 130: openshell.v1.ReportPolicyStatusResponse + (*SandboxPolicyRevision)(nil), // 131: openshell.v1.SandboxPolicyRevision + (*GetSandboxLogsRequest)(nil), // 132: openshell.v1.GetSandboxLogsRequest + (*PushSandboxLogsRequest)(nil), // 133: openshell.v1.PushSandboxLogsRequest + (*PushSandboxLogsResponse)(nil), // 134: openshell.v1.PushSandboxLogsResponse + (*GetSandboxLogsResponse)(nil), // 135: openshell.v1.GetSandboxLogsResponse + (*SupervisorMessage)(nil), // 136: openshell.v1.SupervisorMessage + (*GatewayMessage)(nil), // 137: openshell.v1.GatewayMessage + (*SupervisorHello)(nil), // 138: openshell.v1.SupervisorHello + (*SessionAccepted)(nil), // 139: openshell.v1.SessionAccepted + (*SessionRejected)(nil), // 140: openshell.v1.SessionRejected + (*SupervisorHeartbeat)(nil), // 141: openshell.v1.SupervisorHeartbeat + (*GatewayHeartbeat)(nil), // 142: openshell.v1.GatewayHeartbeat + (*RelayOpen)(nil), // 143: openshell.v1.RelayOpen + (*SshRelayTarget)(nil), // 144: openshell.v1.SshRelayTarget + (*TcpRelayTarget)(nil), // 145: openshell.v1.TcpRelayTarget + (*RelayInit)(nil), // 146: openshell.v1.RelayInit + (*RelayFrame)(nil), // 147: openshell.v1.RelayFrame + (*RelayOpenResult)(nil), // 148: openshell.v1.RelayOpenResult + (*RelayClose)(nil), // 149: openshell.v1.RelayClose + (*L7RequestSample)(nil), // 150: openshell.v1.L7RequestSample + (*DenialSummary)(nil), // 151: openshell.v1.DenialSummary + (*DenialGroupCount)(nil), // 152: openshell.v1.DenialGroupCount + (*NetworkActivitySummary)(nil), // 153: openshell.v1.NetworkActivitySummary + (*PolicyChunk)(nil), // 154: openshell.v1.PolicyChunk + (*DraftPolicyUpdate)(nil), // 155: openshell.v1.DraftPolicyUpdate + (*SubmitPolicyAnalysisRequest)(nil), // 156: openshell.v1.SubmitPolicyAnalysisRequest + (*SubmitPolicyAnalysisResponse)(nil), // 157: openshell.v1.SubmitPolicyAnalysisResponse + (*GetDraftPolicyRequest)(nil), // 158: openshell.v1.GetDraftPolicyRequest + (*GetDraftPolicyResponse)(nil), // 159: openshell.v1.GetDraftPolicyResponse + (*ApproveDraftChunkRequest)(nil), // 160: openshell.v1.ApproveDraftChunkRequest + (*ApproveDraftChunkResponse)(nil), // 161: openshell.v1.ApproveDraftChunkResponse + (*RejectDraftChunkRequest)(nil), // 162: openshell.v1.RejectDraftChunkRequest + (*RejectDraftChunkResponse)(nil), // 163: openshell.v1.RejectDraftChunkResponse + (*ApproveAllDraftChunksRequest)(nil), // 164: openshell.v1.ApproveAllDraftChunksRequest + (*ApproveAllDraftChunksResponse)(nil), // 165: openshell.v1.ApproveAllDraftChunksResponse + (*EditDraftChunkRequest)(nil), // 166: openshell.v1.EditDraftChunkRequest + (*EditDraftChunkResponse)(nil), // 167: openshell.v1.EditDraftChunkResponse + (*UndoDraftChunkRequest)(nil), // 168: openshell.v1.UndoDraftChunkRequest + (*UndoDraftChunkResponse)(nil), // 169: openshell.v1.UndoDraftChunkResponse + (*ClearDraftChunksRequest)(nil), // 170: openshell.v1.ClearDraftChunksRequest + (*ClearDraftChunksResponse)(nil), // 171: openshell.v1.ClearDraftChunksResponse + (*GetDraftHistoryRequest)(nil), // 172: openshell.v1.GetDraftHistoryRequest + (*DraftHistoryEntry)(nil), // 173: openshell.v1.DraftHistoryEntry + (*GetDraftHistoryResponse)(nil), // 174: openshell.v1.GetDraftHistoryResponse + (*PolicyRevisionPayload)(nil), // 175: openshell.v1.PolicyRevisionPayload + (*DraftChunkPayload)(nil), // 176: openshell.v1.DraftChunkPayload + (*StoredPolicyRevision)(nil), // 177: openshell.v1.StoredPolicyRevision + (*StoredDraftChunk)(nil), // 178: openshell.v1.StoredDraftChunk + (*CreateWorkspaceRequest)(nil), // 179: openshell.v1.CreateWorkspaceRequest + (*CreateWorkspaceResponse)(nil), // 180: openshell.v1.CreateWorkspaceResponse + (*GetWorkspaceRequest)(nil), // 181: openshell.v1.GetWorkspaceRequest + (*GetWorkspaceResponse)(nil), // 182: openshell.v1.GetWorkspaceResponse + (*ListWorkspacesRequest)(nil), // 183: openshell.v1.ListWorkspacesRequest + (*ListWorkspacesResponse)(nil), // 184: openshell.v1.ListWorkspacesResponse + (*DeleteWorkspaceRequest)(nil), // 185: openshell.v1.DeleteWorkspaceRequest + (*DeleteWorkspaceResponse)(nil), // 186: openshell.v1.DeleteWorkspaceResponse + (*WorkspaceMember)(nil), // 187: openshell.v1.WorkspaceMember + (*AddWorkspaceMemberRequest)(nil), // 188: openshell.v1.AddWorkspaceMemberRequest + (*AddWorkspaceMemberResponse)(nil), // 189: openshell.v1.AddWorkspaceMemberResponse + (*RemoveWorkspaceMemberRequest)(nil), // 190: openshell.v1.RemoveWorkspaceMemberRequest + (*RemoveWorkspaceMemberResponse)(nil), // 191: openshell.v1.RemoveWorkspaceMemberResponse + (*ListWorkspaceMembersRequest)(nil), // 192: openshell.v1.ListWorkspaceMembersRequest + (*ListWorkspaceMembersResponse)(nil), // 193: openshell.v1.ListWorkspaceMembersResponse + (*ExtensionServiceCredential)(nil), // 194: openshell.v1.ExtensionServiceCredential + nil, // 195: openshell.v1.SandboxSpec.EnvironmentEntry + nil, // 196: openshell.v1.SandboxTemplate.LabelsEntry + nil, // 197: openshell.v1.SandboxTemplate.AnnotationsEntry + nil, // 198: openshell.v1.SandboxTemplate.EnvironmentEntry + nil, // 199: openshell.v1.PlatformEvent.MetadataEntry + nil, // 200: openshell.v1.CreateSandboxRequest.LabelsEntry + nil, // 201: openshell.v1.CreateSandboxRequest.AnnotationsEntry + nil, // 202: openshell.v1.ExecSandboxRequest.EnvironmentEntry + nil, // 203: openshell.v1.SandboxLogLine.FieldsEntry + nil, // 204: openshell.v1.UpdateProviderRequest.CredentialExpiresAtMsEntry + nil, // 205: openshell.v1.StoredProviderCredentialRefreshState.MaterialEntry + nil, // 206: openshell.v1.StoredProviderCredentialRefreshState.AdditionalOutputKeysEntry + nil, // 207: openshell.v1.ConfigureProviderRefreshRequest.MaterialEntry + nil, // 208: openshell.v1.ProviderProfile.AnnotationsEntry + nil, // 209: openshell.v1.GetSandboxProviderEnvironmentResponse.EnvironmentEntry + nil, // 210: openshell.v1.GetSandboxProviderEnvironmentResponse.CredentialExpiresAtMsEntry + nil, // 211: openshell.v1.GetSandboxProviderEnvironmentResponse.DynamicCredentialsEntry + nil, // 212: openshell.v1.GetSandboxProviderEnvironmentResponse.StaticCredentialBindingsEntry + nil, // 213: openshell.v1.UpdateConfigRequest.AnnotationsEntry + nil, // 214: openshell.v1.UpdateConfigResponse.AnnotationsEntry + nil, // 215: openshell.v1.SandboxPolicyRevision.ProvenanceEntry + nil, // 216: openshell.v1.PolicyRevisionPayload.ProvenanceEntry + nil, // 217: openshell.v1.StoredPolicyRevision.ProvenanceEntry + nil, // 218: openshell.v1.CreateWorkspaceRequest.LabelsEntry + (*datamodelv1.ObjectMeta)(nil), // 219: openshell.datamodel.v1.ObjectMeta + (*sandboxv1.SandboxPolicy)(nil), // 220: openshell.sandbox.v1.SandboxPolicy + (*structpb.Struct)(nil), // 221: google.protobuf.Struct + (*datamodelv1.Provider)(nil), // 222: openshell.datamodel.v1.Provider + (*sandboxv1.NetworkEndpoint)(nil), // 223: openshell.sandbox.v1.NetworkEndpoint + (*sandboxv1.NetworkBinary)(nil), // 224: openshell.sandbox.v1.NetworkBinary + (*sandboxv1.SettingValue)(nil), // 225: openshell.sandbox.v1.SettingValue + (*sandboxv1.NetworkPolicyRule)(nil), // 226: openshell.sandbox.v1.NetworkPolicyRule + (*sandboxv1.L7DenyRule)(nil), // 227: openshell.sandbox.v1.L7DenyRule + (*sandboxv1.L7Rule)(nil), // 228: openshell.sandbox.v1.L7Rule + (*datamodelv1.Workspace)(nil), // 229: openshell.datamodel.v1.Workspace + (*sandboxv1.GetSandboxConfigRequest)(nil), // 230: openshell.sandbox.v1.GetSandboxConfigRequest + (*sandboxv1.GetGatewayConfigRequest)(nil), // 231: openshell.sandbox.v1.GetGatewayConfigRequest + (*sandboxv1.GetSandboxConfigResponse)(nil), // 232: openshell.sandbox.v1.GetSandboxConfigResponse + (*sandboxv1.GetGatewayConfigResponse)(nil), // 233: openshell.sandbox.v1.GetGatewayConfigResponse } var file_openshell_proto_depIdxs = []int32{ - 190, // 0: openshell.v1.RefreshSandboxTokenResponse.extension_credentials:type_name -> openshell.v1.ExtensionServiceCredential - 4, // 1: openshell.v1.HealthResponse.status:type_name -> openshell.v1.ServiceStatus - 4, // 2: openshell.v1.GetGatewayInfoResponse.status:type_name -> openshell.v1.ServiceStatus - 16, // 3: openshell.v1.GetGatewayInfoResponse.compute_drivers:type_name -> openshell.v1.ComputeDriverInfo - 17, // 4: openshell.v1.ComputeDriverInfo.capabilities:type_name -> openshell.v1.ComputeDriverCapabilities - 215, // 5: openshell.v1.Sandbox.metadata:type_name -> openshell.datamodel.v1.ObjectMeta - 19, // 6: openshell.v1.Sandbox.spec:type_name -> openshell.v1.SandboxSpec - 23, // 7: openshell.v1.Sandbox.status:type_name -> openshell.v1.SandboxStatus - 191, // 8: openshell.v1.SandboxSpec.environment:type_name -> openshell.v1.SandboxSpec.EnvironmentEntry - 22, // 9: openshell.v1.SandboxSpec.template:type_name -> openshell.v1.SandboxTemplate - 216, // 10: openshell.v1.SandboxSpec.policy:type_name -> openshell.sandbox.v1.SandboxPolicy - 20, // 11: openshell.v1.SandboxSpec.resource_requirements:type_name -> openshell.v1.ResourceRequirements - 21, // 12: openshell.v1.ResourceRequirements.gpu:type_name -> openshell.v1.GpuResourceRequirements - 192, // 13: openshell.v1.SandboxTemplate.labels:type_name -> openshell.v1.SandboxTemplate.LabelsEntry - 193, // 14: openshell.v1.SandboxTemplate.annotations:type_name -> openshell.v1.SandboxTemplate.AnnotationsEntry - 194, // 15: openshell.v1.SandboxTemplate.environment:type_name -> openshell.v1.SandboxTemplate.EnvironmentEntry - 217, // 16: openshell.v1.SandboxTemplate.resources:type_name -> google.protobuf.Struct - 217, // 17: openshell.v1.SandboxTemplate.driver_config:type_name -> google.protobuf.Struct - 24, // 18: openshell.v1.SandboxStatus.conditions:type_name -> openshell.v1.SandboxCondition + 194, // 0: openshell.v1.RefreshSandboxTokenResponse.extension_credentials:type_name -> openshell.v1.ExtensionServiceCredential + 5, // 1: openshell.v1.HealthResponse.status:type_name -> openshell.v1.ServiceStatus + 5, // 2: openshell.v1.GetGatewayInfoResponse.status:type_name -> openshell.v1.ServiceStatus + 17, // 3: openshell.v1.GetGatewayInfoResponse.compute_drivers:type_name -> openshell.v1.ComputeDriverInfo + 18, // 4: openshell.v1.ComputeDriverInfo.capabilities:type_name -> openshell.v1.ComputeDriverCapabilities + 219, // 5: openshell.v1.Sandbox.metadata:type_name -> openshell.datamodel.v1.ObjectMeta + 20, // 6: openshell.v1.Sandbox.spec:type_name -> openshell.v1.SandboxSpec + 24, // 7: openshell.v1.Sandbox.status:type_name -> openshell.v1.SandboxStatus + 195, // 8: openshell.v1.SandboxSpec.environment:type_name -> openshell.v1.SandboxSpec.EnvironmentEntry + 23, // 9: openshell.v1.SandboxSpec.template:type_name -> openshell.v1.SandboxTemplate + 220, // 10: openshell.v1.SandboxSpec.policy:type_name -> openshell.sandbox.v1.SandboxPolicy + 21, // 11: openshell.v1.SandboxSpec.resource_requirements:type_name -> openshell.v1.ResourceRequirements + 22, // 12: openshell.v1.ResourceRequirements.gpu:type_name -> openshell.v1.GpuResourceRequirements + 196, // 13: openshell.v1.SandboxTemplate.labels:type_name -> openshell.v1.SandboxTemplate.LabelsEntry + 197, // 14: openshell.v1.SandboxTemplate.annotations:type_name -> openshell.v1.SandboxTemplate.AnnotationsEntry + 198, // 15: openshell.v1.SandboxTemplate.environment:type_name -> openshell.v1.SandboxTemplate.EnvironmentEntry + 221, // 16: openshell.v1.SandboxTemplate.resources:type_name -> google.protobuf.Struct + 221, // 17: openshell.v1.SandboxTemplate.driver_config:type_name -> google.protobuf.Struct + 25, // 18: openshell.v1.SandboxStatus.conditions:type_name -> openshell.v1.SandboxCondition 0, // 19: openshell.v1.SandboxStatus.phase:type_name -> openshell.v1.SandboxPhase - 195, // 20: openshell.v1.PlatformEvent.metadata:type_name -> openshell.v1.PlatformEvent.MetadataEntry - 19, // 21: openshell.v1.CreateSandboxRequest.spec:type_name -> openshell.v1.SandboxSpec - 196, // 22: openshell.v1.CreateSandboxRequest.labels:type_name -> openshell.v1.CreateSandboxRequest.LabelsEntry - 197, // 23: openshell.v1.CreateSandboxRequest.annotations:type_name -> openshell.v1.CreateSandboxRequest.AnnotationsEntry - 18, // 24: openshell.v1.SandboxResponse.sandbox:type_name -> openshell.v1.Sandbox - 18, // 25: openshell.v1.ListSandboxesResponse.sandboxes:type_name -> openshell.v1.Sandbox - 218, // 26: openshell.v1.ListSandboxProvidersResponse.providers:type_name -> openshell.datamodel.v1.Provider - 18, // 27: openshell.v1.AttachSandboxProviderResponse.sandbox:type_name -> openshell.v1.Sandbox - 18, // 28: openshell.v1.DetachSandboxProviderResponse.sandbox:type_name -> openshell.v1.Sandbox - 50, // 29: openshell.v1.ListServicesResponse.services:type_name -> openshell.v1.ServiceEndpointResponse - 215, // 30: openshell.v1.ServiceEndpoint.metadata:type_name -> openshell.datamodel.v1.ObjectMeta - 49, // 31: openshell.v1.ServiceEndpointResponse.endpoint:type_name -> openshell.v1.ServiceEndpoint - 198, // 32: openshell.v1.ExecSandboxRequest.environment:type_name -> openshell.v1.ExecSandboxRequest.EnvironmentEntry - 54, // 33: openshell.v1.ExecSandboxEvent.stdout:type_name -> openshell.v1.ExecSandboxStdout - 55, // 34: openshell.v1.ExecSandboxEvent.stderr:type_name -> openshell.v1.ExecSandboxStderr - 56, // 35: openshell.v1.ExecSandboxEvent.exit:type_name -> openshell.v1.ExecSandboxExit - 140, // 36: openshell.v1.TcpForwardInit.ssh:type_name -> openshell.v1.SshRelayTarget - 141, // 37: openshell.v1.TcpForwardInit.tcp:type_name -> openshell.v1.TcpRelayTarget - 58, // 38: openshell.v1.TcpForwardFrame.init:type_name -> openshell.v1.TcpForwardInit - 53, // 39: openshell.v1.ExecSandboxInput.start:type_name -> openshell.v1.ExecSandboxRequest - 61, // 40: openshell.v1.ExecSandboxInput.resize:type_name -> openshell.v1.ExecSandboxWindowResize - 215, // 41: openshell.v1.SshSession.metadata:type_name -> openshell.datamodel.v1.ObjectMeta - 18, // 42: openshell.v1.SandboxStreamEvent.sandbox:type_name -> openshell.v1.Sandbox - 65, // 43: openshell.v1.SandboxStreamEvent.log:type_name -> openshell.v1.SandboxLogLine - 25, // 44: openshell.v1.SandboxStreamEvent.event:type_name -> openshell.v1.PlatformEvent - 66, // 45: openshell.v1.SandboxStreamEvent.warning:type_name -> openshell.v1.SandboxStreamWarning - 151, // 46: openshell.v1.SandboxStreamEvent.draft_policy_update:type_name -> openshell.v1.DraftPolicyUpdate - 199, // 47: openshell.v1.SandboxLogLine.fields:type_name -> openshell.v1.SandboxLogLine.FieldsEntry - 218, // 48: openshell.v1.CreateProviderRequest.provider:type_name -> openshell.datamodel.v1.Provider - 218, // 49: openshell.v1.UpdateProviderRequest.provider:type_name -> openshell.datamodel.v1.Provider - 200, // 50: openshell.v1.UpdateProviderRequest.credential_expires_at_ms:type_name -> openshell.v1.UpdateProviderRequest.CredentialExpiresAtMsEntry - 218, // 51: openshell.v1.ProviderResponse.provider:type_name -> openshell.datamodel.v1.Provider - 218, // 52: openshell.v1.ListProvidersResponse.providers:type_name -> openshell.datamodel.v1.Provider - 95, // 53: openshell.v1.ProviderProfileImportItem.profile:type_name -> openshell.v1.ProviderProfile - 78, // 54: openshell.v1.ProviderCredentialTokenGrant.audience_overrides:type_name -> openshell.v1.ProviderCredentialTokenGrantAudienceOverride - 83, // 55: openshell.v1.ProviderProfileCredential.refresh:type_name -> openshell.v1.ProviderCredentialRefresh - 79, // 56: openshell.v1.ProviderProfileCredential.token_grant:type_name -> openshell.v1.ProviderCredentialTokenGrant - 1, // 57: openshell.v1.ProviderCredentialRefresh.strategy:type_name -> openshell.v1.ProviderCredentialRefreshStrategy - 81, // 58: openshell.v1.ProviderCredentialRefresh.material:type_name -> openshell.v1.ProviderCredentialRefreshMaterial - 82, // 59: openshell.v1.ProviderCredentialRefresh.additional_outputs:type_name -> openshell.v1.ProviderCredentialRefreshOutput - 1, // 60: openshell.v1.ProviderCredentialRefreshStatus.strategy:type_name -> openshell.v1.ProviderCredentialRefreshStrategy - 215, // 61: openshell.v1.StoredProviderCredentialRefreshState.metadata:type_name -> openshell.datamodel.v1.ObjectMeta - 1, // 62: openshell.v1.StoredProviderCredentialRefreshState.strategy:type_name -> openshell.v1.ProviderCredentialRefreshStrategy - 201, // 63: openshell.v1.StoredProviderCredentialRefreshState.material:type_name -> openshell.v1.StoredProviderCredentialRefreshState.MaterialEntry - 202, // 64: openshell.v1.StoredProviderCredentialRefreshState.additional_output_keys:type_name -> openshell.v1.StoredProviderCredentialRefreshState.AdditionalOutputKeysEntry - 84, // 65: openshell.v1.GetProviderRefreshStatusResponse.credentials:type_name -> openshell.v1.ProviderCredentialRefreshStatus - 1, // 66: openshell.v1.ConfigureProviderRefreshRequest.strategy:type_name -> openshell.v1.ProviderCredentialRefreshStrategy - 203, // 67: openshell.v1.ConfigureProviderRefreshRequest.material:type_name -> openshell.v1.ConfigureProviderRefreshRequest.MaterialEntry - 84, // 68: openshell.v1.ConfigureProviderRefreshResponse.status:type_name -> openshell.v1.ProviderCredentialRefreshStatus - 84, // 69: openshell.v1.RotateProviderCredentialResponse.status:type_name -> openshell.v1.ProviderCredentialRefreshStatus - 2, // 70: openshell.v1.ProviderProfile.category:type_name -> openshell.v1.ProviderProfileCategory - 80, // 71: openshell.v1.ProviderProfile.credentials:type_name -> openshell.v1.ProviderProfileCredential - 219, // 72: openshell.v1.ProviderProfile.endpoints:type_name -> openshell.sandbox.v1.NetworkEndpoint - 220, // 73: openshell.v1.ProviderProfile.binaries:type_name -> openshell.sandbox.v1.NetworkBinary - 85, // 74: openshell.v1.ProviderProfile.discovery:type_name -> openshell.v1.ProviderProfileDiscovery - 204, // 75: openshell.v1.ProviderProfile.annotations:type_name -> openshell.v1.ProviderProfile.AnnotationsEntry - 215, // 76: openshell.v1.StoredProviderProfile.metadata:type_name -> openshell.datamodel.v1.ObjectMeta - 95, // 77: openshell.v1.StoredProviderProfile.profile:type_name -> openshell.v1.ProviderProfile - 95, // 78: openshell.v1.ProviderProfileResponse.profile:type_name -> openshell.v1.ProviderProfile - 95, // 79: openshell.v1.ListProviderProfilesResponse.profiles:type_name -> openshell.v1.ProviderProfile - 76, // 80: openshell.v1.ImportProviderProfilesRequest.profiles:type_name -> openshell.v1.ProviderProfileImportItem - 77, // 81: openshell.v1.ImportProviderProfilesResponse.diagnostics:type_name -> openshell.v1.ProviderProfileDiagnostic - 95, // 82: openshell.v1.ImportProviderProfilesResponse.profiles:type_name -> openshell.v1.ProviderProfile - 76, // 83: openshell.v1.UpdateProviderProfilesRequest.profile:type_name -> openshell.v1.ProviderProfileImportItem - 77, // 84: openshell.v1.UpdateProviderProfilesResponse.diagnostics:type_name -> openshell.v1.ProviderProfileDiagnostic - 95, // 85: openshell.v1.UpdateProviderProfilesResponse.profile:type_name -> openshell.v1.ProviderProfile - 76, // 86: openshell.v1.LintProviderProfilesRequest.profiles:type_name -> openshell.v1.ProviderProfileImportItem - 77, // 87: openshell.v1.LintProviderProfilesResponse.diagnostics:type_name -> openshell.v1.ProviderProfileDiagnostic - 109, // 88: openshell.v1.StaticCredentialBinding.endpoints:type_name -> openshell.v1.StaticCredentialEndpointBinding - 205, // 89: openshell.v1.GetSandboxProviderEnvironmentResponse.environment:type_name -> openshell.v1.GetSandboxProviderEnvironmentResponse.EnvironmentEntry - 206, // 90: openshell.v1.GetSandboxProviderEnvironmentResponse.credential_expires_at_ms:type_name -> openshell.v1.GetSandboxProviderEnvironmentResponse.CredentialExpiresAtMsEntry - 207, // 91: openshell.v1.GetSandboxProviderEnvironmentResponse.dynamic_credentials:type_name -> openshell.v1.GetSandboxProviderEnvironmentResponse.DynamicCredentialsEntry - 208, // 92: openshell.v1.GetSandboxProviderEnvironmentResponse.static_credential_bindings:type_name -> openshell.v1.GetSandboxProviderEnvironmentResponse.StaticCredentialBindingsEntry - 216, // 93: openshell.v1.UpdateConfigRequest.policy:type_name -> openshell.sandbox.v1.SandboxPolicy - 221, // 94: openshell.v1.UpdateConfigRequest.setting_value:type_name -> openshell.sandbox.v1.SettingValue - 113, // 95: openshell.v1.UpdateConfigRequest.merge_operations:type_name -> openshell.v1.PolicyMergeOperation - 209, // 96: openshell.v1.UpdateConfigRequest.annotations:type_name -> openshell.v1.UpdateConfigRequest.AnnotationsEntry - 114, // 97: openshell.v1.PolicyMergeOperation.add_rule:type_name -> openshell.v1.AddNetworkRule - 115, // 98: openshell.v1.PolicyMergeOperation.remove_endpoint:type_name -> openshell.v1.RemoveNetworkEndpoint - 116, // 99: openshell.v1.PolicyMergeOperation.remove_rule:type_name -> openshell.v1.RemoveNetworkRule - 117, // 100: openshell.v1.PolicyMergeOperation.add_deny_rules:type_name -> openshell.v1.AddDenyRules - 118, // 101: openshell.v1.PolicyMergeOperation.add_allow_rules:type_name -> openshell.v1.AddAllowRules - 119, // 102: openshell.v1.PolicyMergeOperation.remove_binary:type_name -> openshell.v1.RemoveNetworkBinary - 222, // 103: openshell.v1.AddNetworkRule.rule:type_name -> openshell.sandbox.v1.NetworkPolicyRule - 223, // 104: openshell.v1.AddDenyRules.deny_rules:type_name -> openshell.sandbox.v1.L7DenyRule - 224, // 105: openshell.v1.AddAllowRules.rules:type_name -> openshell.sandbox.v1.L7Rule - 210, // 106: openshell.v1.UpdateConfigResponse.annotations:type_name -> openshell.v1.UpdateConfigResponse.AnnotationsEntry - 127, // 107: openshell.v1.GetSandboxPolicyStatusResponse.revision:type_name -> openshell.v1.SandboxPolicyRevision - 127, // 108: openshell.v1.ListSandboxPoliciesResponse.revisions:type_name -> openshell.v1.SandboxPolicyRevision - 3, // 109: openshell.v1.ReportPolicyStatusRequest.status:type_name -> openshell.v1.PolicyStatus - 3, // 110: openshell.v1.SandboxPolicyRevision.status:type_name -> openshell.v1.PolicyStatus - 216, // 111: openshell.v1.SandboxPolicyRevision.policy:type_name -> openshell.sandbox.v1.SandboxPolicy - 211, // 112: openshell.v1.SandboxPolicyRevision.provenance:type_name -> openshell.v1.SandboxPolicyRevision.ProvenanceEntry - 65, // 113: openshell.v1.PushSandboxLogsRequest.logs:type_name -> openshell.v1.SandboxLogLine - 65, // 114: openshell.v1.GetSandboxLogsResponse.logs:type_name -> openshell.v1.SandboxLogLine - 134, // 115: openshell.v1.SupervisorMessage.hello:type_name -> openshell.v1.SupervisorHello - 137, // 116: openshell.v1.SupervisorMessage.heartbeat:type_name -> openshell.v1.SupervisorHeartbeat - 144, // 117: openshell.v1.SupervisorMessage.relay_open_result:type_name -> openshell.v1.RelayOpenResult - 145, // 118: openshell.v1.SupervisorMessage.relay_close:type_name -> openshell.v1.RelayClose - 135, // 119: openshell.v1.GatewayMessage.session_accepted:type_name -> openshell.v1.SessionAccepted - 136, // 120: openshell.v1.GatewayMessage.session_rejected:type_name -> openshell.v1.SessionRejected - 138, // 121: openshell.v1.GatewayMessage.heartbeat:type_name -> openshell.v1.GatewayHeartbeat - 139, // 122: openshell.v1.GatewayMessage.relay_open:type_name -> openshell.v1.RelayOpen - 145, // 123: openshell.v1.GatewayMessage.relay_close:type_name -> openshell.v1.RelayClose - 140, // 124: openshell.v1.RelayOpen.ssh:type_name -> openshell.v1.SshRelayTarget - 141, // 125: openshell.v1.RelayOpen.tcp:type_name -> openshell.v1.TcpRelayTarget - 142, // 126: openshell.v1.RelayFrame.init:type_name -> openshell.v1.RelayInit - 146, // 127: openshell.v1.DenialSummary.l7_request_samples:type_name -> openshell.v1.L7RequestSample - 148, // 128: openshell.v1.NetworkActivitySummary.denials_by_group:type_name -> openshell.v1.DenialGroupCount - 222, // 129: openshell.v1.PolicyChunk.proposed_rule:type_name -> openshell.sandbox.v1.NetworkPolicyRule - 147, // 130: openshell.v1.SubmitPolicyAnalysisRequest.summaries:type_name -> openshell.v1.DenialSummary - 150, // 131: openshell.v1.SubmitPolicyAnalysisRequest.proposed_chunks:type_name -> openshell.v1.PolicyChunk - 149, // 132: openshell.v1.SubmitPolicyAnalysisRequest.network_activity_summaries:type_name -> openshell.v1.NetworkActivitySummary - 150, // 133: openshell.v1.GetDraftPolicyResponse.chunks:type_name -> openshell.v1.PolicyChunk - 222, // 134: openshell.v1.EditDraftChunkRequest.proposed_rule:type_name -> openshell.sandbox.v1.NetworkPolicyRule - 169, // 135: openshell.v1.GetDraftHistoryResponse.entries:type_name -> openshell.v1.DraftHistoryEntry - 216, // 136: openshell.v1.PolicyRevisionPayload.policy:type_name -> openshell.sandbox.v1.SandboxPolicy - 212, // 137: openshell.v1.PolicyRevisionPayload.provenance:type_name -> openshell.v1.PolicyRevisionPayload.ProvenanceEntry - 222, // 138: openshell.v1.DraftChunkPayload.proposed_rule:type_name -> openshell.sandbox.v1.NetworkPolicyRule - 213, // 139: openshell.v1.StoredPolicyRevision.provenance:type_name -> openshell.v1.StoredPolicyRevision.ProvenanceEntry - 214, // 140: openshell.v1.CreateWorkspaceRequest.labels:type_name -> openshell.v1.CreateWorkspaceRequest.LabelsEntry - 225, // 141: openshell.v1.CreateWorkspaceResponse.workspace:type_name -> openshell.datamodel.v1.Workspace - 225, // 142: openshell.v1.GetWorkspaceResponse.workspace:type_name -> openshell.datamodel.v1.Workspace - 225, // 143: openshell.v1.ListWorkspacesResponse.workspaces:type_name -> openshell.datamodel.v1.Workspace - 215, // 144: openshell.v1.WorkspaceMember.metadata:type_name -> openshell.datamodel.v1.ObjectMeta - 5, // 145: openshell.v1.WorkspaceMember.role:type_name -> openshell.v1.WorkspaceRole - 5, // 146: openshell.v1.AddWorkspaceMemberRequest.role:type_name -> openshell.v1.WorkspaceRole - 183, // 147: openshell.v1.AddWorkspaceMemberResponse.member:type_name -> openshell.v1.WorkspaceMember - 183, // 148: openshell.v1.ListWorkspaceMembersResponse.members:type_name -> openshell.v1.WorkspaceMember - 80, // 149: openshell.v1.GetSandboxProviderEnvironmentResponse.DynamicCredentialsEntry.value:type_name -> openshell.v1.ProviderProfileCredential - 110, // 150: openshell.v1.GetSandboxProviderEnvironmentResponse.StaticCredentialBindingsEntry.value:type_name -> openshell.v1.StaticCredentialBinding - 10, // 151: openshell.v1.OpenShell.Health:input_type -> openshell.v1.HealthRequest - 12, // 152: openshell.v1.OpenShell.GetCurrentUser:input_type -> openshell.v1.GetCurrentUserRequest - 14, // 153: openshell.v1.OpenShell.GetGatewayInfo:input_type -> openshell.v1.GetGatewayInfoRequest - 26, // 154: openshell.v1.OpenShell.CreateSandbox:input_type -> openshell.v1.CreateSandboxRequest - 27, // 155: openshell.v1.OpenShell.GetSandbox:input_type -> openshell.v1.GetSandboxRequest - 28, // 156: openshell.v1.OpenShell.ListSandboxes:input_type -> openshell.v1.ListSandboxesRequest - 29, // 157: openshell.v1.OpenShell.ListSandboxProviders:input_type -> openshell.v1.ListSandboxProvidersRequest - 30, // 158: openshell.v1.OpenShell.AttachSandboxProvider:input_type -> openshell.v1.AttachSandboxProviderRequest - 31, // 159: openshell.v1.OpenShell.DetachSandboxProvider:input_type -> openshell.v1.DetachSandboxProviderRequest - 32, // 160: openshell.v1.OpenShell.DeleteSandbox:input_type -> openshell.v1.DeleteSandboxRequest - 33, // 161: openshell.v1.OpenShell.StopSandbox:input_type -> openshell.v1.StopSandboxRequest - 34, // 162: openshell.v1.OpenShell.StartSandbox:input_type -> openshell.v1.StartSandboxRequest - 41, // 163: openshell.v1.OpenShell.CreateSshSession:input_type -> openshell.v1.CreateSshSessionRequest - 43, // 164: openshell.v1.OpenShell.ExposeService:input_type -> openshell.v1.ExposeServiceRequest - 44, // 165: openshell.v1.OpenShell.GetService:input_type -> openshell.v1.GetServiceRequest - 45, // 166: openshell.v1.OpenShell.ListServices:input_type -> openshell.v1.ListServicesRequest - 47, // 167: openshell.v1.OpenShell.DeleteService:input_type -> openshell.v1.DeleteServiceRequest - 51, // 168: openshell.v1.OpenShell.RevokeSshSession:input_type -> openshell.v1.RevokeSshSessionRequest - 53, // 169: openshell.v1.OpenShell.ExecSandbox:input_type -> openshell.v1.ExecSandboxRequest - 59, // 170: openshell.v1.OpenShell.ForwardTcp:input_type -> openshell.v1.TcpForwardFrame - 60, // 171: openshell.v1.OpenShell.ExecSandboxInteractive:input_type -> openshell.v1.ExecSandboxInput - 67, // 172: openshell.v1.OpenShell.CreateProvider:input_type -> openshell.v1.CreateProviderRequest - 68, // 173: openshell.v1.OpenShell.GetProvider:input_type -> openshell.v1.GetProviderRequest - 69, // 174: openshell.v1.OpenShell.ListProviders:input_type -> openshell.v1.ListProvidersRequest - 74, // 175: openshell.v1.OpenShell.ListProviderProfiles:input_type -> openshell.v1.ListProviderProfilesRequest - 75, // 176: openshell.v1.OpenShell.GetProviderProfile:input_type -> openshell.v1.GetProviderProfileRequest - 99, // 177: openshell.v1.OpenShell.ImportProviderProfiles:input_type -> openshell.v1.ImportProviderProfilesRequest - 101, // 178: openshell.v1.OpenShell.UpdateProviderProfiles:input_type -> openshell.v1.UpdateProviderProfilesRequest - 103, // 179: openshell.v1.OpenShell.LintProviderProfiles:input_type -> openshell.v1.LintProviderProfilesRequest - 70, // 180: openshell.v1.OpenShell.UpdateProvider:input_type -> openshell.v1.UpdateProviderRequest - 87, // 181: openshell.v1.OpenShell.GetProviderRefreshStatus:input_type -> openshell.v1.GetProviderRefreshStatusRequest - 89, // 182: openshell.v1.OpenShell.ConfigureProviderRefresh:input_type -> openshell.v1.ConfigureProviderRefreshRequest - 91, // 183: openshell.v1.OpenShell.RotateProviderCredential:input_type -> openshell.v1.RotateProviderCredentialRequest - 93, // 184: openshell.v1.OpenShell.DeleteProviderRefresh:input_type -> openshell.v1.DeleteProviderRefreshRequest - 71, // 185: openshell.v1.OpenShell.DeleteProvider:input_type -> openshell.v1.DeleteProviderRequest - 106, // 186: openshell.v1.OpenShell.DeleteProviderProfile:input_type -> openshell.v1.DeleteProviderProfileRequest - 226, // 187: openshell.v1.OpenShell.GetSandboxConfig:input_type -> openshell.sandbox.v1.GetSandboxConfigRequest - 227, // 188: openshell.v1.OpenShell.GetGatewayConfig:input_type -> openshell.sandbox.v1.GetGatewayConfigRequest - 112, // 189: openshell.v1.OpenShell.UpdateConfig:input_type -> openshell.v1.UpdateConfigRequest - 121, // 190: openshell.v1.OpenShell.GetSandboxPolicyStatus:input_type -> openshell.v1.GetSandboxPolicyStatusRequest - 123, // 191: openshell.v1.OpenShell.ListSandboxPolicies:input_type -> openshell.v1.ListSandboxPoliciesRequest - 125, // 192: openshell.v1.OpenShell.ReportPolicyStatus:input_type -> openshell.v1.ReportPolicyStatusRequest - 108, // 193: openshell.v1.OpenShell.GetSandboxProviderEnvironment:input_type -> openshell.v1.GetSandboxProviderEnvironmentRequest - 128, // 194: openshell.v1.OpenShell.GetSandboxLogs:input_type -> openshell.v1.GetSandboxLogsRequest - 129, // 195: openshell.v1.OpenShell.PushSandboxLogs:input_type -> openshell.v1.PushSandboxLogsRequest - 132, // 196: openshell.v1.OpenShell.ConnectSupervisor:input_type -> openshell.v1.SupervisorMessage - 143, // 197: openshell.v1.OpenShell.RelayStream:input_type -> openshell.v1.RelayFrame - 63, // 198: openshell.v1.OpenShell.WatchSandbox:input_type -> openshell.v1.WatchSandboxRequest - 152, // 199: openshell.v1.OpenShell.SubmitPolicyAnalysis:input_type -> openshell.v1.SubmitPolicyAnalysisRequest - 154, // 200: openshell.v1.OpenShell.GetDraftPolicy:input_type -> openshell.v1.GetDraftPolicyRequest - 156, // 201: openshell.v1.OpenShell.ApproveDraftChunk:input_type -> openshell.v1.ApproveDraftChunkRequest - 158, // 202: openshell.v1.OpenShell.RejectDraftChunk:input_type -> openshell.v1.RejectDraftChunkRequest - 160, // 203: openshell.v1.OpenShell.ApproveAllDraftChunks:input_type -> openshell.v1.ApproveAllDraftChunksRequest - 162, // 204: openshell.v1.OpenShell.EditDraftChunk:input_type -> openshell.v1.EditDraftChunkRequest - 164, // 205: openshell.v1.OpenShell.UndoDraftChunk:input_type -> openshell.v1.UndoDraftChunkRequest - 166, // 206: openshell.v1.OpenShell.ClearDraftChunks:input_type -> openshell.v1.ClearDraftChunksRequest - 168, // 207: openshell.v1.OpenShell.GetDraftHistory:input_type -> openshell.v1.GetDraftHistoryRequest - 6, // 208: openshell.v1.OpenShell.IssueSandboxToken:input_type -> openshell.v1.IssueSandboxTokenRequest - 8, // 209: openshell.v1.OpenShell.RefreshSandboxToken:input_type -> openshell.v1.RefreshSandboxTokenRequest - 175, // 210: openshell.v1.OpenShell.CreateWorkspace:input_type -> openshell.v1.CreateWorkspaceRequest - 177, // 211: openshell.v1.OpenShell.GetWorkspace:input_type -> openshell.v1.GetWorkspaceRequest - 179, // 212: openshell.v1.OpenShell.ListWorkspaces:input_type -> openshell.v1.ListWorkspacesRequest - 181, // 213: openshell.v1.OpenShell.DeleteWorkspace:input_type -> openshell.v1.DeleteWorkspaceRequest - 184, // 214: openshell.v1.OpenShell.AddWorkspaceMember:input_type -> openshell.v1.AddWorkspaceMemberRequest - 186, // 215: openshell.v1.OpenShell.RemoveWorkspaceMember:input_type -> openshell.v1.RemoveWorkspaceMemberRequest - 188, // 216: openshell.v1.OpenShell.ListWorkspaceMembers:input_type -> openshell.v1.ListWorkspaceMembersRequest - 11, // 217: openshell.v1.OpenShell.Health:output_type -> openshell.v1.HealthResponse - 13, // 218: openshell.v1.OpenShell.GetCurrentUser:output_type -> openshell.v1.GetCurrentUserResponse - 15, // 219: openshell.v1.OpenShell.GetGatewayInfo:output_type -> openshell.v1.GetGatewayInfoResponse - 35, // 220: openshell.v1.OpenShell.CreateSandbox:output_type -> openshell.v1.SandboxResponse - 35, // 221: openshell.v1.OpenShell.GetSandbox:output_type -> openshell.v1.SandboxResponse - 36, // 222: openshell.v1.OpenShell.ListSandboxes:output_type -> openshell.v1.ListSandboxesResponse - 37, // 223: openshell.v1.OpenShell.ListSandboxProviders:output_type -> openshell.v1.ListSandboxProvidersResponse - 38, // 224: openshell.v1.OpenShell.AttachSandboxProvider:output_type -> openshell.v1.AttachSandboxProviderResponse - 39, // 225: openshell.v1.OpenShell.DetachSandboxProvider:output_type -> openshell.v1.DetachSandboxProviderResponse - 40, // 226: openshell.v1.OpenShell.DeleteSandbox:output_type -> openshell.v1.DeleteSandboxResponse - 35, // 227: openshell.v1.OpenShell.StopSandbox:output_type -> openshell.v1.SandboxResponse - 35, // 228: openshell.v1.OpenShell.StartSandbox:output_type -> openshell.v1.SandboxResponse - 42, // 229: openshell.v1.OpenShell.CreateSshSession:output_type -> openshell.v1.CreateSshSessionResponse - 50, // 230: openshell.v1.OpenShell.ExposeService:output_type -> openshell.v1.ServiceEndpointResponse - 50, // 231: openshell.v1.OpenShell.GetService:output_type -> openshell.v1.ServiceEndpointResponse - 46, // 232: openshell.v1.OpenShell.ListServices:output_type -> openshell.v1.ListServicesResponse - 48, // 233: openshell.v1.OpenShell.DeleteService:output_type -> openshell.v1.DeleteServiceResponse - 52, // 234: openshell.v1.OpenShell.RevokeSshSession:output_type -> openshell.v1.RevokeSshSessionResponse - 57, // 235: openshell.v1.OpenShell.ExecSandbox:output_type -> openshell.v1.ExecSandboxEvent - 59, // 236: openshell.v1.OpenShell.ForwardTcp:output_type -> openshell.v1.TcpForwardFrame - 57, // 237: openshell.v1.OpenShell.ExecSandboxInteractive:output_type -> openshell.v1.ExecSandboxEvent - 72, // 238: openshell.v1.OpenShell.CreateProvider:output_type -> openshell.v1.ProviderResponse - 72, // 239: openshell.v1.OpenShell.GetProvider:output_type -> openshell.v1.ProviderResponse - 73, // 240: openshell.v1.OpenShell.ListProviders:output_type -> openshell.v1.ListProvidersResponse - 98, // 241: openshell.v1.OpenShell.ListProviderProfiles:output_type -> openshell.v1.ListProviderProfilesResponse - 97, // 242: openshell.v1.OpenShell.GetProviderProfile:output_type -> openshell.v1.ProviderProfileResponse - 100, // 243: openshell.v1.OpenShell.ImportProviderProfiles:output_type -> openshell.v1.ImportProviderProfilesResponse - 102, // 244: openshell.v1.OpenShell.UpdateProviderProfiles:output_type -> openshell.v1.UpdateProviderProfilesResponse - 104, // 245: openshell.v1.OpenShell.LintProviderProfiles:output_type -> openshell.v1.LintProviderProfilesResponse - 72, // 246: openshell.v1.OpenShell.UpdateProvider:output_type -> openshell.v1.ProviderResponse - 88, // 247: openshell.v1.OpenShell.GetProviderRefreshStatus:output_type -> openshell.v1.GetProviderRefreshStatusResponse - 90, // 248: openshell.v1.OpenShell.ConfigureProviderRefresh:output_type -> openshell.v1.ConfigureProviderRefreshResponse - 92, // 249: openshell.v1.OpenShell.RotateProviderCredential:output_type -> openshell.v1.RotateProviderCredentialResponse - 94, // 250: openshell.v1.OpenShell.DeleteProviderRefresh:output_type -> openshell.v1.DeleteProviderRefreshResponse - 105, // 251: openshell.v1.OpenShell.DeleteProvider:output_type -> openshell.v1.DeleteProviderResponse - 107, // 252: openshell.v1.OpenShell.DeleteProviderProfile:output_type -> openshell.v1.DeleteProviderProfileResponse - 228, // 253: openshell.v1.OpenShell.GetSandboxConfig:output_type -> openshell.sandbox.v1.GetSandboxConfigResponse - 229, // 254: openshell.v1.OpenShell.GetGatewayConfig:output_type -> openshell.sandbox.v1.GetGatewayConfigResponse - 120, // 255: openshell.v1.OpenShell.UpdateConfig:output_type -> openshell.v1.UpdateConfigResponse - 122, // 256: openshell.v1.OpenShell.GetSandboxPolicyStatus:output_type -> openshell.v1.GetSandboxPolicyStatusResponse - 124, // 257: openshell.v1.OpenShell.ListSandboxPolicies:output_type -> openshell.v1.ListSandboxPoliciesResponse - 126, // 258: openshell.v1.OpenShell.ReportPolicyStatus:output_type -> openshell.v1.ReportPolicyStatusResponse - 111, // 259: openshell.v1.OpenShell.GetSandboxProviderEnvironment:output_type -> openshell.v1.GetSandboxProviderEnvironmentResponse - 131, // 260: openshell.v1.OpenShell.GetSandboxLogs:output_type -> openshell.v1.GetSandboxLogsResponse - 130, // 261: openshell.v1.OpenShell.PushSandboxLogs:output_type -> openshell.v1.PushSandboxLogsResponse - 133, // 262: openshell.v1.OpenShell.ConnectSupervisor:output_type -> openshell.v1.GatewayMessage - 143, // 263: openshell.v1.OpenShell.RelayStream:output_type -> openshell.v1.RelayFrame - 64, // 264: openshell.v1.OpenShell.WatchSandbox:output_type -> openshell.v1.SandboxStreamEvent - 153, // 265: openshell.v1.OpenShell.SubmitPolicyAnalysis:output_type -> openshell.v1.SubmitPolicyAnalysisResponse - 155, // 266: openshell.v1.OpenShell.GetDraftPolicy:output_type -> openshell.v1.GetDraftPolicyResponse - 157, // 267: openshell.v1.OpenShell.ApproveDraftChunk:output_type -> openshell.v1.ApproveDraftChunkResponse - 159, // 268: openshell.v1.OpenShell.RejectDraftChunk:output_type -> openshell.v1.RejectDraftChunkResponse - 161, // 269: openshell.v1.OpenShell.ApproveAllDraftChunks:output_type -> openshell.v1.ApproveAllDraftChunksResponse - 163, // 270: openshell.v1.OpenShell.EditDraftChunk:output_type -> openshell.v1.EditDraftChunkResponse - 165, // 271: openshell.v1.OpenShell.UndoDraftChunk:output_type -> openshell.v1.UndoDraftChunkResponse - 167, // 272: openshell.v1.OpenShell.ClearDraftChunks:output_type -> openshell.v1.ClearDraftChunksResponse - 170, // 273: openshell.v1.OpenShell.GetDraftHistory:output_type -> openshell.v1.GetDraftHistoryResponse - 7, // 274: openshell.v1.OpenShell.IssueSandboxToken:output_type -> openshell.v1.IssueSandboxTokenResponse - 9, // 275: openshell.v1.OpenShell.RefreshSandboxToken:output_type -> openshell.v1.RefreshSandboxTokenResponse - 176, // 276: openshell.v1.OpenShell.CreateWorkspace:output_type -> openshell.v1.CreateWorkspaceResponse - 178, // 277: openshell.v1.OpenShell.GetWorkspace:output_type -> openshell.v1.GetWorkspaceResponse - 180, // 278: openshell.v1.OpenShell.ListWorkspaces:output_type -> openshell.v1.ListWorkspacesResponse - 182, // 279: openshell.v1.OpenShell.DeleteWorkspace:output_type -> openshell.v1.DeleteWorkspaceResponse - 185, // 280: openshell.v1.OpenShell.AddWorkspaceMember:output_type -> openshell.v1.AddWorkspaceMemberResponse - 187, // 281: openshell.v1.OpenShell.RemoveWorkspaceMember:output_type -> openshell.v1.RemoveWorkspaceMemberResponse - 189, // 282: openshell.v1.OpenShell.ListWorkspaceMembers:output_type -> openshell.v1.ListWorkspaceMembersResponse - 217, // [217:283] is the sub-list for method output_type - 151, // [151:217] is the sub-list for method input_type - 151, // [151:151] is the sub-list for extension type_name - 151, // [151:151] is the sub-list for extension extendee - 0, // [0:151] is the sub-list for field type_name + 199, // 20: openshell.v1.PlatformEvent.metadata:type_name -> openshell.v1.PlatformEvent.MetadataEntry + 20, // 21: openshell.v1.CreateSandboxRequest.spec:type_name -> openshell.v1.SandboxSpec + 200, // 22: openshell.v1.CreateSandboxRequest.labels:type_name -> openshell.v1.CreateSandboxRequest.LabelsEntry + 201, // 23: openshell.v1.CreateSandboxRequest.annotations:type_name -> openshell.v1.CreateSandboxRequest.AnnotationsEntry + 19, // 24: openshell.v1.SandboxResponse.sandbox:type_name -> openshell.v1.Sandbox + 19, // 25: openshell.v1.ListSandboxesResponse.sandboxes:type_name -> openshell.v1.Sandbox + 222, // 26: openshell.v1.ListSandboxProvidersResponse.providers:type_name -> openshell.datamodel.v1.Provider + 19, // 27: openshell.v1.AttachSandboxProviderResponse.sandbox:type_name -> openshell.v1.Sandbox + 19, // 28: openshell.v1.DetachSandboxProviderResponse.sandbox:type_name -> openshell.v1.Sandbox + 51, // 29: openshell.v1.ListServicesResponse.services:type_name -> openshell.v1.ServiceEndpointResponse + 219, // 30: openshell.v1.ServiceEndpoint.metadata:type_name -> openshell.datamodel.v1.ObjectMeta + 50, // 31: openshell.v1.ServiceEndpointResponse.endpoint:type_name -> openshell.v1.ServiceEndpoint + 202, // 32: openshell.v1.ExecSandboxRequest.environment:type_name -> openshell.v1.ExecSandboxRequest.EnvironmentEntry + 55, // 33: openshell.v1.ExecSandboxEvent.stdout:type_name -> openshell.v1.ExecSandboxStdout + 56, // 34: openshell.v1.ExecSandboxEvent.stderr:type_name -> openshell.v1.ExecSandboxStderr + 57, // 35: openshell.v1.ExecSandboxEvent.exit:type_name -> openshell.v1.ExecSandboxExit + 144, // 36: openshell.v1.TcpForwardInit.ssh:type_name -> openshell.v1.SshRelayTarget + 145, // 37: openshell.v1.TcpForwardInit.tcp:type_name -> openshell.v1.TcpRelayTarget + 59, // 38: openshell.v1.TcpForwardFrame.init:type_name -> openshell.v1.TcpForwardInit + 54, // 39: openshell.v1.ExecSandboxInput.start:type_name -> openshell.v1.ExecSandboxRequest + 62, // 40: openshell.v1.ExecSandboxInput.resize:type_name -> openshell.v1.ExecSandboxWindowResize + 219, // 41: openshell.v1.SshSession.metadata:type_name -> openshell.datamodel.v1.ObjectMeta + 19, // 42: openshell.v1.SandboxStreamEvent.sandbox:type_name -> openshell.v1.Sandbox + 66, // 43: openshell.v1.SandboxStreamEvent.log:type_name -> openshell.v1.SandboxLogLine + 26, // 44: openshell.v1.SandboxStreamEvent.event:type_name -> openshell.v1.PlatformEvent + 67, // 45: openshell.v1.SandboxStreamEvent.warning:type_name -> openshell.v1.SandboxStreamWarning + 155, // 46: openshell.v1.SandboxStreamEvent.draft_policy_update:type_name -> openshell.v1.DraftPolicyUpdate + 203, // 47: openshell.v1.SandboxLogLine.fields:type_name -> openshell.v1.SandboxLogLine.FieldsEntry + 222, // 48: openshell.v1.CreateProviderRequest.provider:type_name -> openshell.datamodel.v1.Provider + 222, // 49: openshell.v1.UpdateProviderRequest.provider:type_name -> openshell.datamodel.v1.Provider + 204, // 50: openshell.v1.UpdateProviderRequest.credential_expires_at_ms:type_name -> openshell.v1.UpdateProviderRequest.CredentialExpiresAtMsEntry + 222, // 51: openshell.v1.ProviderResponse.provider:type_name -> openshell.datamodel.v1.Provider + 222, // 52: openshell.v1.ListProvidersResponse.providers:type_name -> openshell.datamodel.v1.Provider + 97, // 53: openshell.v1.ProviderProfileImportItem.profile:type_name -> openshell.v1.ProviderProfile + 79, // 54: openshell.v1.ProviderCredentialTokenGrant.audience_overrides:type_name -> openshell.v1.ProviderCredentialTokenGrantAudienceOverride + 1, // 55: openshell.v1.ProviderCredentialTokenGrant.grant_type:type_name -> openshell.v1.ProviderCredentialTokenGrantType + 80, // 56: openshell.v1.ProviderCredentialTokenGrant.subject_token:type_name -> openshell.v1.ProviderCredentialTokenGrantSubjectToken + 85, // 57: openshell.v1.ProviderProfileCredential.refresh:type_name -> openshell.v1.ProviderCredentialRefresh + 81, // 58: openshell.v1.ProviderProfileCredential.token_grant:type_name -> openshell.v1.ProviderCredentialTokenGrant + 2, // 59: openshell.v1.ProviderCredentialRefresh.strategy:type_name -> openshell.v1.ProviderCredentialRefreshStrategy + 83, // 60: openshell.v1.ProviderCredentialRefresh.material:type_name -> openshell.v1.ProviderCredentialRefreshMaterial + 84, // 61: openshell.v1.ProviderCredentialRefresh.additional_outputs:type_name -> openshell.v1.ProviderCredentialRefreshOutput + 2, // 62: openshell.v1.ProviderCredentialRefreshStatus.strategy:type_name -> openshell.v1.ProviderCredentialRefreshStrategy + 219, // 63: openshell.v1.StoredProviderCredentialRefreshState.metadata:type_name -> openshell.datamodel.v1.ObjectMeta + 2, // 64: openshell.v1.StoredProviderCredentialRefreshState.strategy:type_name -> openshell.v1.ProviderCredentialRefreshStrategy + 205, // 65: openshell.v1.StoredProviderCredentialRefreshState.material:type_name -> openshell.v1.StoredProviderCredentialRefreshState.MaterialEntry + 206, // 66: openshell.v1.StoredProviderCredentialRefreshState.additional_output_keys:type_name -> openshell.v1.StoredProviderCredentialRefreshState.AdditionalOutputKeysEntry + 86, // 67: openshell.v1.GetProviderRefreshStatusResponse.credentials:type_name -> openshell.v1.ProviderCredentialRefreshStatus + 2, // 68: openshell.v1.ConfigureProviderRefreshRequest.strategy:type_name -> openshell.v1.ProviderCredentialRefreshStrategy + 207, // 69: openshell.v1.ConfigureProviderRefreshRequest.material:type_name -> openshell.v1.ConfigureProviderRefreshRequest.MaterialEntry + 86, // 70: openshell.v1.ConfigureProviderRefreshResponse.status:type_name -> openshell.v1.ProviderCredentialRefreshStatus + 86, // 71: openshell.v1.RotateProviderCredentialResponse.status:type_name -> openshell.v1.ProviderCredentialRefreshStatus + 3, // 72: openshell.v1.ProviderProfile.category:type_name -> openshell.v1.ProviderProfileCategory + 82, // 73: openshell.v1.ProviderProfile.credentials:type_name -> openshell.v1.ProviderProfileCredential + 223, // 74: openshell.v1.ProviderProfile.endpoints:type_name -> openshell.sandbox.v1.NetworkEndpoint + 224, // 75: openshell.v1.ProviderProfile.binaries:type_name -> openshell.sandbox.v1.NetworkBinary + 87, // 76: openshell.v1.ProviderProfile.discovery:type_name -> openshell.v1.ProviderProfileDiscovery + 208, // 77: openshell.v1.ProviderProfile.annotations:type_name -> openshell.v1.ProviderProfile.AnnotationsEntry + 219, // 78: openshell.v1.StoredProviderProfile.metadata:type_name -> openshell.datamodel.v1.ObjectMeta + 97, // 79: openshell.v1.StoredProviderProfile.profile:type_name -> openshell.v1.ProviderProfile + 97, // 80: openshell.v1.ProviderProfileResponse.profile:type_name -> openshell.v1.ProviderProfile + 97, // 81: openshell.v1.ListProviderProfilesResponse.profiles:type_name -> openshell.v1.ProviderProfile + 77, // 82: openshell.v1.ImportProviderProfilesRequest.profiles:type_name -> openshell.v1.ProviderProfileImportItem + 78, // 83: openshell.v1.ImportProviderProfilesResponse.diagnostics:type_name -> openshell.v1.ProviderProfileDiagnostic + 97, // 84: openshell.v1.ImportProviderProfilesResponse.profiles:type_name -> openshell.v1.ProviderProfile + 77, // 85: openshell.v1.UpdateProviderProfilesRequest.profile:type_name -> openshell.v1.ProviderProfileImportItem + 78, // 86: openshell.v1.UpdateProviderProfilesResponse.diagnostics:type_name -> openshell.v1.ProviderProfileDiagnostic + 97, // 87: openshell.v1.UpdateProviderProfilesResponse.profile:type_name -> openshell.v1.ProviderProfile + 77, // 88: openshell.v1.LintProviderProfilesRequest.profiles:type_name -> openshell.v1.ProviderProfileImportItem + 78, // 89: openshell.v1.LintProviderProfilesResponse.diagnostics:type_name -> openshell.v1.ProviderProfileDiagnostic + 111, // 90: openshell.v1.StaticCredentialBinding.endpoints:type_name -> openshell.v1.StaticCredentialEndpointBinding + 209, // 91: openshell.v1.GetSandboxProviderEnvironmentResponse.environment:type_name -> openshell.v1.GetSandboxProviderEnvironmentResponse.EnvironmentEntry + 210, // 92: openshell.v1.GetSandboxProviderEnvironmentResponse.credential_expires_at_ms:type_name -> openshell.v1.GetSandboxProviderEnvironmentResponse.CredentialExpiresAtMsEntry + 211, // 93: openshell.v1.GetSandboxProviderEnvironmentResponse.dynamic_credentials:type_name -> openshell.v1.GetSandboxProviderEnvironmentResponse.DynamicCredentialsEntry + 212, // 94: openshell.v1.GetSandboxProviderEnvironmentResponse.static_credential_bindings:type_name -> openshell.v1.GetSandboxProviderEnvironmentResponse.StaticCredentialBindingsEntry + 220, // 95: openshell.v1.UpdateConfigRequest.policy:type_name -> openshell.sandbox.v1.SandboxPolicy + 225, // 96: openshell.v1.UpdateConfigRequest.setting_value:type_name -> openshell.sandbox.v1.SettingValue + 117, // 97: openshell.v1.UpdateConfigRequest.merge_operations:type_name -> openshell.v1.PolicyMergeOperation + 213, // 98: openshell.v1.UpdateConfigRequest.annotations:type_name -> openshell.v1.UpdateConfigRequest.AnnotationsEntry + 118, // 99: openshell.v1.PolicyMergeOperation.add_rule:type_name -> openshell.v1.AddNetworkRule + 119, // 100: openshell.v1.PolicyMergeOperation.remove_endpoint:type_name -> openshell.v1.RemoveNetworkEndpoint + 120, // 101: openshell.v1.PolicyMergeOperation.remove_rule:type_name -> openshell.v1.RemoveNetworkRule + 121, // 102: openshell.v1.PolicyMergeOperation.add_deny_rules:type_name -> openshell.v1.AddDenyRules + 122, // 103: openshell.v1.PolicyMergeOperation.add_allow_rules:type_name -> openshell.v1.AddAllowRules + 123, // 104: openshell.v1.PolicyMergeOperation.remove_binary:type_name -> openshell.v1.RemoveNetworkBinary + 226, // 105: openshell.v1.AddNetworkRule.rule:type_name -> openshell.sandbox.v1.NetworkPolicyRule + 227, // 106: openshell.v1.AddDenyRules.deny_rules:type_name -> openshell.sandbox.v1.L7DenyRule + 228, // 107: openshell.v1.AddAllowRules.rules:type_name -> openshell.sandbox.v1.L7Rule + 214, // 108: openshell.v1.UpdateConfigResponse.annotations:type_name -> openshell.v1.UpdateConfigResponse.AnnotationsEntry + 131, // 109: openshell.v1.GetSandboxPolicyStatusResponse.revision:type_name -> openshell.v1.SandboxPolicyRevision + 131, // 110: openshell.v1.ListSandboxPoliciesResponse.revisions:type_name -> openshell.v1.SandboxPolicyRevision + 4, // 111: openshell.v1.ReportPolicyStatusRequest.status:type_name -> openshell.v1.PolicyStatus + 4, // 112: openshell.v1.SandboxPolicyRevision.status:type_name -> openshell.v1.PolicyStatus + 220, // 113: openshell.v1.SandboxPolicyRevision.policy:type_name -> openshell.sandbox.v1.SandboxPolicy + 215, // 114: openshell.v1.SandboxPolicyRevision.provenance:type_name -> openshell.v1.SandboxPolicyRevision.ProvenanceEntry + 66, // 115: openshell.v1.PushSandboxLogsRequest.logs:type_name -> openshell.v1.SandboxLogLine + 66, // 116: openshell.v1.GetSandboxLogsResponse.logs:type_name -> openshell.v1.SandboxLogLine + 138, // 117: openshell.v1.SupervisorMessage.hello:type_name -> openshell.v1.SupervisorHello + 141, // 118: openshell.v1.SupervisorMessage.heartbeat:type_name -> openshell.v1.SupervisorHeartbeat + 148, // 119: openshell.v1.SupervisorMessage.relay_open_result:type_name -> openshell.v1.RelayOpenResult + 149, // 120: openshell.v1.SupervisorMessage.relay_close:type_name -> openshell.v1.RelayClose + 139, // 121: openshell.v1.GatewayMessage.session_accepted:type_name -> openshell.v1.SessionAccepted + 140, // 122: openshell.v1.GatewayMessage.session_rejected:type_name -> openshell.v1.SessionRejected + 142, // 123: openshell.v1.GatewayMessage.heartbeat:type_name -> openshell.v1.GatewayHeartbeat + 143, // 124: openshell.v1.GatewayMessage.relay_open:type_name -> openshell.v1.RelayOpen + 149, // 125: openshell.v1.GatewayMessage.relay_close:type_name -> openshell.v1.RelayClose + 144, // 126: openshell.v1.RelayOpen.ssh:type_name -> openshell.v1.SshRelayTarget + 145, // 127: openshell.v1.RelayOpen.tcp:type_name -> openshell.v1.TcpRelayTarget + 146, // 128: openshell.v1.RelayFrame.init:type_name -> openshell.v1.RelayInit + 150, // 129: openshell.v1.DenialSummary.l7_request_samples:type_name -> openshell.v1.L7RequestSample + 152, // 130: openshell.v1.NetworkActivitySummary.denials_by_group:type_name -> openshell.v1.DenialGroupCount + 226, // 131: openshell.v1.PolicyChunk.proposed_rule:type_name -> openshell.sandbox.v1.NetworkPolicyRule + 151, // 132: openshell.v1.SubmitPolicyAnalysisRequest.summaries:type_name -> openshell.v1.DenialSummary + 154, // 133: openshell.v1.SubmitPolicyAnalysisRequest.proposed_chunks:type_name -> openshell.v1.PolicyChunk + 153, // 134: openshell.v1.SubmitPolicyAnalysisRequest.network_activity_summaries:type_name -> openshell.v1.NetworkActivitySummary + 154, // 135: openshell.v1.GetDraftPolicyResponse.chunks:type_name -> openshell.v1.PolicyChunk + 226, // 136: openshell.v1.EditDraftChunkRequest.proposed_rule:type_name -> openshell.sandbox.v1.NetworkPolicyRule + 173, // 137: openshell.v1.GetDraftHistoryResponse.entries:type_name -> openshell.v1.DraftHistoryEntry + 220, // 138: openshell.v1.PolicyRevisionPayload.policy:type_name -> openshell.sandbox.v1.SandboxPolicy + 216, // 139: openshell.v1.PolicyRevisionPayload.provenance:type_name -> openshell.v1.PolicyRevisionPayload.ProvenanceEntry + 226, // 140: openshell.v1.DraftChunkPayload.proposed_rule:type_name -> openshell.sandbox.v1.NetworkPolicyRule + 217, // 141: openshell.v1.StoredPolicyRevision.provenance:type_name -> openshell.v1.StoredPolicyRevision.ProvenanceEntry + 218, // 142: openshell.v1.CreateWorkspaceRequest.labels:type_name -> openshell.v1.CreateWorkspaceRequest.LabelsEntry + 229, // 143: openshell.v1.CreateWorkspaceResponse.workspace:type_name -> openshell.datamodel.v1.Workspace + 229, // 144: openshell.v1.GetWorkspaceResponse.workspace:type_name -> openshell.datamodel.v1.Workspace + 229, // 145: openshell.v1.ListWorkspacesResponse.workspaces:type_name -> openshell.datamodel.v1.Workspace + 219, // 146: openshell.v1.WorkspaceMember.metadata:type_name -> openshell.datamodel.v1.ObjectMeta + 6, // 147: openshell.v1.WorkspaceMember.role:type_name -> openshell.v1.WorkspaceRole + 6, // 148: openshell.v1.AddWorkspaceMemberRequest.role:type_name -> openshell.v1.WorkspaceRole + 187, // 149: openshell.v1.AddWorkspaceMemberResponse.member:type_name -> openshell.v1.WorkspaceMember + 187, // 150: openshell.v1.ListWorkspaceMembersResponse.members:type_name -> openshell.v1.WorkspaceMember + 82, // 151: openshell.v1.GetSandboxProviderEnvironmentResponse.DynamicCredentialsEntry.value:type_name -> openshell.v1.ProviderProfileCredential + 112, // 152: openshell.v1.GetSandboxProviderEnvironmentResponse.StaticCredentialBindingsEntry.value:type_name -> openshell.v1.StaticCredentialBinding + 11, // 153: openshell.v1.OpenShell.Health:input_type -> openshell.v1.HealthRequest + 13, // 154: openshell.v1.OpenShell.GetCurrentUser:input_type -> openshell.v1.GetCurrentUserRequest + 15, // 155: openshell.v1.OpenShell.GetGatewayInfo:input_type -> openshell.v1.GetGatewayInfoRequest + 27, // 156: openshell.v1.OpenShell.CreateSandbox:input_type -> openshell.v1.CreateSandboxRequest + 28, // 157: openshell.v1.OpenShell.GetSandbox:input_type -> openshell.v1.GetSandboxRequest + 29, // 158: openshell.v1.OpenShell.ListSandboxes:input_type -> openshell.v1.ListSandboxesRequest + 30, // 159: openshell.v1.OpenShell.ListSandboxProviders:input_type -> openshell.v1.ListSandboxProvidersRequest + 31, // 160: openshell.v1.OpenShell.AttachSandboxProvider:input_type -> openshell.v1.AttachSandboxProviderRequest + 32, // 161: openshell.v1.OpenShell.DetachSandboxProvider:input_type -> openshell.v1.DetachSandboxProviderRequest + 33, // 162: openshell.v1.OpenShell.DeleteSandbox:input_type -> openshell.v1.DeleteSandboxRequest + 34, // 163: openshell.v1.OpenShell.StopSandbox:input_type -> openshell.v1.StopSandboxRequest + 35, // 164: openshell.v1.OpenShell.StartSandbox:input_type -> openshell.v1.StartSandboxRequest + 42, // 165: openshell.v1.OpenShell.CreateSshSession:input_type -> openshell.v1.CreateSshSessionRequest + 44, // 166: openshell.v1.OpenShell.ExposeService:input_type -> openshell.v1.ExposeServiceRequest + 45, // 167: openshell.v1.OpenShell.GetService:input_type -> openshell.v1.GetServiceRequest + 46, // 168: openshell.v1.OpenShell.ListServices:input_type -> openshell.v1.ListServicesRequest + 48, // 169: openshell.v1.OpenShell.DeleteService:input_type -> openshell.v1.DeleteServiceRequest + 52, // 170: openshell.v1.OpenShell.RevokeSshSession:input_type -> openshell.v1.RevokeSshSessionRequest + 54, // 171: openshell.v1.OpenShell.ExecSandbox:input_type -> openshell.v1.ExecSandboxRequest + 60, // 172: openshell.v1.OpenShell.ForwardTcp:input_type -> openshell.v1.TcpForwardFrame + 61, // 173: openshell.v1.OpenShell.ExecSandboxInteractive:input_type -> openshell.v1.ExecSandboxInput + 68, // 174: openshell.v1.OpenShell.CreateProvider:input_type -> openshell.v1.CreateProviderRequest + 69, // 175: openshell.v1.OpenShell.GetProvider:input_type -> openshell.v1.GetProviderRequest + 70, // 176: openshell.v1.OpenShell.ListProviders:input_type -> openshell.v1.ListProvidersRequest + 75, // 177: openshell.v1.OpenShell.ListProviderProfiles:input_type -> openshell.v1.ListProviderProfilesRequest + 76, // 178: openshell.v1.OpenShell.GetProviderProfile:input_type -> openshell.v1.GetProviderProfileRequest + 101, // 179: openshell.v1.OpenShell.ImportProviderProfiles:input_type -> openshell.v1.ImportProviderProfilesRequest + 103, // 180: openshell.v1.OpenShell.UpdateProviderProfiles:input_type -> openshell.v1.UpdateProviderProfilesRequest + 105, // 181: openshell.v1.OpenShell.LintProviderProfiles:input_type -> openshell.v1.LintProviderProfilesRequest + 71, // 182: openshell.v1.OpenShell.UpdateProvider:input_type -> openshell.v1.UpdateProviderRequest + 89, // 183: openshell.v1.OpenShell.GetProviderRefreshStatus:input_type -> openshell.v1.GetProviderRefreshStatusRequest + 91, // 184: openshell.v1.OpenShell.ConfigureProviderRefresh:input_type -> openshell.v1.ConfigureProviderRefreshRequest + 93, // 185: openshell.v1.OpenShell.RotateProviderCredential:input_type -> openshell.v1.RotateProviderCredentialRequest + 95, // 186: openshell.v1.OpenShell.DeleteProviderRefresh:input_type -> openshell.v1.DeleteProviderRefreshRequest + 72, // 187: openshell.v1.OpenShell.DeleteProvider:input_type -> openshell.v1.DeleteProviderRequest + 108, // 188: openshell.v1.OpenShell.DeleteProviderProfile:input_type -> openshell.v1.DeleteProviderProfileRequest + 230, // 189: openshell.v1.OpenShell.GetSandboxConfig:input_type -> openshell.sandbox.v1.GetSandboxConfigRequest + 231, // 190: openshell.v1.OpenShell.GetGatewayConfig:input_type -> openshell.sandbox.v1.GetGatewayConfigRequest + 116, // 191: openshell.v1.OpenShell.UpdateConfig:input_type -> openshell.v1.UpdateConfigRequest + 125, // 192: openshell.v1.OpenShell.GetSandboxPolicyStatus:input_type -> openshell.v1.GetSandboxPolicyStatusRequest + 127, // 193: openshell.v1.OpenShell.ListSandboxPolicies:input_type -> openshell.v1.ListSandboxPoliciesRequest + 129, // 194: openshell.v1.OpenShell.ReportPolicyStatus:input_type -> openshell.v1.ReportPolicyStatusRequest + 110, // 195: openshell.v1.OpenShell.GetSandboxProviderEnvironment:input_type -> openshell.v1.GetSandboxProviderEnvironmentRequest + 114, // 196: openshell.v1.OpenShell.ExchangeProviderSubjectToken:input_type -> openshell.v1.ExchangeProviderSubjectTokenRequest + 132, // 197: openshell.v1.OpenShell.GetSandboxLogs:input_type -> openshell.v1.GetSandboxLogsRequest + 133, // 198: openshell.v1.OpenShell.PushSandboxLogs:input_type -> openshell.v1.PushSandboxLogsRequest + 136, // 199: openshell.v1.OpenShell.ConnectSupervisor:input_type -> openshell.v1.SupervisorMessage + 147, // 200: openshell.v1.OpenShell.RelayStream:input_type -> openshell.v1.RelayFrame + 64, // 201: openshell.v1.OpenShell.WatchSandbox:input_type -> openshell.v1.WatchSandboxRequest + 156, // 202: openshell.v1.OpenShell.SubmitPolicyAnalysis:input_type -> openshell.v1.SubmitPolicyAnalysisRequest + 158, // 203: openshell.v1.OpenShell.GetDraftPolicy:input_type -> openshell.v1.GetDraftPolicyRequest + 160, // 204: openshell.v1.OpenShell.ApproveDraftChunk:input_type -> openshell.v1.ApproveDraftChunkRequest + 162, // 205: openshell.v1.OpenShell.RejectDraftChunk:input_type -> openshell.v1.RejectDraftChunkRequest + 164, // 206: openshell.v1.OpenShell.ApproveAllDraftChunks:input_type -> openshell.v1.ApproveAllDraftChunksRequest + 166, // 207: openshell.v1.OpenShell.EditDraftChunk:input_type -> openshell.v1.EditDraftChunkRequest + 168, // 208: openshell.v1.OpenShell.UndoDraftChunk:input_type -> openshell.v1.UndoDraftChunkRequest + 170, // 209: openshell.v1.OpenShell.ClearDraftChunks:input_type -> openshell.v1.ClearDraftChunksRequest + 172, // 210: openshell.v1.OpenShell.GetDraftHistory:input_type -> openshell.v1.GetDraftHistoryRequest + 7, // 211: openshell.v1.OpenShell.IssueSandboxToken:input_type -> openshell.v1.IssueSandboxTokenRequest + 9, // 212: openshell.v1.OpenShell.RefreshSandboxToken:input_type -> openshell.v1.RefreshSandboxTokenRequest + 179, // 213: openshell.v1.OpenShell.CreateWorkspace:input_type -> openshell.v1.CreateWorkspaceRequest + 181, // 214: openshell.v1.OpenShell.GetWorkspace:input_type -> openshell.v1.GetWorkspaceRequest + 183, // 215: openshell.v1.OpenShell.ListWorkspaces:input_type -> openshell.v1.ListWorkspacesRequest + 185, // 216: openshell.v1.OpenShell.DeleteWorkspace:input_type -> openshell.v1.DeleteWorkspaceRequest + 188, // 217: openshell.v1.OpenShell.AddWorkspaceMember:input_type -> openshell.v1.AddWorkspaceMemberRequest + 190, // 218: openshell.v1.OpenShell.RemoveWorkspaceMember:input_type -> openshell.v1.RemoveWorkspaceMemberRequest + 192, // 219: openshell.v1.OpenShell.ListWorkspaceMembers:input_type -> openshell.v1.ListWorkspaceMembersRequest + 12, // 220: openshell.v1.OpenShell.Health:output_type -> openshell.v1.HealthResponse + 14, // 221: openshell.v1.OpenShell.GetCurrentUser:output_type -> openshell.v1.GetCurrentUserResponse + 16, // 222: openshell.v1.OpenShell.GetGatewayInfo:output_type -> openshell.v1.GetGatewayInfoResponse + 36, // 223: openshell.v1.OpenShell.CreateSandbox:output_type -> openshell.v1.SandboxResponse + 36, // 224: openshell.v1.OpenShell.GetSandbox:output_type -> openshell.v1.SandboxResponse + 37, // 225: openshell.v1.OpenShell.ListSandboxes:output_type -> openshell.v1.ListSandboxesResponse + 38, // 226: openshell.v1.OpenShell.ListSandboxProviders:output_type -> openshell.v1.ListSandboxProvidersResponse + 39, // 227: openshell.v1.OpenShell.AttachSandboxProvider:output_type -> openshell.v1.AttachSandboxProviderResponse + 40, // 228: openshell.v1.OpenShell.DetachSandboxProvider:output_type -> openshell.v1.DetachSandboxProviderResponse + 41, // 229: openshell.v1.OpenShell.DeleteSandbox:output_type -> openshell.v1.DeleteSandboxResponse + 36, // 230: openshell.v1.OpenShell.StopSandbox:output_type -> openshell.v1.SandboxResponse + 36, // 231: openshell.v1.OpenShell.StartSandbox:output_type -> openshell.v1.SandboxResponse + 43, // 232: openshell.v1.OpenShell.CreateSshSession:output_type -> openshell.v1.CreateSshSessionResponse + 51, // 233: openshell.v1.OpenShell.ExposeService:output_type -> openshell.v1.ServiceEndpointResponse + 51, // 234: openshell.v1.OpenShell.GetService:output_type -> openshell.v1.ServiceEndpointResponse + 47, // 235: openshell.v1.OpenShell.ListServices:output_type -> openshell.v1.ListServicesResponse + 49, // 236: openshell.v1.OpenShell.DeleteService:output_type -> openshell.v1.DeleteServiceResponse + 53, // 237: openshell.v1.OpenShell.RevokeSshSession:output_type -> openshell.v1.RevokeSshSessionResponse + 58, // 238: openshell.v1.OpenShell.ExecSandbox:output_type -> openshell.v1.ExecSandboxEvent + 60, // 239: openshell.v1.OpenShell.ForwardTcp:output_type -> openshell.v1.TcpForwardFrame + 58, // 240: openshell.v1.OpenShell.ExecSandboxInteractive:output_type -> openshell.v1.ExecSandboxEvent + 73, // 241: openshell.v1.OpenShell.CreateProvider:output_type -> openshell.v1.ProviderResponse + 73, // 242: openshell.v1.OpenShell.GetProvider:output_type -> openshell.v1.ProviderResponse + 74, // 243: openshell.v1.OpenShell.ListProviders:output_type -> openshell.v1.ListProvidersResponse + 100, // 244: openshell.v1.OpenShell.ListProviderProfiles:output_type -> openshell.v1.ListProviderProfilesResponse + 99, // 245: openshell.v1.OpenShell.GetProviderProfile:output_type -> openshell.v1.ProviderProfileResponse + 102, // 246: openshell.v1.OpenShell.ImportProviderProfiles:output_type -> openshell.v1.ImportProviderProfilesResponse + 104, // 247: openshell.v1.OpenShell.UpdateProviderProfiles:output_type -> openshell.v1.UpdateProviderProfilesResponse + 106, // 248: openshell.v1.OpenShell.LintProviderProfiles:output_type -> openshell.v1.LintProviderProfilesResponse + 73, // 249: openshell.v1.OpenShell.UpdateProvider:output_type -> openshell.v1.ProviderResponse + 90, // 250: openshell.v1.OpenShell.GetProviderRefreshStatus:output_type -> openshell.v1.GetProviderRefreshStatusResponse + 92, // 251: openshell.v1.OpenShell.ConfigureProviderRefresh:output_type -> openshell.v1.ConfigureProviderRefreshResponse + 94, // 252: openshell.v1.OpenShell.RotateProviderCredential:output_type -> openshell.v1.RotateProviderCredentialResponse + 96, // 253: openshell.v1.OpenShell.DeleteProviderRefresh:output_type -> openshell.v1.DeleteProviderRefreshResponse + 107, // 254: openshell.v1.OpenShell.DeleteProvider:output_type -> openshell.v1.DeleteProviderResponse + 109, // 255: openshell.v1.OpenShell.DeleteProviderProfile:output_type -> openshell.v1.DeleteProviderProfileResponse + 232, // 256: openshell.v1.OpenShell.GetSandboxConfig:output_type -> openshell.sandbox.v1.GetSandboxConfigResponse + 233, // 257: openshell.v1.OpenShell.GetGatewayConfig:output_type -> openshell.sandbox.v1.GetGatewayConfigResponse + 124, // 258: openshell.v1.OpenShell.UpdateConfig:output_type -> openshell.v1.UpdateConfigResponse + 126, // 259: openshell.v1.OpenShell.GetSandboxPolicyStatus:output_type -> openshell.v1.GetSandboxPolicyStatusResponse + 128, // 260: openshell.v1.OpenShell.ListSandboxPolicies:output_type -> openshell.v1.ListSandboxPoliciesResponse + 130, // 261: openshell.v1.OpenShell.ReportPolicyStatus:output_type -> openshell.v1.ReportPolicyStatusResponse + 113, // 262: openshell.v1.OpenShell.GetSandboxProviderEnvironment:output_type -> openshell.v1.GetSandboxProviderEnvironmentResponse + 115, // 263: openshell.v1.OpenShell.ExchangeProviderSubjectToken:output_type -> openshell.v1.ExchangeProviderSubjectTokenResponse + 135, // 264: openshell.v1.OpenShell.GetSandboxLogs:output_type -> openshell.v1.GetSandboxLogsResponse + 134, // 265: openshell.v1.OpenShell.PushSandboxLogs:output_type -> openshell.v1.PushSandboxLogsResponse + 137, // 266: openshell.v1.OpenShell.ConnectSupervisor:output_type -> openshell.v1.GatewayMessage + 147, // 267: openshell.v1.OpenShell.RelayStream:output_type -> openshell.v1.RelayFrame + 65, // 268: openshell.v1.OpenShell.WatchSandbox:output_type -> openshell.v1.SandboxStreamEvent + 157, // 269: openshell.v1.OpenShell.SubmitPolicyAnalysis:output_type -> openshell.v1.SubmitPolicyAnalysisResponse + 159, // 270: openshell.v1.OpenShell.GetDraftPolicy:output_type -> openshell.v1.GetDraftPolicyResponse + 161, // 271: openshell.v1.OpenShell.ApproveDraftChunk:output_type -> openshell.v1.ApproveDraftChunkResponse + 163, // 272: openshell.v1.OpenShell.RejectDraftChunk:output_type -> openshell.v1.RejectDraftChunkResponse + 165, // 273: openshell.v1.OpenShell.ApproveAllDraftChunks:output_type -> openshell.v1.ApproveAllDraftChunksResponse + 167, // 274: openshell.v1.OpenShell.EditDraftChunk:output_type -> openshell.v1.EditDraftChunkResponse + 169, // 275: openshell.v1.OpenShell.UndoDraftChunk:output_type -> openshell.v1.UndoDraftChunkResponse + 171, // 276: openshell.v1.OpenShell.ClearDraftChunks:output_type -> openshell.v1.ClearDraftChunksResponse + 174, // 277: openshell.v1.OpenShell.GetDraftHistory:output_type -> openshell.v1.GetDraftHistoryResponse + 8, // 278: openshell.v1.OpenShell.IssueSandboxToken:output_type -> openshell.v1.IssueSandboxTokenResponse + 10, // 279: openshell.v1.OpenShell.RefreshSandboxToken:output_type -> openshell.v1.RefreshSandboxTokenResponse + 180, // 280: openshell.v1.OpenShell.CreateWorkspace:output_type -> openshell.v1.CreateWorkspaceResponse + 182, // 281: openshell.v1.OpenShell.GetWorkspace:output_type -> openshell.v1.GetWorkspaceResponse + 184, // 282: openshell.v1.OpenShell.ListWorkspaces:output_type -> openshell.v1.ListWorkspacesResponse + 186, // 283: openshell.v1.OpenShell.DeleteWorkspace:output_type -> openshell.v1.DeleteWorkspaceResponse + 189, // 284: openshell.v1.OpenShell.AddWorkspaceMember:output_type -> openshell.v1.AddWorkspaceMemberResponse + 191, // 285: openshell.v1.OpenShell.RemoveWorkspaceMember:output_type -> openshell.v1.RemoveWorkspaceMemberResponse + 193, // 286: openshell.v1.OpenShell.ListWorkspaceMembers:output_type -> openshell.v1.ListWorkspaceMembersResponse + 220, // [220:287] is the sub-list for method output_type + 153, // [153:220] is the sub-list for method input_type + 153, // [153:153] is the sub-list for extension type_name + 153, // [153:153] is the sub-list for extension extendee + 0, // [0:153] is the sub-list for field type_name } func init() { file_openshell_proto_init() } @@ -14815,8 +15128,8 @@ func file_openshell_proto_init() { (*SandboxStreamEvent_Warning)(nil), (*SandboxStreamEvent_DraftPolicyUpdate)(nil), } - file_openshell_proto_msgTypes[83].OneofWrappers = []any{} - file_openshell_proto_msgTypes[107].OneofWrappers = []any{ + file_openshell_proto_msgTypes[84].OneofWrappers = []any{} + file_openshell_proto_msgTypes[110].OneofWrappers = []any{ (*PolicyMergeOperation_AddRule)(nil), (*PolicyMergeOperation_RemoveEndpoint)(nil), (*PolicyMergeOperation_RemoveRule)(nil), @@ -14824,36 +15137,36 @@ func file_openshell_proto_init() { (*PolicyMergeOperation_AddAllowRules)(nil), (*PolicyMergeOperation_RemoveBinary)(nil), } - file_openshell_proto_msgTypes[126].OneofWrappers = []any{ + file_openshell_proto_msgTypes[129].OneofWrappers = []any{ (*SupervisorMessage_Hello)(nil), (*SupervisorMessage_Heartbeat)(nil), (*SupervisorMessage_RelayOpenResult)(nil), (*SupervisorMessage_RelayClose)(nil), } - file_openshell_proto_msgTypes[127].OneofWrappers = []any{ + file_openshell_proto_msgTypes[130].OneofWrappers = []any{ (*GatewayMessage_SessionAccepted)(nil), (*GatewayMessage_SessionRejected)(nil), (*GatewayMessage_Heartbeat)(nil), (*GatewayMessage_RelayOpen)(nil), (*GatewayMessage_RelayClose)(nil), } - file_openshell_proto_msgTypes[133].OneofWrappers = []any{ + file_openshell_proto_msgTypes[136].OneofWrappers = []any{ (*RelayOpen_Ssh)(nil), (*RelayOpen_Tcp)(nil), } - file_openshell_proto_msgTypes[137].OneofWrappers = []any{ + file_openshell_proto_msgTypes[140].OneofWrappers = []any{ (*RelayFrame_Init)(nil), (*RelayFrame_Data)(nil), } - file_openshell_proto_msgTypes[167].OneofWrappers = []any{} - file_openshell_proto_msgTypes[168].OneofWrappers = []any{} + file_openshell_proto_msgTypes[170].OneofWrappers = []any{} + file_openshell_proto_msgTypes[171].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: 6, - NumMessages: 209, + NumEnums: 7, + NumMessages: 212, 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 92c94ef299..a3eb9044e4 100644 --- a/sdk/go/proto/openshellv1/openshell_grpc.pb.go +++ b/sdk/go/proto/openshellv1/openshell_grpc.pb.go @@ -66,6 +66,7 @@ const ( OpenShell_ListSandboxPolicies_FullMethodName = "/openshell.v1.OpenShell/ListSandboxPolicies" OpenShell_ReportPolicyStatus_FullMethodName = "/openshell.v1.OpenShell/ReportPolicyStatus" 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" @@ -199,6 +200,9 @@ type OpenShellClient interface { ReportPolicyStatus(ctx context.Context, in *ReportPolicyStatusRequest, opts ...grpc.CallOption) (*ReportPolicyStatusResponse, error) // Get provider environment for a sandbox (called by sandbox supervisor at startup). GetSandboxProviderEnvironment(ctx context.Context, in *GetSandboxProviderEnvironmentRequest, opts ...grpc.CallOption) (*GetSandboxProviderEnvironmentResponse, error) + // Exchange a stored provider subject token for an intermediate token scoped + // to the calling supervisor's SPIFFE identity. + ExchangeProviderSubjectToken(ctx context.Context, in *ExchangeProviderSubjectTokenRequest, opts ...grpc.CallOption) (*ExchangeProviderSubjectTokenResponse, error) // Fetch recent sandbox logs (one-shot). GetSandboxLogs(ctx context.Context, in *GetSandboxLogsRequest, opts ...grpc.CallOption) (*GetSandboxLogsResponse, error) // Push sandbox supervisor logs to the server (client-streaming). @@ -730,6 +734,16 @@ func (c *openShellClient) GetSandboxProviderEnvironment(ctx context.Context, in return out, nil } +func (c *openShellClient) ExchangeProviderSubjectToken(ctx context.Context, in *ExchangeProviderSubjectTokenRequest, opts ...grpc.CallOption) (*ExchangeProviderSubjectTokenResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ExchangeProviderSubjectTokenResponse) + err := c.cc.Invoke(ctx, OpenShell_ExchangeProviderSubjectToken_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + func (c *openShellClient) GetSandboxLogs(ctx context.Context, in *GetSandboxLogsRequest, opts ...grpc.CallOption) (*GetSandboxLogsResponse, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(GetSandboxLogsResponse) @@ -1086,6 +1100,9 @@ type OpenShellServer interface { ReportPolicyStatus(context.Context, *ReportPolicyStatusRequest) (*ReportPolicyStatusResponse, error) // Get provider environment for a sandbox (called by sandbox supervisor at startup). GetSandboxProviderEnvironment(context.Context, *GetSandboxProviderEnvironmentRequest) (*GetSandboxProviderEnvironmentResponse, error) + // Exchange a stored provider subject token for an intermediate token scoped + // to the calling supervisor's SPIFFE identity. + ExchangeProviderSubjectToken(context.Context, *ExchangeProviderSubjectTokenRequest) (*ExchangeProviderSubjectTokenResponse, error) // Fetch recent sandbox logs (one-shot). GetSandboxLogs(context.Context, *GetSandboxLogsRequest) (*GetSandboxLogsResponse, error) // Push sandbox supervisor logs to the server (client-streaming). @@ -1301,6 +1318,9 @@ func (UnimplementedOpenShellServer) ReportPolicyStatus(context.Context, *ReportP func (UnimplementedOpenShellServer) GetSandboxProviderEnvironment(context.Context, *GetSandboxProviderEnvironmentRequest) (*GetSandboxProviderEnvironmentResponse, error) { return nil, status.Error(codes.Unimplemented, "method GetSandboxProviderEnvironment not implemented") } +func (UnimplementedOpenShellServer) ExchangeProviderSubjectToken(context.Context, *ExchangeProviderSubjectTokenRequest) (*ExchangeProviderSubjectTokenResponse, error) { + return nil, status.Error(codes.Unimplemented, "method ExchangeProviderSubjectToken not implemented") +} func (UnimplementedOpenShellServer) GetSandboxLogs(context.Context, *GetSandboxLogsRequest) (*GetSandboxLogsResponse, error) { return nil, status.Error(codes.Unimplemented, "method GetSandboxLogs not implemented") } @@ -2136,6 +2156,24 @@ func _OpenShell_GetSandboxProviderEnvironment_Handler(srv interface{}, ctx conte return interceptor(ctx, in, info, handler) } +func _OpenShell_ExchangeProviderSubjectToken_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ExchangeProviderSubjectTokenRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(OpenShellServer).ExchangeProviderSubjectToken(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: OpenShell_ExchangeProviderSubjectToken_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(OpenShellServer).ExchangeProviderSubjectToken(ctx, req.(*ExchangeProviderSubjectTokenRequest)) + } + return interceptor(ctx, in, info, handler) +} + func _OpenShell_GetSandboxLogs_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(GetSandboxLogsRequest) if err := dec(in); err != nil { @@ -2677,6 +2715,10 @@ var OpenShell_ServiceDesc = grpc.ServiceDesc{ MethodName: "GetSandboxProviderEnvironment", Handler: _OpenShell_GetSandboxProviderEnvironment_Handler, }, + { + MethodName: "ExchangeProviderSubjectToken", + Handler: _OpenShell_ExchangeProviderSubjectToken_Handler, + }, { MethodName: "GetSandboxLogs", Handler: _OpenShell_GetSandboxLogs_Handler, From aad7a62f4b89cbefbcf1deee39a3e9a576a90ec2 Mon Sep 17 00:00:00 2001 From: Gordon Sim Date: Tue, 23 Jun 2026 08:11:48 +0100 Subject: [PATCH 2/8] test(proxy): add further tests for token exchange Signed-off-by: Gordon Sim --- .../src/l7/relay.rs | 302 ++++++++++++++++++ .../src/l7/token_grant_injection.rs | 101 +++++- .../openshell-supervisor-network/src/proxy.rs | 107 ++++++- 3 files changed, 495 insertions(+), 15 deletions(-) diff --git a/crates/openshell-supervisor-network/src/l7/relay.rs b/crates/openshell-supervisor-network/src/l7/relay.rs index 6bd0a98476..045151506e 100644 --- a/crates/openshell-supervisor-network/src/l7/relay.rs +++ b/crates/openshell-supervisor-network/src/l7/relay.rs @@ -3238,6 +3238,37 @@ network_policies: (config, tunnel_engine, ctx, fixture) } + fn rest_token_exchange_relay_context( + resolver_response: std::result::Result<&str, &str>, + ) -> ( + L7EndpointConfig, + TunnelPolicyEngine, + L7EvalContext, + crate::l7::token_grant_injection::test_support::TokenGrantTestFixture, + ) { + let (config, tunnel_engine, mut ctx, _) = + rest_token_grant_relay_context(Ok("unused-token")); + let provider_key = "api.example.test\t8080\t/v1/**\tprovider:access_token"; + let fixture = match resolver_response { + Ok(token) => { + crate::l7::token_grant_injection::test_support::TokenGrantTestFixture::success_token_exchange( + provider_key, + token, + ) + } + Err(error) => { + crate::l7::token_grant_injection::test_support::TokenGrantTestFixture::failure_token_exchange( + provider_key, + error, + ) + } + }; + ctx.dynamic_credentials = Some(fixture.dynamic_credentials()); + ctx.token_grant_resolver = Some(fixture.resolver()); + + (config, tunnel_engine, ctx, fixture) + } + fn middleware_relay_context( middleware_impl: &str, on_error: &str, @@ -3646,6 +3677,36 @@ network_policies: .await; } + fn passthrough_token_exchange_relay_context( + resolver_response: std::result::Result<&str, &str>, + ) -> ( + PolicyGenerationGuard, + L7EvalContext, + crate::l7::token_grant_injection::test_support::TokenGrantTestFixture, + ) { + let (generation_guard, mut ctx, _) = + passthrough_token_grant_relay_context(Ok("unused-token")); + let provider_key = "api.example.test\t8080\t/v1/**\tprovider:access_token"; + let fixture = match resolver_response { + Ok(token) => { + crate::l7::token_grant_injection::test_support::TokenGrantTestFixture::success_token_exchange( + provider_key, + token, + ) + } + Err(error) => { + crate::l7::token_grant_injection::test_support::TokenGrantTestFixture::failure_token_exchange( + provider_key, + error, + ) + } + }; + ctx.dynamic_credentials = Some(fixture.dynamic_credentials()); + ctx.token_grant_resolver = Some(fixture.resolver()); + + (generation_guard, ctx, fixture) + } + fn jsonrpc_test_relay_context() -> (L7EndpointConfig, TunnelPolicyEngine, L7EvalContext) { jsonrpc_test_relay_context_with_path("/rpc") } @@ -4020,6 +4081,128 @@ network_policies: fixture.assert_one_request("api.example.test\t8080\t/v1/**\tprovider:access_token"); } + #[tokio::test] + async fn l7_rest_relay_injects_token_exchange_authorization_header() { + let (config, tunnel_engine, ctx, fixture) = + rest_token_exchange_relay_context(Ok("grant-token")); + let (mut app, mut relay_client) = tokio::io::duplex(8192); + let (mut relay_upstream, mut upstream) = tokio::io::duplex(8192); + let relay = tokio::spawn(async move { + relay_with_inspection( + &config, + tunnel_engine, + &mut relay_client, + &mut relay_upstream, + &ctx, + ) + .await + }); + + app.write_all( + b"GET /v1/projects HTTP/1.1\r\nHost: api.example.test\r\nAuthorization: Bearer stale-token\r\nConnection: close\r\n\r\n", + ) + .await + .unwrap(); + + let mut upstream_request = [0u8; 1024]; + let n = tokio::time::timeout( + std::time::Duration::from_secs(1), + upstream.read(&mut upstream_request), + ) + .await + .expect("request should reach upstream") + .unwrap(); + let upstream_request = String::from_utf8_lossy(&upstream_request[..n]); + + assert!( + upstream_request.starts_with("GET /v1/projects HTTP/1.1\r\n"), + "unexpected upstream request: {upstream_request:?}" + ); + assert!(upstream_request.contains("Authorization: Bearer grant-token\r\n")); + assert!(!upstream_request.contains("stale-token")); + assert_eq!(authorization_header_count(&upstream_request), 1); + + upstream + .write_all(b"HTTP/1.1 204 No Content\r\nContent-Length: 0\r\nConnection: close\r\n\r\n") + .await + .unwrap(); + + let mut client_response = [0u8; 512]; + let n = tokio::time::timeout( + std::time::Duration::from_secs(1), + app.read(&mut client_response), + ) + .await + .expect("response should reach client") + .unwrap(); + assert!(String::from_utf8_lossy(&client_response[..n]).contains("204 No Content")); + drop(app); + + tokio::time::timeout(std::time::Duration::from_secs(1), relay) + .await + .expect("relay should finish") + .unwrap() + .unwrap(); + + fixture.assert_one_token_exchange_request( + "api.example.test\t8080\t/v1/**\tprovider:access_token", + ); + } + + #[tokio::test] + async fn l7_rest_relay_token_exchange_failure_does_not_forward_request() { + let (config, tunnel_engine, ctx, fixture) = + rest_token_exchange_relay_context(Err("oauth unavailable")); + let (mut app, mut relay_client) = tokio::io::duplex(8192); + let (mut relay_upstream, mut upstream) = tokio::io::duplex(8192); + let relay = tokio::spawn(async move { + relay_with_inspection( + &config, + tunnel_engine, + &mut relay_client, + &mut relay_upstream, + &ctx, + ) + .await + }); + + app.write_all( + b"GET /v1/projects HTTP/1.1\r\nHost: api.example.test\r\nConnection: close\r\n\r\n", + ) + .await + .unwrap(); + + tokio::time::timeout(std::time::Duration::from_secs(1), relay) + .await + .expect("relay should finish") + .unwrap() + .unwrap(); + + let mut client_response = [0u8; 512]; + let n = tokio::time::timeout( + std::time::Duration::from_secs(1), + app.read(&mut client_response), + ) + .await + .expect("bad gateway response should reach client") + .unwrap(); + assert!(String::from_utf8_lossy(&client_response[..n]).contains("502 Bad Gateway")); + + let mut upstream_request = [0u8; 128]; + let n = tokio::time::timeout( + std::time::Duration::from_secs(1), + upstream.read(&mut upstream_request), + ) + .await + .expect("upstream should close without forwarded data") + .unwrap(); + assert_eq!(n, 0, "unauthenticated request must not reach upstream"); + + fixture.assert_one_token_exchange_request( + "api.example.test\t8080\t/v1/**\tprovider:access_token", + ); + } + #[tokio::test] async fn l7_rest_middleware_redacts_body_before_upstream() { let (config, tunnel_engine, ctx) = @@ -6373,6 +6556,125 @@ network_policies: fixture.assert_one_request("api.example.test\t8080\t/v1/**\tprovider:access_token"); } + #[tokio::test] + async fn passthrough_relay_injects_token_exchange_authorization_header() { + let (generation_guard, ctx, fixture) = + passthrough_token_exchange_relay_context(Ok("grant-token")); + let (mut app, mut relay_client) = tokio::io::duplex(8192); + let (mut relay_upstream, mut upstream) = tokio::io::duplex(8192); + let relay = tokio::spawn(async move { + relay_passthrough_with_credentials( + &mut relay_client, + &mut relay_upstream, + &ctx, + &generation_guard, + None, + ) + .await + }); + + app.write_all( + b"GET /v1/projects HTTP/1.1\r\nHost: api.example.test\r\nAuthorization: Bearer stale-token\r\nConnection: close\r\n\r\n", + ) + .await + .unwrap(); + + let mut upstream_request = [0u8; 1024]; + let n = tokio::time::timeout( + std::time::Duration::from_secs(1), + upstream.read(&mut upstream_request), + ) + .await + .expect("request should reach upstream") + .unwrap(); + let upstream_request = String::from_utf8_lossy(&upstream_request[..n]); + + assert!(upstream_request.starts_with("GET /v1/projects HTTP/1.1\r\n")); + assert!(upstream_request.contains("Authorization: Bearer grant-token\r\n")); + assert!(!upstream_request.contains("stale-token")); + assert_eq!(authorization_header_count(&upstream_request), 1); + + upstream + .write_all(b"HTTP/1.1 204 No Content\r\nContent-Length: 0\r\nConnection: close\r\n\r\n") + .await + .unwrap(); + + let mut client_response = [0u8; 512]; + let n = tokio::time::timeout( + std::time::Duration::from_secs(1), + app.read(&mut client_response), + ) + .await + .expect("response should reach client") + .unwrap(); + assert!(String::from_utf8_lossy(&client_response[..n]).contains("204 No Content")); + drop(app); + + tokio::time::timeout(std::time::Duration::from_secs(1), relay) + .await + .expect("relay should finish") + .unwrap() + .unwrap(); + + fixture.assert_one_token_exchange_request( + "api.example.test\t8080\t/v1/**\tprovider:access_token", + ); + } + + #[tokio::test] + async fn passthrough_relay_token_exchange_failure_returns_bad_gateway_without_forwarding() { + let (generation_guard, ctx, fixture) = + passthrough_token_exchange_relay_context(Err("oauth unavailable")); + let (mut app, mut relay_client) = tokio::io::duplex(8192); + let (mut relay_upstream, mut upstream) = tokio::io::duplex(8192); + let relay = tokio::spawn(async move { + relay_passthrough_with_credentials( + &mut relay_client, + &mut relay_upstream, + &ctx, + &generation_guard, + None, + ) + .await + }); + + app.write_all( + b"GET /v1/projects HTTP/1.1\r\nHost: api.example.test\r\nConnection: close\r\n\r\n", + ) + .await + .unwrap(); + + tokio::time::timeout(std::time::Duration::from_secs(1), relay) + .await + .expect("relay should finish") + .unwrap() + .unwrap(); + + let mut client_response = [0u8; 512]; + let n = tokio::time::timeout( + std::time::Duration::from_secs(1), + app.read(&mut client_response), + ) + .await + .expect("bad gateway response should reach client") + .unwrap(); + assert!(String::from_utf8_lossy(&client_response[..n]).contains("502 Bad Gateway")); + + let mut upstream_request = [0u8; 128]; + let n = tokio::time::timeout( + std::time::Duration::from_secs(1), + upstream.read(&mut upstream_request), + ) + .await + .expect("upstream should close without forwarded data") + .unwrap(); + assert_eq!(n, 0, "unauthenticated request must not reach upstream"); + + fixture.assert_one_token_exchange_request( + "api.example.test\t8080\t/v1/**\tprovider:access_token", + ); + } + #[test] fn websocket_text_policy_requires_explicit_message_rule() { let data = r#" diff --git a/crates/openshell-supervisor-network/src/l7/token_grant_injection.rs b/crates/openshell-supervisor-network/src/l7/token_grant_injection.rs index 969060bc96..5d7e8005e2 100644 --- a/crates/openshell-supervisor-network/src/l7/token_grant_injection.rs +++ b/crates/openshell-supervisor-network/src/l7/token_grant_injection.rs @@ -391,11 +391,27 @@ pub mod test_support { Self::new(key, Ok(token)) } + pub fn success_token_exchange(key: &str, token: &str) -> Self { + Self::new_with_grant(key, Ok(token), token_exchange_grant()) + } + pub fn failure(key: &str, error: &str) -> Self { Self::new(key, Err(error)) } + pub fn failure_token_exchange(key: &str, error: &str) -> Self { + Self::new_with_grant(key, Err(error), token_exchange_grant()) + } + fn new(key: &str, response: std::result::Result<&str, &str>) -> Self { + Self::new_with_grant(key, response, token_grant()) + } + + fn new_with_grant( + key: &str, + response: std::result::Result<&str, &str>, + token_grant: ProviderCredentialTokenGrant, + ) -> Self { let requests = Arc::new(Mutex::new(Vec::new())); let resolver = Arc::new(FakeTokenGrantResolver { requests: requests.clone(), @@ -409,7 +425,7 @@ pub mod test_support { name: "access_token".to_string(), auth_style: "bearer".to_string(), header_name: "Authorization".to_string(), - token_grant: Some(token_grant()), + token_grant: Some(token_grant), ..Default::default() }, ); @@ -463,6 +479,34 @@ pub mod test_support { ); assert!(request.requested_token_type.is_empty()); } + + pub fn assert_one_token_exchange_request(&self, expected_provider_key: &str) { + let requests = self + .requests + .lock() + .expect("fake token grant requests lock poisoned"); + assert_eq!(requests.len(), 1); + + let request = &requests[0]; + assert_eq!(request.provider_key, expected_provider_key); + assert_eq!(request.token_endpoint, "https://auth.example.com/token"); + assert_eq!(request.jwt_svid_audience, "https://auth.example.com"); + assert_eq!( + request.client_assertion_type, + "urn:ietf:params:oauth:client-assertion-type:jwt-bearer" + ); + assert_eq!(request.audience, "api://example"); + assert_eq!(request.scopes, ["read"]); + assert_eq!(request.cache_ttl_seconds, 300); + assert_eq!( + request.grant_type, + ProviderCredentialTokenGrantType::TokenExchange as i32 + ); + assert_eq!( + request.requested_token_type, + "urn:ietf:params:oauth:token-type:access_token" + ); + } } fn token_grant() -> ProviderCredentialTokenGrant { @@ -481,6 +525,21 @@ pub mod test_support { } } + fn token_exchange_grant() -> ProviderCredentialTokenGrant { + ProviderCredentialTokenGrant { + grant_type: ProviderCredentialTokenGrantType::TokenExchange as i32, + subject_token: Some( + openshell_core::proto::ProviderCredentialTokenGrantSubjectToken { + source: "provider_credential".to_string(), + credential: "subject_token".to_string(), + subject_token_type: "urn:ietf:params:oauth:token-type:access_token".to_string(), + }, + ), + requested_token_type: "urn:ietf:params:oauth:token-type:access_token".to_string(), + ..token_grant() + } + } + impl TokenGrantResolver for FakeTokenGrantResolver { fn obtain<'a>( &'a self, @@ -779,6 +838,46 @@ mod tests { fixture.assert_one_request("api.example.com\t443\t/v1/**\tprovider:access_token"); } + #[tokio::test] + async fn inject_if_needed_passes_token_exchange_grant_to_resolver() { + let fixture = TokenGrantTestFixture::success_token_exchange( + "api.example.com\t443\t/v1/**\tprovider:access_token", + "grant-token", + ); + + let ctx = L7EvalContext { + host: "api.example.com".into(), + port: 443, + policy_name: "api".into(), + binary_path: "/usr/bin/curl".into(), + ancestors: vec![], + cmdline_paths: vec![], + secret_resolver: None, + activity_tx: None, + dynamic_credentials: Some(fixture.dynamic_credentials()), + token_grant_resolver: Some(fixture.resolver()), + ..Default::default() + }; + let req = L7Request { + action: "GET".to_string(), + target: "/v1/projects".to_string(), + query_params: std::collections::HashMap::new(), + raw_header: b"GET /v1/projects HTTP/1.1\r\nHost: api.example.com\r\n\r\n".to_vec(), + body_length: BodyLength::None, + }; + + let rewritten = inject_if_needed(req, &ctx) + .await + .expect("fake token exchange grant should inject"); + let rewritten = + String::from_utf8(rewritten.raw_header).expect("rewritten request should be UTF-8"); + + assert!(rewritten.contains("Authorization: Bearer grant-token\r\n")); + fixture.assert_one_token_exchange_request( + "api.example.com\t443\t/v1/**\tprovider:access_token", + ); + } + #[tokio::test] async fn inject_if_needed_rejects_malformed_resolver_token() { let fixture = TokenGrantTestFixture::success( diff --git a/crates/openshell-supervisor-network/src/proxy.rs b/crates/openshell-supervisor-network/src/proxy.rs index 385627296f..f0c1c34eed 100644 --- a/crates/openshell-supervisor-network/src/proxy.rs +++ b/crates/openshell-supervisor-network/src/proxy.rs @@ -6972,20 +6972,7 @@ network_policies: crate::l7::token_grant_injection::test_support::TokenGrantTestFixture, ) { let provider_key = "api.example.test\t8080\t/v1/**\tprovider:access_token"; - let fixture = match resolver_response { - Ok(token) => { - crate::l7::token_grant_injection::test_support::TokenGrantTestFixture::success( - provider_key, - token, - ) - } - Err(error) => { - crate::l7::token_grant_injection::test_support::TokenGrantTestFixture::failure( - provider_key, - error, - ) - } - }; + let fixture = forward_token_grant_fixture(provider_key, resolver_response, false); let ctx = crate::l7::relay::L7EvalContext { host: "api.example.test".into(), port: 8080, @@ -7003,6 +6990,54 @@ network_policies: (ctx, fixture) } + fn forward_token_exchange_context( + resolver_response: std::result::Result<&str, &str>, + ) -> ( + crate::l7::relay::L7EvalContext, + crate::l7::token_grant_injection::test_support::TokenGrantTestFixture, + ) { + let (mut ctx, _) = forward_token_grant_context(Ok("unused-token")); + let provider_key = "api.example.test\t8080\t/v1/**\tprovider:access_token"; + let fixture = forward_token_grant_fixture(provider_key, resolver_response, true); + ctx.dynamic_credentials = Some(fixture.dynamic_credentials()); + ctx.token_grant_resolver = Some(fixture.resolver()); + + (ctx, fixture) + } + + fn forward_token_grant_fixture( + provider_key: &str, + resolver_response: std::result::Result<&str, &str>, + token_exchange: bool, + ) -> crate::l7::token_grant_injection::test_support::TokenGrantTestFixture { + match (resolver_response, token_exchange) { + (Ok(token), false) => { + crate::l7::token_grant_injection::test_support::TokenGrantTestFixture::success( + provider_key, + token, + ) + } + (Ok(token), true) => { + crate::l7::token_grant_injection::test_support::TokenGrantTestFixture::success_token_exchange( + provider_key, + token, + ) + } + (Err(error), false) => { + crate::l7::token_grant_injection::test_support::TokenGrantTestFixture::failure( + provider_key, + error, + ) + } + (Err(error), true) => { + crate::l7::token_grant_injection::test_support::TokenGrantTestFixture::failure_token_exchange( + provider_key, + error, + ) + } + } + } + fn authorization_header_count(headers: &str) -> usize { headers .lines() @@ -9825,6 +9860,34 @@ network_policies: fixture.assert_one_request("api.example.test\t8080\t/v1/**\tprovider:access_token"); } + #[tokio::test] + async fn forward_proxy_injects_token_exchange_before_rewriting_request() { + let (ctx, fixture) = forward_token_exchange_context(Ok("grant-token")); + let raw = b"GET http://api.example.test:8080/v1/projects HTTP/1.1\r\nHost: api.example.test:8080\r\nAuthorization: Bearer stale-token\r\nConnection: close\r\n\r\n".to_vec(); + + let with_token = inject_token_grant_for_forward_request("GET", "/v1/projects", raw, &ctx) + .await + .expect("forward token exchange should inject"); + let rewritten = rewrite_forward_request( + &with_token, + with_token.len(), + "/v1/projects", + "api.example.test:8080", + None, + false, + ) + .expect("forward request should rewrite"); + let rewritten = String::from_utf8_lossy(&rewritten); + + assert!(rewritten.starts_with("GET /v1/projects HTTP/1.1\r\n")); + assert!(rewritten.contains("Authorization: Bearer grant-token\r\n")); + assert!(!rewritten.contains("stale-token")); + assert_eq!(authorization_header_count(&rewritten), 1); + fixture.assert_one_token_exchange_request( + "api.example.test\t8080\t/v1/**\tprovider:access_token", + ); + } + #[tokio::test] async fn forward_proxy_token_grant_failure_returns_error_before_rewrite() { let (ctx, fixture) = forward_token_grant_context(Err("oauth unavailable")); @@ -9839,6 +9902,22 @@ network_policies: fixture.assert_one_request("api.example.test\t8080\t/v1/**\tprovider:access_token"); } + #[tokio::test] + async fn forward_proxy_token_exchange_failure_returns_error_before_rewrite() { + let (ctx, fixture) = forward_token_exchange_context(Err("oauth unavailable")); + let raw = b"GET http://api.example.test:8080/v1/projects HTTP/1.1\r\nHost: api.example.test:8080\r\nConnection: close\r\n\r\n".to_vec(); + + let err = inject_token_grant_for_forward_request("GET", "/v1/projects", raw, &ctx) + .await + .expect_err("forward token exchange failure should stop request rewriting"); + + assert!(err.to_string().contains("Token grant failed")); + assert!(err.to_string().contains("oauth unavailable")); + fixture.assert_one_token_exchange_request( + "api.example.test\t8080\t/v1/**\tprovider:access_token", + ); + } + #[test] fn test_rewrite_get_request() { let raw = From c5ed9ca94de5da2ec57b690c79b3842faaf13bb1 Mon Sep 17 00:00:00 2001 From: Gordon Sim Date: Tue, 23 Jun 2026 15:35:22 +0100 Subject: [PATCH 3/8] test(provider): add runnable example for token exchange Signed-off-by: Gordon Sim --- examples/spiffe-token-exchange-demo/README.md | 221 ++++++++++++ examples/spiffe-token-exchange-demo/demo.sh | 229 ++++++++++++ .../k8s/kustomization.yaml | 20 ++ .../k8s/protected-service.js | 120 +++++++ .../k8s/spiffe-helper.conf | 8 + .../k8s/token-issuer.js | 333 ++++++++++++++++++ .../k8s/workloads.yaml | 230 ++++++++++++ .../provider-profile.yaml | 53 +++ 8 files changed, 1214 insertions(+) create mode 100644 examples/spiffe-token-exchange-demo/README.md create mode 100755 examples/spiffe-token-exchange-demo/demo.sh create mode 100644 examples/spiffe-token-exchange-demo/k8s/kustomization.yaml create mode 100644 examples/spiffe-token-exchange-demo/k8s/protected-service.js create mode 100644 examples/spiffe-token-exchange-demo/k8s/spiffe-helper.conf create mode 100644 examples/spiffe-token-exchange-demo/k8s/token-issuer.js create mode 100644 examples/spiffe-token-exchange-demo/k8s/workloads.yaml create mode 100644 examples/spiffe-token-exchange-demo/provider-profile.yaml diff --git a/examples/spiffe-token-exchange-demo/README.md b/examples/spiffe-token-exchange-demo/README.md new file mode 100644 index 0000000000..92d790bd72 --- /dev/null +++ b/examples/spiffe-token-exchange-demo/README.md @@ -0,0 +1,221 @@ +# SPIFFE Token Exchange Demo + +This example validates provider dynamic token exchange using SPIFFE JWT-SVIDs. +It runs alongside `examples/spiffe-token-grant-demo` but exercises the +`token_exchange` grant type instead of `client_credentials`. + +The demo deploys three in-cluster workloads: + +| Workload | Purpose | +|---|---| +| `token-exchange-issuer` | Issues a demo user subject token, performs the gateway intermediate token exchange, and performs the supervisor final token exchange | +| `alpha-exchange` | Requires a final bearer token with audience and scope `alpha` | +| `beta-exchange` | Requires a final bearer token with audience and scope `beta` | + +The OpenShell provider profile in `provider-profile.yaml` declares a stored +`subject_token` credential and a runtime `access_token` credential with +`token_grant.grant_type: token_exchange`. + +The profile declares exact Kubernetes service hostnames for `alpha-exchange` +and `beta-exchange`. It intentionally does not set `allowed_ips`, because +cluster service CIDRs vary across Kubernetes installations. + +When a sandbox curls `alpha-exchange` or `beta-exchange`: + +1. The supervisor fetches its SPIFFE JWT-SVID. +2. The supervisor asks the gateway for an intermediate token. +3. The gateway verifies the supervisor SVID, fetches its own gateway JWT-SVID, + and exchanges the stored provider `subject_token` at `token-exchange-issuer`. + The requested intermediate audience is the supervisor SPIFFE ID. +4. The supervisor exchanges the intermediate token at the same token endpoint + for the final alpha/beta access token. +5. The supervisor injects that final token into the outbound HTTP request. + +## Prerequisites + +- A Kubernetes OpenShell dev cluster. +- SPIRE enabled for provider token grants and gateway token exchange. +- Gateway and supervisor access to SPIRE OIDC/JWKS discovery. +- OpenShell configured with the Kubernetes ServiceAccount supervisor bootstrap + path. +- `providers_v2_enabled=true` on the target gateway. +- Local `curl`, `python3`, `openssl`, `nc`, `kubectl`, and `openshell`. +- A registered and logged-in CLI gateway. The script uses `GATEWAY_NAME`, then + `OPENSHELL_GATEWAY`, then the active OpenShell gateway selection. + +For the Helm dev environment, deploy with the SPIRE releases and +`ci/values-spire.yaml` enabled in `deploy/helm/openshell/skaffold.yaml`. + +The demo assumes these SPIFFE ID prefixes: + +| Identity | Prefix | +|---|---| +| Gateway | `spiffe://openshell.local/ns/openshell/sa/` | +| Supervisor | `spiffe://openshell.local/openshell/sandbox/` | + +Override `GATEWAY_TRUST_DOMAIN_PREFIX` or `SUPERVISOR_TRUST_DOMAIN_PREFIX` in +`k8s/workloads.yaml` if your development cluster uses different SPIFFE IDs. + +The demo issuer fetches SPIRE JWKS from the in-cluster OIDC discovery service +to verify JWT-SVID signatures. The issuer pod runs a `spiffe-helper` sidecar +that writes the SPIFFE bundle into a shared volume. The Node issuer uses that +bundle as `SPIRE_JWKS_CA_FILE` when fetching JWKS over HTTPS. + +## Kubeconfig And Mise + +The repository `mise.toml` sets `KUBECONFIG` to the repo-local `kubeconfig` +when your shell activates the OpenShell directory. If you are testing against a +different cluster, run these commands from outside the repository and pass the +target kubeconfig explicitly. + +```bash +export OPENSHELL_REPO=/path/to/OpenShell +export DEMO_KUBECONFIG=/path/to/your/kubeconfig +export OPENSHELL_GATEWAY=local +``` + +## Deploy Workloads + +From a directory outside the repository: + +```bash +ACCESS_TOKEN_SECRET="$(openssl rand -hex 32)" +KUBECONFIG="$DEMO_KUBECONFIG" kubectl -n default create secret generic openshell-spiffe-token-exchange-demo \ + --from-literal=access-token-secret="$ACCESS_TOKEN_SECRET" \ + --dry-run=client \ + -o yaml | KUBECONFIG="$DEMO_KUBECONFIG" kubectl apply -f - +KUBECONFIG="$DEMO_KUBECONFIG" kubectl apply -k "$OPENSHELL_REPO/examples/spiffe-token-exchange-demo/k8s" +KUBECONFIG="$DEMO_KUBECONFIG" kubectl -n default rollout restart deployment/token-exchange-issuer deployment/alpha-exchange deployment/beta-exchange +KUBECONFIG="$DEMO_KUBECONFIG" kubectl -n default rollout status deployment/token-exchange-issuer --timeout=180s +KUBECONFIG="$DEMO_KUBECONFIG" kubectl -n default rollout status deployment/alpha-exchange --timeout=180s +KUBECONFIG="$DEMO_KUBECONFIG" kubectl -n default rollout status deployment/beta-exchange --timeout=180s +``` + +## Register Provider And Test + +Port-forward the local gateway in one terminal: + +```bash +KUBECONFIG="$DEMO_KUBECONFIG" kubectl port-forward -n openshell svc/openshell 8097:8080 +``` + +Copy the Helm-generated TLS client bundle into the CLI config used for this +demo. This uses the same gateway name as `OPENSHELL_GATEWAY`. + +```bash +mkdir -p "${XDG_CONFIG_HOME:-$HOME/.config}/openshell/gateways/${OPENSHELL_GATEWAY}/mtls" +KUBECONFIG="$DEMO_KUBECONFIG" kubectl -n openshell get secret openshell-client-tls \ + -o jsonpath='{.data.ca\.crt}' | base64 -d > "${XDG_CONFIG_HOME:-$HOME/.config}/openshell/gateways/${OPENSHELL_GATEWAY}/mtls/ca.crt" +KUBECONFIG="$DEMO_KUBECONFIG" kubectl -n openshell get secret openshell-client-tls \ + -o jsonpath='{.data.tls\.crt}' | base64 -d > "${XDG_CONFIG_HOME:-$HOME/.config}/openshell/gateways/${OPENSHELL_GATEWAY}/mtls/tls.crt" +KUBECONFIG="$DEMO_KUBECONFIG" kubectl -n openshell get secret openshell-client-tls \ + -o jsonpath='{.data.tls\.key}' | base64 -d > "${XDG_CONFIG_HOME:-$HOME/.config}/openshell/gateways/${OPENSHELL_GATEWAY}/mtls/tls.key" +``` + +Port-forward the token exchange issuer in another terminal and fetch a demo +subject token: + +```bash +KUBECONFIG="$DEMO_KUBECONFIG" kubectl port-forward -n default svc/token-exchange-issuer 18080:80 +SUBJECT_TOKEN="$( + curl -fsS http://127.0.0.1:18080/demo-subject-token | + python3 -c 'import json, sys; print(json.load(sys.stdin)["access_token"])' +)" +``` + +Then run: + +```bash +export GATEWAY=https://127.0.0.1:8097 + +openshell --gateway "$OPENSHELL_GATEWAY" --gateway-endpoint "$GATEWAY" settings set \ + --global --key providers_v2_enabled --value true --yes + +openshell --gateway "$OPENSHELL_GATEWAY" --gateway-endpoint "$GATEWAY" provider profile import \ + -f "$OPENSHELL_REPO/examples/spiffe-token-exchange-demo/provider-profile.yaml" + +openshell --gateway "$OPENSHELL_GATEWAY" --gateway-endpoint "$GATEWAY" provider create \ + --name spiffe-token-exchange-demo \ + --type spiffe-token-exchange-demo \ + --credential "subject_token=${SUBJECT_TOKEN}" + +openshell --gateway "$OPENSHELL_GATEWAY" --gateway-endpoint "$GATEWAY" sandbox create \ + --name spiffe-token-exchange-demo \ + --provider spiffe-token-exchange-demo \ + --keep \ + --no-tty \ + -- echo "sandbox ready" + +openshell --gateway "$OPENSHELL_GATEWAY" --gateway-endpoint "$GATEWAY" sandbox exec \ + --name spiffe-token-exchange-demo \ + --no-tty \ + -- curl -sS http://alpha-exchange.default.svc.cluster.local/ + +openshell --gateway "$OPENSHELL_GATEWAY" --gateway-endpoint "$GATEWAY" sandbox exec \ + --name spiffe-token-exchange-demo \ + --no-tty \ + -- curl -sS http://beta-exchange.default.svc.cluster.local/ +``` + +Expected output includes the demo user as the token subject and the sandbox +SPIFFE ID as the authorized party/client: + +```text +alpha called with path /: + sub: demo-user + aud: alpha, account + scope: alpha profile email + azp: spiffe://openshell.local/openshell/sandbox/ + client_id: spiffe://openshell.local/openshell/sandbox/ + +beta called with path /: + sub: demo-user + aud: beta, account + scope: beta profile email + azp: spiffe://openshell.local/openshell/sandbox/ + client_id: spiffe://openshell.local/openshell/sandbox/ +``` + +The token issuer logs both token exchange phases: + +```bash +KUBECONFIG="$DEMO_KUBECONFIG" kubectl -n default logs deployment/token-exchange-issuer --tail=40 +``` + +Example log lines: + +```text +issued intermediate token for user=demo-user audience=spiffe://openshell.local/openshell/sandbox/ +issued final token for user=demo-user audience=alpha client=spiffe://openshell.local/openshell/sandbox/ +issued final token for user=demo-user audience=beta client=spiffe://openshell.local/openshell/sandbox/ +``` + +## Automated Demo + +`demo.sh` applies the workloads, fetches a demo subject token, registers the +provider profile, creates a sandbox, curls alpha/beta, and deletes the sandbox +with `openshell` on exit. It leaves the Kubernetes demo workloads in place and +prints diagnostics only when the run fails. + +```bash +cd /tmp +KUBECONFIG="$DEMO_KUBECONFIG" bash "$OPENSHELL_REPO/examples/spiffe-token-exchange-demo/demo.sh" +``` + +The script reuses your normal OpenShell CLI config so it can load the stored +OIDC token for `OPENSHELL_GATEWAY`. If you set `ISOLATED_CONFIG=1`, register +and log in to the gateway in that isolated config before running the demo. + +## Cleanup + +Delete the sandbox through OpenShell: + +```bash +openshell --gateway "$OPENSHELL_GATEWAY" --gateway-endpoint "$GATEWAY" sandbox delete spiffe-token-exchange-demo +``` + +Delete the demo workloads with Kubernetes: + +```bash +KUBECONFIG="$DEMO_KUBECONFIG" kubectl delete -k "$OPENSHELL_REPO/examples/spiffe-token-exchange-demo/k8s" +``` diff --git a/examples/spiffe-token-exchange-demo/demo.sh b/examples/spiffe-token-exchange-demo/demo.sh new file mode 100755 index 0000000000..e50f989454 --- /dev/null +++ b/examples/spiffe-token-exchange-demo/demo.sh @@ -0,0 +1,229 @@ +#!/usr/bin/env bash + +# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROFILE_FILE="${SCRIPT_DIR}/provider-profile.yaml" +K8S_DIR="${SCRIPT_DIR}/k8s" + +SANDBOX_NAME="${SANDBOX_NAME:-spiffe-token-demo}" +PROVIDER_NAME="${PROVIDER_NAME:-spiffe-token-exchange-demo}" +PROFILE_ID="${PROFILE_ID:-spiffe-token-exchange-demo}" +PORT_FORWARD_PORT="${PORT_FORWARD_PORT:-8097}" +TOKEN_ISSUER_PORT="${TOKEN_ISSUER_PORT:-18080}" +GATEWAY_ENDPOINT="${GATEWAY_ENDPOINT:-https://127.0.0.1:${PORT_FORWARD_PORT}}" +KEEP_SANDBOX="${KEEP_SANDBOX:-0}" +ISOLATED_CONFIG="${ISOLATED_CONFIG:-0}" +ACCESS_TOKEN_SECRET="${ACCESS_TOKEN_SECRET:-$(openssl rand -hex 32)}" + +TEMP_CONFIG_HOME="" +if [[ "$ISOLATED_CONFIG" == "1" ]]; then + TEMP_CONFIG_HOME="$(mktemp -d)" + export XDG_CONFIG_HOME="$TEMP_CONFIG_HOME" +fi + +default_gateway_name() { + if [[ -n "${GATEWAY_NAME:-}" ]]; then + printf "%s\n" "$GATEWAY_NAME" + return + fi + if [[ -n "${OPENSHELL_GATEWAY:-}" ]]; then + printf "%s\n" "$OPENSHELL_GATEWAY" + return + fi + + local config_home="${XDG_CONFIG_HOME:-$HOME/.config}" + if [[ -s "${config_home}/openshell/active_gateway" ]]; then + head -n1 "${config_home}/openshell/active_gateway" + return + fi + if [[ -s /etc/openshell/active_gateway ]]; then + head -n1 /etc/openshell/active_gateway + return + fi + + printf "k8s\n" +} + +GATEWAY_NAME="$(default_gateway_name)" + +PF_PID="" +TOKEN_PF_PID="" + +dump_diagnostics() { + set +e + + printf "\n=== diagnostics: openshell sandbox logs ===\n" >&2 + "${OS[@]}" logs "$SANDBOX_NAME" -n 120 --source sandbox >&2 + + printf "\n=== diagnostics: gateway logs ===\n" >&2 + kubectl -n openshell logs -l app.kubernetes.io/name=openshell,app.kubernetes.io/instance=openshell \ + --tail=120 --prefix=true >&2 + + printf "\n=== diagnostics: token exchange issuer logs ===\n" >&2 + kubectl -n default logs -l app=token-exchange-issuer --tail=120 --prefix=true >&2 + + printf "\n=== diagnostics: alpha logs ===\n" >&2 + kubectl -n default logs -l app=alpha-exchange --tail=60 --prefix=true >&2 + + printf "\n=== diagnostics: beta logs ===\n" >&2 + kubectl -n default logs -l app=beta-exchange --tail=60 --prefix=true >&2 + + printf "\n=== diagnostics: gateway port-forward log ===\n" >&2 + sed 's/^/gateway-port-forward> /' /tmp/openshell-spiffe-token-exchange-demo-gateway-port-forward.log >&2 + + printf "\n=== diagnostics: token issuer port-forward log ===\n" >&2 + sed 's/^/issuer-port-forward> /' /tmp/openshell-spiffe-token-exchange-demo-issuer-port-forward.log >&2 +} + +cleanup() { + if [[ "$KEEP_SANDBOX" != "1" ]]; then + openshell --gateway "$GATEWAY_NAME" --gateway-endpoint "$GATEWAY_ENDPOINT" sandbox delete "$SANDBOX_NAME" >/dev/null 2>&1 || true + fi + if [[ -n "$PF_PID" ]]; then + kill "$PF_PID" >/dev/null 2>&1 || true + fi + if [[ -n "$TOKEN_PF_PID" ]]; then + kill "$TOKEN_PF_PID" >/dev/null 2>&1 || true + fi + if [[ -n "$TEMP_CONFIG_HOME" ]]; then + rm -rf "$TEMP_CONFIG_HOME" + fi +} + +on_exit() { + local status="$1" + if [[ "$status" -ne 0 ]]; then + dump_diagnostics || true + fi + cleanup + exit "$status" +} +trap 'on_exit $?' EXIT + +run() { + printf "\n$ %s\n" "$*" + "$@" +} + +wait_for_port() { + local port="$1" + local label="$2" + for _ in $(seq 1 60); do + if nc -z 127.0.0.1 "$port" >/dev/null 2>&1; then + return 0 + fi + sleep 0.25 + done + printf "%s port-forward did not become ready\n" "$label" >&2 + exit 1 +} + +assert_contains() { + local haystack="$1" + local needle="$2" + if [[ "$haystack" != *"$needle"* ]]; then + printf "expected output to contain: %s\n" "$needle" >&2 + printf "actual output:\n%s\n" "$haystack" >&2 + exit 1 + fi +} + +install_gateway_tls_bundle() { + local config_home="${XDG_CONFIG_HOME:-$HOME/.config}" + local tls_dir="${config_home}/openshell/gateways/${GATEWAY_NAME}/mtls" + mkdir -p "$tls_dir" + kubectl -n openshell get secret openshell-client-tls \ + -o jsonpath='{.data.ca\.crt}' | base64 -d >"${tls_dir}/ca.crt" + kubectl -n openshell get secret openshell-client-tls \ + -o jsonpath='{.data.tls\.crt}' | base64 -d >"${tls_dir}/tls.crt" + kubectl -n openshell get secret openshell-client-tls \ + -o jsonpath='{.data.tls\.key}' | base64 -d >"${tls_dir}/tls.key" +} + +subject_token_from_json() { + python3 -c 'import json, sys; print(json.load(sys.stdin)["access_token"])' +} + +sandbox_curl_until() { + local label="$1" + local url="$2" + local expected="$3" + local output="" + + for attempt in $(seq 1 12); do + printf "\n$ openshell sandbox exec %s curl (attempt %s)\n" "$label" "$attempt" + if output=$("${OS[@]}" sandbox exec --name "$SANDBOX_NAME" --no-tty -- curl -sS --max-time 10 "$url" 2>&1); then + printf "%s\n" "$output" + if [[ "$output" == *"$expected"* ]]; then + SANDBOX_CURL_OUTPUT="$output" + return 0 + fi + else + printf "%s\n" "$output" + fi + sleep 2 + done + + printf "timed out waiting for %s to return expected output\n" "$label" >&2 + printf "last output:\n%s\n" "$output" >&2 + exit 1 +} + +OS=(openshell --gateway "$GATEWAY_NAME" --gateway-endpoint "$GATEWAY_ENDPOINT") + +printf "Using OpenShell gateway '%s' at %s\n" "$GATEWAY_NAME" "$GATEWAY_ENDPOINT" + +printf "\n$ kubectl -n default create secret generic openshell-spiffe-token-exchange-demo --from-literal=access-token-secret=*** --dry-run=client -o yaml | kubectl apply -f -\n" +kubectl -n default create secret generic openshell-spiffe-token-exchange-demo \ + --from-literal=access-token-secret="$ACCESS_TOKEN_SECRET" \ + --dry-run=client \ + -o yaml | kubectl apply -f - + +run kubectl apply -k "$K8S_DIR" +run kubectl -n default rollout restart deployment/token-exchange-issuer deployment/alpha-exchange deployment/beta-exchange +run kubectl -n default rollout status deployment/token-exchange-issuer --timeout=180s +run kubectl -n default rollout status deployment/alpha-exchange --timeout=180s +run kubectl -n default rollout status deployment/beta-exchange --timeout=180s + +kubectl -n openshell port-forward svc/openshell "${PORT_FORWARD_PORT}:8080" >/tmp/openshell-spiffe-token-exchange-demo-gateway-port-forward.log 2>&1 & +PF_PID=$! +wait_for_port "$PORT_FORWARD_PORT" "gateway" +install_gateway_tls_bundle + +kubectl -n default port-forward svc/token-exchange-issuer "${TOKEN_ISSUER_PORT}:80" >/tmp/openshell-spiffe-token-exchange-demo-issuer-port-forward.log 2>&1 & +TOKEN_PF_PID=$! +wait_for_port "$TOKEN_ISSUER_PORT" "token issuer" + +SUBJECT_TOKEN="$(curl -fsS "http://127.0.0.1:${TOKEN_ISSUER_PORT}/demo-subject-token" | subject_token_from_json)" + +"${OS[@]}" sandbox delete "$SANDBOX_NAME" >/dev/null 2>&1 || true +"${OS[@]}" provider delete "$PROVIDER_NAME" >/dev/null 2>&1 || true +"${OS[@]}" provider profile delete "$PROFILE_ID" >/dev/null 2>&1 || true + +run "${OS[@]}" settings set --global --key providers_v2_enabled --value true --yes +run "${OS[@]}" provider profile lint -f "$PROFILE_FILE" +run "${OS[@]}" provider profile import -f "$PROFILE_FILE" +run "${OS[@]}" provider create --name "$PROVIDER_NAME" --type "$PROFILE_ID" --credential "subject_token=${SUBJECT_TOKEN}" +run "${OS[@]}" sandbox create --name "$SANDBOX_NAME" --provider "$PROVIDER_NAME" --keep --no-tty -- echo "sandbox ready" + +sandbox_curl_until "alpha" "http://alpha-exchange.default.svc.cluster.local/" "alpha called with path /:" +ALPHA_OUTPUT="$SANDBOX_CURL_OUTPUT" +assert_contains "$ALPHA_OUTPUT" "alpha called with path /:" +assert_contains "$ALPHA_OUTPUT" "sub: demo-user" +assert_contains "$ALPHA_OUTPUT" "aud: alpha, account" +assert_contains "$ALPHA_OUTPUT" "scope: alpha profile email" +assert_contains "$ALPHA_OUTPUT" "azp: spiffe://openshell.local/openshell/sandbox/" + +sandbox_curl_until "beta" "http://beta-exchange.default.svc.cluster.local/" "beta called with path /:" +BETA_OUTPUT="$SANDBOX_CURL_OUTPUT" +assert_contains "$BETA_OUTPUT" "beta called with path /:" +assert_contains "$BETA_OUTPUT" "sub: demo-user" +assert_contains "$BETA_OUTPUT" "aud: beta, account" +assert_contains "$BETA_OUTPUT" "scope: beta profile email" +assert_contains "$BETA_OUTPUT" "azp: spiffe://openshell.local/openshell/sandbox/" + +printf "\nSPIFFE token exchange demo succeeded.\n" diff --git a/examples/spiffe-token-exchange-demo/k8s/kustomization.yaml b/examples/spiffe-token-exchange-demo/k8s/kustomization.yaml new file mode 100644 index 0000000000..d4cdcb80a0 --- /dev/null +++ b/examples/spiffe-token-exchange-demo/k8s/kustomization.yaml @@ -0,0 +1,20 @@ +# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +generatorOptions: + disableNameSuffixHash: true + +configMapGenerator: + - name: openshell-spiffe-token-exchange-demo-scripts + files: + - token-issuer.js + - protected-service.js + - name: openshell-spiffe-token-exchange-demo-helper + files: + - spiffe-helper.conf + +resources: + - workloads.yaml diff --git a/examples/spiffe-token-exchange-demo/k8s/protected-service.js b/examples/spiffe-token-exchange-demo/k8s/protected-service.js new file mode 100644 index 0000000000..263601c705 --- /dev/null +++ b/examples/spiffe-token-exchange-demo/k8s/protected-service.js @@ -0,0 +1,120 @@ +// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +const http = require("http"); +const crypto = require("crypto"); + +const PORT = Number(process.env.PORT || 8080); +const SERVICE_NAME = process.env.SERVICE_NAME || "alpha"; +const EXPECTED_AUDIENCE = process.env.EXPECTED_AUDIENCE || SERVICE_NAME; +const EXPECTED_SCOPE = process.env.EXPECTED_SCOPE || SERVICE_NAME; +const ACCESS_TOKEN_ISSUER = + process.env.ACCESS_TOKEN_ISSUER || "http://token-exchange-issuer.default.svc.cluster.local"; +const ACCESS_TOKEN_SECRET = process.env.ACCESS_TOKEN_SECRET; + +if (!ACCESS_TOKEN_SECRET) { + throw new Error("ACCESS_TOKEN_SECRET is required"); +} + +function b64urlDecode(value) { + const padded = `${value}${"=".repeat((4 - (value.length % 4)) % 4)}`; + return Buffer.from(padded.replace(/-/g, "+").replace(/_/g, "/"), "base64"); +} + +function b64urlEncode(value) { + return Buffer.from(value) + .toString("base64") + .replace(/=/g, "") + .replace(/\+/g, "-") + .replace(/\//g, "_"); +} + +function parseJwt(jwt) { + const parts = jwt.split("."); + if (parts.length !== 3) { + throw new Error("JWT must contain three segments"); + } + return { + payload: JSON.parse(b64urlDecode(parts[1]).toString("utf8")), + signingInput: `${parts[0]}.${parts[1]}`, + signature: parts[2], + }; +} + +function verifyAccessToken(jwt) { + const parsed = parseJwt(jwt); + const expected = b64urlEncode( + crypto.createHmac("sha256", ACCESS_TOKEN_SECRET).update(parsed.signingInput).digest(), + ); + if ( + parsed.signature.length !== expected.length || + !crypto.timingSafeEqual(Buffer.from(parsed.signature), Buffer.from(expected)) + ) { + throw new Error("access token signature validation failed"); + } + + const now = Math.floor(Date.now() / 1000); + if (parsed.payload.exp && parsed.payload.exp <= now) { + throw new Error("access token expired"); + } + if (parsed.payload.iss !== ACCESS_TOKEN_ISSUER) { + throw new Error(`unexpected access token issuer ${parsed.payload.iss}`); + } + if (parsed.payload.demo_token_use !== "final") { + throw new Error("expected final token"); + } + const aud = Array.isArray(parsed.payload.aud) ? parsed.payload.aud : [parsed.payload.aud]; + if (!aud.includes(EXPECTED_AUDIENCE)) { + throw new Error(`access token audience did not include ${EXPECTED_AUDIENCE}`); + } + const scopes = String(parsed.payload.scope || "").split(/\s+/).filter(Boolean); + if (!scopes.includes(EXPECTED_SCOPE)) { + throw new Error(`access token scope did not include ${EXPECTED_SCOPE}`); + } + return parsed.payload; +} + +function text(res, status, body) { + res.writeHead(status, { + "content-type": "text/plain", + "content-length": Buffer.byteLength(body), + }); + res.end(body); +} + +http + .createServer((req, res) => { + try { + if (req.url === "/healthz") { + return text(res, 200, "ok\n"); + } + const auth = req.headers.authorization || ""; + const token = auth.startsWith("Bearer ") ? auth.slice("Bearer ".length) : ""; + if (!token) { + console.warn(`${SERVICE_NAME} rejected request path=${req.url} reason=missing_bearer_token`); + return text(res, 401, `${SERVICE_NAME} missing bearer token\n`); + } + const claims = verifyAccessToken(token); + const aud = Array.isArray(claims.aud) ? claims.aud.join(", ") : claims.aud; + console.log( + `${SERVICE_NAME} accepted request path=${req.url} sub=${claims.sub} aud="${aud}" scope="${claims.scope}" client_id=${claims.client_id}`, + ); + return text( + res, + 200, + `${SERVICE_NAME} called with path ${req.url}:\n` + + ` sub: ${claims.sub}\n` + + ` aud: ${aud}\n` + + ` iss: ${claims.iss}\n` + + ` scope: ${claims.scope}\n` + + ` azp: ${claims.azp}\n` + + ` client_id: ${claims.client_id}\n`, + ); + } catch (error) { + console.warn(`${SERVICE_NAME} rejected request path=${req.url} reason="${error.message}"`); + return text(res, 403, `${SERVICE_NAME} rejected token: ${error.message}\n`); + } + }) + .listen(PORT, "0.0.0.0", () => { + console.log(`${SERVICE_NAME} listening on ${PORT}`); + }); diff --git a/examples/spiffe-token-exchange-demo/k8s/spiffe-helper.conf b/examples/spiffe-token-exchange-demo/k8s/spiffe-helper.conf new file mode 100644 index 0000000000..93987d00c9 --- /dev/null +++ b/examples/spiffe-token-exchange-demo/k8s/spiffe-helper.conf @@ -0,0 +1,8 @@ +# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +agent_address = "/run/spire/sockets/spire-agent.sock" +cert_dir = "/target" +svid_file_name = "svid.pem" +svid_key_file_name = "key.pem" +svid_bundle_file_name = "bundle.pem" diff --git a/examples/spiffe-token-exchange-demo/k8s/token-issuer.js b/examples/spiffe-token-exchange-demo/k8s/token-issuer.js new file mode 100644 index 0000000000..e4cc22af56 --- /dev/null +++ b/examples/spiffe-token-exchange-demo/k8s/token-issuer.js @@ -0,0 +1,333 @@ +// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +const http = require("http"); +const https = require("https"); +const crypto = require("crypto"); +const fs = require("fs"); + +const TOKEN_EXCHANGE_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:token-exchange"; +const JWT_SPIFFE_ASSERTION_TYPE = "urn:ietf:params:oauth:client-assertion-type:jwt-spiffe"; + +const PORT = Number(process.env.PORT || 8080); +const JWKS_URI = + process.env.SPIRE_JWKS_URI || + "https://spire-spiffe-oidc-discovery-provider.spire.svc.cluster.local/keys"; +const SPIRE_ISSUER = + process.env.SPIRE_ISSUER || + "https://spire-spiffe-oidc-discovery-provider.spire.svc.cluster.local"; +const JWT_SVID_AUDIENCE = + process.env.JWT_SVID_AUDIENCE || "http://token-exchange-issuer.default.svc.cluster.local"; +const SUPERVISOR_TRUST_DOMAIN_PREFIX = + process.env.SUPERVISOR_TRUST_DOMAIN_PREFIX || "spiffe://openshell.local/openshell/sandbox/"; +const GATEWAY_TRUST_DOMAIN_PREFIX = + process.env.GATEWAY_TRUST_DOMAIN_PREFIX || "spiffe://openshell.local/ns/openshell/sa/"; +const ACCESS_TOKEN_ISSUER = + process.env.ACCESS_TOKEN_ISSUER || "http://token-exchange-issuer.default.svc.cluster.local"; +const ACCESS_TOKEN_SECRET = process.env.ACCESS_TOKEN_SECRET; +const DEMO_USER_SUBJECT = process.env.DEMO_USER_SUBJECT || "demo-user"; +const SPIRE_JWKS_CA_FILE = process.env.SPIRE_JWKS_CA_FILE || ""; + +if (!ACCESS_TOKEN_SECRET) { + throw new Error("ACCESS_TOKEN_SECRET is required"); +} + +let cachedJwks; +let cachedJwksAt = 0; + +function b64urlDecode(value) { + const padded = `${value}${"=".repeat((4 - (value.length % 4)) % 4)}`; + return Buffer.from(padded.replace(/-/g, "+").replace(/_/g, "/"), "base64"); +} + +function b64urlEncode(value) { + return Buffer.from(value) + .toString("base64") + .replace(/=/g, "") + .replace(/\+/g, "-") + .replace(/\//g, "_"); +} + +function parseJwt(jwt) { + const parts = jwt.split("."); + if (parts.length !== 3) { + throw new Error("JWT must contain three segments"); + } + return { + header: JSON.parse(b64urlDecode(parts[0]).toString("utf8")), + payload: JSON.parse(b64urlDecode(parts[1]).toString("utf8")), + signingInput: `${parts[0]}.${parts[1]}`, + signature: b64urlDecode(parts[2]), + signatureB64: parts[2], + }; +} + +async function jwks() { + const now = Date.now(); + if (cachedJwks && now - cachedJwksAt < 60000) { + return cachedJwks; + } + cachedJwks = await fetchJson(JWKS_URI); + cachedJwksAt = now; + return cachedJwks; +} + +function fetchJson(url) { + return new Promise((resolve, reject) => { + const parsed = new URL(url); + const isHttps = parsed.protocol === "https:"; + const client = isHttps ? https : http; + const options = {}; + if (isHttps && SPIRE_JWKS_CA_FILE) { + options.ca = fs.readFileSync(SPIRE_JWKS_CA_FILE); + } + + const req = client.get(parsed, options, (res) => { + const chunks = []; + res.on("data", (chunk) => chunks.push(chunk)); + res.on("end", () => { + const body = Buffer.concat(chunks).toString("utf8"); + if (res.statusCode < 200 || res.statusCode >= 300) { + reject(new Error(`JWKS fetch failed with HTTP ${res.statusCode}: ${body}`)); + return; + } + try { + resolve(JSON.parse(body)); + } catch (error) { + reject(error); + } + }); + }); + req.on("error", reject); + req.setTimeout(10000, () => req.destroy(new Error("JWKS fetch timed out"))); + }); +} + +function hasAudience(payload, expected) { + const aud = Array.isArray(payload.aud) ? payload.aud : [payload.aud]; + return aud.includes(expected); +} + +async function verifyJwtSvid(jwt, subjectPrefix) { + const parsed = parseJwt(jwt); + if (parsed.header.alg !== "RS256") { + throw new Error(`unsupported JWT-SVID alg ${parsed.header.alg}`); + } + + const keys = await jwks(); + const jwk = keys.keys.find((key) => key.kid === parsed.header.kid); + if (!jwk) { + throw new Error(`no JWKS key for kid ${parsed.header.kid}`); + } + + const verifier = crypto.createVerify("RSA-SHA256"); + verifier.update(parsed.signingInput); + verifier.end(); + const publicKey = crypto.createPublicKey({ key: jwk, format: "jwk" }); + if (!verifier.verify(publicKey, parsed.signature)) { + throw new Error("JWT-SVID signature validation failed"); + } + + const now = Math.floor(Date.now() / 1000); + if (parsed.payload.exp && parsed.payload.exp <= now) { + throw new Error("JWT-SVID expired"); + } + if (parsed.payload.nbf && parsed.payload.nbf > now + 30) { + throw new Error("JWT-SVID not active yet"); + } + if (parsed.payload.iss !== SPIRE_ISSUER) { + throw new Error(`unexpected JWT-SVID issuer ${parsed.payload.iss}`); + } + if (!hasAudience(parsed.payload, JWT_SVID_AUDIENCE)) { + throw new Error(`JWT-SVID audience did not include ${JWT_SVID_AUDIENCE}`); + } + if (!String(parsed.payload.sub || "").startsWith(subjectPrefix)) { + throw new Error(`JWT-SVID subject did not start with ${subjectPrefix}`); + } + return parsed.payload; +} + +function signAccessToken(payload) { + const header = b64urlEncode(JSON.stringify({ alg: "HS256", typ: "JWT" })); + const body = b64urlEncode(JSON.stringify(payload)); + const signingInput = `${header}.${body}`; + const signature = crypto + .createHmac("sha256", ACCESS_TOKEN_SECRET) + .update(signingInput) + .digest(); + return `${signingInput}.${b64urlEncode(signature)}`; +} + +function verifyAccessToken(jwt, tokenUse) { + const parsed = parseJwt(jwt); + const expected = b64urlEncode( + crypto.createHmac("sha256", ACCESS_TOKEN_SECRET).update(parsed.signingInput).digest(), + ); + if ( + parsed.signatureB64.length !== expected.length || + !crypto.timingSafeEqual(Buffer.from(parsed.signatureB64), Buffer.from(expected)) + ) { + throw new Error("token signature validation failed"); + } + + const now = Math.floor(Date.now() / 1000); + if (parsed.payload.exp && parsed.payload.exp <= now) { + throw new Error("token expired"); + } + if (parsed.payload.iss !== ACCESS_TOKEN_ISSUER) { + throw new Error(`unexpected token issuer ${parsed.payload.iss}`); + } + if (parsed.payload.demo_token_use !== tokenUse) { + throw new Error(`expected ${tokenUse} token`); + } + return parsed.payload; +} + +function issueDemoSubjectToken() { + const now = Math.floor(Date.now() / 1000); + return signAccessToken({ + iss: ACCESS_TOKEN_ISSUER, + sub: DEMO_USER_SUBJECT, + aud: ["openshell-gateway", "account"], + scope: "openid profile email", + demo_token_use: "user_subject", + iat: now, + exp: now + 1800, + }); +} + +function json(res, status, body) { + const payload = JSON.stringify(body); + res.writeHead(status, { + "content-type": "application/json", + "content-length": Buffer.byteLength(payload), + }); + res.end(payload); +} + +async function bodyText(req) { + const chunks = []; + for await (const chunk of req) { + chunks.push(chunk); + if (Buffer.concat(chunks).length > 1024 * 1024) { + throw new Error("request body too large"); + } + } + return Buffer.concat(chunks).toString("utf8"); +} + +async function handleTokenExchange(req, res) { + const params = new URLSearchParams(await bodyText(req)); + if (params.get("grant_type") !== TOKEN_EXCHANGE_GRANT_TYPE) { + return json(res, 400, { error: "unsupported_grant_type" }); + } + if (params.get("client_assertion_type") !== JWT_SPIFFE_ASSERTION_TYPE) { + return json(res, 400, { error: "unsupported_client_assertion_type" }); + } + + const jwtSvid = params.get("client_assertion"); + if (!jwtSvid) { + return json(res, 400, { error: "missing_client_assertion" }); + } + const subjectToken = params.get("subject_token"); + if (!subjectToken) { + return json(res, 400, { error: "missing_subject_token" }); + } + + const audience = params.get("audience") || ""; + const requestedScopes = (params.get("scope") || "").split(/\s+/).filter(Boolean); + const now = Math.floor(Date.now() / 1000); + + const userToken = (() => { + try { + return verifyAccessToken(subjectToken, "user_subject"); + } catch (_error) { + return null; + } + })(); + + if (userToken) { + const gatewaySvid = await verifyJwtSvid(jwtSvid, GATEWAY_TRUST_DOMAIN_PREFIX); + if (!audience.startsWith(SUPERVISOR_TRUST_DOMAIN_PREFIX)) { + return json(res, 400, { error: "unsupported_intermediate_audience", audience }); + } + const intermediateToken = signAccessToken({ + iss: ACCESS_TOKEN_ISSUER, + sub: userToken.sub, + aud: [audience], + scope: userToken.scope || "openid profile email", + azp: gatewaySvid.sub, + client_id: gatewaySvid.sub, + demo_token_use: "intermediate", + iat: now, + exp: now + 300, + }); + console.log(`issued intermediate token for user=${userToken.sub} audience=${audience}`); + return json(res, 200, { + access_token: intermediateToken, + token_type: "Bearer", + expires_in: 300, + }); + } + + const supervisorSvid = await verifyJwtSvid(jwtSvid, SUPERVISOR_TRUST_DOMAIN_PREFIX); + const intermediateToken = verifyAccessToken(subjectToken, "intermediate"); + if (!hasAudience(intermediateToken, supervisorSvid.sub)) { + return json(res, 403, { error: "intermediate_token_audience_mismatch" }); + } + if (!["alpha", "beta"].includes(audience)) { + return json(res, 400, { error: "unsupported_audience", audience }); + } + if (!requestedScopes.includes(audience)) { + return json(res, 403, { error: "missing_matching_scope" }); + } + + const accessToken = signAccessToken({ + iss: ACCESS_TOKEN_ISSUER, + sub: intermediateToken.sub, + aud: [audience, "account"], + scope: `${requestedScopes.join(" ")} profile email`, + azp: supervisorSvid.sub, + client_id: supervisorSvid.sub, + demo_token_use: "final", + iat: now, + exp: now + 300, + }); + + console.log( + `issued final token for user=${intermediateToken.sub} audience=${audience} client=${supervisorSvid.sub}`, + ); + return json(res, 200, { + access_token: accessToken, + token_type: "Bearer", + expires_in: 300, + scope: `${requestedScopes.join(" ")} profile email`, + }); +} + +http + .createServer(async (req, res) => { + try { + if (req.url === "/healthz") { + res.writeHead(200, { "content-type": "text/plain" }); + return res.end("ok\n"); + } + if (req.method === "GET" && req.url === "/demo-subject-token") { + return json(res, 200, { + access_token: issueDemoSubjectToken(), + token_type: "Bearer", + expires_in: 1800, + }); + } + if (req.method === "POST" && req.url === "/token") { + return await handleTokenExchange(req, res); + } + return json(res, 404, { error: "not_found" }); + } catch (error) { + console.error(error); + return json(res, 500, { error: "server_error", message: error.message }); + } + }) + .listen(PORT, "0.0.0.0", () => { + console.log(`token exchange issuer listening on ${PORT}`); + }); diff --git a/examples/spiffe-token-exchange-demo/k8s/workloads.yaml b/examples/spiffe-token-exchange-demo/k8s/workloads.yaml new file mode 100644 index 0000000000..2bb1639214 --- /dev/null +++ b/examples/spiffe-token-exchange-demo/k8s/workloads.yaml @@ -0,0 +1,230 @@ +# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +apiVersion: apps/v1 +kind: Deployment +metadata: + name: token-exchange-issuer + namespace: default + labels: + app: token-exchange-issuer +spec: + replicas: 1 + selector: + matchLabels: + app: token-exchange-issuer + template: + metadata: + labels: + app: token-exchange-issuer + spec: + containers: + - name: token-exchange-issuer + image: node:22-alpine + imagePullPolicy: IfNotPresent + command: ["node", "/demo/token-issuer.js"] + ports: + - name: http + containerPort: 8080 + env: + - name: ACCESS_TOKEN_SECRET + valueFrom: + secretKeyRef: + name: openshell-spiffe-token-exchange-demo + key: access-token-secret + - name: ACCESS_TOKEN_ISSUER + value: http://token-exchange-issuer.default.svc.cluster.local + - name: SPIRE_JWKS_URI + value: https://spire-spiffe-oidc-discovery-provider.spire.svc.cluster.local/keys + - name: SPIRE_JWKS_CA_FILE + value: /etc/x509/spiffe-bundle/bundle.pem + - name: SPIRE_ISSUER + value: https://spire-spiffe-oidc-discovery-provider.spire.svc.cluster.local + - name: JWT_SVID_AUDIENCE + value: http://token-exchange-issuer.default.svc.cluster.local + - name: SUPERVISOR_TRUST_DOMAIN_PREFIX + value: spiffe://openshell.local/openshell/sandbox/ + - name: GATEWAY_TRUST_DOMAIN_PREFIX + value: spiffe://openshell.local/ns/openshell/sa/ + - name: DEMO_USER_SUBJECT + value: demo-user + readinessProbe: + httpGet: + path: /healthz + port: http + volumeMounts: + - name: scripts + mountPath: /demo + readOnly: true + - name: spiffe-bundle + mountPath: /etc/x509/spiffe-bundle + readOnly: true + - name: spiffe-helper + image: ghcr.io/spiffe/spiffe-helper:0.11.0 + imagePullPolicy: IfNotPresent + args: ["-config", "/etc/spiffe-helper/spiffe-helper.conf"] + volumeMounts: + - name: spiffe-socket + mountPath: /run/spire/sockets + readOnly: true + - name: spiffe-bundle + mountPath: /target + - name: helper-config + mountPath: /etc/spiffe-helper + readOnly: true + volumes: + - name: scripts + configMap: + name: openshell-spiffe-token-exchange-demo-scripts + - name: helper-config + configMap: + name: openshell-spiffe-token-exchange-demo-helper + - name: spiffe-socket + csi: + driver: csi.spiffe.io + readOnly: true + - name: spiffe-bundle + emptyDir: {} +--- +apiVersion: v1 +kind: Service +metadata: + name: token-exchange-issuer + namespace: default +spec: + selector: + app: token-exchange-issuer + ports: + - name: http + port: 80 + targetPort: http +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: alpha-exchange + namespace: default + labels: + app: alpha-exchange +spec: + replicas: 1 + selector: + matchLabels: + app: alpha-exchange + template: + metadata: + labels: + app: alpha-exchange + spec: + containers: + - name: alpha-exchange + image: node:22-alpine + imagePullPolicy: IfNotPresent + command: ["node", "/demo/protected-service.js"] + ports: + - name: http + containerPort: 8080 + env: + - name: SERVICE_NAME + value: alpha + - name: EXPECTED_AUDIENCE + value: alpha + - name: EXPECTED_SCOPE + value: alpha + - name: ACCESS_TOKEN_SECRET + valueFrom: + secretKeyRef: + name: openshell-spiffe-token-exchange-demo + key: access-token-secret + - name: ACCESS_TOKEN_ISSUER + value: http://token-exchange-issuer.default.svc.cluster.local + readinessProbe: + httpGet: + path: /healthz + port: http + volumeMounts: + - name: scripts + mountPath: /demo + readOnly: true + volumes: + - name: scripts + configMap: + name: openshell-spiffe-token-exchange-demo-scripts +--- +apiVersion: v1 +kind: Service +metadata: + name: alpha-exchange + namespace: default +spec: + selector: + app: alpha-exchange + ports: + - name: http + port: 80 + targetPort: http +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: beta-exchange + namespace: default + labels: + app: beta-exchange +spec: + replicas: 1 + selector: + matchLabels: + app: beta-exchange + template: + metadata: + labels: + app: beta-exchange + spec: + containers: + - name: beta-exchange + image: node:22-alpine + imagePullPolicy: IfNotPresent + command: ["node", "/demo/protected-service.js"] + ports: + - name: http + containerPort: 8080 + env: + - name: SERVICE_NAME + value: beta + - name: EXPECTED_AUDIENCE + value: beta + - name: EXPECTED_SCOPE + value: beta + - name: ACCESS_TOKEN_SECRET + valueFrom: + secretKeyRef: + name: openshell-spiffe-token-exchange-demo + key: access-token-secret + - name: ACCESS_TOKEN_ISSUER + value: http://token-exchange-issuer.default.svc.cluster.local + readinessProbe: + httpGet: + path: /healthz + port: http + volumeMounts: + - name: scripts + mountPath: /demo + readOnly: true + volumes: + - name: scripts + configMap: + name: openshell-spiffe-token-exchange-demo-scripts +--- +apiVersion: v1 +kind: Service +metadata: + name: beta-exchange + namespace: default +spec: + selector: + app: beta-exchange + ports: + - name: http + port: 80 + targetPort: http diff --git a/examples/spiffe-token-exchange-demo/provider-profile.yaml b/examples/spiffe-token-exchange-demo/provider-profile.yaml new file mode 100644 index 0000000000..0c751ed007 --- /dev/null +++ b/examples/spiffe-token-exchange-demo/provider-profile.yaml @@ -0,0 +1,53 @@ +# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +id: spiffe-token-exchange-demo +display_name: SPIFFE token exchange demo +description: Dynamic token exchange for alpha/beta demo services using stored user subject tokens and SPIFFE JWT-SVID authentication +category: other +credentials: + - name: subject_token + description: Demo user subject token stored on the provider for token exchange + required: true + - name: access_token + description: Access token obtained via RFC 8693 token exchange + required: false + auth_style: bearer + header_name: Authorization + token_grant: + grant_type: token_exchange + token_endpoint: http://token-exchange-issuer.default.svc.cluster.local/token + audience: demo-default + jwt_svid_audience: http://token-exchange-issuer.default.svc.cluster.local + client_assertion_type: urn:ietf:params:oauth:client-assertion-type:jwt-spiffe + scopes: [demo] + cache_ttl_seconds: 60 + subject_token: + source: provider_credential + credential: subject_token + subject_token_type: urn:ietf:params:oauth:token-type:access_token + audience_overrides: + - host: alpha-exchange.default.svc.cluster.local + port: 80 + audience: alpha + scopes: [alpha] + - host: beta-exchange.default.svc.cluster.local + port: 80 + audience: beta + scopes: [beta] +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: + - /usr/bin/curl + - /usr/local/bin/curl From 0f928e4a6fcd6f1584aadcc277f13837f9bec543 Mon Sep 17 00:00:00 2001 From: Gordon Sim Date: Tue, 23 Jun 2026 20:23:19 +0100 Subject: [PATCH 4/8] test(e2e): cover Podman token exchange grants Signed-off-by: Gordon Sim --- crates/openshell-core/src/driver_utils.rs | 3 + crates/openshell-driver-podman/src/config.rs | 8 + .../openshell-driver-podman/src/container.rs | 75 +- .../src/l7/token_grant_injection.rs | 19 +- .../src/process.rs | 217 +++-- .../openshell-supervisor-process/src/ssh.rs | 50 +- e2e/rust/Cargo.lock | 571 ++++++++++++- e2e/rust/Cargo.toml | 10 + e2e/rust/e2e-podman.sh | 6 + e2e/rust/tests/provider_token_exchange.rs | 784 ++++++++++++++++++ e2e/with-podman-gateway.sh | 13 + 11 files changed, 1600 insertions(+), 156 deletions(-) create mode 100644 e2e/rust/tests/provider_token_exchange.rs diff --git a/crates/openshell-core/src/driver_utils.rs b/crates/openshell-core/src/driver_utils.rs index ae621fde08..597ea61675 100644 --- a/crates/openshell-core/src/driver_utils.rs +++ b/crates/openshell-core/src/driver_utils.rs @@ -348,6 +348,9 @@ pub fn read_upstream_proxy_credential_file(path: &str) -> Result Ok(buf) } +/// Container-side directory where the provider SPIFFE Workload API socket is mounted. +pub const PROVIDER_SPIFFE_WORKLOAD_API_SOCKET_MOUNT_DIR: &str = "/spiffe-workload-api"; + /// Return the XDG state path for a driver's sandbox JWT token file. /// /// The resulting path is `$XDG_STATE_HOME/openshell/[/]//sandbox.jwt`. diff --git a/crates/openshell-driver-podman/src/config.rs b/crates/openshell-driver-podman/src/config.rs index 783611e507..b3a3c548e5 100644 --- a/crates/openshell-driver-podman/src/config.rs +++ b/crates/openshell-driver-podman/src/config.rs @@ -126,6 +126,9 @@ pub struct PodmanComputeConfig { /// `template.driver_config`. #[serde(default)] pub enable_bind_mounts: bool, + /// Host path to a SPIFFE Workload API Unix socket exposed to sandbox + /// supervisors for provider token exchange client assertions. + pub provider_spiffe_workload_api_socket: Option, /// Health check interval in seconds for sandbox containers. /// /// Podman runs the health check command at this interval to determine @@ -503,6 +506,7 @@ impl Default for PodmanComputeConfig { guest_tls_key: None, sandbox_pids_limit: DEFAULT_SANDBOX_PIDS_LIMIT, enable_bind_mounts: false, + provider_spiffe_workload_api_socket: None, health_check_interval_secs: DEFAULT_HEALTH_CHECK_INTERVAL_SECS, https_proxy: None, no_proxy: None, @@ -534,6 +538,10 @@ impl std::fmt::Debug for PodmanComputeConfig { .field("guest_tls_key", &self.guest_tls_key) .field("sandbox_pids_limit", &self.sandbox_pids_limit) .field("enable_bind_mounts", &self.enable_bind_mounts) + .field( + "provider_spiffe_workload_api_socket", + &self.provider_spiffe_workload_api_socket, + ) .field( "health_check_interval_secs", &self.health_check_interval_secs, diff --git a/crates/openshell-driver-podman/src/container.rs b/crates/openshell-driver-podman/src/container.rs index d011790950..4a0a978286 100644 --- a/crates/openshell-driver-podman/src/container.rs +++ b/crates/openshell-driver-podman/src/container.rs @@ -14,7 +14,6 @@ use openshell_core::{driver_mounts, proto_struct}; use serde::Serialize; use serde_json::Value; use std::collections::{BTreeMap, HashSet}; -#[cfg(target_os = "linux")] use std::path::Path; /// Returns `true` when `SELinux` is enabled (enforcing or permissive). @@ -65,6 +64,8 @@ const TLS_KEY_MOUNT_PATH: &str = openshell_core::driver_utils::TLS_KEY_MOUNT_PAT const SANDBOX_TOKEN_MOUNT_PATH: &str = openshell_core::driver_utils::SANDBOX_TOKEN_MOUNT_PATH; const UPSTREAM_PROXY_AUTH_MOUNT_PATH: &str = openshell_core::driver_utils::UPSTREAM_PROXY_AUTH_MOUNT_PATH; +const PROVIDER_SPIFFE_WORKLOAD_API_SOCKET_MOUNT_DIR: &str = + openshell_core::driver_utils::PROVIDER_SPIFFE_WORKLOAD_API_SOCKET_MOUNT_DIR; /// Directory inside sandbox containers where the supervisor binary is mounted. const SUPERVISOR_MOUNT_DIR: &str = openshell_core::driver_utils::SUPERVISOR_CONTAINER_DIR; @@ -544,6 +545,13 @@ fn build_env( ); } + if let Some(socket_path) = provider_spiffe_workload_api_socket_env_value(config) { + env.insert( + openshell_core::sandbox_env::PROVIDER_SPIFFE_WORKLOAD_API_SOCKET.into(), + socket_path, + ); + } + env.remove(openshell_core::sandbox_env::SANDBOX_TOKEN); env.remove(openshell_core::sandbox_env::SANDBOX_TOKEN_FILE); // Prevent user-supplied environment from overriding the TLS server name @@ -1284,6 +1292,17 @@ pub fn build_container_spec_for_image( options: opts, }); } + if let Some(path) = provider_spiffe_workload_api_socket_mount_source(config) { + // No SELinux relabel - the SPIRE agent socket is shared host + // infrastructure and must keep its existing SELinux context. + let ro = vec!["ro".into(), "rbind".into()]; + m.push(Mount { + kind: "bind".into(), + source: path.display().to_string(), + destination: PROVIDER_SPIFFE_WORKLOAD_API_SOCKET_MOUNT_DIR.into(), + options: ro, + }); + } m.extend(user_mounts.mounts); m }, @@ -1330,6 +1349,33 @@ pub fn build_container_spec_for_image( Ok(serde_json::to_value(container_spec).expect("ContainerSpec serialization cannot fail")) } +fn provider_spiffe_workload_api_socket_env_value(config: &PodmanComputeConfig) -> Option { + let host_path = config.provider_spiffe_workload_api_socket.as_ref()?; + let raw = host_path.to_str()?; + if raw.starts_with("tcp:") { + return Some(raw.to_string()); + } + let host_path = raw + .strip_prefix("unix:") + .map_or(host_path.as_path(), Path::new); + let file_name = host_path.file_name()?.to_str()?; + Some(format!( + "{PROVIDER_SPIFFE_WORKLOAD_API_SOCKET_MOUNT_DIR}/{file_name}" + )) +} + +fn provider_spiffe_workload_api_socket_mount_source(config: &PodmanComputeConfig) -> Option<&Path> { + let host_path = config.provider_spiffe_workload_api_socket.as_ref()?; + let raw = host_path.to_str()?; + if raw.starts_with("tcp:") { + return None; + } + let host_path = raw + .strip_prefix("unix:") + .map_or(host_path.as_path(), Path::new); + host_path.parent() +} + fn hostadd_entries(config: &PodmanComputeConfig) -> Vec { let host_gateway_ip = config.host_gateway_ip.trim(); if host_gateway_ip.is_empty() { @@ -2927,6 +2973,33 @@ mod tests { ); } + #[test] + fn container_spec_includes_provider_spiffe_socket_when_configured() { + let sandbox = test_sandbox("spiffe-id", "spiffe-name"); + let mut config = test_config(); + config.provider_spiffe_workload_api_socket = + Some(std::path::PathBuf::from("/host/spire-agent.sock")); + + let spec = build_container_spec(&sandbox, &config); + + let env_map = spec["env"].as_object().expect("env should be an object"); + assert_eq!( + env_map + .get(openshell_core::sandbox_env::PROVIDER_SPIFFE_WORKLOAD_API_SOCKET) + .and_then(|v| v.as_str()), + Some("/spiffe-workload-api/spire-agent.sock"), + ); + + let mounts = spec["mounts"] + .as_array() + .expect("mounts should be an array"); + assert!(mounts.iter().any(|m| { + m["type"].as_str() == Some("bind") + && m["source"].as_str() == Some("/host") + && m["destination"].as_str() == Some(PROVIDER_SPIFFE_WORKLOAD_API_SOCKET_MOUNT_DIR) + })); + } + #[test] fn container_spec_omits_tls_without_config() { let sandbox = test_sandbox("notls-id", "notls-name"); diff --git a/crates/openshell-supervisor-network/src/l7/token_grant_injection.rs b/crates/openshell-supervisor-network/src/l7/token_grant_injection.rs index 5d7e8005e2..fc440c6547 100644 --- a/crates/openshell-supervisor-network/src/l7/token_grant_injection.rs +++ b/crates/openshell-supervisor-network/src/l7/token_grant_injection.rs @@ -357,7 +357,8 @@ fn inject_header(raw_header: &[u8], header_name: &str, header_value: &str) -> Re pub mod test_support { use super::*; use openshell_core::proto::{ - ProviderCredentialTokenGrant, ProviderCredentialTokenGrantType, ProviderProfileCredential, + ProviderCredentialTokenGrant, ProviderCredentialTokenGrantSubjectToken, + ProviderCredentialTokenGrantType, ProviderProfileCredential, }; use std::collections::HashMap; use std::sync::{Arc, Mutex}; @@ -493,7 +494,7 @@ pub mod test_support { assert_eq!(request.jwt_svid_audience, "https://auth.example.com"); assert_eq!( request.client_assertion_type, - "urn:ietf:params:oauth:client-assertion-type:jwt-bearer" + "urn:ietf:params:oauth:client-assertion-type:jwt-spiffe" ); assert_eq!(request.audience, "api://example"); assert_eq!(request.scopes, ["read"]); @@ -527,14 +528,14 @@ pub mod test_support { fn token_exchange_grant() -> ProviderCredentialTokenGrant { ProviderCredentialTokenGrant { + client_assertion_type: "urn:ietf:params:oauth:client-assertion-type:jwt-spiffe" + .to_string(), grant_type: ProviderCredentialTokenGrantType::TokenExchange as i32, - subject_token: Some( - openshell_core::proto::ProviderCredentialTokenGrantSubjectToken { - source: "provider_credential".to_string(), - credential: "subject_token".to_string(), - subject_token_type: "urn:ietf:params:oauth:token-type:access_token".to_string(), - }, - ), + subject_token: Some(ProviderCredentialTokenGrantSubjectToken { + source: "provider_credential".to_string(), + credential: "user_oidc_token".to_string(), + subject_token_type: "urn:ietf:params:oauth:token-type:id_token".to_string(), + }), requested_token_type: "urn:ietf:params:oauth:token-type:access_token".to_string(), ..token_grant() } diff --git a/crates/openshell-supervisor-process/src/process.rs b/crates/openshell-supervisor-process/src/process.rs index 659fe3dc06..2193a6d5d6 100644 --- a/crates/openshell-supervisor-process/src/process.rs +++ b/crates/openshell-supervisor-process/src/process.rs @@ -9,14 +9,14 @@ use crate::managed_children; #[cfg(target_os = "linux")] use crate::netns::NetworkNamespace; use crate::sandbox; -use miette::{IntoDiagnostic, Result}; +use miette::{IntoDiagnostic, Result, WrapErr}; use nix::sys::signal::{self, Signal}; use nix::unistd::{Gid, Group, Pid, Uid, User}; use openshell_core::policy::{NetworkMode, SandboxPolicy}; use std::collections::HashMap; use std::ffi::CString; #[cfg(target_os = "linux")] -use std::os::fd::{AsRawFd, OwnedFd, RawFd}; +use std::os::fd::RawFd; #[cfg(target_os = "linux")] use std::os::unix::ffi::OsStrExt; #[cfg(unix)] @@ -27,6 +27,8 @@ use std::path::PathBuf; use std::process::Stdio; #[cfg(target_os = "linux")] use std::sync::OnceLock; +#[cfg(target_os = "linux")] +use std::sync::mpsc; use tokio::process::{Child, Command}; use tracing::{debug, info}; @@ -321,11 +323,13 @@ static SUPERVISOR_IDENTITY_MOUNT_NS: OnceLock, } #[cfg(target_os = "linux")] type SupervisorIdentityNsRef = &'static SupervisorIdentityMountNamespace; +#[cfg(target_os = "linux")] +type SupervisorIdentitySpawnJob = Box; #[cfg(target_os = "linux")] impl SupervisorIdentityMountNamespace { @@ -334,13 +338,9 @@ impl SupervisorIdentityMountNamespace { return Ok(None); }; Ok(Some(Self { - fd: create_supervisor_identity_mount_namespace(&target)?, + spawn_tx: start_supervisor_identity_spawn_worker(target)?, })) } - - pub fn enter_for_child(&self) -> std::io::Result<()> { - set_mount_namespace(self.fd.as_raw_fd()) - } } #[cfg(target_os = "linux")] @@ -371,6 +371,100 @@ pub fn supervisor_identity_mount_from_env() -> Result std::io::Result { + let namespace = supervisor_identity_mount_from_env() + .map_err(|err| std::io::Error::other(err.to_string()))?; + let Some(namespace) = namespace else { + return cmd.spawn(); + }; + namespace.spawn_tokio_command(cmd) +} + +#[cfg(target_os = "linux")] +pub fn spawn_std_command_with_supervisor_identity_namespace( + mut cmd: std::process::Command, +) -> std::io::Result { + let namespace = supervisor_identity_mount_from_env() + .map_err(|err| std::io::Error::other(err.to_string()))?; + let Some(namespace) = namespace else { + return cmd.spawn(); + }; + namespace.spawn_std_command(cmd) +} + +#[cfg(target_os = "linux")] +impl SupervisorIdentityMountNamespace { + fn spawn_tokio_command(&self, mut cmd: Command) -> std::io::Result { + let (result_tx, result_rx) = mpsc::channel(); + let handle = tokio::runtime::Handle::current(); + self.spawn_tx + .send(Box::new(move || { + let _guard = handle.enter(); + let _ = result_tx.send(cmd.spawn()); + })) + .map_err(|_| std::io::Error::other("supervisor identity spawn worker stopped"))?; + result_rx + .recv() + .map_err(|_| std::io::Error::other("supervisor identity spawn worker dropped result"))? + } + + fn spawn_std_command( + &self, + mut cmd: std::process::Command, + ) -> std::io::Result { + let (result_tx, result_rx) = mpsc::channel(); + self.spawn_tx + .send(Box::new(move || { + let _ = result_tx.send(cmd.spawn()); + })) + .map_err(|_| std::io::Error::other("supervisor identity spawn worker stopped"))?; + result_rx + .recv() + .map_err(|_| std::io::Error::other("supervisor identity spawn worker dropped result"))? + } +} + +#[cfg(target_os = "linux")] +fn start_supervisor_identity_spawn_worker( + target: PathBuf, +) -> Result> { + let (spawn_tx, spawn_rx) = mpsc::channel::(); + let (ready_tx, ready_rx) = mpsc::channel::>(); + std::thread::Builder::new() + .name("openshell-identity-spawn".into()) + .spawn(move || { + let setup = (|| -> std::io::Result<()> { + private_mount_namespace()?; + let target = + cstring_path(&target).map_err(|err| std::io::Error::other(err.to_string()))?; + mount_empty_tmpfs(&target) + })(); + let ready = match &setup { + Ok(()) => Ok(()), + Err(err) => Err(std::io::Error::new( + err.kind(), + format!("supervisor identity setup failed: {err}"), + )), + }; + let _ = ready_tx.send(ready); + if setup.is_err() { + return; + } + while let Ok(job) = spawn_rx.recv() { + job(); + } + }) + .map_err(|err| miette::miette!("failed to spawn supervisor identity worker: {err}"))?; + ready_rx + .recv() + .map_err(|err| miette::miette!("supervisor identity worker did not start: {err}"))? + .map_err(|err| miette::miette!("{err}"))?; + Ok(spawn_tx) +} + #[cfg(target_os = "linux")] fn supervisor_identity_socket_path_from_env() -> Option<(&'static str, String)> { std::env::var(openshell_core::sandbox_env::PROVIDER_SPIFFE_WORKLOAD_API_SOCKET) @@ -391,10 +485,7 @@ fn supervisor_identity_mount_target(socket_path: &str) -> Result return Ok(None); } if trimmed.starts_with("tcp:") { - return Err(miette::miette!( - "{} must be a UNIX socket path so sandbox child processes can hide it", - openshell_core::sandbox_env::PROVIDER_SPIFFE_WORKLOAD_API_SOCKET - )); + return Ok(None); } let path = trimmed.strip_prefix("unix:").unwrap_or(trimmed); let path = Path::new(path); @@ -437,52 +528,12 @@ fn cstring_path(path: &Path) -> Result { .map_err(|_| miette::miette!("path contains an interior NUL byte: {}", path.display())) } -#[cfg(target_os = "linux")] -fn create_supervisor_identity_mount_namespace(target: &Path) -> Result { - let original_ns = open_current_mount_namespace() - .map_err(|err| miette::miette!("failed to open original mount namespace: {err}"))?; - - private_mount_namespace() - .map_err(|err| miette::miette!("failed to create supervisor identity namespace: {err}"))?; - - let target = cstring_path(target)?; - let result = (|| -> Result { - mount_empty_tmpfs(&target).map_err(|err| { - miette::miette!("failed to hide supervisor identity mount from child namespace: {err}") - })?; - open_current_mount_namespace() - .map_err(|err| miette::miette!("failed to open sanitized mount namespace: {err}")) - })(); - - set_mount_namespace(original_ns.as_raw_fd()).map_err(|restore_err| { - let result_msg = result.as_ref().err().map_or_else( - || "sanitized namespace was created".to_string(), - ToString::to_string, - ); - miette::miette!( - "failed to restore original mount namespace after supervisor identity isolation setup: \ - {restore_err}; setup result: {result_msg}" - ) - })?; - - result -} - -#[cfg(target_os = "linux")] -fn open_current_mount_namespace() -> std::io::Result { - let file = std::fs::File::open("/proc/thread-self/ns/mnt")?; - Ok(file.into()) -} - #[cfg(target_os = "linux")] fn private_mount_namespace() -> std::io::Result<()> { #[allow(unsafe_code)] let rc = unsafe { libc::unshare(libc::CLONE_NEWNS) }; if rc != 0 { - return Err(std::io::Error::other(format!( - "failed to create private mount namespace: {}", - std::io::Error::last_os_error() - ))); + return Err(std::io::Error::last_os_error()); } #[allow(unsafe_code)] @@ -497,23 +548,7 @@ fn private_mount_namespace() -> std::io::Result<()> { ) }; if rc != 0 { - return Err(std::io::Error::other(format!( - "failed to mark mount namespace private: {}", - std::io::Error::last_os_error() - ))); - } - Ok(()) -} - -#[cfg(target_os = "linux")] -fn set_mount_namespace(fd: RawFd) -> std::io::Result<()> { - #[allow(unsafe_code)] - let rc = unsafe { libc::setns(fd, libc::CLONE_NEWNS) }; - if rc != 0 { - return Err(std::io::Error::other(format!( - "failed to enter mount namespace: {}", - std::io::Error::last_os_error() - ))); + return Err(std::io::Error::last_os_error()); } Ok(()) } @@ -533,10 +568,7 @@ fn mount_empty_tmpfs(target: &CString) -> std::io::Result<()> { ) }; if rc != 0 { - return Err(std::io::Error::other(format!( - "failed to hide supervisor identity mount from child process: {}", - std::io::Error::last_os_error() - ))); + return Err(std::io::Error::last_os_error()); } Ok(()) } @@ -696,15 +728,6 @@ impl ProcessHandle { #[cfg(target_os = "linux")] let prepared_sandbox = prepare_child_sandbox(policy, workspace.root(), enforcement_mode) .map_err(|err| miette::miette!("Failed to prepare sandbox: {err}"))?; - #[cfg(target_os = "linux")] - let supervisor_identity_mount = if enforcement_mode.uses_privileged_process_setup() { - supervisor_identity_mount_from_env().map_err(|err| { - miette::miette!("Failed to prepare supervisor identity isolation: {err}") - })? - } else { - None - }; - // Set up process group for signal handling (non-interactive mode only). // In interactive mode, we inherit the parent's process group to maintain // proper terminal control for shells and interactive programs. @@ -724,19 +747,17 @@ impl ProcessHandle { libc::setpgid(0, 0); } - // Enter network namespace before applying other restrictions + // Enter network namespace before applying other restrictions. if let Some(fd) = netns_fd { let result = libc::setns(fd, libc::CLONE_NEWNET); if result != 0 { - return Err(std::io::Error::last_os_error()); + return Err(std::io::Error::other(format!( + "failed to enter network namespace: {}", + std::io::Error::last_os_error() + ))); } } - #[cfg(target_os = "linux")] - if let Some(mount) = supervisor_identity_mount { - mount.enter_for_child()?; - } - // Drop privileges. initgroups/setgid/setuid need access to // /etc/group and /etc/passwd which would be blocked if // Landlock were already enforced. @@ -761,7 +782,15 @@ impl ProcessHandle { } } - let child = cmd.spawn().into_diagnostic()?; + #[cfg(target_os = "linux")] + let child = spawn_command_with_supervisor_identity_namespace(cmd) + .into_diagnostic() + .wrap_err("failed to spawn sandbox entrypoint process")?; + #[cfg(not(target_os = "linux"))] + let child = cmd + .spawn() + .into_diagnostic() + .wrap_err("failed to spawn sandbox entrypoint process")?; let pid = child.id().unwrap_or(0); managed_children::register(pid); @@ -3642,7 +3671,11 @@ mod tests { #[test] fn supervisor_identity_mount_target_rejects_unhideable_endpoints() { - assert!(supervisor_identity_mount_target("tcp:127.0.0.1:8081").is_err()); + assert_eq!( + supervisor_identity_mount_target("tcp:127.0.0.1:8081") + .expect("tcp endpoint should not require mount hiding"), + None + ); assert!(supervisor_identity_mount_target("spiffe-workload-api/spire-agent.sock").is_err()); assert!(supervisor_identity_mount_target("/spire-agent.sock").is_err()); } diff --git a/crates/openshell-supervisor-process/src/ssh.rs b/crates/openshell-supervisor-process/src/ssh.rs index 07302da953..968bfd90ef 100644 --- a/crates/openshell-supervisor-process/src/ssh.rs +++ b/crates/openshell-supervisor-process/src/ssh.rs @@ -885,9 +885,12 @@ fn spawn_pty_shell( enforcement_mode, #[cfg(target_os = "linux")] prepared_sandbox, - )?; + ); } + #[cfg(target_os = "linux")] + let mut child = crate::process::spawn_std_command_with_supervisor_identity_namespace(cmd)?; + #[cfg(not(target_os = "linux"))] let mut child = cmd.spawn()?; #[cfg(target_os = "linux")] let child_pid = child.id(); @@ -1040,9 +1043,12 @@ fn spawn_pipe_exec( enforcement_mode, #[cfg(target_os = "linux")] prepared_sandbox, - )?; + ); } + #[cfg(target_os = "linux")] + let mut child = crate::process::spawn_std_command_with_supervisor_identity_namespace(cmd)?; + #[cfg(not(target_os = "linux"))] let mut child = cmd.spawn()?; #[cfg(target_os = "linux")] let child_pid = child.id(); @@ -1183,19 +1189,11 @@ mod unsafe_pty { resolved_identity: ResolvedProcessIdentity, enforcement_mode: ProcessEnforcementMode, #[cfg(target_os = "linux")] prepared: Option, - ) -> anyhow::Result<()> { + ) { // Wrap in Option so we can .take() it out of the FnMut closure. // pre_exec is only called once (after fork, before exec). #[cfg(target_os = "linux")] let mut prepared = prepared; - #[cfg(target_os = "linux")] - let supervisor_identity_mount = if enforcement_mode.uses_privileged_process_setup() { - crate::process::supervisor_identity_mount_from_env().map_err(|err| { - anyhow::anyhow!("failed to prepare supervisor identity isolation: {err}") - })? - } else { - None - }; unsafe { cmd.pre_exec(move || { setsid().map_err(|err| std::io::Error::other(err.to_string()))?; @@ -1207,13 +1205,10 @@ mod unsafe_pty { resolved_identity, enforcement_mode, #[cfg(target_os = "linux")] - supervisor_identity_mount, - #[cfg(target_os = "linux")] prepared.take(), ) }); } - Ok(()) } /// Pre-exec hook for pipe-based (non-PTY) exec. @@ -1235,17 +1230,9 @@ mod unsafe_pty { resolved_identity: ResolvedProcessIdentity, enforcement_mode: ProcessEnforcementMode, #[cfg(target_os = "linux")] prepared: Option, - ) -> anyhow::Result<()> { + ) { #[cfg(target_os = "linux")] let mut prepared = prepared; - #[cfg(target_os = "linux")] - let supervisor_identity_mount = if enforcement_mode.uses_privileged_process_setup() { - crate::process::supervisor_identity_mount_from_env().map_err(|err| { - anyhow::anyhow!("failed to prepare supervisor identity isolation: {err}") - })? - } else { - None - }; unsafe { cmd.pre_exec(move || { enter_netns_and_sandbox( @@ -1254,13 +1241,10 @@ mod unsafe_pty { resolved_identity, enforcement_mode, #[cfg(target_os = "linux")] - supervisor_identity_mount, - #[cfg(target_os = "linux")] prepared.take(), ) }); } - Ok(()) } fn enter_netns_and_sandbox( @@ -1268,9 +1252,6 @@ mod unsafe_pty { policy: &SandboxPolicy, resolved_identity: ResolvedProcessIdentity, enforcement_mode: ProcessEnforcementMode, - #[cfg(target_os = "linux")] supervisor_identity_mount: Option< - &crate::process::SupervisorIdentityMountNamespace, - >, #[cfg(target_os = "linux")] prepared: Option, ) -> std::io::Result<()> { // Enter network namespace before dropping privileges. @@ -1289,11 +1270,6 @@ mod unsafe_pty { #[cfg(not(target_os = "linux"))] let _ = netns_fd; - #[cfg(target_os = "linux")] - if let Some(mount) = supervisor_identity_mount { - mount.enter_for_child()?; - } - // Drop privileges. initgroups/setgid/setuid need /etc/group and // /etc/passwd which would be blocked if Landlock were already enforced. if enforcement_mode.uses_privileged_process_setup() { @@ -1894,8 +1870,7 @@ mod tests { ) .expect("prepare should succeed in test environment"), ), - ) - .expect("install pre_exec should succeed"); + ); let output = cmd .spawn() @@ -1950,8 +1925,7 @@ mod tests { ProcessEnforcementMode::Full, #[cfg(target_os = "linux")] None, - ) - .expect("install pre_exec should succeed"); + ); let output = cmd .spawn() diff --git a/e2e/rust/Cargo.lock b/e2e/rust/Cargo.lock index 679e2c326d..bbccb7496a 100644 --- a/e2e/rust/Cargo.lock +++ b/e2e/rust/Cargo.lock @@ -8,12 +8,72 @@ version = "1.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" +[[package]] +name = "async-trait" +version = "0.1.91" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae36dc4177970ef04fde5178d3e2429882def40e57a451f919c098f72baa6cec" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + [[package]] name = "atomic-waker" version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" +[[package]] +name = "autocfg" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" + +[[package]] +name = "axum" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31b698c5f9a010f6573133b09e0de5408834d0c82f8d7475a89fc1867a71cd90" +dependencies = [ + "axum-core", + "bytes", + "futures-util", + "http", + "http-body", + "http-body-util", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "serde_core", + "sync_wrapper", + "tower", + "tower-layer", + "tower-service", +] + +[[package]] +name = "axum-core" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "http-body-util", + "mime", + "pin-project-lite", + "sync_wrapper", + "tower-layer", + "tower-service", +] + [[package]] name = "base64" version = "0.22.1" @@ -78,12 +138,28 @@ dependencies = [ "serde_repr", ] +[[package]] +name = "bumpalo" +version = "3.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" + [[package]] name = "bytes" version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" +[[package]] +name = "cc" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5add81bb678e6cb321aff7fa0dc7689ad82b112dbc032cea19f91d6b8e3582b9" +dependencies = [ + "find-msvc-tools", + "shlex", +] + [[package]] name = "cfg-if" version = "1.0.4" @@ -115,6 +191,12 @@ dependencies = [ "typenum", ] +[[package]] +name = "deranged" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" + [[package]] name = "digest" version = "0.10.7" @@ -127,20 +209,20 @@ dependencies = [ [[package]] name = "displaydoc" -version = "0.2.6" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f" +checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn 3.0.3", ] [[package]] name = "either" -version = "1.16.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" +checksum = "9e5e8f6c15a24b9a3ee5efec809ccd006d3b30e8b3bb63c39af737c7f87daa1d" [[package]] name = "equivalent" @@ -155,7 +237,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys", + "windows-sys 0.61.2", ] [[package]] @@ -164,6 +246,18 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223" +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + [[package]] name = "form_urlencoded" version = "1.2.2" @@ -245,6 +339,19 @@ dependencies = [ "version_check", ] +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi", + "wasm-bindgen", +] + [[package]] name = "getrandom" version = "0.3.4" @@ -268,6 +375,25 @@ dependencies = [ "r-efi 6.0.0", ] +[[package]] +name = "h2" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6cb093c84e8bd9b188d4c4a8cb6579fc016968d14c99882163cd3ff402a4f155" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + [[package]] name = "hashbrown" version = "0.17.1" @@ -282,9 +408,9 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "http" -version = "1.4.2" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6970f50e31d6fc17d3fa27329444bfa74e196cf62e95052a3f6fee181dba6425" +checksum = "918d3568bebf352712bc2ef3d46a8bcf1a75b373be6539de198e9105cbbf9ce0" dependencies = [ "bytes", "itoa", @@ -335,6 +461,7 @@ dependencies = [ "bytes", "futures-channel", "futures-core", + "h2", "http", "http-body", "httparse", @@ -360,6 +487,19 @@ dependencies = [ "tower-service", ] +[[package]] +name = "hyper-timeout" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b90d566bffbce6a75bd8b09a05aa8c2cb1fabb6cb348f8840c9e4c90a0d83b0" +dependencies = [ + "hyper", + "hyper-util", + "pin-project-lite", + "tokio", + "tower-service", +] + [[package]] name = "hyper-util" version = "0.1.20" @@ -523,6 +663,32 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" +[[package]] +name = "js-sys" +version = "0.3.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53b44bfcdb3f8d5837a46dae1ca9660a837176eee74a28b229bc626816589102" +dependencies = [ + "cfg-if", + "futures-util", + "wasm-bindgen", +] + +[[package]] +name = "jsonwebtoken" +version = "9.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a87cc7a48537badeae96744432de36f4be2b4a34a05a5ef32e9dd8a1c169dde" +dependencies = [ + "base64", + "js-sys", + "pem", + "ring", + "serde", + "serde_json", + "simple_asn1", +] + [[package]] name = "libc" version = "0.2.189" @@ -566,12 +732,24 @@ version = "0.4.33" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" +[[package]] +name = "matchit" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" + [[package]] name = "memchr" version = "2.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + [[package]] name = "mio" version = "1.2.2" @@ -580,7 +758,7 @@ checksum = "30d65c71f1ce40ab09135ce117d742b9f8a19ff91a41a8b57ed50bc2de59c427" dependencies = [ "libc", "wasi", - "windows-sys", + "windows-sys 0.61.2", ] [[package]] @@ -595,6 +773,40 @@ dependencies = [ "libc", ] +[[package]] +name = "num-bigint" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c89e69e7e0f03bea5ef08013795c25018e101932225a656383bd384495ecc367" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-conv" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + [[package]] name = "once_cell" version = "1.21.4" @@ -613,6 +825,7 @@ dependencies = [ "http-body-util", "hyper", "hyper-util", + "jsonwebtoken", "nix", "prost", "rand", @@ -624,6 +837,10 @@ dependencies = [ "sha2", "tempfile", "tokio", + "tokio-stream", + "tonic", + "tonic-prost", + "tower", "url", ] @@ -650,12 +867,42 @@ dependencies = [ "windows-link", ] +[[package]] +name = "pem" +version = "3.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d30c53c26bc5b31a98cd02d20f25a7c8567146caf63ed593a9d87b2775291be" +dependencies = [ + "base64", + "serde_core", +] + [[package]] name = "percent-encoding" version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" +[[package]] +name = "pin-project" +version = "1.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2466b2336ed02bcdca6b294417127b90ec92038d1d5c4fbeac971a922e0e0924" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c96395f0a926bc13b1c17622aaddda1ecb55d49c8f1bf9777e4d877800a43f8b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + [[package]] name = "pin-project-lite" version = "0.2.17" @@ -671,6 +918,12 @@ dependencies = [ "zerovec", ] +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + [[package]] name = "ppv-lite86" version = "0.2.21" @@ -771,6 +1024,20 @@ dependencies = [ "bitflags", ] +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.17", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + [[package]] name = "rustix" version = "1.1.4" @@ -781,9 +1048,15 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys", + "windows-sys 0.61.2", ] +[[package]] +name = "rustversion" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" + [[package]] name = "ryu" version = "1.0.23" @@ -924,6 +1197,12 @@ dependencies = [ "digest", ] +[[package]] +name = "shlex" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" + [[package]] name = "signal-hook-registry" version = "1.4.8" @@ -934,6 +1213,18 @@ dependencies = [ "libc", ] +[[package]] +name = "simple_asn1" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d585997b0ac10be3c5ee635f1bab02d512760d14b7c468801ac8a01d9ae5f1d" +dependencies = [ + "num-bigint", + "num-traits", + "thiserror", + "time", +] + [[package]] name = "slab" version = "0.4.12" @@ -953,7 +1244,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4" dependencies = [ "libc", - "windows-sys", + "windows-sys 0.61.2", ] [[package]] @@ -984,6 +1275,12 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" + [[package]] name = "synstructure" version = "0.13.2" @@ -1005,7 +1302,7 @@ dependencies = [ "getrandom 0.4.3", "once_cell", "rustix", - "windows-sys", + "windows-sys 0.61.2", ] [[package]] @@ -1028,6 +1325,36 @@ dependencies = [ "syn 3.0.3", ] +[[package]] +name = "time" +version = "0.3.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e1d5e639ff6bab73cb6885cc7e7b1de96c3f32c68ec55f3952614bec1092244" +dependencies = [ + "deranged", + "num-conv", + "powerfmt", + "serde_core", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109" + +[[package]] +name = "time-macros" +version = "0.2.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e689342a48d2ea927c87ea50cabf8594854bf940e9310208848d680d668ed85" +dependencies = [ + "num-conv", + "time-core", +] + [[package]] name = "tinystr" version = "0.8.3" @@ -1052,18 +1379,29 @@ dependencies = [ "signal-hook-registry", "socket2", "tokio-macros", - "windows-sys", + "windows-sys 0.61.2", ] [[package]] name = "tokio-macros" -version = "2.7.1" +version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6328af13490e73a9b4694030fafd93f8c8c6a9dede33e821c3fc63eddf8042ba" +checksum = "78773a2a397f451582ce068015985c33193cf6dea8b74d2a639fe457b2f07b0e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn 3.0.3", +] + +[[package]] +name = "tokio-stream" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3d06f0b082ba57c26b79407372e57cf2a1e28124f78e9479fe80322cf53420b" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", ] [[package]] @@ -1080,6 +1418,71 @@ dependencies = [ "tokio", ] +[[package]] +name = "tonic" +version = "0.14.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac2a5518c70fa84342385732db33fb3f44bc4cc748936eb5833d2df34d6445ef" +dependencies = [ + "async-trait", + "axum", + "base64", + "bytes", + "h2", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-timeout", + "hyper-util", + "percent-encoding", + "pin-project", + "socket2", + "sync_wrapper", + "tokio", + "tokio-stream", + "tower", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tonic-prost" +version = "0.14.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50849f68853be452acf590cde0b146665b8d507b3b8af17261df47e02c209ea0" +dependencies = [ + "bytes", + "prost", + "tonic", +] + +[[package]] +name = "tower" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" +dependencies = [ + "futures-core", + "futures-util", + "indexmap", + "pin-project-lite", + "slab", + "sync_wrapper", + "tokio", + "tokio-util", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + [[package]] name = "tower-service" version = "0.3.3" @@ -1093,9 +1496,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" dependencies = [ "pin-project-lite", + "tracing-attributes", "tracing-core", ] +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + [[package]] name = "tracing-core" version = "0.1.36" @@ -1123,6 +1538,12 @@ version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + [[package]] name = "url" version = "2.5.8" @@ -1171,6 +1592,51 @@ dependencies = [ "wit-bindgen", ] +[[package]] +name = "wasm-bindgen" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b067c0c11094aef6b7a801c1e34a26affafdf3d051dba08456b868789aaf9a4" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "167ce5e579f6bcf889c4f7175a8a5a585de84e8ff93976ce393efa5f2837aab1" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3997c7839262f4ef12cf90b818d6340c18e80f263f1a94bf157d0ec4420380e" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn 2.0.119", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc1b4cb0cc549fcf58d7dfc081778139b3d283a081644e833e84682ad71cea24" +dependencies = [ + "unicode-ident", +] + [[package]] name = "winapi" version = "0.3.9" @@ -1199,6 +1665,15 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + [[package]] name = "windows-sys" version = "0.61.2" @@ -1208,6 +1683,70 @@ dependencies = [ "windows-link", ] +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + [[package]] name = "wit-bindgen" version = "0.57.1" diff --git a/e2e/rust/Cargo.toml b/e2e/rust/Cargo.toml index 078dabfdf7..843106a6fa 100644 --- a/e2e/rust/Cargo.toml +++ b/e2e/rust/Cargo.toml @@ -91,6 +91,11 @@ name = "vm_gateway_start" path = "tests/vm_gateway_start.rs" required-features = ["e2e-vm"] +[[test]] +name = "provider_token_exchange" +path = "tests/provider_token_exchange.rs" +required-features = ["e2e-podman"] + [[test]] name = "readyz_health" path = "tests/readyz_health.rs" @@ -169,8 +174,10 @@ futures-util = "0.3" http-body-util = "0.1" hyper = { version = "1", features = ["client", "http1"] } hyper-util = { version = "0.1", features = ["tokio"] } +jsonwebtoken = "9" prost = "0.14" tokio = { version = "1.43", features = ["full"] } +tokio-stream = { version = "0.1", features = ["net"] } tempfile = "3" sha1 = "0.10" sha2 = "0.10" @@ -179,6 +186,9 @@ rand = "0.9" serde = { version = "1", features = ["derive"] } serde_json = "1" serde_yml = "0.0.12" +tonic = { version = "0.14", features = ["transport"] } +tonic-prost = "0.14" +tower = "0.5" url = "2" nix = { version = "0.29", features = ["user"] } diff --git a/e2e/rust/e2e-podman.sh b/e2e/rust/e2e-podman.sh index 16796c0562..548a80af7f 100755 --- a/e2e/rust/e2e-podman.sh +++ b/e2e/rust/e2e-podman.sh @@ -17,6 +17,12 @@ if [ "${E2E_TEST}" = "gpu" ] && [ -z "${OPENSHELL_E2E_WORKLOAD_MANIFEST:-}" ] && echo "note: running Podman GPU e2e without a workload manifest; workload validation will log an explicit skip. Build one with 'CONTAINER_ENGINE=podman mise run e2e:workloads:build' or set OPENSHELL_E2E_WORKLOAD_MANIFEST." fi +if [ "${E2E_TEST}" = "provider_token_exchange" ]; then + export OPENSHELL_E2E_SPIFFE_FIXTURE="${OPENSHELL_E2E_SPIFFE_FIXTURE:-1}" +fi + +cargo build -p openshell-cli + TEST_ARGS=( cargo test --manifest-path "${ROOT}/e2e/rust/Cargo.toml" --features "${E2E_FEATURES}" diff --git a/e2e/rust/tests/provider_token_exchange.rs b/e2e/rust/tests/provider_token_exchange.rs new file mode 100644 index 0000000000..fe6ce39405 --- /dev/null +++ b/e2e/rust/tests/provider_token_exchange.rs @@ -0,0 +1,784 @@ +// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +#![cfg(feature = "e2e-podman")] + +use std::collections::HashMap; +use std::convert::Infallible; +use std::fs; +use std::io::Write as _; +use std::net::{Ipv4Addr, SocketAddr}; +use std::os::unix::fs::PermissionsExt as _; +use std::path::{Path, PathBuf}; +use std::process::Stdio; +use std::sync::Arc; +use std::task::{Context, Poll}; +use std::time::{Duration, SystemTime, UNIX_EPOCH}; + +use base64::Engine as _; +use futures_util::future::BoxFuture; +use jsonwebtoken::{Algorithm, EncodingKey, Header}; +use openshell_e2e::harness::binary::openshell_cmd; +use openshell_e2e::harness::port::find_free_port; +use openshell_e2e::harness::sandbox::SandboxGuard; +use serde_json::json; +use tempfile::NamedTempFile; +use tokio::io::{AsyncReadExt as _, AsyncWriteExt as _}; +use tokio::net::{TcpListener, UnixListener}; +use tokio::process::Command; +use tokio_stream::wrappers::{ReceiverStream, TcpListenerStream, UnixListenerStream}; +use tonic::body::Body as TonicBody; +use tonic::codegen::{Body, http}; +use tonic::{Request, Response, Status}; + +const TRUST_DOMAIN: &str = "openshell-e2e.test"; +const ISSUER: &str = "https://spiffe.openshell-e2e.test"; +const KEY_ID: &str = "openshell-e2e-test-key"; +const USER_SUBJECT_TOKEN: &str = "stored-user-token"; +const INTERMEDIATE_TOKEN: &str = "intermediate-token"; +const FINAL_ACCESS_TOKEN: &str = "final-access-token"; +const TOKEN_TYPE_ACCESS_TOKEN: &str = "urn:ietf:params:oauth:token-type:access_token"; +const CLIENT_ASSERTION_TYPE: &str = "urn:ietf:params:oauth:client-assertion-type:jwt-spiffe"; + +const TEST_RSA_PRIVATE_KEY: &str = r"-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCvCoZ0mVHpCHsF +zeeqw2caNIe/eb4BQUccFPhZfRnF7sCfyB84zTBmuwG2umRBdjFnVsfIIZRp2HcD +OESrRYYiE1RGfjBXImGVg2Wtza0HYhL1sLyX1eaEefylxoilmApAgWDh9p36h8J2 +s5YHwyXPTttx4DpdWDnxju1iNmwoIB8uVE/5amWgbNvlETMBOcB1RxDHtnVy+xJz +jjjrzK4Qz9WsUTHAvngdi4Yyxvci+yKpjYTg5+UWxmAN6iW522TpLe32MDb5Ug1d +trBvvepWmdQ6CBwPhBHCt/sMoSJAYSO4RKeBnBjeLQBXFTxaOv5iTGIsRTX3K471 +epHp3cT5AgMBAAECggEASQlRv/4nZN5SgsH/K8v7zb3kdHsmUly8AJYpaCGgauvr +uN/mUyueyga2uNl+MqhQBef6VWHZjO6y/gdw86v/Q2GgVQebQQhKAnpAp2w+Ceoc +siKMFqi8VkOWLU+xPbM6d97kH3TpRxt1g1T8wYFmWeF0BEiE4eUJzGaQW14M9BJ+ +G0QxmP/zjX9cNpVeApKTjBWKiH4CXG3DuI3pJ93VOMpUlOsrdLXvKGTze0e01itr +MX/MHHTE+VXB4FB+/zKSA4c36egi676OSXrGC/GDmM8ntJ4CUGeD5uZsMSADiAUn +iccv5iGRWVMIKxUS5Q4k0jy8uWuK+QVP4Y6cQWYArwKBgQDhuSNORBNpIGRfsKGN +iJo/h+qinz6pEIpa3D3oVl7rpkyvgIyaTwfXvC1vfdS9V5VIel2gV2Cx0OrI8yrr +nQu1JuNV/rLmtvqX321fgBLRdoiqF3pAy1gbmdUz1elerAIYL578gXQ6jg1bbdic +kJpn0MsoDUJGwvJnXcgLqG7q3wKBgQDGhRIa4oJsj1vqICc8zt8YsCAcot3vjWLH +588X7JdBGOWJdWxfdmGXQRn5Zw9UhMQnYa3uyTBPeVcXopThlPotYeuFhLSU856T +IJzfpzCJzC4zIQayoyvJFrKe7N70iUQ986dewYy9oxQhHvFKd/qe4ylbzZJXpthX +eWEuuBSjJwKBgGkqXt6qLPj/1IQYwUw15tfOtW0LEKCoSi3HCzjidNsJ4hSqqdeD +Fr5WuDyHvcRxt+XKzTBVRYHTOnBhiw+3XasK8UQxpJyFh/+WY1jpTNs2hLnqslTZ +6LUDWSgLc+1d6qPmHAa9Ma/OWz7L0O4xGR9hUiXY95YMYe/y668yzGq1AoGBAJyU +Gsqfu7U6gYmxoKEine6QBFPx1dD7GF2KJdq93jMXGvyHZFoLOkAdtgnz0rCcI0bY +kWKUxwj4MMxQjNM8OPMQl75xBCmz2XA8Od9htDQLmqjzNKAzePabc3lMZTJFDlE6 +29kuGf79IIRbLn/JECDAFT/2baW60Ep2T0OVJ5njAoGAfaCaQ4aVgjI027q7Y5qP +KfNSI8uuA8PLqmUY30I9KFWzN6VDLu00eKa90F4w3CeWRRQWXW1+007tTz3V1mNw +20A24Fi3HGQmXc7NyuLDODTJsWBICuOemCnRkvcxIlxb+ec7jp+XRmzDwKkzSnVN +pM2zFU8SeVkvHKlEuoHaP0s= +-----END PRIVATE KEY-----"; //notsecret + +const TEST_RSA_MODULUS_HEX: &str = concat!( + "af0a86749951e9087b05cde7aac3671a3487bf79be0141471c14f8597d19c5eec09fc81f38cd3066bb01b6ba", + "644176316756c7c8219469d877033844ab4586221354467e30572261958365adcdad076212f5b0bc97d5e684", + "79fca5c688a5980a408160e1f69dfa87c276b39607c325cf4edb71e03a5d5839f18eed62366c28201f2e54", + "4ff96a65a06cdbe511330139c0754710c7b67572fb12738e38ebccae10cfd5ac5131c0be781d8b8632c6", + "f722fb22a98d84e0e7e516c6600dea25b9db64e92dedf63036f9520d5db6b06fbdea5699d43a081c0f84", + "11c2b7fb0ca122406123b844a7819c18de2d0057153c5a3afe624c622c4535f72b8ef57a91e9ddc4f9" +); + +#[derive(Clone, PartialEq, prost::Message)] +struct JwtsvidRequest { + #[prost(string, repeated, tag = "1")] + audience: Vec, + #[prost(string, tag = "2")] + spiffe_id: String, +} + +#[derive(Clone, PartialEq, prost::Message)] +struct JwtsvidResponse { + #[prost(message, repeated, tag = "1")] + svids: Vec, +} + +#[derive(Clone, PartialEq, prost::Message)] +struct Jwtsvid { + #[prost(string, tag = "1")] + spiffe_id: String, + #[prost(string, tag = "2")] + svid: String, + #[prost(string, tag = "3")] + hint: String, +} + +#[derive(Clone, PartialEq, prost::Message)] +struct JwtBundlesRequest {} + +#[derive(Clone, PartialEq, prost::Message)] +struct JwtBundlesResponse { + #[prost(map = "string, bytes", tag = "1")] + bundles: HashMap>, +} + +#[derive(Clone)] +struct SpiffeWorkloadApi { + subject: Arc, + jwks: Arc>, + encoding_key: Arc, +} + +impl SpiffeWorkloadApi { + fn jwt_svid(&self, audience: Vec) -> Result { + let now = unix_timestamp(); + let mut header = Header::new(Algorithm::RS256); + header.kid = Some(KEY_ID.to_string()); + let claims = json!({ + "iss": ISSUER, + "sub": self.subject.as_ref(), + "aud": audience, + "iat": now, + "exp": now + 3600, + }); + jsonwebtoken::encode(&header, &claims, &self.encoding_key) + .map_err(|err| Status::internal(format!("sign JWT-SVID: {err}"))) + } +} + +#[derive(Clone)] +struct SpiffeWorkloadApiServer { + inner: Arc, +} + +impl SpiffeWorkloadApiServer { + fn new(inner: SpiffeWorkloadApi) -> Self { + Self { + inner: Arc::new(inner), + } + } +} + +impl tower::Service> for SpiffeWorkloadApiServer +where + B: Body + Send + 'static, + B::Error: Into> + Send + 'static, +{ + type Response = http::Response; + type Error = Infallible; + type Future = BoxFuture<'static, Result>; + + fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll> { + Poll::Ready(Ok(())) + } + + fn call(&mut self, req: http::Request) -> Self::Future { + match req.uri().path() { + "/SpiffeWorkloadAPI/FetchJWTSVID" => { + #[derive(Clone)] + struct FetchJwtsvidSvc(Arc); + impl tonic::server::UnaryService for FetchJwtsvidSvc { + type Response = JwtsvidResponse; + type Future = BoxFuture<'static, Result, Status>>; + + fn call(&mut self, request: Request) -> Self::Future { + let inner = Arc::clone(&self.0); + Box::pin(async move { + let request = request.into_inner(); + let svid = inner.jwt_svid(request.audience)?; + Ok(Response::new(JwtsvidResponse { + svids: vec![Jwtsvid { + spiffe_id: inner.subject.to_string(), + svid, + hint: String::new(), + }], + })) + }) + } + } + + let inner = Arc::clone(&self.inner); + Box::pin(async move { + let codec = tonic_prost::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec); + Ok(grpc.unary(FetchJwtsvidSvc(inner), req).await) + }) + } + "/SpiffeWorkloadAPI/FetchJWTBundles" => { + #[derive(Clone)] + struct FetchJwtBundlesSvc(Arc); + impl tonic::server::ServerStreamingService for FetchJwtBundlesSvc { + type Response = JwtBundlesResponse; + type ResponseStream = ReceiverStream>; + type Future = + BoxFuture<'static, Result, Status>>; + + fn call(&mut self, _request: Request) -> Self::Future { + let inner = Arc::clone(&self.0); + Box::pin(async move { + let mut bundles = HashMap::new(); + bundles.insert(TRUST_DOMAIN.to_string(), inner.jwks.as_ref().clone()); + let (tx, rx) = tokio::sync::mpsc::channel(1); + tx.send(Ok(JwtBundlesResponse { bundles })) + .await + .map_err(|err| Status::internal(format!("send bundle: {err}")))?; + Ok(Response::new(ReceiverStream::new(rx))) + }) + } + } + + let inner = Arc::clone(&self.inner); + Box::pin(async move { + let codec = tonic_prost::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec); + Ok(grpc.server_streaming(FetchJwtBundlesSvc(inner), req).await) + }) + } + _ => Box::pin(async move { + let mut response = http::Response::new(TonicBody::empty()); + response.headers_mut().insert( + tonic::Status::GRPC_STATUS, + (tonic::Code::Unimplemented as i32).into(), + ); + response.headers_mut().insert( + http::header::CONTENT_TYPE, + tonic::metadata::GRPC_CONTENT_TYPE, + ); + Ok(response) + }), + } + } +} + +impl tonic::server::NamedService for SpiffeWorkloadApiServer { + const NAME: &'static str = "SpiffeWorkloadAPI"; +} + +struct FixtureHandle { + task: tokio::task::JoinHandle<()>, +} + +impl Drop for FixtureHandle { + fn drop(&mut self) { + self.task.abort(); + } +} + +fn unix_timestamp() -> i64 { + SystemTime::now() + .duration_since(UNIX_EPOCH) + .expect("system time should be after unix epoch") + .as_secs() + .try_into() + .expect("timestamp should fit i64") +} + +fn jwks() -> Vec { + let modulus = hex::decode(TEST_RSA_MODULUS_HEX).expect("valid test RSA modulus hex"); + let n = base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(modulus); + let e = base64::engine::general_purpose::URL_SAFE_NO_PAD.encode([0x01, 0x00, 0x01]); + serde_json::to_vec(&json!({ + "keys": [{ + "kty": "RSA", + "kid": KEY_ID, + "use": "sig", + "alg": "RS256", + "n": n, + "e": e, + }] + })) + .expect("JWKS should serialize") +} + +async fn start_spiffe_workload_api(path: &Path, subject: &str) -> FixtureHandle { + let api = SpiffeWorkloadApi { + subject: Arc::::from(subject), + jwks: Arc::new(jwks()), + encoding_key: Arc::new( + EncodingKey::from_rsa_pem(TEST_RSA_PRIVATE_KEY.as_bytes()) + .expect("test RSA key should parse"), + ), + }; + let endpoint = path.to_string_lossy(); + if endpoint.starts_with("tcp:") { + let listen = std::env::var("OPENSHELL_E2E_PROVIDER_SPIFFE_LISTEN") + .expect("OPENSHELL_E2E_PROVIDER_SPIFFE_LISTEN must be set for TCP SPIFFE fixture"); + let listener = TcpListener::bind(&listen) + .await + .expect("bind TCP SPIFFE Workload API fixture"); + let incoming = TcpListenerStream::new(listener); + let task = tokio::spawn(async move { + let result = tonic::transport::Server::builder() + .add_service(SpiffeWorkloadApiServer::new(api)) + .serve_with_incoming(incoming) + .await; + if let Err(err) = result { + eprintln!("SPIFFE Workload API fixture failed: {err}"); + } + }); + return FixtureHandle { task }; + } + let _ = fs::remove_file(path); + let listener = UnixListener::bind(path).expect("bind SPIFFE Workload API socket"); + let mut permissions = fs::metadata(path) + .expect("stat SPIFFE Workload API socket") + .permissions(); + permissions.set_mode(0o777); + fs::set_permissions(path, permissions).expect("chmod SPIFFE Workload API socket"); + let incoming = UnixListenerStream::new(listener); + let task = tokio::spawn(async move { + let result = tonic::transport::Server::builder() + .add_service(SpiffeWorkloadApiServer::new(api)) + .serve_with_incoming(incoming) + .await; + if let Err(err) = result { + eprintln!("SPIFFE Workload API fixture failed: {err}"); + } + }); + FixtureHandle { task } +} + +async fn start_gateway_token_endpoint(port: u16) -> FixtureHandle { + let listener = TcpListener::bind(SocketAddr::from((Ipv4Addr::LOCALHOST, port))) + .await + .expect("bind gateway token endpoint"); + let task = tokio::spawn(async move { + loop { + let Ok((mut stream, _peer)) = listener.accept().await else { + break; + }; + tokio::spawn(async move { + let mut buf = vec![0_u8; 8192]; + let n = stream.read(&mut buf).await.unwrap_or(0); + let request = String::from_utf8_lossy(&buf[..n]); + let access_token = if request.starts_with("POST /token ") + && request.contains("subject_token=stored-user-token") + && request.contains("client_assertion=") + { + Some(INTERMEDIATE_TOKEN) + } else if request.starts_with("POST /token ") + && request.contains("subject_token=intermediate-token") + && request.contains("client_assertion=") + { + Some(FINAL_ACCESS_TOKEN) + } else { + None + }; + let (status, body) = if let Some(access_token) = access_token { + ( + "HTTP/1.1 200 OK", + json!({ + "access_token": access_token, + "token_type": "Bearer", + "expires_in": 300 + }) + .to_string(), + ) + } else { + ( + "HTTP/1.1 400 Bad Request", + json!({"error": "unexpected_token_exchange"}).to_string(), + ) + }; + let response = format!( + "{status}\r\ncontent-type: application/json\r\ncontent-length: {}\r\nconnection: close\r\n\r\n{body}", + body.len() + ); + let _ = stream.write_all(response.as_bytes()).await; + }); + } + }); + FixtureHandle { task } +} + +async fn start_protected_target(port: u16) -> FixtureHandle { + let listener = TcpListener::bind(SocketAddr::from((Ipv4Addr::UNSPECIFIED, port))) + .await + .expect("bind protected target"); + let task = tokio::spawn(async move { + loop { + let Ok((mut stream, _peer)) = listener.accept().await else { + break; + }; + tokio::spawn(async move { + let mut buf = vec![0_u8; 8192]; + let n = stream.read(&mut buf).await.unwrap_or(0); + let request = String::from_utf8_lossy(&buf[..n]); + let ok = request.lines().any(|line| { + line.eq_ignore_ascii_case(&format!( + "authorization: Bearer {FINAL_ACCESS_TOKEN}" + )) + }); + let (status, body) = if ok { + ("HTTP/1.1 200 OK", "token-exchange-ok") + } else { + ("HTTP/1.1 401 Unauthorized", "missing-final-token") + }; + let response = format!( + "{status}\r\ncontent-type: text/plain\r\ncontent-length: {}\r\nconnection: close\r\n\r\n{body}", + body.len() + ); + let _ = stream.write_all(response.as_bytes()).await; + }); + } + }); + FixtureHandle { task } +} + +async fn run_cli(args: &[&str]) -> Result { + let output = openshell_cmd() + .args(args) + .output() + .await + .map_err(|err| format!("spawn openshell: {err}"))?; + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + let combined = format!("{stdout}{stderr}"); + if output.status.success() { + Ok(combined) + } else { + Err(format!( + "openshell {:?} failed with {:?}:\n{combined}", + args, + output.status.code() + )) + } +} + +async fn run_cli_ignore_error(args: &[&str]) { + let _ = openshell_cmd().args(args).output().await; +} + +fn write_profile(profile_type: &str, token_port: u16, target_port: u16) -> NamedTempFile { + let token_endpoint = format!("http://127.0.0.1:{token_port}/token"); + let mut file = tempfile::Builder::new() + .suffix(".yaml") + .tempfile() + .expect("create provider profile temp file"); + let profile = format!( + r#"id: {profile_type} +display_name: Podman token exchange e2e +description: Podman e2e provider profile for two-stage token exchange +category: other +credentials: + - name: subject_token + description: Stored user subject token + required: true + - name: access_token + description: Access token obtained through token exchange + required: false + auth_style: bearer + header_name: Authorization + token_grant: + grant_type: token_exchange + token_endpoint: {token_endpoint} + audience: final-audience + jwt_svid_audience: {token_endpoint} + client_assertion_type: {CLIENT_ASSERTION_TYPE} + requested_token_type: {TOKEN_TYPE_ACCESS_TOKEN} + cache_ttl_seconds: 30 + subject_token: + source: provider_credential + credential: subject_token + subject_token_type: {TOKEN_TYPE_ACCESS_TOKEN} +endpoints: + - host: host.openshell.internal + port: {target_port} + protocol: rest + tls: none + access: read-write + enforcement: enforce + allowed_ips: + - 10.0.0.0/8 + - 172.0.0.0/8 + - 192.168.0.0/16 +binaries: + - /usr/bin/curl + - /usr/local/bin/curl +"# + ); + file.write_all(profile.as_bytes()) + .expect("write provider profile"); + file.flush().expect("flush provider profile"); + file +} + +fn sandbox_script(token_port: u16) -> String { + let _ = token_port; + r#"set -eu +echo token-server-ready +while true; do sleep 60; done +"# + .to_string() +} + +fn container_token_endpoint_script() -> String { + format!( + r#" +import json +import sys +from http.server import BaseHTTPRequestHandler, HTTPServer +from urllib.parse import parse_qs + +PORT = int(sys.argv[1]) + +class Handler(BaseHTTPRequestHandler): + def do_POST(self): + if self.path != "/token": + self.send_response(404) + self.end_headers() + return + length = int(self.headers.get("content-length", "0")) + params = parse_qs(self.rfile.read(length).decode()) + subject_token = params.get("subject_token", [""])[0] + client_assertion = params.get("client_assertion", [""])[0] + if subject_token == "{USER_SUBJECT_TOKEN}" and client_assertion: + access_token = "{INTERMEDIATE_TOKEN}" + elif subject_token == "{INTERMEDIATE_TOKEN}" and client_assertion: + access_token = "{FINAL_ACCESS_TOKEN}" + else: + self.send_response(400) + body = json.dumps({{"error": "unexpected_token_exchange"}}).encode() + self.send_header("content-type", "application/json") + self.send_header("content-length", str(len(body))) + self.end_headers() + self.wfile.write(body) + return + body = json.dumps({{ + "access_token": access_token, + "token_type": "Bearer", + "expires_in": 300, + }}).encode() + self.send_response(200) + self.send_header("content-type", "application/json") + self.send_header("content-length", str(len(body))) + self.end_headers() + self.wfile.write(body) + + def log_message(self, fmt, *args): + return + +HTTPServer(("127.0.0.1", PORT), Handler).serve_forever() +"# + ) +} + +async fn start_container_token_endpoint(sandbox_name: &str, token_port: u16) -> Result<(), String> { + let socket = std::env::var("OPENSHELL_PODMAN_SOCKET") + .map_err(|_| "OPENSHELL_PODMAN_SOCKET must be set by e2e-podman.sh".to_string())?; + let container_name = podman_container_name_for_sandbox(&socket, sandbox_name).await?; + let mut cmd = Command::new("podman"); + cmd.arg("--url") + .arg(format!("unix://{socket}")) + .arg("exec") + .arg("-d") + .arg(&container_name) + .arg("python3") + .arg("-c") + .arg(container_token_endpoint_script()) + .arg(token_port.to_string()); + apply_podman_config_env(&mut cmd); + let output = cmd + .output() + .await + .map_err(|err| format!("spawn podman exec token endpoint: {err}"))?; + if !output.status.success() { + return Err(format!( + "podman exec token endpoint failed: {}{}", + String::from_utf8_lossy(&output.stdout), + String::from_utf8_lossy(&output.stderr) + )); + } + + for _ in 0..20 { + if container_loopback_port_ready(&container_name, token_port).await { + return Ok(()); + } + tokio::time::sleep(Duration::from_millis(250)).await; + } + Err(format!( + "container token endpoint did not become ready on 127.0.0.1:{token_port}" + )) +} + +async fn podman_container_name_for_sandbox( + socket: &str, + sandbox_name: &str, +) -> Result { + let mut cmd = Command::new("podman"); + cmd.arg("--url") + .arg(format!("unix://{socket}")) + .arg("ps") + .arg("--filter") + .arg(format!("label=openshell.ai/sandbox-name={sandbox_name}")) + .arg("--format") + .arg("{{.Names}}"); + apply_podman_config_env(&mut cmd); + let output = cmd + .output() + .await + .map_err(|err| format!("spawn podman ps for sandbox container: {err}"))?; + if !output.status.success() { + return Err(format!( + "podman ps for sandbox container failed: {}{}", + String::from_utf8_lossy(&output.stdout), + String::from_utf8_lossy(&output.stderr) + )); + } + let names = String::from_utf8_lossy(&output.stdout) + .lines() + .map(str::trim) + .filter(|line| !line.is_empty()) + .map(str::to_string) + .collect::>(); + match names.as_slice() { + [name] => Ok(name.clone()), + [] => Err(format!( + "no running Podman container found for sandbox '{sandbox_name}'" + )), + _ => Err(format!( + "multiple running Podman containers found for sandbox '{sandbox_name}': {}", + names.join(", ") + )), + } +} + +fn apply_podman_config_env(cmd: &mut Command) { + if std::env::var_os("OPENSHELL_E2E_CONTAINER_ENGINE_UNSET_XDG_CONFIG_HOME").is_some() { + cmd.env_remove("XDG_CONFIG_HOME"); + } else if let Some(value) = std::env::var_os("OPENSHELL_E2E_CONTAINER_ENGINE_XDG_CONFIG_HOME") { + cmd.env("XDG_CONFIG_HOME", value); + } +} + +async fn container_loopback_port_ready(container_name: &str, token_port: u16) -> bool { + let Ok(socket) = std::env::var("OPENSHELL_PODMAN_SOCKET") else { + return false; + }; + let probe = format!( + "import socket; s=socket.create_connection(('127.0.0.1', {token_port}), 1); s.close()" + ); + let mut cmd = Command::new("podman"); + cmd.arg("--url") + .arg(format!("unix://{socket}")) + .arg("exec") + .arg(container_name) + .arg("python3") + .arg("-c") + .arg(probe); + apply_podman_config_env(&mut cmd); + cmd.stdout(Stdio::null()).stderr(Stdio::null()); + cmd.status() + .await + .map(|status| status.success()) + .unwrap_or(false) +} + +async fn sandbox_exec_curl(sandbox_name: &str, target_port: u16) -> Result { + let url = format!("http://host.openshell.internal:{target_port}/resource"); + for _ in 0..20 { + let output = openshell_cmd() + .args([ + "sandbox", + "exec", + "--name", + sandbox_name, + "--no-tty", + "--", + "curl", + "-fsS", + &url, + ]) + .output() + .await + .map_err(|err| format!("spawn openshell sandbox exec: {err}"))?; + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + let combined = format!("{stdout}{stderr}"); + if output.status.success() { + return Ok(combined); + } + tokio::time::sleep(Duration::from_millis(500)).await; + } + Err(format!("curl to protected target did not succeed at {url}")) +} + +#[tokio::test] +async fn podman_provider_token_exchange_injects_bearer_header() { + let gateway_socket = PathBuf::from( + std::env::var("OPENSHELL_E2E_GATEWAY_SPIFFE_SOCKET") + .expect("OPENSHELL_E2E_GATEWAY_SPIFFE_SOCKET must be set by e2e-podman.sh"), + ); + let provider_socket = PathBuf::from( + std::env::var("OPENSHELL_E2E_PROVIDER_SPIFFE_SOCKET") + .expect("OPENSHELL_E2E_PROVIDER_SPIFFE_SOCKET must be set by e2e-podman.sh"), + ); + + let profile_type = format!("podman-token-exchange-e2e-{}", std::process::id()); + let provider_name = format!("podman-token-exchange-e2e-{}", std::process::id()); + let token_port = find_free_port(); + let target_port = find_free_port(); + let token_endpoint = format!("http://127.0.0.1:{token_port}/token"); + let gateway_subject = format!("spiffe://{TRUST_DOMAIN}/openshell/gateway"); + let supervisor_subject = format!("spiffe://{TRUST_DOMAIN}/openshell/sandbox/e2e"); + + let _gateway_spiffe = start_spiffe_workload_api(&gateway_socket, &gateway_subject).await; + let _provider_spiffe = start_spiffe_workload_api(&provider_socket, &supervisor_subject).await; + let _gateway_token = start_gateway_token_endpoint(token_port).await; + let _target = start_protected_target(target_port).await; + + run_cli(&[ + "settings", + "set", + "--global", + "--key", + "providers_v2_enabled", + "--value", + "true", + "--yes", + ]) + .await + .expect("enable providers v2"); + + run_cli_ignore_error(&["provider", "delete", &provider_name, "--yes"]).await; + run_cli_ignore_error(&["provider", "profile", "delete", &profile_type, "--yes"]).await; + + let profile = write_profile(&profile_type, token_port, target_port); + let profile_path = profile + .path() + .to_str() + .expect("profile path should be UTF-8"); + run_cli(&["provider", "profile", "import", "-f", profile_path]) + .await + .expect("import provider profile"); + run_cli(&[ + "provider", + "create", + "--name", + &provider_name, + "--type", + &profile_type, + "--credential", + &format!("subject_token={USER_SUBJECT_TOKEN}"), + ]) + .await + .expect("create provider"); + + let script = sandbox_script(token_port); + let mut sandbox = SandboxGuard::create_keep_with_args( + &["--provider", &provider_name], + &["sh", "-lc", &script], + "token-server-ready", + ) + .await + .unwrap_or_else(|err| { + panic!( + "sandbox should complete token exchange against {token_endpoint} and protected target port {target_port}:\n{err}" + ) + }); + start_container_token_endpoint(&sandbox.name, token_port) + .await + .expect("start container token endpoint"); + let curl_output = sandbox_exec_curl(&sandbox.name, target_port) + .await + .expect("curl protected target from kept sandbox"); + + run_cli_ignore_error(&["provider", "delete", &provider_name, "--yes"]).await; + run_cli_ignore_error(&["provider", "profile", "delete", &profile_type, "--yes"]).await; + sandbox.cleanup().await; + + assert!( + curl_output.contains("token-exchange-ok"), + "protected target should receive the final exchanged bearer token:\n{}", + curl_output + ); +} diff --git a/e2e/with-podman-gateway.sh b/e2e/with-podman-gateway.sh index cd52e007ab..59ea8423ec 100755 --- a/e2e/with-podman-gateway.sh +++ b/e2e/with-podman-gateway.sh @@ -86,6 +86,15 @@ podman_cmd() { WORKDIR_PARENT="${TMPDIR:-/tmp}" WORKDIR_PARENT="${WORKDIR_PARENT%/}" WORKDIR="$(mktemp -d "${WORKDIR_PARENT}/openshell-e2e-podman.XXXXXX")" +if [ "${OPENSHELL_E2E_SPIFFE_FIXTURE:-0}" = "1" ]; then + mkdir -p "${WORKDIR}/spiffe" + export OPENSHELL_E2E_GATEWAY_SPIFFE_SOCKET="${OPENSHELL_E2E_GATEWAY_SPIFFE_SOCKET:-${WORKDIR}/spiffe/gateway.sock}" + 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}" + export OPENSHELL_E2E_PROVIDER_SPIFFE_SOCKET="tcp:169.254.1.2:${OPENSHELL_E2E_PROVIDER_SPIFFE_PORT}" + fi +fi GATEWAY_BIN="" CLI_BIN="" GATEWAY_PID="" @@ -456,6 +465,9 @@ cp "${ROOT}/deploy/rpm/gateway.toml.default" "${GATEWAY_CONFIG}" printf 'guest_tls_cert = %s\n' "$(toml_string "${PKI_DIR}/client/tls.crt")" printf 'guest_tls_key = %s\n' "$(toml_string "${PKI_DIR}/client/tls.key")" printf 'enable_bind_mounts = true\n' + if [ -n "${OPENSHELL_E2E_PROVIDER_SPIFFE_SOCKET:-}" ]; then + printf 'provider_spiffe_workload_api_socket = %s\n' "$(toml_string "${OPENSHELL_E2E_PROVIDER_SPIFFE_SOCKET}")" + fi # The in-process Podman driver reads `socket_path` from TOML only — the # OPENSHELL_PODMAN_SOCKET env var is honoured by the standalone driver # binary, not the in-process driver used here. Pin the socket to the one @@ -500,6 +512,7 @@ e2e_export_gateway_restart_metadata \ OPENSHELL_SUPERVISOR_IMAGE="${SUPERVISOR_IMAGE}" \ OPENSHELL_NETWORK_NAME="${PODMAN_NETWORK_NAME}" \ +OPENSHELL_GATEWAY_SPIFFE_WORKLOAD_API_SOCKET="${OPENSHELL_E2E_GATEWAY_SPIFFE_SOCKET:-}" \ "${GATEWAY_BIN}" "${GATEWAY_ARGS[@]}" >"${GATEWAY_LOG}" 2>&1 & GATEWAY_PID=$! printf '%s\n' "${GATEWAY_PID}" >"${GATEWAY_PID_FILE}" From 7f707151c9e8b8cd5f5ca46953a0634dc09082d6 Mon Sep 17 00:00:00 2001 From: Gordon Sim Date: Thu, 30 Jul 2026 16:54:11 +0100 Subject: [PATCH 5/8] refactor(oauth): extract duplicated functionality from server and supervisor Signed-off-by: Gordon Sim --- Cargo.lock | 2 + crates/openshell-core/Cargo.toml | 4 +- crates/openshell-core/src/lib.rs | 2 + crates/openshell-core/src/oauth.rs | 766 ++++++++++++++++++ crates/openshell-server/Cargo.toml | 2 +- crates/openshell-server/src/grpc/provider.rs | 240 +----- .../openshell-supervisor-network/Cargo.toml | 2 +- .../src/token_grant.rs | 522 +----------- 8 files changed, 839 insertions(+), 701 deletions(-) create mode 100644 crates/openshell-core/src/oauth.rs diff --git a/Cargo.lock b/Cargo.lock index b3dd2dbc03..7441a6bb19 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2532,6 +2532,7 @@ dependencies = [ "tokio", "tokio-rustls 0.26.4", "tower-service", + "webpki-roots", ] [[package]] @@ -5539,6 +5540,7 @@ dependencies = [ "wasm-bindgen", "wasm-bindgen-futures", "web-sys", + "webpki-roots", ] [[package]] diff --git a/crates/openshell-core/Cargo.toml b/crates/openshell-core/Cargo.toml index 8b28fafa23..2d7610415a 100644 --- a/crates/openshell-core/Cargo.toml +++ b/crates/openshell-core/Cargo.toml @@ -42,7 +42,9 @@ default = ["telemetry"] ## `--no-default-features` (plus any other features you need) for a build that ## contains no telemetry endpoint, no HTTP client, and no emission code at all. driver-extraction = ["dep:tar", "dep:tempfile"] -telemetry = ["dep:reqwest", "dep:chrono"] +telemetry = ["dep:reqwest", "dep:chrono", "reqwest?/blocking", "reqwest?/rustls-tls-webpki-roots"] +## OAuth2 token request helpers used by supervisor and gateway. +oauth = ["dep:reqwest"] [build-dependencies] tonic-prost-build = { workspace = true } diff --git a/crates/openshell-core/src/lib.rs b/crates/openshell-core/src/lib.rs index 32f2041832..2bd44cb67f 100644 --- a/crates/openshell-core/src/lib.rs +++ b/crates/openshell-core/src/lib.rs @@ -29,6 +29,8 @@ pub mod jwt; pub mod metadata; pub mod middleware; pub mod net; +#[cfg(feature = "oauth")] +pub mod oauth; pub mod paths; pub mod policy; pub mod progress; diff --git a/crates/openshell-core/src/oauth.rs b/crates/openshell-core/src/oauth.rs new file mode 100644 index 0000000000..c68ecfa6b4 --- /dev/null +++ b/crates/openshell-core/src/oauth.rs @@ -0,0 +1,766 @@ +// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +//! Shared `OAuth2` token request helpers. +//! +//! Provides [`post_oauth_token_grant`] (client credentials) and +//! [`post_oauth_token_exchange`] (RFC 8693 token exchange) — typed functions +//! that assemble form parameters, POST to a validated `OAuth2` token endpoint, +//! parse the JSON response, and validate the returned access token. + +use std::net::IpAddr; + +use miette::{IntoDiagnostic, Result, WrapErr}; +use serde::Deserialize; + +pub const DEFAULT_CLIENT_ASSERTION_TYPE: &str = + "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"; +pub const ACCESS_TOKEN_TYPE: &str = "urn:ietf:params:oauth:token-type:access_token"; +const TOKEN_EXCHANGE_GRANT_TYPE: &str = "urn:ietf:params:oauth:grant-type:token-exchange"; + +const MAX_OAUTH_ERROR_FIELD_LEN: usize = 256; + +/// `OAuth2` token response. +#[derive(Debug, Clone)] +pub struct OAuthTokenResponse { + pub access_token: String, + pub expires_in: i64, + pub token_type: String, +} + +#[derive(Debug, Deserialize)] +struct RawTokenResponse { + access_token: String, + #[serde(default)] + expires_in: i64, + #[serde(default)] + token_type: String, +} + +#[derive(Debug, Deserialize)] +struct OAuthErrorResponse { + error: Option, + error_description: Option, +} + +async fn post_oauth_token_request( + client: &reqwest::Client, + token_endpoint: &str, + mut form_params: Vec<(&str, &str)>, + audience: &str, + scopes: &[String], +) -> Result { + let token_endpoint_url = parse_token_endpoint_url(token_endpoint)?; + + let audience_param; + if !audience.is_empty() { + audience_param = audience.to_string(); + form_params.push(("audience", &audience_param)); + } + + let scope_param; + if !scopes.is_empty() { + scope_param = 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 = response + .text() + .await + .unwrap_or_else(|_| "".to_string()); + return Err(miette::miette!("{}", failure_message(status, &body))); + } + + let raw = response + .json::() + .await + .into_diagnostic() + .wrap_err("failed to parse token response as JSON")?; + validate_access_token(&raw.access_token)?; + Ok(OAuthTokenResponse { + access_token: raw.access_token, + expires_in: raw.expires_in, + token_type: raw.token_type, + }) +} + +/// Client credentials grant form fields. +pub struct TokenGrantParams<'a> { + pub client_assertion: &'a str, + pub client_assertion_type: &'a str, + pub audience: &'a str, + pub scopes: &'a [String], +} + +/// POST a client credentials grant request to an `OAuth2` token endpoint. +/// +/// Applies the default for `client_assertion_type` and conditionally includes +/// `audience` and `scope`. +pub async fn post_oauth_token_grant( + client: &reqwest::Client, + token_endpoint: &str, + params: &TokenGrantParams<'_>, +) -> Result { + let client_assertion_type = effective_client_assertion_type(params.client_assertion_type); + let form_params = vec![ + ("grant_type", "client_credentials"), + ("client_assertion_type", client_assertion_type), + ("client_assertion", params.client_assertion), + ]; + post_oauth_token_request( + client, + token_endpoint, + form_params, + params.audience, + params.scopes, + ) + .await +} + +/// RFC 8693 token-exchange form fields. +pub struct TokenExchangeParams<'a> { + pub client_assertion: &'a str, + pub client_assertion_type: &'a str, + pub subject_token: &'a str, + pub subject_token_type: &'a str, + pub audience: &'a str, + pub scopes: &'a [String], + pub requested_token_type: &'a str, +} + +/// POST an RFC 8693 token-exchange request to an `OAuth2` token endpoint. +/// +/// Assembles the standard token-exchange form fields, applies defaults for +/// `client_assertion_type`, `subject_token_type`, and `requested_token_type`, +/// and conditionally includes `audience` and `scope`. +pub async fn post_oauth_token_exchange( + client: &reqwest::Client, + token_endpoint: &str, + params: &TokenExchangeParams<'_>, +) -> Result { + let client_assertion_type = effective_client_assertion_type(params.client_assertion_type); + let subject_token_type = effective_token_type(params.subject_token_type); + let requested_token_type = effective_token_type(params.requested_token_type); + let form_params = vec![ + ("grant_type", TOKEN_EXCHANGE_GRANT_TYPE), + ("client_assertion_type", client_assertion_type), + ("client_assertion", params.client_assertion), + ("subject_token", params.subject_token), + ("subject_token_type", subject_token_type), + ("requested_token_type", requested_token_type), + ]; + post_oauth_token_request( + client, + token_endpoint, + form_params, + params.audience, + params.scopes, + ) + .await +} + +pub fn effective_client_assertion_type(client_assertion_type: &str) -> &str { + if client_assertion_type.trim().is_empty() { + DEFAULT_CLIENT_ASSERTION_TYPE + } else { + client_assertion_type + } +} + +pub fn effective_token_type(token_type: &str) -> &str { + if token_type.trim().is_empty() { + ACCESS_TOKEN_TYPE + } else { + token_type + } +} + +fn parse_token_endpoint_url(token_endpoint: &str) -> Result { + let url = reqwest::Url::parse(token_endpoint) + .into_diagnostic() + .wrap_err("token_endpoint must be an absolute URL")?; + if token_endpoint_transport_allowed(&url) { + return Ok(url); + } + Err(miette::miette!( + "token_endpoint must use https, except http for loopback or in-cluster service hosts" + )) +} + +fn token_endpoint_transport_allowed(url: &reqwest::Url) -> bool { + match url.scheme() { + "https" => true, + "http" => url + .host_str() + .is_some_and(|host| is_loopback_host(host) || is_kubernetes_service_host(host)), + _ => false, + } +} + +fn is_loopback_host(host: &str) -> bool { + let host = host.trim_matches(['[', ']']); + if host.eq_ignore_ascii_case("localhost") { + return true; + } + match host.parse::() { + Ok(IpAddr::V4(v4)) => v4.is_loopback(), + Ok(IpAddr::V6(v6)) => { + v6.is_loopback() || v6.to_ipv4_mapped().is_some_and(|v4| v4.is_loopback()) + } + Err(_) => false, + } +} + +fn is_kubernetes_service_host(host: &str) -> bool { + let host = host.trim_end_matches('.').to_ascii_lowercase(); + let labels = host.split('.').collect::>(); + let is_service_name = labels.len() == 3 && labels[2] == "svc"; + let is_cluster_local_service = + labels.len() == 5 && labels[2] == "svc" && labels[3] == "cluster" && labels[4] == "local"; + (is_service_name || is_cluster_local_service) && labels.iter().all(|label| !label.is_empty()) +} + +pub fn validate_access_token(token: &str) -> Result<()> { + if token.is_empty() || !is_token68(token) { + return Err(miette::miette!( + "token grant returned a malformed access token" + )); + } + Ok(()) +} + +fn is_token68(token: &str) -> bool { + let mut padding_started = false; + let mut saw_value = false; + for byte in token.bytes() { + if byte == b'=' { + padding_started = true; + continue; + } + if padding_started || !is_token68_value_byte(byte) { + return false; + } + saw_value = true; + } + saw_value +} + +fn is_token68_value_byte(byte: u8) -> bool { + byte.is_ascii_alphanumeric() || matches!(byte, b'-' | b'.' | b'_' | b'~' | b'+' | b'/') +} + +fn failure_message(status: reqwest::StatusCode, body: &str) -> String { + let Ok(error_response) = serde_json::from_str::(body) else { + return format!("token grant failed with status {status}"); + }; + let error = error_response + .error + .as_deref() + .map(sanitize_oauth_error_field) + .filter(|value| !value.is_empty()); + let description = error_response + .error_description + .as_deref() + .map(sanitize_oauth_error_field) + .filter(|value| !value.is_empty()); + match (error, description) { + (Some(error), Some(description)) => { + format!( + "token grant failed with status {status}: error={error}; error_description={description}" + ) + } + (Some(error), None) => { + format!("token grant failed with status {status}: error={error}") + } + (None, Some(description)) => { + format!("token grant failed with status {status}: error_description={description}") + } + (None, None) => format!("token grant failed with status {status}"), + } +} + +fn sanitize_oauth_error_field(value: &str) -> String { + value + .chars() + .map(|ch| if ch.is_control() { ' ' } else { ch }) + .take(MAX_OAUTH_ERROR_FIELD_LEN) + .collect::() + .trim() + .to_string() +} + +#[cfg(test)] +mod tests { + use super::*; + use std::collections::HashMap; + use tokio::io::{AsyncReadExt, AsyncWriteExt}; + use tokio::net::TcpListener; + + #[derive(Debug)] + struct CapturedRequest { + form: HashMap, + } + + fn test_client() -> reqwest::Client { + reqwest::Client::builder() + .timeout(std::time::Duration::from_secs(5)) + .no_proxy() + .redirect(reqwest::redirect::Policy::none()) + .build() + .expect("test client") + } + + async fn token_endpoint_once( + status: &str, + body: &str, + ) -> (String, tokio::task::JoinHandle) { + let listener = TcpListener::bind("127.0.0.1:0") + .await + .expect("bind token endpoint"); + let addr = listener.local_addr().expect("token endpoint local addr"); + let status = status.to_string(); + let body = body.to_string(); + let handle = tokio::spawn(async move { + let (mut stream, _) = listener.accept().await.expect("accept"); + let mut buf = Vec::new(); + let mut chunk = [0u8; 512]; + let mut expected_len = None; + loop { + let n = stream.read(&mut chunk).await.expect("read"); + assert!(n > 0); + buf.extend_from_slice(&chunk[..n]); + if expected_len.is_none() + && let Some(header_end) = header_end(&buf) + { + let headers = String::from_utf8_lossy(&buf[..header_end]); + let cl = headers + .lines() + .find_map(|line| { + let (name, value) = line.split_once(':')?; + name.eq_ignore_ascii_case("content-length") + .then(|| value.trim().parse::().ok()) + .flatten() + }) + .unwrap_or(0); + expected_len = Some(header_end + cl); + } + if expected_len.is_some_and(|len| buf.len() >= len) { + break; + } + } + let header_end = header_end(&buf).unwrap(); + let form_body = String::from_utf8_lossy(&buf[header_end..]).to_string(); + let form = parse_form_body(&form_body); + let response = format!( + "HTTP/1.1 {status}\r\nContent-Type: application/json\r\nContent-Length: {}\r\nConnection: close\r\n\r\n{body}", + body.len(), + ); + stream.write_all(response.as_bytes()).await.expect("write"); + CapturedRequest { form } + }); + (format!("http://{addr}/token"), handle) + } + + async fn token_endpoint_redirect_once(location: &str) -> (String, tokio::task::JoinHandle<()>) { + let listener = TcpListener::bind("127.0.0.1:0") + .await + .expect("bind token endpoint"); + let addr = listener.local_addr().expect("token endpoint local addr"); + let location = location.to_string(); + let handle = tokio::spawn(async move { + let (mut stream, _) = listener.accept().await.expect("accept"); + let mut buf = vec![0u8; 512]; + let _ = stream.read(&mut buf).await; + let response = format!( + "HTTP/1.1 302 Found\r\nLocation: {location}\r\nContent-Length: 0\r\nConnection: close\r\n\r\n", + ); + stream.write_all(response.as_bytes()).await.expect("write"); + }); + (format!("http://{addr}/token"), handle) + } + + fn header_end(buf: &[u8]) -> Option { + buf.windows(4) + .position(|w| w == b"\r\n\r\n") + .map(|idx| idx + 4) + } + + fn parse_form_body(body: &str) -> HashMap { + body.split('&') + .filter(|part| !part.is_empty()) + .filter_map(|part| { + let (name, value) = part.split_once('=')?; + Some((decode_form_component(name), decode_form_component(value))) + }) + .collect() + } + + fn decode_form_component(value: &str) -> String { + let bytes = value.as_bytes(); + let mut decoded = Vec::with_capacity(bytes.len()); + let mut idx = 0; + while idx < bytes.len() { + match bytes[idx] { + b'+' => { + decoded.push(b' '); + idx += 1; + } + b'%' if idx + 2 < bytes.len() => { + let hex = &value[idx + 1..idx + 3]; + if let Ok(byte) = u8::from_str_radix(hex, 16) { + decoded.push(byte); + idx += 3; + } else { + decoded.push(bytes[idx]); + idx += 1; + } + } + byte => { + decoded.push(byte); + idx += 1; + } + } + } + String::from_utf8(decoded).expect("form body should be UTF-8") + } + + fn empty_grant_params() -> TokenGrantParams<'static> { + TokenGrantParams { + client_assertion: "jwt-svid-token", + client_assertion_type: "", + audience: "", + scopes: &[], + } + } + + #[tokio::test] + async fn posts_form_params_and_parses_success_response() { + let (endpoint, request) = token_endpoint_once( + "200 OK", + r#"{"access_token":"access-123","token_type":"Bearer","expires_in":42}"#, + ) + .await; + let client = test_client(); + + let response = post_oauth_token_grant(&client, &endpoint, &empty_grant_params()) + .await + .expect("should succeed"); + let request = request.await.expect("endpoint task"); + + assert_eq!(response.access_token, "access-123"); + assert_eq!(response.expires_in, 42); + assert_eq!(response.token_type, "Bearer"); + assert_eq!( + request.form.get("grant_type").map(String::as_str), + Some("client_credentials") + ); + assert_eq!( + request.form.get("client_assertion").map(String::as_str), + Some("jwt-svid-token") + ); + } + + #[tokio::test] + async fn rejects_malformed_access_token() { + let (endpoint, request) = token_endpoint_once( + "200 OK", + r#"{"access_token":"access-123\r\nX-Injected: yes"}"#, + ) + .await; + let client = test_client(); + + let err = post_oauth_token_grant(&client, &endpoint, &empty_grant_params()) + .await + .expect_err("malformed token should fail"); + let _ = request.await; + + assert_eq!( + err.to_string(), + "token grant returned a malformed access token" + ); + } + + #[tokio::test] + async fn does_not_follow_redirects() { + let (endpoint, handle) = token_endpoint_redirect_once("http://127.0.0.1:1/stolen").await; + let client = test_client(); + + let err = post_oauth_token_grant(&client, &endpoint, &empty_grant_params()) + .await + .expect_err("redirect should fail"); + let _ = handle.await; + + assert_eq!(err.to_string(), "token grant failed with status 302 Found"); + } + + #[tokio::test] + async fn reports_sanitized_oauth_error() { + let (endpoint, request) = token_endpoint_once( + "401 Unauthorized", + r#"{"error":"invalid_client","error_description":"bad assertion"}"#, + ) + .await; + let client = test_client(); + + let err = post_oauth_token_grant(&client, &endpoint, &empty_grant_params()) + .await + .expect_err("should fail on OAuth error"); + let _ = request.await; + + assert_eq!( + err.to_string(), + "token grant failed with status 401 Unauthorized: error=invalid_client; error_description=bad assertion" + ); + } + + #[tokio::test] + async fn does_not_echo_unstructured_error_body() { + let (endpoint, request) = token_endpoint_once( + "500 Internal Server Error", + "internal stack trace with implementation details", + ) + .await; + let client = test_client(); + + let err = post_oauth_token_grant(&client, &endpoint, &empty_grant_params()) + .await + .expect_err("should fail on server error"); + let _ = request.await; + let message = err.to_string(); + + assert_eq!( + message, + "token grant failed with status 500 Internal Server Error" + ); + assert!(!message.contains("stack trace")); + } + + #[tokio::test] + async fn reports_malformed_success_json() { + let (endpoint, request) = token_endpoint_once("200 OK", r#"{"access_token":42"#).await; + let client = test_client(); + + let err = post_oauth_token_grant(&client, &endpoint, &empty_grant_params()) + .await + .expect_err("should fail on malformed JSON"); + let _ = request.await; + + assert!( + err.to_string() + .contains("failed to parse token response as JSON") + ); + } + + #[tokio::test] + async fn token_exchange_posts_rfc8693_form_fields() { + let (endpoint, request) = token_endpoint_once( + "200 OK", + r#"{"access_token":"exchanged-token","token_type":"Bearer","expires_in":60}"#, + ) + .await; + let client = test_client(); + let scopes = vec!["read".to_string(), "write".to_string()]; + + let response = post_oauth_token_exchange( + &client, + &endpoint, + &TokenExchangeParams { + client_assertion: "jwt-svid-token", + client_assertion_type: "urn:ietf:params:oauth:client-assertion-type:jwt-spiffe", + subject_token: "subject-access-token", + subject_token_type: ACCESS_TOKEN_TYPE, + audience: "api://resource", + scopes: &scopes, + requested_token_type: "urn:ietf:params:oauth:token-type:id_token", + }, + ) + .await + .expect("token exchange should succeed"); + let request = request.await.expect("endpoint task"); + + assert_eq!(response.access_token, "exchanged-token"); + assert_eq!(response.expires_in, 60); + assert_eq!( + request.form.get("grant_type").map(String::as_str), + Some(TOKEN_EXCHANGE_GRANT_TYPE) + ); + assert_eq!( + request + .form + .get("client_assertion_type") + .map(String::as_str), + Some("urn:ietf:params:oauth:client-assertion-type:jwt-spiffe") + ); + assert_eq!( + request.form.get("client_assertion").map(String::as_str), + Some("jwt-svid-token") + ); + assert_eq!( + request.form.get("subject_token").map(String::as_str), + Some("subject-access-token") + ); + assert_eq!( + request.form.get("subject_token_type").map(String::as_str), + Some(ACCESS_TOKEN_TYPE) + ); + assert_eq!( + request.form.get("audience").map(String::as_str), + Some("api://resource") + ); + assert_eq!( + request.form.get("scope").map(String::as_str), + Some("read write") + ); + assert_eq!( + request.form.get("requested_token_type").map(String::as_str), + Some("urn:ietf:params:oauth:token-type:id_token") + ); + } + + #[tokio::test] + async fn token_exchange_applies_defaults_for_empty_type_fields() { + let (endpoint, request) = + token_endpoint_once("200 OK", r#"{"access_token":"exchanged-token"}"#).await; + let client = test_client(); + + post_oauth_token_exchange( + &client, + &endpoint, + &TokenExchangeParams { + client_assertion: "jwt-svid-token", + client_assertion_type: "", + subject_token: "subject-token", + subject_token_type: "", + audience: "", + scopes: &[], + requested_token_type: "", + }, + ) + .await + .expect("token exchange should succeed"); + let request = request.await.expect("endpoint task"); + + assert_eq!( + request + .form + .get("client_assertion_type") + .map(String::as_str), + Some(DEFAULT_CLIENT_ASSERTION_TYPE) + ); + assert_eq!( + request.form.get("subject_token_type").map(String::as_str), + Some(ACCESS_TOKEN_TYPE) + ); + assert_eq!( + request.form.get("requested_token_type").map(String::as_str), + Some(ACCESS_TOKEN_TYPE) + ); + assert!(!request.form.contains_key("audience")); + assert!(!request.form.contains_key("scope")); + } + + #[test] + fn token_endpoint_url_allows_https_loopback_and_in_cluster_http() { + for endpoint in [ + "https://auth.example.com/token", + "http://127.0.0.1:8080/token", + "http://[::1]:8080/token", + "http://token-issuer.default.svc.cluster.local/token", + "http://token-issuer.default.svc/token", + ] { + parse_token_endpoint_url(endpoint).expect("should be allowed"); + } + } + + #[test] + fn token_endpoint_url_rejects_plain_http_non_cluster_hosts() { + for endpoint in [ + "http://auth.example.com/token", + "http://keycloak/realms/openshell/protocol/openid-connect/token", + "http://token-issuer.default.svc.evil.com/token", + "ftp://auth.example.com/token", + "/relative/token", + ] { + assert!( + parse_token_endpoint_url(endpoint).is_err(), + "should be rejected: {endpoint}" + ); + } + } + + #[test] + fn validate_access_token_accepts_token68_values() { + for token in [ + "abcXYZ123-._~+/", + "eyJhbGciOiJSUzI1NiJ9.payload.sig", + "token==", + ] { + validate_access_token(token).expect("should be accepted"); + } + } + + #[test] + fn validate_access_token_rejects_non_token68_values() { + for token in [ + "", + "token with spaces", + "token\r\nX-Injected: yes", + "token\u{7f}", + "tokené", + "token=continued", + "==", + ] { + let err = validate_access_token(token).expect_err("should be rejected"); + assert_eq!( + err.to_string(), + "token grant returned a malformed access token" + ); + } + } + + #[test] + fn failure_message_reports_oauth_error_fields() { + let message = failure_message( + reqwest::StatusCode::UNAUTHORIZED, + r#"{"error":"invalid_client","error_description":"Invalid client credentials"}"#, + ); + assert_eq!( + message, + "token grant failed with status 401 Unauthorized: error=invalid_client; error_description=Invalid client credentials" + ); + } + + #[test] + fn failure_message_omits_unstructured_response_body() { + let message = failure_message( + reqwest::StatusCode::INTERNAL_SERVER_ERROR, + "internal error containing implementation details", + ); + assert_eq!( + message, + "token grant failed with status 500 Internal Server Error" + ); + } + + #[test] + fn failure_message_sanitizes_oauth_error_fields() { + let long_description = "a".repeat(MAX_OAUTH_ERROR_FIELD_LEN + 20); + let body = + format!(r#"{{"error":"invalid_client\n","error_description":"{long_description}"}}"#); + let message = failure_message(reqwest::StatusCode::UNAUTHORIZED, &body); + assert!(!message.contains('\n')); + assert!(message.contains("error=invalid_client")); + assert!(message.contains(&"a".repeat(MAX_OAUTH_ERROR_FIELD_LEN))); + assert!(!message.contains(&"a".repeat(MAX_OAUTH_ERROR_FIELD_LEN + 1))); + } +} diff --git a/crates/openshell-server/Cargo.toml b/crates/openshell-server/Cargo.toml index 102f602fdb..b5ebf1fa74 100644 --- a/crates/openshell-server/Cargo.toml +++ b/crates/openshell-server/Cargo.toml @@ -16,7 +16,7 @@ path = "src/main.rs" [dependencies] openshell-bootstrap = { path = "../openshell-bootstrap" } -openshell-core = { path = "../openshell-core", default-features = false } +openshell-core = { path = "../openshell-core", default-features = false, features = ["oauth"] } openshell-driver-db-credstore = { path = "../openshell-driver-db-credstore" } openshell-driver-kubernetes-secrets = { path = "../openshell-driver-kubernetes-secrets" } openshell-driver-vault = { path = "../openshell-driver-vault" } diff --git a/crates/openshell-server/src/grpc/provider.rs b/crates/openshell-server/src/grpc/provider.rs index 3d8fe393c9..af2fc44396 100644 --- a/crates/openshell-server/src/grpc/provider.rs +++ b/crates/openshell-server/src/grpc/provider.rs @@ -23,7 +23,6 @@ use openshell_core::telemetry::{ use openshell_policy::ProviderPolicyLayer; use prost::Message; use std::collections::{HashMap, HashSet}; -use std::error::Error as StdError; use tonic::Status; use tracing::warn; @@ -2026,18 +2025,14 @@ use openshell_providers::{ normalize_profile_id, normalize_provider_type, strategy_output_env_key, strategy_output_spec, strategy_primary_env_key, validate_profile_set, }; -use serde::Deserialize; use std::sync::{Arc, LazyLock, RwLock}; use tonic::{Request, Response}; use crate::auth::principal::Principal; use crate::auth::workspace_authz::{MinWorkspaceRole, authorize_workspace, require_platform_admin}; - -const TOKEN_EXCHANGE_GRANT_TYPE: &str = "urn:ietf:params:oauth:grant-type:token-exchange"; -const DEFAULT_CLIENT_ASSERTION_TYPE: &str = - "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"; -const DEFAULT_TOKEN_TYPE: &str = "urn:ietf:params:oauth:token-type:access_token"; -const MAX_OAUTH_ERROR_FIELD_LEN: usize = 256; +use openshell_core::oauth::{ + self, TokenExchangeParams, effective_client_assertion_type, effective_token_type, +}; const DEFAULT_INTERMEDIATE_TOKEN_CACHE_TTL_SECONDS: i64 = 300; const MAX_INTERMEDIATE_TOKEN_CACHE_TTL_SECONDS: i64 = 3600; const INTERMEDIATE_TOKEN_CACHE_EXPIRY_SKEW_SECONDS: i64 = 30; @@ -2082,21 +2077,21 @@ impl IntermediateTokenCache { } } - fn get(&self, key: &str) -> Option { + fn get(&self, key: &str) -> Option { let now_ms = crate::persistence::current_time_ms(); let tokens = self.tokens.read().ok()?; let cached = tokens.get(key)?; if cached.expires_at_ms <= now_ms { return None; } - Some(TokenExchangeResponseBody { + Some(oauth::OAuthTokenResponse { access_token: cached.access_token.clone(), expires_in: cached.expires_at_ms.saturating_sub(now_ms) / 1000, token_type: cached.token_type.clone(), }) } - fn set(&self, key: String, token: &TokenExchangeResponseBody, expires_at_ms: i64) { + fn set(&self, key: String, token: &oauth::OAuthTokenResponse, expires_at_ms: i64) { if let Ok(mut tokens) = self.tokens.write() { let now_ms = crate::persistence::current_time_ms(); tokens.retain(|_, cached| cached.expires_at_ms > now_ms); @@ -2117,21 +2112,6 @@ impl IntermediateTokenCache { } } -#[derive(Debug, Deserialize)] -struct TokenExchangeResponseBody { - access_token: String, - #[serde(default)] - expires_in: i64, - #[serde(default)] - token_type: String, -} - -#[derive(Debug, Deserialize)] -struct OAuthErrorResponse { - error: Option, - error_description: Option, -} - async fn authorize_and_resolve_profile_workspace( state: &Arc, principal: &Principal, @@ -2156,6 +2136,7 @@ async fn authorize_and_resolve_profile_workspace( super::workspace::resolve_workspace(state.store.as_ref(), &authz.workspace).await } } + pub(super) async fn handle_create_provider( state: &Arc, request: Request, @@ -3648,7 +3629,7 @@ fn intermediate_token_cache_key(input: IntermediateTokenCacheKeyInput<'_>) -> St } fn intermediate_token_cache_expires_at_ms( - token: &TokenExchangeResponseBody, + token: &oauth::OAuthTokenResponse, cache_ttl_seconds: i64, subject_token_expires_at_ms: i64, supervisor_svid_exp_seconds: i64, @@ -3790,17 +3771,6 @@ async fn validate_supervisor_jwt_svid( Ok(unverified) } -fn format_error_chain(prefix: &str, error: &dyn StdError) -> String { - let mut message = format!("{prefix}: {error}"); - let mut source = error.source(); - while let Some(err) = source { - message.push_str(": "); - message.push_str(&err.to_string()); - source = err.source(); - } - message -} - fn parse_unverified_spiffe_claims(token: &str) -> Result { parse_unverified_jwt_svid_claims(token).map_err(jwt_svid_parse_error_status) } @@ -3817,112 +3787,23 @@ async fn perform_intermediate_token_exchange( subject_token_type: &str, audience: &str, requested_token_type: &str, -) -> Result { - let token_endpoint_url = parse_token_endpoint_url(token_endpoint)?; - let client_assertion_type = effective_client_assertion_type(client_assertion_type); - let subject_token_type = effective_token_type(subject_token_type); - let requested_token_type = effective_token_type(requested_token_type); - let form_params = [ - ("grant_type", TOKEN_EXCHANGE_GRANT_TYPE), - ("client_assertion_type", client_assertion_type), - ("client_assertion", gateway_jwt_svid), - ("subject_token", subject_token), - ("subject_token_type", subject_token_type), - ("audience", audience), - ("requested_token_type", requested_token_type), - ]; - - let response = token_exchange_http_client()? - .post(token_endpoint_url) - .form(&form_params) - .send() - .await - .map_err(|e| { - Status::internal(format_error_chain( - "provider token exchange request failed", - &e, - )) - })?; - if !response.status().is_success() { - let status = response.status(); - let body = response - .text() - .await - .unwrap_or_else(|_| "".to_string()); - return Err(Status::failed_precondition(token_exchange_failure_message( - status, &body, - ))); - } - let body = response - .json::() - .await - .map_err(|e| { - Status::internal(format!( - "provider token exchange response parse failed: {e}" - )) - })?; - validate_oauth_access_token(&body.access_token)?; - Ok(body) -} - -fn parse_token_endpoint_url(token_endpoint: &str) -> Result { - let url = reqwest::Url::parse(token_endpoint) - .map_err(|_| Status::invalid_argument("token_endpoint must be an absolute URL"))?; - if token_endpoint_transport_allowed(&url) { - return Ok(url); - } - Err(Status::invalid_argument( - "token_endpoint must use https, except http for loopback or in-cluster service hosts", - )) -} - -fn token_endpoint_transport_allowed(url: &reqwest::Url) -> bool { - match url.scheme() { - "https" => true, - "http" => url - .host_str() - .is_some_and(|host| is_loopback_host(host) || is_kubernetes_service_host(host)), - _ => false, - } -} - -fn is_loopback_host(host: &str) -> bool { - let host = host.trim_matches(['[', ']']); - if host.eq_ignore_ascii_case("localhost") { - return true; - } - match host.parse::() { - Ok(std::net::IpAddr::V4(v4)) => v4.is_loopback(), - Ok(std::net::IpAddr::V6(v6)) => { - v6.is_loopback() || v6.to_ipv4_mapped().is_some_and(|v4| v4.is_loopback()) - } - Err(_) => false, - } -} - -fn is_kubernetes_service_host(host: &str) -> bool { - let host = host.trim_end_matches('.').to_ascii_lowercase(); - let labels = host.split('.').collect::>(); - let is_service_name = labels.len() == 3 && labels[2] == "svc"; - let is_cluster_local_service = - labels.len() == 5 && labels[2] == "svc" && labels[3] == "cluster" && labels[4] == "local"; - (is_service_name || is_cluster_local_service) && labels.iter().all(|label| !label.is_empty()) -} - -fn effective_client_assertion_type(client_assertion_type: &str) -> &str { - if client_assertion_type.trim().is_empty() { - DEFAULT_CLIENT_ASSERTION_TYPE - } else { - client_assertion_type - } -} - -fn effective_token_type(token_type: &str) -> &str { - if token_type.trim().is_empty() { - DEFAULT_TOKEN_TYPE - } else { - token_type - } +) -> Result { + let client = token_exchange_http_client()?; + oauth::post_oauth_token_exchange( + client, + token_endpoint, + &TokenExchangeParams { + client_assertion: gateway_jwt_svid, + client_assertion_type, + subject_token, + subject_token_type, + audience, + scopes: &[], + requested_token_type, + }, + ) + .await + .map_err(|e| Status::failed_precondition(e.to_string())) } fn effective_jwt_svid_audience(token_endpoint: &str, jwt_svid_audience: &str) -> String { @@ -3943,77 +3824,6 @@ fn derive_issuer_from_token_endpoint(token_endpoint: &str) -> String { token_endpoint.to_string() } -fn validate_oauth_access_token(token: &str) -> Result<(), Status> { - if token.is_empty() || !is_token68(token) { - return Err(Status::internal( - "provider token exchange returned a malformed access token", - )); - } - Ok(()) -} - -fn is_token68(token: &str) -> bool { - let mut padding_started = false; - let mut saw_value = false; - for byte in token.bytes() { - if byte == b'=' { - padding_started = true; - continue; - } - if padding_started || !is_token68_value_byte(byte) { - return false; - } - saw_value = true; - } - saw_value -} - -fn is_token68_value_byte(byte: u8) -> bool { - byte.is_ascii_alphanumeric() || matches!(byte, b'-' | b'.' | b'_' | b'~' | b'+' | b'/') -} - -fn token_exchange_failure_message(status: reqwest::StatusCode, body: &str) -> String { - let Ok(error_response) = serde_json::from_str::(body) else { - return format!("provider token exchange failed with status {status}"); - }; - let error = error_response - .error - .as_deref() - .map(sanitize_oauth_error_field) - .filter(|value| !value.is_empty()); - let description = error_response - .error_description - .as_deref() - .map(sanitize_oauth_error_field) - .filter(|value| !value.is_empty()); - match (error, description) { - (Some(error), Some(description)) => { - format!( - "provider token exchange failed with status {status}: error={error}; error_description={description}" - ) - } - (Some(error), None) => { - format!("provider token exchange failed with status {status}: error={error}") - } - (None, Some(description)) => { - format!( - "provider token exchange failed with status {status}: error_description={description}" - ) - } - (None, None) => format!("provider token exchange failed with status {status}"), - } -} - -fn sanitize_oauth_error_field(value: &str) -> String { - value - .chars() - .map(|ch| if ch.is_control() { ' ' } else { ch }) - .take(MAX_OAUTH_ERROR_FIELD_LEN) - .collect::() - .trim() - .to_string() -} - pub(super) async fn handle_get_provider_refresh_status( state: &Arc, request: Request, diff --git a/crates/openshell-supervisor-network/Cargo.toml b/crates/openshell-supervisor-network/Cargo.toml index ae36554be7..671d4ce750 100644 --- a/crates/openshell-supervisor-network/Cargo.toml +++ b/crates/openshell-supervisor-network/Cargo.toml @@ -11,7 +11,7 @@ repository.workspace = true rust-version.workspace = true [dependencies] -openshell-core = { path = "../openshell-core" } +openshell-core = { path = "../openshell-core", features = ["oauth"] } openshell-ocsf = { path = "../openshell-ocsf" } openshell-policy = { path = "../openshell-policy" } openshell-router = { path = "../openshell-router" } diff --git a/crates/openshell-supervisor-network/src/token_grant.rs b/crates/openshell-supervisor-network/src/token_grant.rs index 23a45ab14b..15aea6fccf 100644 --- a/crates/openshell-supervisor-network/src/token_grant.rs +++ b/crates/openshell-supervisor-network/src/token_grant.rs @@ -34,14 +34,16 @@ use std::collections::HashMap; use std::future::Future; -use std::net::IpAddr; use std::sync::{Arc, LazyLock, RwLock}; use std::time::{Duration, SystemTime, UNIX_EPOCH}; use miette::{IntoDiagnostic, Result, WrapErr}; +use openshell_core::oauth::{ + self, ACCESS_TOKEN_TYPE, OAuthTokenResponse, TokenExchangeParams, TokenGrantParams, + effective_client_assertion_type, effective_token_type, +}; use openshell_core::proto::ProviderCredentialTokenGrantType; use openshell_core::sandbox_env; -use serde::Deserialize; use spiffe::WorkloadApiClient; /// Token cache shared across all provider token grants. @@ -55,33 +57,9 @@ static TOKEN_GRANT_HTTP_CLIENT: LazyLock = LazyLock::new(|| { .build() .expect("token grant HTTP client configuration should be valid") }); -const MAX_OAUTH_ERROR_FIELD_LEN: usize = 256; const DEFAULT_TOKEN_CACHE_TTL_SECONDS: i64 = 300; const TOKEN_CACHE_EXPIRY_SKEW_SECONDS: i64 = 30; const MAX_TOKEN_EXPIRES_IN_SECONDS: i64 = 3600; -const DEFAULT_CLIENT_ASSERTION_TYPE: &str = - "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"; -const DEFAULT_TOKEN_TYPE: &str = "urn:ietf:params:oauth:token-type:access_token"; - -/// `OAuth2` token response from the authorization server. -#[derive(Debug, Clone, Deserialize)] -struct TokenResponse { - access_token: String, - #[serde(default)] - #[allow(dead_code)] - token_type: String, - #[serde(default)] - expires_in: i64, - #[serde(default)] - #[allow(dead_code)] - scope: String, -} - -#[derive(Debug, Deserialize)] -struct OAuthErrorResponse { - error: Option, - error_description: Option, -} /// Cached access token with expiration metadata. #[derive(Debug, Clone)] @@ -232,17 +210,17 @@ pub async fn obtain_provider_token(request: ObtainProviderTokenRequest<'_>) -> R ) })?; validate_access_token(&intermediate.access_token)?; - let intermediate_subject_token_type = - final_exchange_subject_token_type(request.requested_token_type); perform_token_exchange( request.token_endpoint, - &jwt_svid, - request.client_assertion_type, - &intermediate.access_token, - intermediate_subject_token_type, - request.audience, - request.scopes, - request.requested_token_type, + &TokenExchangeParams { + client_assertion: &jwt_svid, + client_assertion_type: request.client_assertion_type, + subject_token: &intermediate.access_token, + subject_token_type: ACCESS_TOKEN_TYPE, + audience: request.audience, + scopes: request.scopes, + requested_token_type: request.requested_token_type, + }, ) .await } @@ -271,11 +249,8 @@ async fn obtain_provider_token_with_grant( ) -> Result where F: FnOnce(String) -> Fut, - Fut: Future>, + Fut: Future>, { - // Derive authorization server audience from token endpoint - // For Keycloak: https://auth.example.com/realms/openshell/protocol/openid-connect/token - // -> https://auth.example.com/realms/openshell let jwt_audience = effective_jwt_svid_audience(input.token_endpoint, input.jwt_svid_audience); let cache_key = token_cache_key(TokenCacheKeyInput { provider_name: input.provider_name, @@ -288,19 +263,16 @@ where requested_token_type: effective_token_type(input.requested_token_type), }); - // Check cache first if let Some(cached) = input.cache.get(&cache_key) { return Ok(cached); } let token_response = grant(jwt_audience).await?; - validate_access_token(&token_response.access_token)?; let cache_ttl_seconds = token_cache_ttl_seconds(input.cache_ttl_override, token_response.expires_in); let expires_at_ms = current_time_ms().saturating_add(cache_ttl_seconds.saturating_mul(1000)); - // Cache the token input.cache.set( cache_key, token_response.access_token.clone(), @@ -350,221 +322,34 @@ fn provider_spiffe_workload_api_socket_from_env() -> Result { }) } -/// Perform `OAuth2` JWT client assertion grant. -/// -/// POSTs to the token endpoint with: -/// - `grant_type=client_credentials` -/// - `client_assertion_type=` -/// - `client_assertion=` (client identity is in the JWT's `sub` claim) -/// - `audience=` (if provided) -/// - `scope=` (if provided) -/// -/// Note: `client_id` is NOT included - the client is identified by the `sub` claim -/// in the JWT-SVID itself. async fn perform_token_grant( token_endpoint: &str, jwt_svid: &str, client_assertion_type: &str, audience: &str, scopes: &[String], -) -> Result { - let token_endpoint_url = parse_token_endpoint_url(token_endpoint)?; - let client_assertion_type = effective_client_assertion_type(client_assertion_type); - let mut form_params = vec![ - ("grant_type", "client_credentials"), - ("client_assertion_type", client_assertion_type), - ("client_assertion", jwt_svid), - ]; - - // Add audience if provided - let audience_param; - if !audience.is_empty() { - audience_param = audience.to_string(); - form_params.push(("audience", &audience_param)); - } - - // Add scopes if provided - let scope_param; - if !scopes.is_empty() { - scope_param = scopes.join(" "); - form_params.push(("scope", &scope_param)); - } - - // POST to token endpoint - let response = TOKEN_GRANT_HTTP_CLIENT - .post(token_endpoint_url) - .form(&form_params) - .send() - .await - .into_diagnostic() - .wrap_err_with(|| format!("failed to POST to token endpoint {token_endpoint}"))?; - - // Check response status - if !response.status().is_success() { - let status = response.status(); - let body = response - .text() - .await - .unwrap_or_else(|_| "".to_string()); - return Err(miette::miette!( - "{}", - token_grant_failure_message(status, &body) - )); - } - - // Parse token response - let token_response = response - .json::() - .await - .into_diagnostic() - .wrap_err("failed to parse token response as JSON")?; - validate_access_token(&token_response.access_token)?; - Ok(token_response) +) -> Result { + oauth::post_oauth_token_grant( + &TOKEN_GRANT_HTTP_CLIENT, + token_endpoint, + &TokenGrantParams { + client_assertion: jwt_svid, + client_assertion_type, + audience, + scopes, + }, + ) + .await } -#[allow(clippy::too_many_arguments)] async fn perform_token_exchange( token_endpoint: &str, - jwt_svid: &str, - client_assertion_type: &str, - subject_token: &str, - subject_token_type: &str, - audience: &str, - scopes: &[String], - requested_token_type: &str, -) -> Result { - let token_endpoint_url = parse_token_endpoint_url(token_endpoint)?; - let client_assertion_type = effective_client_assertion_type(client_assertion_type); - let subject_token_type = effective_token_type(subject_token_type); - let requested_token_type = effective_token_type(requested_token_type); - let mut form_params = vec![ - ( - "grant_type", - "urn:ietf:params:oauth:grant-type:token-exchange", - ), - ("client_assertion_type", client_assertion_type), - ("client_assertion", jwt_svid), - ("subject_token", subject_token), - ("subject_token_type", subject_token_type), - ("requested_token_type", requested_token_type), - ]; - - let audience_param; - if !audience.is_empty() { - audience_param = audience.to_string(); - form_params.push(("audience", &audience_param)); - } - - let scope_param; - if !scopes.is_empty() { - scope_param = scopes.join(" "); - form_params.push(("scope", &scope_param)); - } - - let response = TOKEN_GRANT_HTTP_CLIENT - .post(token_endpoint_url) - .form(&form_params) - .send() - .await - .into_diagnostic() - .wrap_err_with(|| format!("failed to POST token exchange to {token_endpoint}"))?; - - if !response.status().is_success() { - let status = response.status(); - let body = response - .text() - .await - .unwrap_or_else(|_| "".to_string()); - return Err(miette::miette!( - "{}", - token_grant_failure_message(status, &body) - )); - } - - let token_response = response - .json::() - .await - .into_diagnostic() - .wrap_err("failed to parse token exchange response as JSON")?; - validate_access_token(&token_response.access_token)?; - Ok(token_response) -} - -fn parse_token_endpoint_url(token_endpoint: &str) -> Result { - let url = reqwest::Url::parse(token_endpoint) - .into_diagnostic() - .wrap_err("token_endpoint must be an absolute URL")?; - if token_endpoint_transport_allowed(&url) { - return Ok(url); - } - - Err(miette::miette!( - "token_endpoint must use https, except http for loopback or in-cluster service hosts" - )) -} - -fn token_endpoint_transport_allowed(url: &reqwest::Url) -> bool { - match url.scheme() { - "https" => true, - "http" => url - .host_str() - .is_some_and(|host| is_loopback_host(host) || is_kubernetes_service_host(host)), - _ => false, - } -} - -fn is_loopback_host(host: &str) -> bool { - let host = host.trim_matches(['[', ']']); - if host.eq_ignore_ascii_case("localhost") { - return true; - } - - match host.parse::() { - Ok(IpAddr::V4(v4)) => v4.is_loopback(), - Ok(IpAddr::V6(v6)) => { - v6.is_loopback() || v6.to_ipv4_mapped().is_some_and(|v4| v4.is_loopback()) - } - Err(_) => false, - } -} - -fn is_kubernetes_service_host(host: &str) -> bool { - let host = host.trim_end_matches('.').to_ascii_lowercase(); - let labels = host.split('.').collect::>(); - let is_service_name = labels.len() == 3 && labels[2] == "svc"; - let is_cluster_local_service = - labels.len() == 5 && labels[2] == "svc" && labels[3] == "cluster" && labels[4] == "local"; - (is_service_name || is_cluster_local_service) && labels.iter().all(|label| !label.is_empty()) -} - -pub fn validate_access_token(token: &str) -> Result<()> { - if token.is_empty() || !is_token68(token) { - return Err(miette::miette!( - "token grant returned a malformed access token" - )); - } - Ok(()) -} - -fn is_token68(token: &str) -> bool { - let mut padding_started = false; - let mut saw_value = false; - for byte in token.bytes() { - if byte == b'=' { - padding_started = true; - continue; - } - if padding_started || !is_token68_value_byte(byte) { - return false; - } - saw_value = true; - } - saw_value + params: &TokenExchangeParams<'_>, +) -> Result { + oauth::post_oauth_token_exchange(&TOKEN_GRANT_HTTP_CLIENT, token_endpoint, params).await } -fn is_token68_value_byte(byte: u8) -> bool { - byte.is_ascii_alphanumeric() || matches!(byte, b'-' | b'.' | b'_' | b'~' | b'+' | b'/') -} +pub use oauth::validate_access_token; fn token_cache_ttl_seconds(cache_ttl_override: i64, expires_in: i64) -> i64 { if cache_ttl_override > 0 { @@ -614,26 +399,6 @@ fn effective_jwt_svid_audience(token_endpoint: &str, jwt_svid_audience: &str) -> } } -fn effective_client_assertion_type(client_assertion_type: &str) -> &str { - if client_assertion_type.trim().is_empty() { - DEFAULT_CLIENT_ASSERTION_TYPE - } else { - client_assertion_type - } -} - -fn effective_token_type(token_type: &str) -> &str { - if token_type.trim().is_empty() { - DEFAULT_TOKEN_TYPE - } else { - token_type - } -} - -fn final_exchange_subject_token_type(requested_token_type: &str) -> &str { - effective_token_type(requested_token_type) -} - fn supervisor_gateway_endpoint_from_env() -> Result { std::env::var(sandbox_env::ENDPOINT) .ok() @@ -682,48 +447,6 @@ fn token_cache_key(input: TokenCacheKeyInput<'_>) -> String { ) } -fn token_grant_failure_message(status: reqwest::StatusCode, body: &str) -> String { - let Ok(error_response) = serde_json::from_str::(body) else { - return format!("token grant failed with status {status}"); - }; - - let error = error_response - .error - .as_deref() - .map(sanitize_oauth_error_field) - .filter(|value| !value.is_empty()); - let description = error_response - .error_description - .as_deref() - .map(sanitize_oauth_error_field) - .filter(|value| !value.is_empty()); - - match (error, description) { - (Some(error), Some(description)) => { - format!( - "token grant failed with status {status}: error={error}; error_description={description}" - ) - } - (Some(error), None) => { - format!("token grant failed with status {status}: error={error}") - } - (None, Some(description)) => { - format!("token grant failed with status {status}: error_description={description}") - } - (None, None) => format!("token grant failed with status {status}"), - } -} - -fn sanitize_oauth_error_field(value: &str) -> String { - value - .chars() - .map(|ch| if ch.is_control() { ' ' } else { ch }) - .take(MAX_OAUTH_ERROR_FIELD_LEN) - .collect::() - .trim() - .to_string() -} - /// Get current Unix timestamp in milliseconds. fn current_time_ms() -> i64 { let millis = SystemTime::now() @@ -736,6 +459,7 @@ fn current_time_ms() -> i64 { #[cfg(test)] mod tests { use super::*; + use openshell_core::oauth::{ACCESS_TOKEN_TYPE, DEFAULT_CLIENT_ASSERTION_TYPE}; use std::collections::HashMap; use std::sync::{ Arc, @@ -957,11 +681,10 @@ mod tests { let grant_calls = input.grant_calls.clone(); async move { let call = grant_calls.fetch_add(1, Ordering::SeqCst) + 1; - Ok(TokenResponse { + Ok(OAuthTokenResponse { access_token: format!("token-{call}"), token_type: "Bearer".to_string(), expires_in: input.expires_in, - scope: input.scopes.join(" "), }) } }, @@ -1030,79 +753,6 @@ mod tests { assert_eq!(audience, "spiffe://custom-audience"); } - #[test] - fn final_exchange_subject_token_type_uses_intermediate_requested_token_type() { - let stored_subject_token_type = "urn:ietf:params:oauth:token-type:id_token"; - let requested_token_type = "urn:ietf:params:oauth:token-type:access_token"; - - assert_ne!(stored_subject_token_type, requested_token_type); - assert_eq!( - final_exchange_subject_token_type(requested_token_type), - requested_token_type - ); - assert_eq!(final_exchange_subject_token_type(""), DEFAULT_TOKEN_TYPE); - } - - #[test] - fn validate_access_token_accepts_token68_values() { - for token in [ - "abcXYZ123-._~+/", - "eyJhbGciOiJSUzI1NiJ9.payload.sig", - "token==", - ] { - validate_access_token(token).expect("token68 bearer token should be accepted"); - } - } - - #[test] - fn validate_access_token_rejects_header_injection_and_non_token68_values() { - for token in [ - "", - "token with spaces", - "token\r\nX-Injected: yes", - "token\u{7f}", - "tokené", - "token=continued", - "==", - ] { - let err = validate_access_token(token) - .expect_err("malformed bearer token should be rejected"); - assert_eq!( - err.to_string(), - "token grant returned a malformed access token" - ); - } - } - - #[test] - fn token_endpoint_url_allows_https_loopback_and_in_cluster_http() { - for endpoint in [ - "https://auth.example.com/token", - "http://127.0.0.1:8080/token", - "http://[::1]:8080/token", - "http://token-issuer.default.svc.cluster.local/token", - "http://token-issuer.default.svc/token", - ] { - parse_token_endpoint_url(endpoint).expect("token endpoint should be allowed"); - } - } - - #[test] - fn token_endpoint_url_rejects_plain_http_non_cluster_hosts() { - for endpoint in [ - "http://auth.example.com/token", - "http://keycloak/realms/openshell/protocol/openid-connect/token", - "http://token-issuer.default.svc.evil.com/token", - "ftp://auth.example.com/token", - "/relative/token", - ] { - assert!( - parse_token_endpoint_url(endpoint).is_err(), - "token endpoint should be rejected: {endpoint}" - ); - } - } - #[test] fn token_cache_key_varies_by_resource_audience_and_scopes() { let provider_name = "alpha.default.svc.cluster.local\t80\t\tprovider:access_token"; @@ -1119,7 +769,7 @@ mod tests { audience: "alpha", scopes: &alpha_scopes, grant_type: ProviderCredentialTokenGrantType::ClientCredentials, - requested_token_type: DEFAULT_TOKEN_TYPE, + requested_token_type: ACCESS_TOKEN_TYPE, }); let different_audience = token_cache_key(TokenCacheKeyInput { provider_name, @@ -1129,7 +779,7 @@ mod tests { audience: "delta", scopes: &alpha_scopes, grant_type: ProviderCredentialTokenGrantType::ClientCredentials, - requested_token_type: DEFAULT_TOKEN_TYPE, + requested_token_type: ACCESS_TOKEN_TYPE, }); let different_scopes = token_cache_key(TokenCacheKeyInput { provider_name, @@ -1139,7 +789,7 @@ mod tests { audience: "alpha", scopes: &delta_scopes, grant_type: ProviderCredentialTokenGrantType::ClientCredentials, - requested_token_type: DEFAULT_TOKEN_TYPE, + requested_token_type: ACCESS_TOKEN_TYPE, }); let different_assertion_type = token_cache_key(TokenCacheKeyInput { provider_name, @@ -1149,7 +799,7 @@ mod tests { audience: "alpha", scopes: &alpha_scopes, grant_type: ProviderCredentialTokenGrantType::ClientCredentials, - requested_token_type: DEFAULT_TOKEN_TYPE, + requested_token_type: ACCESS_TOKEN_TYPE, }); assert_ne!(base, different_audience); @@ -1171,7 +821,7 @@ mod tests { audience: "alpha", scopes: &scopes, grant_type: ProviderCredentialTokenGrantType::TokenExchange, - requested_token_type: DEFAULT_TOKEN_TYPE, + requested_token_type: ACCESS_TOKEN_TYPE, }); let revision_two = token_cache_key(TokenCacheKeyInput { provider_name: "api.example.test\t443\t/v1/**\trev:2\tprovider:access_token", @@ -1181,7 +831,7 @@ mod tests { audience: "alpha", scopes: &scopes, grant_type: ProviderCredentialTokenGrantType::TokenExchange, - requested_token_type: DEFAULT_TOKEN_TYPE, + requested_token_type: ACCESS_TOKEN_TYPE, }); assert_ne!(revision_one, revision_two); @@ -1329,7 +979,7 @@ mod tests { audience, scopes: &scopes, grant_type: ProviderCredentialTokenGrantType::ClientCredentials, - requested_token_type: DEFAULT_TOKEN_TYPE, + requested_token_type: ACCESS_TOKEN_TYPE, }); cache.set( cache_key, @@ -1355,61 +1005,6 @@ mod tests { assert_eq!(grant_calls.load(Ordering::SeqCst), 1); } - #[tokio::test] - async fn obtain_provider_token_rejects_malformed_token_before_cache() { - let cache = TokenCache::new(); - let scopes = vec!["read".to_string()]; - let provider_name = "api.example.test\t443\t/v1/**\tprovider:access_token"; - let token_endpoint = "https://auth.example.com/token"; - let jwt_svid_audience = "https://auth.example.com"; - let audience = "api://resource"; - - let err = obtain_provider_token_with_grant( - ObtainProviderTokenInput { - cache: &cache, - provider_name, - token_endpoint, - jwt_svid_audience, - client_assertion_type: DEFAULT_CLIENT_ASSERTION_TYPE, - audience, - scopes: &scopes, - cache_ttl_override: 0, - grant_type: ProviderCredentialTokenGrantType::ClientCredentials, - requested_token_type: "", - }, - |_| async { - Ok(TokenResponse { - access_token: "access-123\r\nX-Injected: yes".to_string(), - token_type: "Bearer".to_string(), - expires_in: 60, - scope: "read".to_string(), - }) - }, - ) - .await - .expect_err("malformed access token should fail before caching"); - - let cache_key = token_cache_key(TokenCacheKeyInput { - provider_name, - token_endpoint, - jwt_svid_audience, - client_assertion_type: DEFAULT_CLIENT_ASSERTION_TYPE, - audience, - scopes: &scopes, - grant_type: ProviderCredentialTokenGrantType::ClientCredentials, - requested_token_type: DEFAULT_TOKEN_TYPE, - }); - - assert_eq!( - err.to_string(), - "token grant returned a malformed access token" - ); - assert!( - cache.get(&cache_key).is_none(), - "malformed access token must not be cached" - ); - } - #[tokio::test] async fn obtain_provider_token_cache_ttl_override_extends_zero_expires_in() { let cache = TokenCache::new(); @@ -1632,43 +1227,4 @@ mod tests { .contains("failed to parse token response as JSON") ); } - - #[test] - fn token_grant_failure_message_reports_oauth_error_fields() { - let message = token_grant_failure_message( - reqwest::StatusCode::UNAUTHORIZED, - r#"{"error":"invalid_client","error_description":"Invalid client credentials"}"#, - ); - - assert_eq!( - message, - "token grant failed with status 401 Unauthorized: error=invalid_client; error_description=Invalid client credentials" - ); - } - - #[test] - fn token_grant_failure_message_omits_unstructured_response_body() { - let message = token_grant_failure_message( - reqwest::StatusCode::INTERNAL_SERVER_ERROR, - "internal error containing implementation details", - ); - - assert_eq!( - message, - "token grant failed with status 500 Internal Server Error" - ); - } - - #[test] - fn token_grant_failure_message_sanitizes_oauth_error_fields() { - let long_description = "a".repeat(MAX_OAUTH_ERROR_FIELD_LEN + 20); - let body = - format!(r#"{{"error":"invalid_client\n","error_description":"{long_description}"}}"#); - let message = token_grant_failure_message(reqwest::StatusCode::UNAUTHORIZED, &body); - - assert!(!message.contains('\n')); - assert!(message.contains("error=invalid_client")); - assert!(message.contains(&"a".repeat(MAX_OAUTH_ERROR_FIELD_LEN))); - assert!(!message.contains(&"a".repeat(MAX_OAUTH_ERROR_FIELD_LEN + 1))); - } } From ca6d6ca3eb6e8ab6ae9a63c2ebfdf8ecfc8c770e Mon Sep 17 00:00:00 2001 From: Gordon Sim Date: Thu, 30 Jul 2026 20:57:55 +0100 Subject: [PATCH 6/8] fix(provider): evict nearest-to-expiry entry from intermediate token cache Signed-off-by: Gordon Sim --- crates/openshell-server/src/grpc/provider.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/crates/openshell-server/src/grpc/provider.rs b/crates/openshell-server/src/grpc/provider.rs index af2fc44396..b41db613a3 100644 --- a/crates/openshell-server/src/grpc/provider.rs +++ b/crates/openshell-server/src/grpc/provider.rs @@ -2096,7 +2096,10 @@ impl IntermediateTokenCache { let now_ms = crate::persistence::current_time_ms(); tokens.retain(|_, cached| cached.expires_at_ms > now_ms); if tokens.len() >= MAX_INTERMEDIATE_TOKEN_CACHE_ENTRIES - && let Some(evict_key) = tokens.keys().next().cloned() + && let Some(evict_key) = tokens + .iter() + .min_by_key(|(_, cached)| cached.expires_at_ms) + .map(|(k, _)| k.clone()) { tokens.remove(&evict_key); } From a923e529b6378e1cc8882849c45b0d39bfdbf26a Mon Sep 17 00:00:00 2001 From: Gordon Sim Date: Wed, 12 Aug 2026 19:44:46 +0100 Subject: [PATCH 7/8] doc(supervisor): add podman example for token exchange Signed-off-by: Gordon Sim --- examples/spiffe-token-exchange-demo/README.md | 10 + .../k8s/token-issuer.js | 15 +- .../podman/README.md | 316 ++++++++ .../spiffe-token-exchange-demo/podman/demo.sh | 675 ++++++++++++++++++ .../podman/provider-profile.yaml | 58 ++ .../podman/spire/agent.conf | 37 + .../podman/spire/common.sh | 157 ++++ .../podman/spire/oidc-discovery-provider.conf | 19 + .../podman/spire/register-gateway.sh | 40 ++ .../podman/spire/register-sandbox.sh | 43 ++ .../podman/spire/server.conf | 39 + .../podman/spire/start-agent.sh | 78 ++ .../podman/spire/start-server-oidc.sh | 80 +++ .../podman/start-gateway.sh | 233 ++++++ 14 files changed, 1797 insertions(+), 3 deletions(-) create mode 100644 examples/spiffe-token-exchange-demo/podman/README.md create mode 100755 examples/spiffe-token-exchange-demo/podman/demo.sh create mode 100644 examples/spiffe-token-exchange-demo/podman/provider-profile.yaml create mode 100644 examples/spiffe-token-exchange-demo/podman/spire/agent.conf create mode 100755 examples/spiffe-token-exchange-demo/podman/spire/common.sh create mode 100644 examples/spiffe-token-exchange-demo/podman/spire/oidc-discovery-provider.conf create mode 100755 examples/spiffe-token-exchange-demo/podman/spire/register-gateway.sh create mode 100755 examples/spiffe-token-exchange-demo/podman/spire/register-sandbox.sh create mode 100644 examples/spiffe-token-exchange-demo/podman/spire/server.conf create mode 100755 examples/spiffe-token-exchange-demo/podman/spire/start-agent.sh create mode 100755 examples/spiffe-token-exchange-demo/podman/spire/start-server-oidc.sh create mode 100755 examples/spiffe-token-exchange-demo/podman/start-gateway.sh diff --git a/examples/spiffe-token-exchange-demo/README.md b/examples/spiffe-token-exchange-demo/README.md index 92d790bd72..9c6a50b9c2 100644 --- a/examples/spiffe-token-exchange-demo/README.md +++ b/examples/spiffe-token-exchange-demo/README.md @@ -206,6 +206,16 @@ The script reuses your normal OpenShell CLI config so it can load the stored OIDC token for `OPENSHELL_GATEWAY`. If you set `ISOLATED_CONFIG=1`, register and log in to the gateway in that isolated config before running the demo. +## Podman Demo + +A local Podman variant lives in `podman/`. It reuses the same dummy token +issuer and protected service code, starts local SPIRE and demo service +containers, manually registers one sandbox SPIFFE entry, and runs the same +alpha/beta token exchange checks. + +See `podman/README.md` for the required gateway configuration and script +usage. + ## Cleanup Delete the sandbox through OpenShell: diff --git a/examples/spiffe-token-exchange-demo/k8s/token-issuer.js b/examples/spiffe-token-exchange-demo/k8s/token-issuer.js index e4cc22af56..1a5d0e7527 100644 --- a/examples/spiffe-token-exchange-demo/k8s/token-issuer.js +++ b/examples/spiffe-token-exchange-demo/k8s/token-issuer.js @@ -27,6 +27,10 @@ const ACCESS_TOKEN_ISSUER = const ACCESS_TOKEN_SECRET = process.env.ACCESS_TOKEN_SECRET; const DEMO_USER_SUBJECT = process.env.DEMO_USER_SUBJECT || "demo-user"; const SPIRE_JWKS_CA_FILE = process.env.SPIRE_JWKS_CA_FILE || ""; +const JWT_SVID_VERIFY_ALGORITHMS = { + ES256: { algorithm: "sha256", dsaEncoding: "ieee-p1363" }, + RS256: { algorithm: "RSA-SHA256" }, +}; if (!ACCESS_TOKEN_SECRET) { throw new Error("ACCESS_TOKEN_SECRET is required"); @@ -110,7 +114,8 @@ function hasAudience(payload, expected) { async function verifyJwtSvid(jwt, subjectPrefix) { const parsed = parseJwt(jwt); - if (parsed.header.alg !== "RS256") { + const verifyAlgorithm = JWT_SVID_VERIFY_ALGORITHMS[parsed.header.alg]; + if (!verifyAlgorithm) { throw new Error(`unsupported JWT-SVID alg ${parsed.header.alg}`); } @@ -120,11 +125,15 @@ async function verifyJwtSvid(jwt, subjectPrefix) { throw new Error(`no JWKS key for kid ${parsed.header.kid}`); } - const verifier = crypto.createVerify("RSA-SHA256"); + const verifier = crypto.createVerify(verifyAlgorithm.algorithm); verifier.update(parsed.signingInput); verifier.end(); const publicKey = crypto.createPublicKey({ key: jwk, format: "jwk" }); - if (!verifier.verify(publicKey, parsed.signature)) { + const verifyOptions = { key: publicKey }; + if (verifyAlgorithm.dsaEncoding) { + verifyOptions.dsaEncoding = verifyAlgorithm.dsaEncoding; + } + if (!verifier.verify(verifyOptions, parsed.signature)) { throw new Error("JWT-SVID signature validation failed"); } diff --git a/examples/spiffe-token-exchange-demo/podman/README.md b/examples/spiffe-token-exchange-demo/podman/README.md new file mode 100644 index 0000000000..d8fdde4b4b --- /dev/null +++ b/examples/spiffe-token-exchange-demo/podman/README.md @@ -0,0 +1,316 @@ +# Podman SPIFFE Token Exchange Demo + +This variant runs the SPIFFE token exchange demo with local Podman containers +instead of Kubernetes workloads. + +The first version is intentionally single-sandbox. The script creates one +concrete SPIRE registration entry for the sandbox after OpenShell creates it. +It does not rely on SPIRE templating one entry into many per-sandbox SPIFFE IDs. + +## What Runs + +`demo.sh` starts these local Podman containers on the `openshell` network by +default: + +| Container | Purpose | +|---|---| +| `openshell-spiffe-demo-spire-server` | Local SPIRE server | +| `openshell-spiffe-demo-spire-agent` | Local SPIRE agent with a Workload API socket | +| `openshell-spiffe-demo-spire-oidc` | SPIRE OIDC discovery provider for JWKS | +| `openshell-spiffe-demo-token-issuer` | Dummy IdP/token exchange endpoint | +| `openshell-spiffe-demo-alpha` | Protected alpha service | +| `openshell-spiffe-demo-beta` | Protected beta service | + +The OpenShell gateway is not started by this script. Start it separately with +the Podman driver and SPIFFE provider token grant settings. + +For the most self-contained path, set `START_GATEWAY=1`. The script starts a +gateway container on the same Podman network as the token issuer and protected +services. That makes Podman's DNS alias +`token-exchange-issuer.default.svc.cluster.local` resolve inside the gateway +without host DNS changes. + +## Gateway Requirements + +Start the gateway with: + +```shell +export OPENSHELL_GATEWAY_SPIFFE_WORKLOAD_API_SOCKET=/path/to/demo/spire-agent.sock +``` + +Configure the Podman driver with the same socket path: + +```toml +[openshell.drivers.podman] +network_name = "openshell" +provider_spiffe_workload_api_socket = "/path/to/demo/spire-agent.sock" +``` + +When `demo.sh` starts SPIRE itself, it prints the exact socket path to use. +Restart the gateway with that path before the final alpha/beta calls if the +gateway was not already configured. + +The provider profile uses +`http://token-exchange-issuer.default.svc.cluster.local:8080/token` by default. +The token issuer container has that Podman network alias, so sandboxes on the +same Podman network can resolve it. The gateway also performs the intermediate +token exchange, so a host-running gateway must be able to resolve and reach the +same name. For local testing, add a hosts/DNS entry that maps +`token-exchange-issuer.default.svc.cluster.local` to the host address serving +the published `TOKEN_ISSUER_PORT`, or run the gateway in an environment attached +to the same Podman network. + +`START_GATEWAY=1` automates that same-network gateway setup. It mounts the host +Podman socket into the gateway container, writes a temporary gateway config with +`compute_drivers = ["podman"]`, and mounts the SPIRE Workload API socket at the +same absolute host path so the gateway can pass that path to sibling sandbox +containers. + +The script looks for a Podman API socket in the usual rootless and rootful +locations, plus `podman system connection list`. If none exists, it starts a +temporary rootless API service under the script's temporary directory and stops +it during cleanup. The self-started socket is mounted into demo containers with +Podman relabeling. Set `PODMAN_SOCKET=/path/to/podman.sock` or +`PODMAN_SOCKET=unix:///path/to/podman.sock` to use a specific existing socket. +The SPIRE agent and managed gateway containers run with +`--security-opt label=disable` because both must connect to the rootless Podman +API Unix socket. + +SPIRE runs as a non-root user in its upstream images. The script creates +throwaway state and socket directories under its temporary directory with broad +write permissions so rootless Podman UID mappings can create the SQLite +datastore and Workload API sockets. + +## Run + +From anywhere: + +```shell +export OPENSHELL_REPO=/path/to/OpenShell +export GATEWAY_NAME=local +export GATEWAY_ENDPOINT=http://127.0.0.1:8080 + +bash "$OPENSHELL_REPO/examples/spiffe-token-exchange-demo/podman/demo.sh" +``` + +Self-contained local path: + +```shell +START_GATEWAY=1 \ +bash "$OPENSHELL_REPO/examples/spiffe-token-exchange-demo/podman/demo.sh" +``` + +Common overrides: + +```shell +SANDBOX_NAME=spiffe-podman-demo +PODMAN_NETWORK=openshell +TOKEN_ISSUER_PORT=18080 +MANAGED_GATEWAY_PORT=18082 +MANAGED_GATEWAY_HEALTH_PORT=18083 +GATEWAY_IMAGE=ghcr.io/nvidia/openshell/gateway:latest +SANDBOX_IMAGE=ghcr.io/nvidia/openshell/sandbox:latest +SUPERVISOR_IMAGE=ghcr.io/nvidia/openshell/supervisor:latest +SANDBOX_IMAGE_PULL_POLICY=missing +PODMAN_STOP_TIMEOUT_SECS=3 +KEEP_DEMO=1 +KEEP_SANDBOX=1 +``` + +Keep `SANDBOX_NAME` at 19 characters or fewer for the Podman driver. The +default `spiffe-podman-demo` is 18 characters. + +`PODMAN_STOP_TIMEOUT_SECS` controls how long the managed gateway asks Podman to +wait for sandbox containers to stop before Podman force-kills them during +cleanup. The short demo default avoids waiting on long SIGTERM grace periods. + +When testing branch-local images with `START_GATEWAY=1`, override all three +OpenShell runtime images: + +```shell +START_GATEWAY=1 \ +GATEWAY_IMAGE=localhost/openshell/gateway:branch \ +SANDBOX_IMAGE=localhost/openshell/sandbox:branch \ +SUPERVISOR_IMAGE=localhost/openshell/supervisor:branch \ +SANDBOX_IMAGE_PULL_POLICY=never \ +bash "$OPENSHELL_REPO/examples/spiffe-token-exchange-demo/podman/demo.sh" +``` + +`SANDBOX_IMAGE` renders to `[openshell.drivers.podman].default_image`. +`SUPERVISOR_IMAGE` renders to `[openshell.drivers.podman].supervisor_image`. + +Use `START_SPIRE=0` only when you already have SPIRE running and can provide a +host path to a Workload API socket: + +```shell +START_SPIRE=0 \ +SPIRE_AGENT_SOCKET_HOST_PATH=/run/spire/agent.sock \ +bash "$OPENSHELL_REPO/examples/spiffe-token-exchange-demo/podman/demo.sh" +``` + +## SPIRE Startup Scripts + +The full demo uses these scripts internally, and you can also run them directly +when you want to manage SPIRE separately from the token exchange flow: + +```shell +SPIRE_STATE_DIR="$(mktemp -d)" \ +SPIRE_ENV_FILE=/tmp/openshell-spire-server.env \ +bash "$OPENSHELL_REPO/examples/spiffe-token-exchange-demo/podman/spire/start-server-oidc.sh" + +source /tmp/openshell-spire-server.env + +SPIRE_STATE_DIR="$(mktemp -d)" \ +SPIRE_ENV_FILE=/tmp/openshell-spire-agent.env \ +bash "$OPENSHELL_REPO/examples/spiffe-token-exchange-demo/podman/spire/start-agent.sh" + +source /tmp/openshell-spire-agent.env +printf "Workload API socket: %s\n" "$SPIRE_AGENT_SOCKET_HOST_PATH" +``` + +`start-server-oidc.sh` starts the SPIRE server and OIDC discovery provider. +`start-agent.sh` generates a join token from the server container and starts a +SPIRE agent with the Docker-compatible Podman workload attestor. Both scripts +honor the same image, container, network, trust-domain, and SPIRE parent ID +environment variables as `demo.sh`. + +## Gateway Startup Script + +After starting the SPIRE agent, start a Podman-backed OpenShell gateway with: + +```shell +SPIRE_AGENT_ENV_FILE=/tmp/openshell-spire-agent.env \ +GATEWAY_ENV_FILE=/tmp/openshell-spiffe-gateway.env \ +bash "$OPENSHELL_REPO/examples/spiffe-token-exchange-demo/podman/start-gateway.sh" +``` + +The gateway listens on `http://127.0.0.1:8888` by default, with health checks on +`http://127.0.0.1:8889`. Override those ports with `GATEWAY_PORT` and +`GATEWAY_HEALTH_PORT`. + +`start-gateway.sh` mounts the SPIRE agent Workload API socket into the gateway +container and writes a temporary gateway config with the Podman driver enabled. +Register the gateway SPIFFE entry as a separate step: + +```shell +GATEWAY_SELECTORS="docker:label:openshell.spiffe-demo:gateway" \ +bash "$OPENSHELL_REPO/examples/spiffe-token-exchange-demo/podman/spire/register-gateway.sh" +``` + +The script also honors `GATEWAY_IMAGE`, `SANDBOX_IMAGE`, `SUPERVISOR_IMAGE`, +`SANDBOX_IMAGE_PULL_POLICY`, `PODMAN_NETWORK`, `PODMAN_SOCKET`, and +`PODMAN_STOP_TIMEOUT_SECS`. + +To require OIDC login for user-facing gateway calls, set `GATEWAY_OIDC_ISSUER`. +The script then renders `[openshell.gateway.oidc]` and defaults +`allow_unauthenticated_users` to `false`: + +```shell +SPIRE_AGENT_ENV_FILE=/tmp/openshell-spire-agent.env \ +GATEWAY_OIDC_ISSUER=https://idp.example.com/realms/openshell \ +GATEWAY_OIDC_AUDIENCE=openshell-cli \ +GATEWAY_OIDC_CLIENT_ID=openshell-cli \ +GATEWAY_OIDC_LOGIN_SCOPES="openid profile email" \ +bash "$OPENSHELL_REPO/examples/spiffe-token-exchange-demo/podman/start-gateway.sh" +``` + +The script prints the matching `openshell gateway add ... --oidc-issuer ...` +command after the gateway is ready. You can also run it directly: + +```shell +openshell gateway add http://127.0.0.1:8888 \ + --name podman-spiffe-demo \ + --oidc-issuer https://idp.example.com/realms/openshell \ + --oidc-client-id openshell-cli \ + --oidc-audience openshell-cli \ + --oidc-scopes "openid profile email" +``` + +OIDC-related overrides: + +- `GATEWAY_OIDC_ISSUER` +- `GATEWAY_OIDC_AUDIENCE`, default `openshell-cli` +- `GATEWAY_OIDC_JWKS_TTL_SECS`, default `3600` +- `GATEWAY_OIDC_ROLES_CLAIM`, default `realm_access.roles` +- `GATEWAY_OIDC_ADMIN_ROLE`, default `openshell-admin` +- `GATEWAY_OIDC_USER_ROLE`, default `openshell-user` +- `GATEWAY_OIDC_SCOPES_CLAIM`, default empty +- `GATEWAY_OIDC_CLIENT_ID`, default `openshell-cli` +- `GATEWAY_OIDC_LOGIN_SCOPES`, default empty +- `GATEWAY_ALLOW_UNAUTHENTICATED_USERS`, default `false` when OIDC is enabled + and `true` otherwise + +## SPIRE Registration + +The helpers are: + +```shell +examples/spiffe-token-exchange-demo/podman/spire/register-gateway.sh +examples/spiffe-token-exchange-demo/podman/spire/register-sandbox.sh +``` + +Defaults: + +- SPIRE agent parent ID: + `spiffe://openshell.local/openshell/spire-agent/demo` +- Gateway SPIFFE ID: + `spiffe://openshell.local/openshell/gateway/demo` +- Gateway selectors: + `unix:uid:` plus `unix:path:` when + `openshell-server` is on `PATH` +- Managed gateway selectors: + `docker:label:openshell.spiffe-demo:gateway` +- Sandbox SPIFFE ID: + `spiffe://openshell.local/openshell/sandbox/` +- Sandbox selectors: + `docker:label:openshell.managed:true` and + `docker:label:openshell.ai/sandbox-id:` + +If your gateway binary is not named `openshell-server` or is not on `PATH`, set +`GATEWAY_WORKLOAD_PATH` or provide `GATEWAY_SELECTORS`: + +```shell +GATEWAY_WORKLOAD_PATH=/path/to/openshell-server \ + examples/spiffe-token-exchange-demo/podman/spire/register-gateway.sh +``` + +If your SPIRE setup cannot use Docker-style selectors against Podman, provide +explicit selectors: + +```shell +SANDBOX_SELECTORS="selector:a selector:b" \ + examples/spiffe-token-exchange-demo/podman/spire/register-sandbox.sh "$SANDBOX_ID" +``` + +## Expected Output + +The alpha and beta calls should include the demo user and the sandbox SPIFFE ID: + +```text +alpha called with path /: + sub: demo-user + aud: alpha, account + scope: alpha profile email + azp: spiffe://openshell.local/openshell/sandbox/ + client_id: spiffe://openshell.local/openshell/sandbox/ +``` + +## Cleanup + +The script deletes demo containers and the sandbox on exit unless you set +`KEEP_DEMO=1` or `KEEP_SANDBOX=1`. + +Manual cleanup: + +```shell +openshell --gateway "$GATEWAY_NAME" --gateway-endpoint "$GATEWAY_ENDPOINT" \ + sandbox delete spiffe-podman-demo + +podman rm -f \ + openshell-spiffe-demo-token-issuer \ + openshell-spiffe-demo-alpha \ + openshell-spiffe-demo-beta \ + openshell-spiffe-demo-spire-oidc \ + openshell-spiffe-demo-spire-agent \ + openshell-spiffe-demo-spire-server +``` diff --git a/examples/spiffe-token-exchange-demo/podman/demo.sh b/examples/spiffe-token-exchange-demo/podman/demo.sh new file mode 100755 index 0000000000..0c943c15f6 --- /dev/null +++ b/examples/spiffe-token-exchange-demo/podman/demo.sh @@ -0,0 +1,675 @@ +#!/usr/bin/env bash + +# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +DEMO_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)" +PROFILE_TEMPLATE="${SCRIPT_DIR}/provider-profile.yaml" +TOKEN_ISSUER_JS="${DEMO_ROOT}/k8s/token-issuer.js" +PROTECTED_SERVICE_JS="${DEMO_ROOT}/k8s/protected-service.js" + +SANDBOX_NAME="${SANDBOX_NAME:-spiffe-podman-demo}" +PROVIDER_NAME="${PROVIDER_NAME:-spiffe-token-exchange-demo-podman}" +PROFILE_ID="${PROFILE_ID:-spiffe-token-exchange-demo-podman}" +START_GATEWAY="${START_GATEWAY:-0}" +MANAGED_GATEWAY_PORT="${MANAGED_GATEWAY_PORT:-18082}" +MANAGED_GATEWAY_HEALTH_PORT="${MANAGED_GATEWAY_HEALTH_PORT:-18083}" +if [[ -z "${GATEWAY_ENDPOINT:-}" ]]; then + if [[ "$START_GATEWAY" == "1" ]]; then + GATEWAY_ENDPOINT="http://127.0.0.1:${MANAGED_GATEWAY_PORT}" + else + GATEWAY_ENDPOINT="http://127.0.0.1:8080" + fi +fi +PODMAN_NETWORK="${PODMAN_NETWORK:-openshell}" +TOKEN_ISSUER_PORT="${TOKEN_ISSUER_PORT:-18080}" +OIDC_PORT="${OIDC_PORT:-18081}" +TOKEN_ISSUER_SERVICE_HOST="${TOKEN_ISSUER_SERVICE_HOST:-token-exchange-issuer.default.svc.cluster.local}" +KEEP_SANDBOX="${KEEP_SANDBOX:-0}" +KEEP_DEMO="${KEEP_DEMO:-0}" +START_SPIRE="${START_SPIRE:-1}" +ACCESS_TOKEN_SECRET="${ACCESS_TOKEN_SECRET:-$(openssl rand -hex 32)}" +TRUST_DOMAIN="${TRUST_DOMAIN:-openshell.local}" +SPIRE_AGENT_PARENT_ID="${SPIRE_AGENT_PARENT_ID:-spiffe://${TRUST_DOMAIN}/openshell/spire-agent/demo}" +SPIRE_AGENT_SOCKET_HOST_PATH="${SPIRE_AGENT_SOCKET_HOST_PATH:-}" +SPIRE_SERVER_IMAGE="${SPIRE_SERVER_IMAGE:-ghcr.io/spiffe/spire-server:1.12.4}" +SPIRE_AGENT_IMAGE="${SPIRE_AGENT_IMAGE:-ghcr.io/spiffe/spire-agent:1.12.4}" +SPIRE_OIDC_IMAGE="${SPIRE_OIDC_IMAGE:-ghcr.io/spiffe/oidc-discovery-provider:1.12.4}" +NODE_IMAGE="${NODE_IMAGE:-node:22-alpine}" +SPIRE_SERVER_CONTAINER="${SPIRE_SERVER_CONTAINER:-openshell-spiffe-demo-spire-server}" +SPIRE_AGENT_CONTAINER="${SPIRE_AGENT_CONTAINER:-openshell-spiffe-demo-spire-agent}" +SPIRE_OIDC_CONTAINER="${SPIRE_OIDC_CONTAINER:-openshell-spiffe-demo-spire-oidc}" +GATEWAY_CONTAINER="${GATEWAY_CONTAINER:-openshell-spiffe-demo-gateway}" +GATEWAY_IMAGE="${GATEWAY_IMAGE:-ghcr.io/nvidia/openshell/gateway:latest}" +SANDBOX_IMAGE="${SANDBOX_IMAGE:-}" +SUPERVISOR_IMAGE="${SUPERVISOR_IMAGE:-}" +SANDBOX_IMAGE_PULL_POLICY="${SANDBOX_IMAGE_PULL_POLICY:-missing}" +PODMAN_STOP_TIMEOUT_SECS="${PODMAN_STOP_TIMEOUT_SECS:-3}" +TOKEN_ISSUER_CONTAINER="${TOKEN_ISSUER_CONTAINER:-openshell-spiffe-demo-token-issuer}" +ALPHA_CONTAINER="${ALPHA_CONTAINER:-openshell-spiffe-demo-alpha}" +BETA_CONTAINER="${BETA_CONTAINER:-openshell-spiffe-demo-beta}" + +TMP_DIR="$(mktemp -d)" +RENDERED_PROFILE="${TMP_DIR}/provider-profile.yaml" +MOUNT_DIR="${TMP_DIR}/mounts" +TOKEN_ISSUER_JS_MOUNT="${MOUNT_DIR}/token-issuer.js" +PROTECTED_SERVICE_JS_MOUNT="${MOUNT_DIR}/protected-service.js" +PODMAN_SERVICE_PID="" +PODMAN_SOCKET_DEMO_OWNED="0" + +default_gateway_name() { + if [[ -n "${GATEWAY_NAME:-}" ]]; then + printf "%s\n" "$GATEWAY_NAME" + return + fi + if [[ -n "${OPENSHELL_GATEWAY:-}" ]]; then + printf "%s\n" "$OPENSHELL_GATEWAY" + return + fi + if [[ "$START_GATEWAY" == "1" ]]; then + printf "podman-spiffe-demo\n" + return + fi + + local config_home="${XDG_CONFIG_HOME:-$HOME/.config}" + if [[ -s "${config_home}/openshell/active_gateway" ]]; then + head -n1 "${config_home}/openshell/active_gateway" + return + fi + if [[ -s /etc/openshell/active_gateway ]]; then + head -n1 /etc/openshell/active_gateway + return + fi + + printf "local\n" +} + +GATEWAY_NAME="$(default_gateway_name)" +OS=(openshell --gateway "$GATEWAY_NAME" --gateway-endpoint "$GATEWAY_ENDPOINT") + +run() { + printf "\n$ %s\n" "$*" + "$@" +} + +prepare_demo_mounts() { + mkdir -p "$MOUNT_DIR" + cp "$TOKEN_ISSUER_JS" "$TOKEN_ISSUER_JS_MOUNT" + cp "$PROTECTED_SERVICE_JS" "$PROTECTED_SERVICE_JS_MOUNT" + chmod 0644 \ + "$TOKEN_ISSUER_JS_MOUNT" \ + "$PROTECTED_SERVICE_JS_MOUNT" +} + +require_cmd() { + local cmd="$1" + if ! command -v "$cmd" >/dev/null 2>&1; then + printf "missing required command: %s\n" "$cmd" >&2 + exit 1 + fi +} + +wait_for_port() { + local port="$1" + local label="$2" + for _ in $(seq 1 80); do + if nc -z 127.0.0.1 "$port" >/dev/null 2>&1; then + return 0 + fi + sleep 0.25 + done + printf "%s did not become reachable on 127.0.0.1:%s\n" "$label" "$port" >&2 + exit 1 +} + +subject_token_from_json() { + python3 -c 'import json, sys; print(json.load(sys.stdin)["access_token"])' +} + +assert_contains() { + local haystack="$1" + local needle="$2" + if [[ "$haystack" != *"$needle"* ]]; then + printf "expected output to contain: %s\n" "$needle" >&2 + printf "actual output:\n%s\n" "$haystack" >&2 + exit 1 + fi +} + +require_uint() { + local name="$1" + local value="$2" + if [[ ! "$value" =~ ^[0-9]+$ ]]; then + printf "%s must be a non-negative integer, got: %s\n" "$name" "$value" >&2 + exit 1 + fi +} + +toml_string_escape() { + local value="$1" + value="${value//\\/\\\\}" + value="${value//\"/\\\"}" + printf "%s\n" "$value" +} + +normalize_podman_socket_path() { + local socket_path="$1" + if [[ "$socket_path" == unix://* ]]; then + socket_path="${socket_path#unix://}" + fi + printf "%s\n" "$socket_path" +} + +detect_podman_socket() { + if [[ -n "${PODMAN_SOCKET:-}" ]]; then + normalize_podman_socket_path "$PODMAN_SOCKET" + return + fi + if [[ -n "${XDG_RUNTIME_DIR:-}" && -S "${XDG_RUNTIME_DIR}/podman/podman.sock" ]]; then + printf "%s\n" "${XDG_RUNTIME_DIR}/podman/podman.sock" + return + fi + if [[ -S "/run/user/$(id -u)/podman/podman.sock" ]]; then + printf "%s\n" "/run/user/$(id -u)/podman/podman.sock" + return + fi + if [[ -S /run/podman/podman.sock ]]; then + printf "%s\n" /run/podman/podman.sock + return + fi + if [[ -S /var/run/docker.sock ]]; then + printf "%s\n" /var/run/docker.sock + return + fi + local connection_socket + connection_socket="$( + podman system connection list --format '{{.URI}}' 2>/dev/null | + sed -n 's|^unix://||p' | + while IFS= read -r candidate; do + if [[ -S "$candidate" ]]; then + printf "%s\n" "$candidate" + break + fi + done + )" + if [[ -n "$connection_socket" ]]; then + printf "%s\n" "$connection_socket" + return + fi + return 1 +} + +start_podman_service() { + local socket_path="$1" + + mkdir -p "$(dirname "$socket_path")" + printf "\n$ podman system service --time=0 unix://%s\n" "$socket_path" >&2 + podman system service --time=0 "unix://${socket_path}" & + PODMAN_SERVICE_PID="$!" + + for _ in $(seq 1 80); do + if [[ -S "$socket_path" ]]; then + chmod 0666 "$socket_path" || true + return + fi + if ! kill -0 "$PODMAN_SERVICE_PID" >/dev/null 2>&1; then + wait "$PODMAN_SERVICE_PID" || true + PODMAN_SERVICE_PID="" + break + fi + sleep 0.25 + done + + printf "Podman API service did not create %s; set PODMAN_SOCKET to a running API socket\n" "$socket_path" >&2 + exit 1 +} + +ensure_podman_socket() { + local detected_socket + if detected_socket="$(detect_podman_socket)"; then + PODMAN_SOCKET="$detected_socket" + PODMAN_SOCKET_DEMO_OWNED="0" + return + fi + + local socket_dir="${TMP_DIR}/podman" + local socket_path="${socket_dir}/podman.sock" + + printf "\nNo Podman API socket found; starting a temporary rootless Podman API service.\n" >&2 + start_podman_service "$socket_path" + PODMAN_SOCKET="$socket_path" + PODMAN_SOCKET_DEMO_OWNED="1" +} + +podman_socket_volume() { + local source="$1" + local target="$2" + if [[ "$PODMAN_SOCKET_DEMO_OWNED" == "1" ]]; then + printf "%s:%s:z\n" "$source" "$target" + else + printf "%s:%s\n" "$source" "$target" + fi +} + +cleanup_container() { + podman rm -f "$1" >/dev/null 2>&1 || true +} + +dump_diagnostics() { + set +e + + printf "\n=== diagnostics: openshell sandbox logs ===\n" >&2 + "${OS[@]}" logs "$SANDBOX_NAME" -n 120 --source sandbox >&2 + + printf "\n=== diagnostics: podman containers ===\n" >&2 + podman ps -a --filter "name=openshell-spiffe-demo" >&2 + + for container in \ + "$SPIRE_SERVER_CONTAINER" \ + "$SPIRE_AGENT_CONTAINER" \ + "$SPIRE_OIDC_CONTAINER" \ + "$GATEWAY_CONTAINER" \ + "$TOKEN_ISSUER_CONTAINER" \ + "$ALPHA_CONTAINER" \ + "$BETA_CONTAINER"; do + printf "\n=== diagnostics: %s logs ===\n" "$container" >&2 + podman logs "$container" >&2 + done + + printf "\n=== diagnostics: sandbox container labels ===\n" >&2 + podman ps -a \ + --filter "label=openshell.ai/sandbox-name=${SANDBOX_NAME}" \ + --format '{{.ID}} {{.Names}} {{.Labels}}' >&2 +} + +cleanup() { + if [[ "$KEEP_SANDBOX" != "1" ]]; then + "${OS[@]}" sandbox delete "$SANDBOX_NAME" >/dev/null 2>&1 || true + fi + + if [[ "$KEEP_DEMO" != "1" ]]; then + cleanup_container "$TOKEN_ISSUER_CONTAINER" + cleanup_container "$ALPHA_CONTAINER" + cleanup_container "$BETA_CONTAINER" + if [[ "$START_GATEWAY" == "1" ]]; then + cleanup_container "$GATEWAY_CONTAINER" + fi + if [[ "$START_SPIRE" == "1" ]]; then + cleanup_container "$SPIRE_OIDC_CONTAINER" + cleanup_container "$SPIRE_AGENT_CONTAINER" + cleanup_container "$SPIRE_SERVER_CONTAINER" + fi + if [[ -n "$PODMAN_SERVICE_PID" ]]; then + kill "$PODMAN_SERVICE_PID" >/dev/null 2>&1 || true + wait "$PODMAN_SERVICE_PID" >/dev/null 2>&1 || true + PODMAN_SERVICE_PID="" + fi + rm -rf "$TMP_DIR" + else + printf "\nKeeping demo resources. Temporary files: %s\n" "$TMP_DIR" >&2 + if [[ -n "$PODMAN_SERVICE_PID" ]]; then + printf "Temporary Podman API service PID: %s\n" "$PODMAN_SERVICE_PID" >&2 + fi + fi +} + +on_exit() { + local status="$1" + if [[ "$status" -ne 0 ]]; then + dump_diagnostics || true + fi + cleanup + exit "$status" +} +trap 'on_exit $?' EXIT + +ensure_network() { + if ! podman network exists "$PODMAN_NETWORK" >/dev/null 2>&1; then + run podman network create "$PODMAN_NETWORK" + fi +} + +start_spire() { + local podman_socket="$1" + local spire_state_dir="${TMP_DIR}/spire" + local server_env="${TMP_DIR}/spire-server.env" + local agent_env="${TMP_DIR}/spire-agent.env" + + PODMAN_NETWORK="$PODMAN_NETWORK" \ + TRUST_DOMAIN="$TRUST_DOMAIN" \ + SPIRE_AGENT_PARENT_ID="$SPIRE_AGENT_PARENT_ID" \ + SPIRE_SERVER_IMAGE="$SPIRE_SERVER_IMAGE" \ + SPIRE_OIDC_IMAGE="$SPIRE_OIDC_IMAGE" \ + SPIRE_SERVER_CONTAINER="$SPIRE_SERVER_CONTAINER" \ + SPIRE_OIDC_CONTAINER="$SPIRE_OIDC_CONTAINER" \ + SPIRE_STATE_DIR="$spire_state_dir" \ + SPIRE_ENV_FILE="$server_env" \ + OIDC_PORT="$OIDC_PORT" \ + bash "${SCRIPT_DIR}/spire/start-server-oidc.sh" + + # shellcheck disable=SC1090 + source "$server_env" + + PODMAN_NETWORK="$PODMAN_NETWORK" \ + TRUST_DOMAIN="$TRUST_DOMAIN" \ + SPIRE_AGENT_PARENT_ID="$SPIRE_AGENT_PARENT_ID" \ + SPIRE_AGENT_IMAGE="$SPIRE_AGENT_IMAGE" \ + SPIRE_SERVER_CONTAINER="$SPIRE_SERVER_CONTAINER" \ + SPIRE_AGENT_CONTAINER="$SPIRE_AGENT_CONTAINER" \ + SPIRE_STATE_DIR="$SPIRE_STATE_DIR" \ + SPIRE_ENV_FILE="$agent_env" \ + PODMAN_SOCKET="$podman_socket" \ + PODMAN_SOCKET_DEMO_OWNED="$PODMAN_SOCKET_DEMO_OWNED" \ + bash "${SCRIPT_DIR}/spire/start-agent.sh" + + # shellcheck disable=SC1090 + source "$agent_env" +} + +write_managed_gateway_config() { + local podman_socket_in_container="$1" + local gateway_dir="${TMP_DIR}/gateway" + local jwt_dir="${gateway_dir}/jwt" + local config_path="${gateway_dir}/gateway.toml" + local sandbox_image_line="" + local supervisor_image_line="" + + mkdir -p "$jwt_dir" + if [[ ! -s "${jwt_dir}/signing.pem" ]]; then + openssl genpkey -algorithm ed25519 -out "${jwt_dir}/signing.pem" >/dev/null 2>&1 + openssl pkey -in "${jwt_dir}/signing.pem" -pubout -out "${jwt_dir}/public.pem" >/dev/null 2>&1 + openssl rand -hex 8 >"${jwt_dir}/kid" + fi + if [[ -n "$SANDBOX_IMAGE" ]]; then + sandbox_image_line="default_image = \"$(toml_string_escape "$SANDBOX_IMAGE")\"" + fi + if [[ -n "$SUPERVISOR_IMAGE" ]]; then + supervisor_image_line="supervisor_image = \"$(toml_string_escape "$SUPERVISOR_IMAGE")\"" + fi + + cat >"$config_path" </dev/null 2>&1 || + curl -fsS "http://127.0.0.1:${MANAGED_GATEWAY_HEALTH_PORT}/healthz" >/dev/null 2>&1; then + return + fi + sleep 0.5 + done + + printf "managed OpenShell gateway did not become ready at %s\n" "$GATEWAY_ENDPOINT" >&2 + exit 1 +} + +start_demo_services() { + cleanup_container "$TOKEN_ISSUER_CONTAINER" + cleanup_container "$ALPHA_CONTAINER" + cleanup_container "$BETA_CONTAINER" + + run podman run -d \ + --name "$TOKEN_ISSUER_CONTAINER" \ + --network "$PODMAN_NETWORK" \ + --network-alias token-exchange-issuer \ + --network-alias "$TOKEN_ISSUER_SERVICE_HOST" \ + -p "127.0.0.1:${TOKEN_ISSUER_PORT}:8080" \ + -v "${TOKEN_ISSUER_JS_MOUNT}:/demo/token-issuer.js:ro,z" \ + -e "ACCESS_TOKEN_SECRET=${ACCESS_TOKEN_SECRET}" \ + -e "ACCESS_TOKEN_ISSUER=${TOKEN_ISSUER_BASE_URL}" \ + -e "SPIRE_JWKS_URI=http://spire-oidc:8080/keys" \ + -e "SPIRE_ISSUER=http://spire-oidc:8080" \ + -e "JWT_SVID_AUDIENCE=${TOKEN_ISSUER_BASE_URL}" \ + -e "SUPERVISOR_TRUST_DOMAIN_PREFIX=spiffe://${TRUST_DOMAIN}/openshell/sandbox/" \ + -e "GATEWAY_TRUST_DOMAIN_PREFIX=spiffe://${TRUST_DOMAIN}/openshell/gateway/" \ + -e "DEMO_USER_SUBJECT=demo-user" \ + "$NODE_IMAGE" \ + node /demo/token-issuer.js + + run podman run -d \ + --name "$ALPHA_CONTAINER" \ + --network "$PODMAN_NETWORK" \ + --network-alias alpha-exchange \ + --network-alias alpha-exchange.default.svc.cluster.local \ + -v "${PROTECTED_SERVICE_JS_MOUNT}:/demo/protected-service.js:ro,z" \ + -e SERVICE_NAME=alpha \ + -e EXPECTED_AUDIENCE=alpha \ + -e EXPECTED_SCOPE=alpha \ + -e "ACCESS_TOKEN_SECRET=${ACCESS_TOKEN_SECRET}" \ + -e "ACCESS_TOKEN_ISSUER=${TOKEN_ISSUER_BASE_URL}" \ + "$NODE_IMAGE" \ + node /demo/protected-service.js + + run podman run -d \ + --name "$BETA_CONTAINER" \ + --network "$PODMAN_NETWORK" \ + --network-alias beta-exchange \ + --network-alias beta-exchange.default.svc.cluster.local \ + -v "${PROTECTED_SERVICE_JS_MOUNT}:/demo/protected-service.js:ro,z" \ + -e SERVICE_NAME=beta \ + -e EXPECTED_AUDIENCE=beta \ + -e EXPECTED_SCOPE=beta \ + -e "ACCESS_TOKEN_SECRET=${ACCESS_TOKEN_SECRET}" \ + -e "ACCESS_TOKEN_ISSUER=${TOKEN_ISSUER_BASE_URL}" \ + "$NODE_IMAGE" \ + node /demo/protected-service.js + + wait_for_port "$TOKEN_ISSUER_PORT" "token issuer" +} + +render_provider_profile() { + sed "s|__TOKEN_ISSUER_BASE_URL__|${TOKEN_ISSUER_BASE_URL}|g" \ + "$PROFILE_TEMPLATE" >"$RENDERED_PROFILE" +} + +sandbox_id() { + local output + output="$("${OS[@]}" sandbox get "$SANDBOX_NAME")" + awk '/Id:/ && !found { print $2; found=1 }' <<<"$output" +} + +register_gateway_entry() { + if [[ "${REGISTER_GATEWAY_ENTRY:-1}" != "1" ]]; then + return + fi + if [[ "$START_GATEWAY" == "1" && -z "${GATEWAY_SELECTORS:-}" ]]; then + GATEWAY_SELECTORS="docker:label:openshell.spiffe-demo:gateway" + fi + TRUST_DOMAIN="$TRUST_DOMAIN" \ + GATEWAY_SELECTORS="${GATEWAY_SELECTORS:-}" \ + SPIRE_AGENT_PARENT_ID="$SPIRE_AGENT_PARENT_ID" \ + SPIRE_SERVER_CONTAINER="$SPIRE_SERVER_CONTAINER" \ + bash "${SCRIPT_DIR}/spire/register-gateway.sh" +} + +register_sandbox_entry() { + local id="$1" + TRUST_DOMAIN="$TRUST_DOMAIN" \ + SPIRE_AGENT_PARENT_ID="$SPIRE_AGENT_PARENT_ID" \ + SPIRE_SERVER_CONTAINER="$SPIRE_SERVER_CONTAINER" \ + bash "${SCRIPT_DIR}/spire/register-sandbox.sh" "$id" +} + +sandbox_curl_until() { + local label="$1" + local url="$2" + local expected="$3" + local output="" + + for attempt in $(seq 1 18); do + printf "\n$ openshell sandbox exec %s curl (attempt %s)\n" "$label" "$attempt" + if output=$("${OS[@]}" sandbox exec --name "$SANDBOX_NAME" --no-tty -- curl -sS --max-time 10 "$url" 2>&1); then + printf "%s\n" "$output" + if [[ "$output" == *"$expected"* ]]; then + SANDBOX_CURL_OUTPUT="$output" + return 0 + fi + else + printf "%s\n" "$output" + fi + sleep 2 + done + + printf "timed out waiting for %s to return expected output\n" "$label" >&2 + printf "last output:\n%s\n" "$output" >&2 + exit 1 +} + +require_cmd podman +require_cmd openssl +require_cmd openshell +require_cmd curl +require_cmd python3 +require_cmd nc +require_cmd awk +require_cmd sed + +require_uint PODMAN_STOP_TIMEOUT_SECS "$PODMAN_STOP_TIMEOUT_SECS" +prepare_demo_mounts +ensure_network + +ensure_podman_socket +if [[ "$START_SPIRE" == "1" ]]; then + start_spire "$PODMAN_SOCKET" +elif [[ -z "$SPIRE_AGENT_SOCKET_HOST_PATH" ]]; then + printf "START_SPIRE=0 requires SPIRE_AGENT_SOCKET_HOST_PATH\n" >&2 + exit 1 +fi + +if [[ "$START_GATEWAY" == "1" ]]; then + start_gateway "$PODMAN_SOCKET" +fi + +if [[ -z "${TOKEN_ISSUER_BASE_URL:-}" ]]; then + TOKEN_ISSUER_BASE_URL="http://${TOKEN_ISSUER_SERVICE_HOST}:8080" +fi + +printf "\nUsing OpenShell gateway '%s' at %s\n" "$GATEWAY_NAME" "$GATEWAY_ENDPOINT" +printf "Using Podman network '%s'\n" "$PODMAN_NETWORK" +printf "Using token issuer base URL '%s'\n" "$TOKEN_ISSUER_BASE_URL" +printf "SPIRE agent Workload API socket for gateway and Podman driver: %s\n" "$SPIRE_AGENT_SOCKET_HOST_PATH" +if [[ "$START_GATEWAY" == "1" ]]; then + printf "Managed gateway container: %s\n" "$GATEWAY_CONTAINER" + printf "Managed gateway image: %s\n" "$GATEWAY_IMAGE" + if [[ -n "$SANDBOX_IMAGE" ]]; then + printf "Managed sandbox image: %s\n" "$SANDBOX_IMAGE" + fi + if [[ -n "$SUPERVISOR_IMAGE" ]]; then + printf "Managed supervisor image: %s\n" "$SUPERVISOR_IMAGE" + fi + printf "Managed sandbox image pull policy: %s\n\n" "$SANDBOX_IMAGE_PULL_POLICY" + printf "Managed Podman stop timeout: %s seconds\n\n" "$PODMAN_STOP_TIMEOUT_SECS" +else + printf "\nThe gateway must already be running with:\n" + printf " OPENSHELL_GATEWAY_SPIFFE_WORKLOAD_API_SOCKET=%s\n" "$SPIRE_AGENT_SOCKET_HOST_PATH" + printf " [openshell.drivers.podman].provider_spiffe_workload_api_socket=%s\n" "$SPIRE_AGENT_SOCKET_HOST_PATH" + printf " [openshell.drivers.podman].network_name=%s\n\n" "$PODMAN_NETWORK" + printf "The gateway process must be able to resolve and reach %s.\n" "$TOKEN_ISSUER_SERVICE_HOST" + printf "For a host-running gateway, add local DNS/hosts routing to the published issuer port %s if needed.\n\n" "$TOKEN_ISSUER_PORT" +fi + +register_gateway_entry +start_demo_services +render_provider_profile + +SUBJECT_TOKEN="$(curl -fsS "http://127.0.0.1:${TOKEN_ISSUER_PORT}/demo-subject-token" | subject_token_from_json)" + +"${OS[@]}" sandbox delete "$SANDBOX_NAME" >/dev/null 2>&1 || true +"${OS[@]}" provider delete "$PROVIDER_NAME" >/dev/null 2>&1 || true +"${OS[@]}" provider profile delete "$PROFILE_ID" >/dev/null 2>&1 || true + +run "${OS[@]}" settings set --global --key providers_v2_enabled --value true --yes +run "${OS[@]}" provider profile lint -f "$RENDERED_PROFILE" +run "${OS[@]}" provider profile import -f "$RENDERED_PROFILE" +run "${OS[@]}" provider create --name "$PROVIDER_NAME" --type "$PROFILE_ID" --credential "subject_token=${SUBJECT_TOKEN}" +run "${OS[@]}" sandbox create --name "$SANDBOX_NAME" --provider "$PROVIDER_NAME" --keep --no-tty -- echo "sandbox ready" + +SANDBOX_ID="$(sandbox_id)" +if [[ -z "$SANDBOX_ID" ]]; then + printf "could not determine sandbox ID from openshell sandbox get\n" >&2 + exit 1 +fi +printf "\nSandbox ID: %s\n" "$SANDBOX_ID" + +register_sandbox_entry "$SANDBOX_ID" + +sandbox_curl_until "alpha" "http://alpha-exchange:8080/" "alpha called with path /:" +ALPHA_OUTPUT="$SANDBOX_CURL_OUTPUT" +assert_contains "$ALPHA_OUTPUT" "sub: demo-user" +assert_contains "$ALPHA_OUTPUT" "aud: alpha, account" +assert_contains "$ALPHA_OUTPUT" "scope: alpha profile email" +assert_contains "$ALPHA_OUTPUT" "azp: spiffe://${TRUST_DOMAIN}/openshell/sandbox/" +assert_contains "$ALPHA_OUTPUT" "client_id: spiffe://${TRUST_DOMAIN}/openshell/sandbox/" + +sandbox_curl_until "beta" "http://beta-exchange:8080/" "beta called with path /:" +BETA_OUTPUT="$SANDBOX_CURL_OUTPUT" +assert_contains "$BETA_OUTPUT" "sub: demo-user" +assert_contains "$BETA_OUTPUT" "aud: beta, account" +assert_contains "$BETA_OUTPUT" "scope: beta profile email" +assert_contains "$BETA_OUTPUT" "azp: spiffe://${TRUST_DOMAIN}/openshell/sandbox/" +assert_contains "$BETA_OUTPUT" "client_id: spiffe://${TRUST_DOMAIN}/openshell/sandbox/" + +printf "\nPodman SPIFFE token exchange demo succeeded.\n" diff --git a/examples/spiffe-token-exchange-demo/podman/provider-profile.yaml b/examples/spiffe-token-exchange-demo/podman/provider-profile.yaml new file mode 100644 index 0000000000..c98a19555b --- /dev/null +++ b/examples/spiffe-token-exchange-demo/podman/provider-profile.yaml @@ -0,0 +1,58 @@ +# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +# Template rendered by podman/demo.sh. +# __TOKEN_ISSUER_BASE_URL__ must be reachable from both the gateway process and +# the sandbox container. For local HTTP, OpenShell profile validation permits +# loopback hosts and Kubernetes-style service DNS names. + +id: spiffe-token-exchange-demo-podman +display_name: SPIFFE token exchange demo (Podman) +description: Dynamic token exchange for local Podman alpha/beta demo services using stored user subject tokens and SPIFFE JWT-SVID authentication +category: other +credentials: + - name: subject_token + description: Demo user subject token stored on the provider for token exchange + required: true + - name: access_token + description: Access token obtained via RFC 8693 token exchange + required: false + auth_style: bearer + header_name: Authorization + token_grant: + grant_type: token_exchange + token_endpoint: __TOKEN_ISSUER_BASE_URL__/token + audience: demo-default + jwt_svid_audience: __TOKEN_ISSUER_BASE_URL__ + client_assertion_type: urn:ietf:params:oauth:client-assertion-type:jwt-spiffe + scopes: [demo] + cache_ttl_seconds: 60 + subject_token: + source: provider_credential + credential: subject_token + subject_token_type: urn:ietf:params:oauth:token-type:access_token + audience_overrides: + - host: alpha-exchange + port: 8080 + audience: alpha + scopes: [alpha] + - host: beta-exchange + port: 8080 + audience: beta + scopes: [beta] +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: + - /usr/bin/curl + - /usr/local/bin/curl diff --git a/examples/spiffe-token-exchange-demo/podman/spire/agent.conf b/examples/spiffe-token-exchange-demo/podman/spire/agent.conf new file mode 100644 index 0000000000..0dbf8daba7 --- /dev/null +++ b/examples/spiffe-token-exchange-demo/podman/spire/agent.conf @@ -0,0 +1,37 @@ +# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +agent { + data_dir = "/run/spire/agent/data" + log_level = "INFO" + server_address = "spire-server" + server_port = "8081" + socket_path = "/run/spire/agent/sockets/agent.sock" + trust_domain = "openshell.local" + insecure_bootstrap = true +} + +plugins { + NodeAttestor "join_token" { + plugin_data {} + } + + KeyManager "disk" { + plugin_data { + directory = "/run/spire/agent/data" + } + } + + WorkloadAttestor "unix" { + plugin_data {} + } + + # Podman exposes a Docker-compatible API. The demo mounts the Podman socket + # here so SPIRE can use Docker-style selectors for the sandbox container + # when the local SPIRE version supports them. + WorkloadAttestor "docker" { + plugin_data { + docker_socket_path = "unix:///run/podman/podman.sock" + } + } +} diff --git a/examples/spiffe-token-exchange-demo/podman/spire/common.sh b/examples/spiffe-token-exchange-demo/podman/spire/common.sh new file mode 100755 index 0000000000..24aae8dc93 --- /dev/null +++ b/examples/spiffe-token-exchange-demo/podman/spire/common.sh @@ -0,0 +1,157 @@ +#!/usr/bin/env bash + +# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +set -euo pipefail + +SPIRE_SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +PODMAN_NETWORK="${PODMAN_NETWORK:-openshell}" +TRUST_DOMAIN="${TRUST_DOMAIN:-openshell.local}" +SPIRE_AGENT_PARENT_ID="${SPIRE_AGENT_PARENT_ID:-spiffe://${TRUST_DOMAIN}/openshell/spire-agent/demo}" +SPIRE_SERVER_IMAGE="${SPIRE_SERVER_IMAGE:-ghcr.io/spiffe/spire-server:1.12.4}" +SPIRE_AGENT_IMAGE="${SPIRE_AGENT_IMAGE:-ghcr.io/spiffe/spire-agent:1.12.4}" +SPIRE_OIDC_IMAGE="${SPIRE_OIDC_IMAGE:-ghcr.io/spiffe/oidc-discovery-provider:1.12.4}" +SPIRE_SERVER_CONTAINER="${SPIRE_SERVER_CONTAINER:-openshell-spiffe-demo-spire-server}" +SPIRE_AGENT_CONTAINER="${SPIRE_AGENT_CONTAINER:-openshell-spiffe-demo-spire-agent}" +SPIRE_OIDC_CONTAINER="${SPIRE_OIDC_CONTAINER:-openshell-spiffe-demo-spire-oidc}" +SPIRE_SERVER_BIN="${SPIRE_SERVER_BIN:-/opt/spire/bin/spire-server}" +OIDC_PORT="${OIDC_PORT:-18081}" +SPIRE_STATE_DIR="${SPIRE_STATE_DIR:-}" +SPIRE_ENV_FILE="${SPIRE_ENV_FILE:-}" +CLEANUP_EXISTING="${CLEANUP_EXISTING:-1}" +PODMAN_SOCKET_DEMO_OWNED="${PODMAN_SOCKET_DEMO_OWNED:-0}" + +run() { + printf "\n$ %s\n" "$*" >&2 + "$@" +} + +require_cmd() { + local cmd="$1" + if ! command -v "$cmd" >/dev/null 2>&1; then + printf "missing required command: %s\n" "$cmd" >&2 + exit 1 + fi +} + +cleanup_container() { + podman rm -f "$1" >/dev/null 2>&1 || true +} + +ensure_network() { + if ! podman network exists "$PODMAN_NETWORK" >/dev/null 2>&1; then + run podman network create "$PODMAN_NETWORK" + fi +} + +normalize_podman_socket_path() { + local socket_path="$1" + if [[ "$socket_path" == unix://* ]]; then + socket_path="${socket_path#unix://}" + fi + printf "%s\n" "$socket_path" +} + +detect_podman_socket() { + if [[ -n "${PODMAN_SOCKET:-}" ]]; then + normalize_podman_socket_path "$PODMAN_SOCKET" + return + fi + if [[ -n "${XDG_RUNTIME_DIR:-}" && -S "${XDG_RUNTIME_DIR}/podman/podman.sock" ]]; then + printf "%s\n" "${XDG_RUNTIME_DIR}/podman/podman.sock" + return + fi + if [[ -S "/run/user/$(id -u)/podman/podman.sock" ]]; then + printf "%s\n" "/run/user/$(id -u)/podman/podman.sock" + return + fi + if [[ -S /run/podman/podman.sock ]]; then + printf "%s\n" /run/podman/podman.sock + return + fi + if [[ -S /var/run/docker.sock ]]; then + printf "%s\n" /var/run/docker.sock + return + fi + local connection_socket + connection_socket="$( + podman system connection list --format '{{.URI}}' 2>/dev/null | + sed -n 's|^unix://||p' | + while IFS= read -r candidate; do + if [[ -S "$candidate" ]]; then + printf "%s\n" "$candidate" + break + fi + done + )" + if [[ -n "$connection_socket" ]]; then + printf "%s\n" "$connection_socket" + return + fi + return 1 +} + +podman_socket_volume() { + local source="$1" + local target="$2" + if [[ "$PODMAN_SOCKET_DEMO_OWNED" == "1" ]]; then + printf "%s:%s:z\n" "$source" "$target" + else + printf "%s:%s\n" "$source" "$target" + fi +} + +quote_env_value() { + printf "%q" "$1" +} + +write_env_line() { + local name="$1" + local value="$2" + if [[ -n "$SPIRE_ENV_FILE" ]]; then + printf "%s=%s\n" "$name" "$(quote_env_value "$value")" >>"$SPIRE_ENV_FILE" + fi +} + +reset_env_file() { + if [[ -n "$SPIRE_ENV_FILE" ]]; then + mkdir -p "$(dirname "$SPIRE_ENV_FILE")" + : >"$SPIRE_ENV_FILE" + fi +} + +copy_config() { + local source="$1" + local dest="$2" + mkdir -p "$(dirname "$dest")" + cp "$source" "$dest" + chmod 0644 "$dest" +} + +wait_for_socket() { + local path="$1" + local label="$2" + for _ in $(seq 1 80); do + if [[ -S "$path" ]]; then + return + fi + sleep 0.25 + done + printf "%s was not created at %s\n" "$label" "$path" >&2 + return 1 +} + +wait_for_http() { + local url="$1" + local label="$2" + for _ in $(seq 1 80); do + if curl -fsS "$url" >/dev/null 2>&1; then + return + fi + sleep 0.25 + done + printf "%s did not become ready at %s\n" "$label" "$url" >&2 + return 1 +} diff --git a/examples/spiffe-token-exchange-demo/podman/spire/oidc-discovery-provider.conf b/examples/spiffe-token-exchange-demo/podman/spire/oidc-discovery-provider.conf new file mode 100644 index 0000000000..9a1093cba9 --- /dev/null +++ b/examples/spiffe-token-exchange-demo/podman/spire/oidc-discovery-provider.conf @@ -0,0 +1,19 @@ +# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +log_level = "INFO" +domains = ["spire-oidc", "127.0.0.1", "localhost"] +allow_insecure_scheme = true +insecure_addr = "0.0.0.0:8080" +jwt_issuer = "http://spire-oidc:8080" +jwks_uri = "http://spire-oidc:8080/keys" + +server_api { + address = "unix:///run/spire/server/private/api.sock" +} + +health_checks { + bind_port = "8008" + ready_path = "/ready" + live_path = "/live" +} diff --git a/examples/spiffe-token-exchange-demo/podman/spire/register-gateway.sh b/examples/spiffe-token-exchange-demo/podman/spire/register-gateway.sh new file mode 100755 index 0000000000..88cfca4630 --- /dev/null +++ b/examples/spiffe-token-exchange-demo/podman/spire/register-gateway.sh @@ -0,0 +1,40 @@ +#!/usr/bin/env bash + +# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +set -euo pipefail + +TRUST_DOMAIN="${TRUST_DOMAIN:-openshell.local}" +GATEWAY_SPIFFE_ID="${GATEWAY_SPIFFE_ID:-spiffe://${TRUST_DOMAIN}/openshell/gateway/demo}" +SPIRE_AGENT_PARENT_ID="${SPIRE_AGENT_PARENT_ID:-spiffe://${TRUST_DOMAIN}/openshell/spire-agent/demo}" +SPIRE_SERVER_SOCKET="${SPIRE_SERVER_SOCKET:-/run/spire/server/private/api.sock}" +SPIRE_SERVER_CONTAINER="${SPIRE_SERVER_CONTAINER:-openshell-spiffe-demo-spire-server}" +SPIRE_SERVER_BIN="${SPIRE_SERVER_BIN:-/opt/spire/bin/spire-server}" + +if [[ -n "${GATEWAY_SELECTORS:-}" ]]; then + read -r -a selectors <<<"$GATEWAY_SELECTORS" +else + gateway_path="${GATEWAY_WORKLOAD_PATH:-$(command -v openshell-server || true)}" + if [[ -n "$gateway_path" ]]; then + selectors=("unix:uid:$(id -u)" "unix:path:${gateway_path}") + else + printf "GATEWAY_WORKLOAD_PATH is unset and openshell-server is not on PATH; falling back to unix:uid only\n" >&2 + selectors=("unix:uid:$(id -u)") + fi +fi + +args=( + entry create + -socketPath "$SPIRE_SERVER_SOCKET" + -parentID "$SPIRE_AGENT_PARENT_ID" + -spiffeID "$GATEWAY_SPIFFE_ID" + -jwtSVIDTTL 300 +) + +for selector in "${selectors[@]}"; do + args+=(-selector "$selector") +done + +printf "Registering gateway SPIFFE entry: %s\n" "$GATEWAY_SPIFFE_ID" >&2 +podman exec "$SPIRE_SERVER_CONTAINER" "$SPIRE_SERVER_BIN" "${args[@]}" diff --git a/examples/spiffe-token-exchange-demo/podman/spire/register-sandbox.sh b/examples/spiffe-token-exchange-demo/podman/spire/register-sandbox.sh new file mode 100755 index 0000000000..5213421cfe --- /dev/null +++ b/examples/spiffe-token-exchange-demo/podman/spire/register-sandbox.sh @@ -0,0 +1,43 @@ +#!/usr/bin/env bash + +# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +set -euo pipefail + +if [[ $# -ne 1 ]]; then + printf "usage: %s \n" "$0" >&2 + exit 2 +fi + +SANDBOX_ID="$1" +TRUST_DOMAIN="${TRUST_DOMAIN:-openshell.local}" +SANDBOX_SPIFFE_ID="${SANDBOX_SPIFFE_ID:-spiffe://${TRUST_DOMAIN}/openshell/sandbox/${SANDBOX_ID}}" +SPIRE_AGENT_PARENT_ID="${SPIRE_AGENT_PARENT_ID:-spiffe://${TRUST_DOMAIN}/openshell/spire-agent/demo}" +SPIRE_SERVER_SOCKET="${SPIRE_SERVER_SOCKET:-/run/spire/server/private/api.sock}" +SPIRE_SERVER_CONTAINER="${SPIRE_SERVER_CONTAINER:-openshell-spiffe-demo-spire-server}" +SPIRE_SERVER_BIN="${SPIRE_SERVER_BIN:-/opt/spire/bin/spire-server}" + +if [[ -n "${SANDBOX_SELECTORS:-}" ]]; then + read -r -a selectors <<<"$SANDBOX_SELECTORS" +else + selectors=( + "docker:label:openshell.managed:true" + "docker:label:openshell.ai/sandbox-id:${SANDBOX_ID}" + ) +fi + +args=( + entry create + -socketPath "$SPIRE_SERVER_SOCKET" + -parentID "$SPIRE_AGENT_PARENT_ID" + -spiffeID "$SANDBOX_SPIFFE_ID" + -jwtSVIDTTL 300 +) + +for selector in "${selectors[@]}"; do + args+=(-selector "$selector") +done + +printf "Registering sandbox SPIFFE entry: %s\n" "$SANDBOX_SPIFFE_ID" >&2 +podman exec "$SPIRE_SERVER_CONTAINER" "$SPIRE_SERVER_BIN" "${args[@]}" diff --git a/examples/spiffe-token-exchange-demo/podman/spire/server.conf b/examples/spiffe-token-exchange-demo/podman/spire/server.conf new file mode 100644 index 0000000000..59c7f9d154 --- /dev/null +++ b/examples/spiffe-token-exchange-demo/podman/spire/server.conf @@ -0,0 +1,39 @@ +# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +server { + bind_address = "0.0.0.0" + bind_port = "8081" + socket_path = "/run/spire/server/private/api.sock" + trust_domain = "openshell.local" + data_dir = "/run/spire/server/data" + log_level = "INFO" + jwt_issuer = "http://spire-oidc:8080" + default_x509_svid_ttl = "1h" + default_jwt_svid_ttl = "5m" + + ca_subject { + country = ["US"] + organization = ["OpenShell Demo"] + common_name = "OpenShell Demo SPIRE" + } +} + +plugins { + DataStore "sql" { + plugin_data { + database_type = "sqlite3" + connection_string = "/run/spire/server/data/datastore.sqlite3" + } + } + + NodeAttestor "join_token" { + plugin_data {} + } + + KeyManager "disk" { + plugin_data { + keys_path = "/run/spire/server/data/keys.json" + } + } +} diff --git a/examples/spiffe-token-exchange-demo/podman/spire/start-agent.sh b/examples/spiffe-token-exchange-demo/podman/spire/start-agent.sh new file mode 100755 index 0000000000..a0fab6eddd --- /dev/null +++ b/examples/spiffe-token-exchange-demo/podman/spire/start-agent.sh @@ -0,0 +1,78 @@ +#!/usr/bin/env bash + +# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +# shellcheck source=common.sh +source "${SCRIPT_DIR}/common.sh" + +require_cmd podman +require_cmd awk +require_cmd sed + +if [[ -z "$SPIRE_STATE_DIR" ]]; then + SPIRE_STATE_DIR="$(mktemp -d)" +fi + +if ! PODMAN_SOCKET="$(detect_podman_socket)"; then + printf "could not find a Podman API socket; set PODMAN_SOCKET\n" >&2 + exit 1 +fi + +reset_env_file +ensure_network + +agent_dir="${SPIRE_STATE_DIR}/agent" +mount_dir="${SPIRE_STATE_DIR}/mounts" +agent_conf_mount="${mount_dir}/agent.conf" +SPIRE_AGENT_SOCKET_HOST_PATH="${SPIRE_AGENT_SOCKET_HOST_PATH:-${agent_dir}/sockets/agent.sock}" + +mkdir -p "${agent_dir}/data" "${agent_dir}/sockets" "$mount_dir" +chmod 0777 "$SPIRE_STATE_DIR" "$agent_dir" "${agent_dir}/data" "${agent_dir}/sockets" +copy_config "${SPIRE_SCRIPT_DIR}/agent.conf" "$agent_conf_mount" + +if [[ "$CLEANUP_EXISTING" == "1" ]]; then + cleanup_container "$SPIRE_AGENT_CONTAINER" +fi + +join_token="$( + podman exec "$SPIRE_SERVER_CONTAINER" "$SPIRE_SERVER_BIN" token generate \ + -socketPath /run/spire/server/private/api.sock \ + -spiffeID "$SPIRE_AGENT_PARENT_ID" | + awk '/Token:/ { print $2; exit }' +)" +if [[ -z "$join_token" ]]; then + printf "failed to generate SPIRE agent join token\n" >&2 + exit 1 +fi + +run podman run -d \ + --name "$SPIRE_AGENT_CONTAINER" \ + --network "$PODMAN_NETWORK" \ + --pid=host \ + --security-opt label=disable \ + -v "${agent_conf_mount}:/run/spire/config/agent.conf:ro,z" \ + -v "${agent_dir}:/run/spire/agent:z" \ + -v "$(podman_socket_volume "$PODMAN_SOCKET" /run/podman/podman.sock)" \ + "$SPIRE_AGENT_IMAGE" \ + -config /run/spire/config/agent.conf -joinToken "$join_token" + +if ! wait_for_socket "$SPIRE_AGENT_SOCKET_HOST_PATH" "SPIRE agent Workload API socket"; then + podman logs "$SPIRE_AGENT_CONTAINER" >&2 || true + exit 1 +fi + +write_env_line SPIRE_STATE_DIR "$SPIRE_STATE_DIR" +write_env_line SPIRE_AGENT_DIR "$agent_dir" +write_env_line SPIRE_AGENT_SOCKET_HOST_PATH "$SPIRE_AGENT_SOCKET_HOST_PATH" +write_env_line PODMAN_SOCKET "$PODMAN_SOCKET" + +printf "SPIRE agent container: %s\n" "$SPIRE_AGENT_CONTAINER" +printf "SPIRE agent parent ID: %s\n" "$SPIRE_AGENT_PARENT_ID" +printf "SPIRE agent Workload API socket: %s\n" "$SPIRE_AGENT_SOCKET_HOST_PATH" +if [[ -n "$SPIRE_ENV_FILE" ]]; then + printf "Wrote environment file: %s\n" "$SPIRE_ENV_FILE" +fi diff --git a/examples/spiffe-token-exchange-demo/podman/spire/start-server-oidc.sh b/examples/spiffe-token-exchange-demo/podman/spire/start-server-oidc.sh new file mode 100755 index 0000000000..321ff9a2ea --- /dev/null +++ b/examples/spiffe-token-exchange-demo/podman/spire/start-server-oidc.sh @@ -0,0 +1,80 @@ +#!/usr/bin/env bash + +# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +# shellcheck source=common.sh +source "${SCRIPT_DIR}/common.sh" + +require_cmd podman +require_cmd curl + +if [[ -z "$SPIRE_STATE_DIR" ]]; then + SPIRE_STATE_DIR="$(mktemp -d)" +fi + +reset_env_file +ensure_network + +server_dir="${SPIRE_STATE_DIR}/server" +mount_dir="${SPIRE_STATE_DIR}/mounts" +server_conf_mount="${mount_dir}/server.conf" +oidc_conf_mount="${mount_dir}/oidc-discovery-provider.conf" +server_socket_host_path="${server_dir}/private/api.sock" + +mkdir -p "${server_dir}/data" "${server_dir}/private" "$mount_dir" +chmod 0777 "$SPIRE_STATE_DIR" "$server_dir" "${server_dir}/data" "${server_dir}/private" +copy_config "${SPIRE_SCRIPT_DIR}/server.conf" "$server_conf_mount" +copy_config "${SPIRE_SCRIPT_DIR}/oidc-discovery-provider.conf" "$oidc_conf_mount" + +if [[ "$CLEANUP_EXISTING" == "1" ]]; then + cleanup_container "$SPIRE_OIDC_CONTAINER" + cleanup_container "$SPIRE_SERVER_CONTAINER" +fi + +printf "Starting SPIRE server container: %s\n" "$SPIRE_SERVER_CONTAINER" >&2 +run podman run -d \ + --name "$SPIRE_SERVER_CONTAINER" \ + --network "$PODMAN_NETWORK" \ + --network-alias spire-server \ + -v "${server_conf_mount}:/run/spire/config/server.conf:ro,z" \ + -v "${server_dir}:/run/spire/server:z" \ + "$SPIRE_SERVER_IMAGE" \ + -config /run/spire/config/server.conf + +if ! wait_for_socket "$server_socket_host_path" "SPIRE server API socket"; then + podman logs "$SPIRE_SERVER_CONTAINER" >&2 || true + exit 1 +fi + +printf "Starting SPIRE OIDC discovery provider container: %s\n" "$SPIRE_OIDC_CONTAINER" >&2 +run podman run -d \ + --name "$SPIRE_OIDC_CONTAINER" \ + --network "$PODMAN_NETWORK" \ + --network-alias spire-oidc \ + -p "127.0.0.1:${OIDC_PORT}:8080" \ + -v "${oidc_conf_mount}:/run/spire/config/oidc-discovery-provider.conf:ro,z" \ + -v "${server_dir}/private:/run/spire/server/private:z" \ + "$SPIRE_OIDC_IMAGE" \ + -config /run/spire/config/oidc-discovery-provider.conf + +if ! wait_for_http "http://127.0.0.1:${OIDC_PORT}/keys" "SPIRE OIDC discovery provider"; then + podman logs "$SPIRE_OIDC_CONTAINER" >&2 || true + exit 1 +fi + +write_env_line SPIRE_STATE_DIR "$SPIRE_STATE_DIR" +write_env_line SPIRE_SERVER_DIR "$server_dir" +write_env_line SPIRE_SERVER_SOCKET_HOST_PATH "$server_socket_host_path" +write_env_line SPIRE_OIDC_KEYS_URL "http://127.0.0.1:${OIDC_PORT}/keys" + +printf "SPIRE server container: %s\n" "$SPIRE_SERVER_CONTAINER" +printf "SPIRE server API socket: %s\n" "$server_socket_host_path" +printf "SPIRE OIDC discovery provider container: %s\n" "$SPIRE_OIDC_CONTAINER" +printf "SPIRE OIDC JWKS URL: http://127.0.0.1:%s/keys\n" "$OIDC_PORT" +if [[ -n "$SPIRE_ENV_FILE" ]]; then + printf "Wrote environment file: %s\n" "$SPIRE_ENV_FILE" +fi diff --git a/examples/spiffe-token-exchange-demo/podman/start-gateway.sh b/examples/spiffe-token-exchange-demo/podman/start-gateway.sh new file mode 100755 index 0000000000..fea89a117a --- /dev/null +++ b/examples/spiffe-token-exchange-demo/podman/start-gateway.sh @@ -0,0 +1,233 @@ +#!/usr/bin/env bash + +# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +# shellcheck source=spire/common.sh +source "${SCRIPT_DIR}/spire/common.sh" + +require_cmd podman +require_cmd curl +require_cmd openssl + +if [[ -n "${SPIRE_AGENT_ENV_FILE:-}" ]]; then + # shellcheck disable=SC1090 + source "$SPIRE_AGENT_ENV_FILE" +fi + +GATEWAY_CONTAINER="${GATEWAY_CONTAINER:-openshell-spiffe-demo-gateway}" +GATEWAY_IMAGE="${GATEWAY_IMAGE:-ghcr.io/nvidia/openshell/gateway:latest}" +GATEWAY_ID="${GATEWAY_ID:-podman-spiffe-demo}" +GATEWAY_PORT="${GATEWAY_PORT:-8888}" +GATEWAY_HEALTH_PORT="${GATEWAY_HEALTH_PORT:-8889}" +GATEWAY_STATE_DIR="${GATEWAY_STATE_DIR:-}" +GATEWAY_ENV_FILE="${GATEWAY_ENV_FILE:-}" +SANDBOX_IMAGE="${SANDBOX_IMAGE:-}" +SUPERVISOR_IMAGE="${SUPERVISOR_IMAGE:-}" +SANDBOX_IMAGE_PULL_POLICY="${SANDBOX_IMAGE_PULL_POLICY:-missing}" +PODMAN_STOP_TIMEOUT_SECS="${PODMAN_STOP_TIMEOUT_SECS:-3}" +GATEWAY_OIDC_ISSUER="${GATEWAY_OIDC_ISSUER:-}" +GATEWAY_OIDC_AUDIENCE="${GATEWAY_OIDC_AUDIENCE:-openshell-cli}" +GATEWAY_OIDC_JWKS_TTL_SECS="${GATEWAY_OIDC_JWKS_TTL_SECS:-3600}" +GATEWAY_OIDC_ROLES_CLAIM="${GATEWAY_OIDC_ROLES_CLAIM:-realm_access.roles}" +GATEWAY_OIDC_ADMIN_ROLE="${GATEWAY_OIDC_ADMIN_ROLE:-openshell-admin}" +GATEWAY_OIDC_USER_ROLE="${GATEWAY_OIDC_USER_ROLE:-openshell-user}" +GATEWAY_OIDC_SCOPES_CLAIM="${GATEWAY_OIDC_SCOPES_CLAIM:-}" +GATEWAY_OIDC_CLIENT_ID="${GATEWAY_OIDC_CLIENT_ID:-openshell-cli}" +GATEWAY_OIDC_LOGIN_SCOPES="${GATEWAY_OIDC_LOGIN_SCOPES:-}" +if [[ -z "${GATEWAY_ALLOW_UNAUTHENTICATED_USERS:-}" ]]; then + if [[ -n "$GATEWAY_OIDC_ISSUER" ]]; then + GATEWAY_ALLOW_UNAUTHENTICATED_USERS="false" + else + GATEWAY_ALLOW_UNAUTHENTICATED_USERS="true" + fi +fi + +if [[ -z "${SPIRE_AGENT_SOCKET_HOST_PATH:-}" ]]; then + printf "SPIRE_AGENT_SOCKET_HOST_PATH is required; source start-agent.sh's env file or set SPIRE_AGENT_ENV_FILE\n" >&2 + exit 1 +fi +if [[ ! -S "$SPIRE_AGENT_SOCKET_HOST_PATH" ]]; then + printf "SPIRE agent Workload API socket does not exist: %s\n" "$SPIRE_AGENT_SOCKET_HOST_PATH" >&2 + exit 1 +fi +if ! PODMAN_SOCKET="$(detect_podman_socket)"; then + printf "could not find a Podman API socket; set PODMAN_SOCKET\n" >&2 + exit 1 +fi +if [[ ! "$PODMAN_STOP_TIMEOUT_SECS" =~ ^[0-9]+$ ]]; then + printf "PODMAN_STOP_TIMEOUT_SECS must be a non-negative integer, got: %s\n" "$PODMAN_STOP_TIMEOUT_SECS" >&2 + exit 1 +fi +if [[ ! "$GATEWAY_OIDC_JWKS_TTL_SECS" =~ ^[0-9]+$ ]]; then + printf "GATEWAY_OIDC_JWKS_TTL_SECS must be a non-negative integer, got: %s\n" "$GATEWAY_OIDC_JWKS_TTL_SECS" >&2 + exit 1 +fi +if [[ "$GATEWAY_ALLOW_UNAUTHENTICATED_USERS" != "true" && "$GATEWAY_ALLOW_UNAUTHENTICATED_USERS" != "false" ]]; then + printf "GATEWAY_ALLOW_UNAUTHENTICATED_USERS must be true or false, got: %s\n" "$GATEWAY_ALLOW_UNAUTHENTICATED_USERS" >&2 + exit 1 +fi + +if [[ -z "$GATEWAY_STATE_DIR" ]]; then + GATEWAY_STATE_DIR="$(mktemp -d)" +fi + +toml_string_escape() { + local value="$1" + value="${value//\\/\\\\}" + value="${value//\"/\\\"}" + printf "%s\n" "$value" +} + +write_gateway_env_line() { + local name="$1" + local value="$2" + if [[ -n "$GATEWAY_ENV_FILE" ]]; then + printf "%s=%s\n" "$name" "$(quote_env_value "$value")" >>"$GATEWAY_ENV_FILE" + fi +} + +reset_gateway_env_file() { + if [[ -n "$GATEWAY_ENV_FILE" ]]; then + mkdir -p "$(dirname "$GATEWAY_ENV_FILE")" + : >"$GATEWAY_ENV_FILE" + fi +} + +write_gateway_config() { + local podman_socket_in_container="$1" + local jwt_dir="${GATEWAY_STATE_DIR}/jwt" + local config_path="${GATEWAY_STATE_DIR}/gateway.toml" + local sandbox_image_line="" + local supervisor_image_line="" + local oidc_block="" + + mkdir -p "$jwt_dir" + if [[ ! -s "${jwt_dir}/signing.pem" ]]; then + openssl genpkey -algorithm ed25519 -out "${jwt_dir}/signing.pem" >/dev/null 2>&1 + openssl pkey -in "${jwt_dir}/signing.pem" -pubout -out "${jwt_dir}/public.pem" >/dev/null 2>&1 + openssl rand -hex 8 >"${jwt_dir}/kid" + fi + if [[ -n "$SANDBOX_IMAGE" ]]; then + sandbox_image_line="default_image = \"$(toml_string_escape "$SANDBOX_IMAGE")\"" + fi + if [[ -n "$SUPERVISOR_IMAGE" ]]; then + supervisor_image_line="supervisor_image = \"$(toml_string_escape "$SUPERVISOR_IMAGE")\"" + fi + if [[ -n "$GATEWAY_OIDC_ISSUER" ]]; then + oidc_block=" +[openshell.gateway.oidc] +issuer = \"$(toml_string_escape "$GATEWAY_OIDC_ISSUER")\" +audience = \"$(toml_string_escape "$GATEWAY_OIDC_AUDIENCE")\" +jwks_ttl_secs = ${GATEWAY_OIDC_JWKS_TTL_SECS} +roles_claim = \"$(toml_string_escape "$GATEWAY_OIDC_ROLES_CLAIM")\" +admin_role = \"$(toml_string_escape "$GATEWAY_OIDC_ADMIN_ROLE")\" +user_role = \"$(toml_string_escape "$GATEWAY_OIDC_USER_ROLE")\" +scopes_claim = \"$(toml_string_escape "$GATEWAY_OIDC_SCOPES_CLAIM")\" +" + fi + + cat >"$config_path" <&2 || true + exit 1 +fi + +write_gateway_env_line GATEWAY_STATE_DIR "$GATEWAY_STATE_DIR" +write_gateway_env_line GATEWAY_CONFIG "$gateway_config" +write_gateway_env_line GATEWAY_CONTAINER "$GATEWAY_CONTAINER" +write_gateway_env_line GATEWAY_ENDPOINT "http://127.0.0.1:${GATEWAY_PORT}" +write_gateway_env_line GATEWAY_HEALTH_ENDPOINT "http://127.0.0.1:${GATEWAY_HEALTH_PORT}" +write_gateway_env_line GATEWAY_OIDC_ISSUER "$GATEWAY_OIDC_ISSUER" +write_gateway_env_line GATEWAY_OIDC_AUDIENCE "$GATEWAY_OIDC_AUDIENCE" +write_gateway_env_line GATEWAY_OIDC_CLIENT_ID "$GATEWAY_OIDC_CLIENT_ID" +write_gateway_env_line GATEWAY_OIDC_LOGIN_SCOPES "$GATEWAY_OIDC_LOGIN_SCOPES" + +printf "OpenShell gateway container: %s\n" "$GATEWAY_CONTAINER" +printf "OpenShell gateway endpoint: http://127.0.0.1:%s\n" "$GATEWAY_PORT" +printf "OpenShell gateway health endpoint: http://127.0.0.1:%s\n" "$GATEWAY_HEALTH_PORT" +printf "OpenShell gateway config: %s\n" "$gateway_config" +printf "SPIRE agent Workload API socket: %s\n" "$SPIRE_AGENT_SOCKET_HOST_PATH" +if [[ -n "$GATEWAY_OIDC_ISSUER" ]]; then + printf "OpenShell gateway OIDC issuer: %s\n" "$GATEWAY_OIDC_ISSUER" + printf "Register/login with:\n" + printf " openshell gateway add http://127.0.0.1:%s --name %s --oidc-issuer %s --oidc-client-id %s --oidc-audience %s" \ + "$GATEWAY_PORT" "$GATEWAY_ID" "$GATEWAY_OIDC_ISSUER" "$GATEWAY_OIDC_CLIENT_ID" "$GATEWAY_OIDC_AUDIENCE" + if [[ -n "$GATEWAY_OIDC_LOGIN_SCOPES" ]]; then + printf " --oidc-scopes %s" "$GATEWAY_OIDC_LOGIN_SCOPES" + fi + printf "\n" +fi +if [[ -n "$GATEWAY_ENV_FILE" ]]; then + printf "Wrote environment file: %s\n" "$GATEWAY_ENV_FILE" +fi From 7e7360a454c776913189d14a7375364be9f53394 Mon Sep 17 00:00:00 2001 From: Gordon Sim Date: Thu, 13 Aug 2026 21:20:40 +0100 Subject: [PATCH 8/8] feat(sandbox): add delegated identity for token exchange Signed-off-by: Gordon Sim --- architecture/sandbox.md | 22 +- crates/openshell-cli/src/main.rs | 252 + crates/openshell-cli/src/run.rs | 738 +- .../tests/ensure_providers_integration.rs | 58 + .../openshell-cli/tests/mtls_integration.rs | 58 + .../tests/provider_commands_integration.rs | 58 + .../sandbox_create_lifecycle_integration.rs | 58 + .../sandbox_name_fallback_integration.rs | 58 + crates/openshell-core/src/config.rs | 14 + crates/openshell-core/src/metadata.rs | 87 +- crates/openshell-core/src/oauth.rs | 94 + .../src/proto_json.rs | 1 + .../src/runtime.rs | 1 + crates/openshell-providers/src/profiles.rs | 60 +- crates/openshell-sdk/src/client.rs | 1 + crates/openshell-sdk/tests/client_mock.rs | 49 + crates/openshell-server/src/auth/oidc.rs | 32 +- crates/openshell-server/src/cli.rs | 7 + crates/openshell-server/src/compute/mod.rs | 13 +- crates/openshell-server/src/config_file.rs | 2 + .../src/delegated_identity.rs | 1639 ++++ crates/openshell-server/src/grpc/mod.rs | 79 +- crates/openshell-server/src/grpc/policy.rs | 16 + crates/openshell-server/src/grpc/provider.rs | 63 +- crates/openshell-server/src/grpc/sandbox.rs | 414 +- crates/openshell-server/src/grpc/service.rs | 10 + crates/openshell-server/src/lib.rs | 1 + crates/openshell-server/tests/common/mod.rs | 58 + .../tests/supervisor_relay_integration.rs | 56 + .../src/token_grant.rs | 198 +- crates/openshell-tui/src/lib.rs | 1 + docs/reference/gateway-config.mdx | 2 + docs/sandboxes/providers-v2.mdx | 63 +- proto/openshell.proto | 201 +- sdk/go/proto/openshellv1/openshell.pb.go | 7528 ++++++++++------- sdk/go/proto/openshellv1/openshell_grpc.pb.go | 414 +- 36 files changed, 9104 insertions(+), 3302 deletions(-) create mode 100644 crates/openshell-server/src/delegated_identity.rs diff --git a/architecture/sandbox.md b/architecture/sandbox.md index 8ee43094b3..e92b5525bc 100644 --- a/architecture/sandbox.md +++ b/architecture/sandbox.md @@ -265,13 +265,23 @@ Provider profiles can also declare dynamic token grants. For matching HTTP endpoints, the supervisor obtains or exchanges OAuth2 access tokens, caches them, and injects them before forwarding the request. `client_credentials` grants use the supervisor SPIFFE JWT-SVID directly as the client assertion. -`token_exchange` grants ask the gateway to broker an intermediate token using a -stored provider subject credential and the gateway's own SPIFFE JWT-SVID; the -supervisor then exchanges that intermediate token for the final upstream token -using its own JWT-SVID. The gateway validates that its own JWT-SVID has the +`token_exchange` grants ask the gateway to broker an intermediate token using +either a stored provider subject credential or the sandbox creator's delegated +OIDC identity, plus the gateway's own SPIFFE JWT-SVID; the supervisor then +exchanges that intermediate token for the final upstream token using its own +JWT-SVID. Delegated identity is opt-in at sandbox creation, stores one +gateway-scoped credential per issuer/client/user subject, and stores only a +per-sandbox authorization window on the sandbox. Only the delegating user can +extend or withdraw that window; workspace admins and platform admins can still +delete the sandbox to recover workspace resources. The gateway rejects exchange +after expiry, withdrawal, missing credential state, or credential revocation. +The gateway validates that its own JWT-SVID has the requested audience, a SPIFFE subject, and a non-expired `exp` claim when -present. It also validates that the stored subject credential is declared by the -provider profile, and that the supervisor JWT-SVID is a well-formed +present. For provider-credential subject tokens, it also validates that the +stored subject credential is declared by the provider profile. For delegated +identity subject tokens, it validates that the sandbox was created with active +delegation for the stored credential principal. The gateway also verifies that +the supervisor JWT-SVID is a well-formed three-segment JWT with a SPIFFE subject in the same trust domain as the gateway SVID. The gateway verifies the supervisor JWT-SVID signature with JWT bundles fetched from its SPIFFE Workload API. Token grant endpoints are HTTPS-only diff --git a/crates/openshell-cli/src/main.rs b/crates/openshell-cli/src/main.rs index 5fceabf08a..319eb5204c 100644 --- a/crates/openshell-cli/src/main.rs +++ b/crates/openshell-cli/src/main.rs @@ -564,6 +564,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 { @@ -1102,6 +1109,62 @@ enum ProviderProfileCommands { }, } +#[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. + #[arg(long, default_value_t = 100)] + limit: u32, + + /// Number of credentials to skip. + #[arg(long, default_value_t = 0)] + offset: u32, + + /// 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) // ----------------------------------------------------------------------- @@ -1405,6 +1468,11 @@ enum SandboxCommands { #[arg(long = "provider")] providers: Vec, + /// Delegate the current OIDC identity to the sandbox for a bounded duration. + /// Accepts positive durations with m, h, or d suffixes, for example 30m, 8h, or 7d. + #[arg(long = "delegate-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)] @@ -1659,6 +1727,41 @@ enum SandboxCommands { /// Manage providers attached to a sandbox. #[command(subcommand)] Provider(SandboxProviderCommands), + + /// 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, + }, } #[derive(Subcommand, Debug)] @@ -2979,6 +3082,7 @@ async fn run_async() -> Result<()> { memory, driver_config_json, providers, + delegate_identity_for, policy, forward, tty, @@ -3069,6 +3173,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, command: &command, @@ -3279,6 +3384,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?; + } + }, } } } @@ -3563,6 +3699,61 @@ async fn run_async() -> Result<()> { } } } + 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 { + limit, + offset, + ids, + output, + } => { + run::delegated_identity_credential_list( + endpoint, + limit, + offset, + 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); @@ -3694,6 +3885,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") @@ -4414,6 +4612,60 @@ mod tests { )); } + #[test] + fn delegated_credential_commands_parse() { + let list = Cli::try_parse_from(["openshell", "delegated-credential", "list", "--ids"]) + .expect("delegated credential list should parse"); + assert!(matches!( + list.command, + Some(Commands::DelegatedCredential { + command: Some(DelegatedCredentialCommands::List { + ids: true, + output: OutputFormat::Table, + .. + }) + }) + )); + + 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 provider_profile_commands_parse() { let export = Cli::try_parse_from([ diff --git a/crates/openshell-cli/src/run.rs b/crates/openshell-cli/src/run.rs index 4e8de99862..9b4ca2dd16 100644 --- a/crates/openshell-cli/src/run.rs +++ b/crates/openshell-cli/src/run.rs @@ -23,7 +23,7 @@ pub use crate::commands::gateway::{ }; use crate::policy_update::build_policy_update_plan; -use crate::tls::{TlsOptions, grpc_client, grpc_inference_client}; +use crate::tls::{GrpcClient, TlsOptions, grpc_client, grpc_inference_client}; use dialoguer::Confirm; use futures::StreamExt; use indicatif::{ProgressBar, ProgressStyle}; @@ -36,25 +36,29 @@ use openshell_core::proto::ProviderProfileCategory; use openshell_core::proto::{ ApproveAllDraftChunksRequest, ApproveDraftChunkRequest, AttachSandboxProviderRequest, ClearDraftChunksRequest, ConfigureProviderRefreshRequest, CreateProviderRequest, - CreateSandboxRequest, CreateSshSessionRequest, DeleteInferenceRouteRequest, - DeleteProviderProfileRequest, DeleteProviderRefreshRequest, DeleteProviderRequest, - DeleteSandboxRequest, DeleteServiceRequest, DetachSandboxProviderRequest, ExecSandboxRequest, - ExposeServiceRequest, GetCurrentUserRequest, GetDraftHistoryRequest, GetDraftPolicyRequest, + CreateSandboxRequest, CreateSshSessionRequest, DelegatedIdentityCredentialSummary, + DelegatedIdentityRequest, DeleteDelegatedIdentityCredentialRequest, + DeleteInferenceRouteRequest, DeleteProviderProfileRequest, DeleteProviderRefreshRequest, + DeleteProviderRequest, DeleteSandboxRequest, DeleteServiceRequest, + DetachSandboxProviderRequest, ExecSandboxRequest, ExposeServiceRequest, + ExtendSandboxDelegatedIdentityRequest, GetCurrentUserRequest, + GetDelegatedIdentityCredentialStatusRequest, GetDraftHistoryRequest, GetDraftPolicyRequest, GetGatewayConfigRequest, GetInferenceRouteRequest, GetProviderProfileRequest, GetProviderRefreshStatusRequest, GetProviderRequest, GetSandboxConfigRequest, - GetSandboxConfigResponse, GetSandboxLogsRequest, GetSandboxPolicyStatusRequest, - GetSandboxRequest, GetServiceRequest, GpuResourceRequirements, ImportProviderProfilesRequest, - LintProviderProfilesRequest, ListProviderProfilesRequest, ListProvidersRequest, + GetSandboxConfigResponse, GetSandboxDelegatedIdentityStatusRequest, GetSandboxLogsRequest, + GetSandboxPolicyStatusRequest, GetSandboxRequest, GetServiceRequest, GpuResourceRequirements, + ImportProviderProfilesRequest, LintProviderProfilesRequest, + ListDelegatedIdentityCredentialsRequest, ListProviderProfilesRequest, ListProvidersRequest, ListSandboxPoliciesRequest, ListSandboxProvidersRequest, ListSandboxesRequest, ListServicesRequest, PolicySource, PolicyStatus, Provider, ProviderCredentialRefreshStatus, ProviderCredentialRefreshStrategy, ProviderCredentialTokenGrantType, ProviderProfile, ProviderProfileDiagnostic, ProviderProfileImportItem, RejectDraftChunkRequest, - ResourceRequirements, RevokeSshSessionRequest, RotateProviderCredentialRequest, Sandbox, - SandboxPhase, SandboxPolicy, SandboxSpec, SandboxTemplate, ServiceEndpointResponse, - SetInferenceRouteRequest, SettingScope, StartSandboxRequest, StopSandboxRequest, - TcpForwardFrame, TcpForwardInit, TcpRelayTarget, UpdateConfigRequest, - UpdateProviderProfilesRequest, UpdateProviderRequest, WatchSandboxRequest, exec_sandbox_event, - setting_value, tcp_forward_init, + ResourceRequirements, RevokeDelegatedIdentityCredentialRequest, RevokeSshSessionRequest, + RotateProviderCredentialRequest, Sandbox, SandboxPhase, SandboxPolicy, SandboxSpec, + SandboxTemplate, ServiceEndpointResponse, SetInferenceRouteRequest, SettingScope, + StartSandboxRequest, StopSandboxRequest, TcpForwardFrame, TcpForwardInit, TcpRelayTarget, + UpdateConfigRequest, UpdateProviderProfilesRequest, UpdateProviderRequest, WatchSandboxRequest, + WithdrawSandboxDelegatedIdentityRequest, exec_sandbox_event, setting_value, tcp_forward_init, }; use openshell_core::settings; use openshell_core::{ObjectId, ObjectName, ObjectWorkspace}; @@ -384,6 +388,7 @@ pub struct SandboxCreateConfig<'a> { pub environment: HashMap, pub approval_mode: &'a str, pub output: &'a str, + pub delegate_identity_for: Option<&'a str>, } impl Default for SandboxCreateConfig<'_> { @@ -408,6 +413,7 @@ impl Default for SandboxCreateConfig<'_> { environment: HashMap::new(), approval_mode: "manual", output: "table", + delegate_identity_for: None, } } } @@ -440,6 +446,7 @@ pub async fn sandbox_create( environment, approval_mode, output, + delegate_identity_for, } = config; if editor.is_some() && !command.is_empty() { @@ -502,6 +509,9 @@ pub async fn sandbox_create( 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 = build_sandbox_resource_limits(cpu, memory)?; @@ -521,6 +531,8 @@ pub async fn sandbox_create( }; let resource_requirements = gpu_requirements.map(|gpu| ResourceRequirements { gpu: Some(gpu) }); + let delegated_identity = + delegated_identity_request(gateway_name, tls, delegate_identity_for).await?; let request = CreateSandboxRequest { spec: Some(SandboxSpec { @@ -535,6 +547,7 @@ pub async fn sandbox_create( labels, annotations: HashMap::new(), workspace: workspace.to_string(), + delegated_identity, }; let response = match client.create_sandbox(request).await { @@ -1261,6 +1274,622 @@ pub async fn sandbox_sync_command( Ok(()) } +async fn delegated_identity_request( + gateway_name: &str, + tls: &TlsOptions, + duration: Option<&str>, +) -> Result> { + let Some(duration) = duration else { + return Ok(None); + }; + let duration_ms = parse_delegated_identity_duration_ms(duration)?; + let bundle = + crate::oidc_auth::ensure_valid_oidc_token_bundle(gateway_name, tls.gateway_insecure) + .await + .map_err(|err| { + miette::miette!( + "failed to load or refresh OIDC token for delegated identity: {err}" + ) + })?; + let scopes = openshell_bootstrap::load_gateway_metadata(gateway_name) + .ok() + .and_then(|metadata| metadata.oidc_scopes); + let bundle = + crate::oidc_auth::oidc_refresh_token(&bundle, scopes.as_deref(), tls.gateway_insecure) + .await + .and_then(|refreshed| { + openshell_bootstrap::oidc_token::store_oidc_token(gateway_name, &refreshed)?; + Ok(refreshed) + }) + .map_err(|err| { + miette::miette!( + "failed to refresh local OIDC token for delegated identity: {err}\n\ + Re-authenticate with `openshell gateway logout` followed by `openshell gateway login`, then retry this command." + ) + })?; + let refresh_token = bundle.refresh_token.ok_or_else(|| { + miette::miette!( + "--delegate-identity-for requires a local OIDC refresh token; run `openshell gateway login` and ensure the gateway OIDC client issues refresh tokens" + ) + })?; + let now_ms = current_time_ms(); + Ok(Some(DelegatedIdentityRequest { + delegated_until_ms: now_ms.saturating_add(duration_ms), + issuer: bundle.issuer, + client_id: bundle.client_id, + refresh_token, + access_token: bundle.access_token, + scopes: scopes.unwrap_or_default(), + audience: openshell_bootstrap::load_gateway_metadata(gateway_name) + .ok() + .and_then(|metadata| metadata.oidc_audience) + .unwrap_or_default(), + })) +} + +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: workspace.to_string(), + }) + .await + .map_err(|status| miette::miette!(status.to_string()))? + .into_inner() + .provider; + let Some(provider) = provider else { + continue; + }; + let profile_id = normalize_provider_type(&provider.r#type).unwrap_or(&provider.r#type); + let profile = client + .get_provider_profile(GetProviderProfileRequest { + id: profile_id.to_string(), + workspace: provider.profile_workspace.clone(), + }) + .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 --delegate-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: workspace.to_string(), + }) + .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 status = sandbox_delegation_status( + response.credential_missing, + response.credential_revoked_at_ms, + delegation.withdrawn_at_ms, + delegation.delegated_until_ms, + response.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(delegation.delegated_until_ms, response.now_ms) + ), + "withdrawn" => println!( + "Withdrawn: {}", + format_age(delegation.withdrawn_at_ms, response.now_ms) + ), + "revoked" => println!( + "Revoked: {}", + format_age(response.credential_revoked_at_ms, response.now_ms) + ), + "credential-missing" => println!("Credential: missing"), + "expired" => println!( + "Expired: {}", + format_age(delegation.delegated_until_ms, response.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: workspace.to_string(), + }) + .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 mut client = grpc_client(server, tls).await?; + let delegated_identity = delegated_identity_request(gateway_name, tls, Some(duration)) + .await? + .expect("duration was provided"); + let response = client + .extend_sandbox_delegated_identity(ExtendSandboxDelegatedIdentityRequest { + name: name.to_string(), + workspace: workspace.to_string(), + delegated_identity: Some(delegated_identity), + }) + .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, + limit: u32, + offset: u32, + ids_only: bool, + output: &str, + tls: &TlsOptions, +) -> Result<()> { + let mut client = grpc_client(server, tls).await?; + let credentials = client + .list_delegated_identity_credentials(ListDelegatedIdentityCredentialsRequest { + limit, + offset, + }) + .await + .map_err(|status| miette::miette!(status.to_string()))? + .into_inner() + .credentials; + + if crate::output::print_output_collection( + output, + &credentials, + delegated_identity_credential_to_json, + )? { + return Ok(()); + } + + 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 view = serde_json::json!({ + "credential": delegated_identity_credential_to_json(&credential), + "now_ms": response.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, response.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.map(|meta| meta.created_at_ms).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": credential.access_token_expires_at_ms, + "scopes": credential.scopes, + "audience": credential.audience, + "last_refresh_at_ms": credential.last_refresh_at_ms, + "revoked_at_ms": credential.revoked_at_ms, + }) +} + +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(credential.last_refresh_at_ms, 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.map(|meta| meta.created_at_ms).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(credential.last_refresh_at_ms, now) + ); + if credential.revoked_at_ms > 0 { + println!( + " {:<22} {}", + "revoked:", + format_age(credential.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 { + if credential.revoked_at_ms > 0 { + "revoked" + } else if credential.access_token_expires_at_ms > 0 + && credential.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 { + if delegated_credential_status(credential, now) != "active" { + "-".to_string() + } else if credential.access_token_expires_at_ms == 0 { + "unknown".to_string() + } else { + format_remaining_duration(credential.access_token_expires_at_ms, now) + } +} + +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 @@ -1615,7 +2244,7 @@ pub async fn service_forward_tcp( } async fn create_forward_session_token( - client: &mut crate::tls::GrpcClient, + client: &mut GrpcClient, sandbox_id: &str, ) -> std::result::Result { let response = client @@ -1628,7 +2257,7 @@ async fn create_forward_session_token( } async fn fetch_ready_sandbox_for_forward( - client: &mut crate::tls::GrpcClient, + client: &mut GrpcClient, name: &str, workspace: &str, ) -> Result { @@ -1718,7 +2347,7 @@ fn parse_tcp_forward_spec(local: Option<&str>, default_port: u16) -> Result<(Str } async fn forward_one_tcp_connection( - client: &mut crate::tls::GrpcClient, + client: &mut GrpcClient, socket: tokio::net::TcpStream, sandbox_id: String, target_host: String, @@ -1832,7 +2461,7 @@ impl Drop for TaskGuard { } async fn sandbox_exec_interactive_grpc( - mut client: crate::tls::GrpcClient, + mut client: GrpcClient, sandbox: &Sandbox, command: &[String], workdir: Option<&str>, @@ -2475,7 +3104,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 { @@ -2565,7 +3194,7 @@ fn inferred_provider_type(command: &[String]) -> Option { /// Returns a deduplicated list of provider **names** suitable for /// `SandboxSpec.providers`. pub async fn ensure_required_providers( - client: &mut crate::tls::GrpcClient, + client: &mut GrpcClient, explicit_names: &[String], inferred_types: &[String], auto_providers_override: Option, @@ -2687,7 +3316,7 @@ pub async fn ensure_required_providers( /// defaults to the type and retries with suffixes on conflict (used for /// inferred provider types). async fn auto_create_provider( - client: &mut crate::tls::GrpcClient, + client: &mut GrpcClient, provider_type: &str, preferred_name: Option<&str>, auto_providers_override: Option, @@ -3213,7 +3842,7 @@ fn read_gcloud_adc() -> Result<(String, String, String)> { } async fn rollback_provider_create_after_gcloud_adc_failure( - client: &mut crate::tls::GrpcClient, + client: &mut GrpcClient, provider_name: &str, stage: &str, source: &Status, @@ -3267,7 +3896,7 @@ fn service_url_for_gateway(service_url: &str, gateway_endpoint: &str) -> String service_url.to_string() } -async fn gateway_providers_v2_enabled(client: &mut crate::tls::GrpcClient) -> Result { +async fn gateway_providers_v2_enabled(client: &mut GrpcClient) -> Result { let response = client .get_gateway_config(GetGatewayConfigRequest {}) .await @@ -3287,7 +3916,7 @@ async fn gateway_providers_v2_enabled(client: &mut crate::tls::GrpcClient) -> Re } async fn fetch_provider_profile( - client: &mut crate::tls::GrpcClient, + client: &mut GrpcClient, provider_type: &str, workspace: &str, ) -> Result { @@ -3314,7 +3943,7 @@ async fn fetch_provider_profile( } async fn discover_existing_provider_data( - client: &mut crate::tls::GrpcClient, + client: &mut GrpcClient, provider_type: &str, workspace: &str, ) -> Result> { @@ -8750,4 +9379,63 @@ mod tests { assert!(json["revision"].is_null()); assert!(json["policy"].is_null()); } + + #[test] + fn delegated_identity_human_status_formats_are_state_oriented() { + let now = 10_000; + assert_eq!( + super::sandbox_delegation_status(false, 0, 0, 70_000, now), + "active" + ); + assert_eq!( + super::sandbox_delegation_status(false, 0, 9_000, 70_000, now), + "withdrawn" + ); + assert_eq!( + super::sandbox_delegation_status(false, 0, 0, 9_000, now), + "expired" + ); + assert_eq!( + super::sandbox_delegation_status(false, 8_000, 0, 70_000, now), + "revoked" + ); + assert_eq!( + super::sandbox_delegation_status(true, 0, 0, 70_000, now), + "credential-missing" + ); + + assert_eq!(super::format_remaining_duration(70_000, now), "1m"); + assert_eq!(super::format_age(7_000, now), "3s ago"); + assert_eq!(super::format_optional_age(0, now), "never"); + } + + #[test] + fn delegated_identity_credential_status_formats_are_redacted_and_readable() { + let now = 10_000; + let mut credential = openshell_core::proto::DelegatedIdentityCredentialSummary { + access_token_expires_at_ms: 70_000, + last_refresh_at_ms: 5_000, + ..Default::default() + }; + + assert_eq!( + super::delegated_credential_status(&credential, now), + "active" + ); + assert_eq!(super::credential_access_valid_for(&credential, now), "1m"); + + credential.access_token_expires_at_ms = 9_000; + assert_eq!( + super::delegated_credential_status(&credential, now), + "expired" + ); + assert_eq!(super::credential_access_valid_for(&credential, now), "-"); + + credential.revoked_at_ms = 8_000; + assert_eq!( + super::delegated_credential_status(&credential, now), + "revoked" + ); + assert_eq!(super::credential_access_valid_for(&credential, now), "-"); + } } diff --git a/crates/openshell-cli/tests/ensure_providers_integration.rs b/crates/openshell-cli/tests/ensure_providers_integration.rs index 1b26be46f3..8eb1e0458e 100644 --- a/crates/openshell-cli/tests/ensure_providers_integration.rs +++ b/crates/openshell-cli/tests/ensure_providers_integration.rs @@ -126,6 +126,64 @@ 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 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 7bf45c1ba7..4d687e639b 100644 --- a/crates/openshell-cli/tests/mtls_integration.rs +++ b/crates/openshell-cli/tests/mtls_integration.rs @@ -81,6 +81,64 @@ 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 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 9c5597076c..d0a8390ead 100644 --- a/crates/openshell-cli/tests/provider_commands_integration.rs +++ b/crates/openshell-cli/tests/provider_commands_integration.rs @@ -144,6 +144,64 @@ 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 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 c263aa6640..ca43f5088e 100644 --- a/crates/openshell-cli/tests/sandbox_create_lifecycle_integration.rs +++ b/crates/openshell-cli/tests/sandbox_create_lifecycle_integration.rs @@ -131,6 +131,64 @@ 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 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_name_fallback_integration.rs b/crates/openshell-cli/tests/sandbox_name_fallback_integration.rs index 09975cd52f..3dd7783caf 100644 --- a/crates/openshell-cli/tests/sandbox_name_fallback_integration.rs +++ b/crates/openshell-cli/tests/sandbox_name_fallback_integration.rs @@ -94,6 +94,64 @@ 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 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 fcbdeb73b6..7e4a31da39 100644 --- a/crates/openshell-core/src/config.rs +++ b/crates/openshell-core/src/config.rs @@ -37,6 +37,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")] @@ -485,6 +488,9 @@ pub struct Config { /// TTL for SSH session tokens, in seconds. 0 disables expiry. pub ssh_session_ttl_secs: u64, + /// 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 @@ -840,6 +846,7 @@ impl Config { credential_drivers: Vec::new(), default_credential_driver: None, ssh_session_ttl_secs: default_ssh_session_ttl_secs(), + 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(), @@ -930,6 +937,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/metadata.rs b/crates/openshell-core/src/metadata.rs index 8794c11d5d..c2fe720026 100644 --- a/crates/openshell-core/src/metadata.rs +++ b/crates/openshell-core/src/metadata.rs @@ -6,7 +6,8 @@ //! These traits provide uniform access to `ObjectMeta` fields across all resource types. use crate::proto::{ - InferenceRoute, ObjectForTest, Provider, Sandbox, SandboxStatus, ServiceEndpoint, SshSession, + DelegatedIdentityCredential, InferenceRoute, ObjectForTest, Provider, Sandbox, + SandboxDelegatedIdentityRecord, SandboxStatus, ServiceEndpoint, SshSession, StoredProviderCredentialRefreshState, StoredProviderProfile, Workspace, WorkspaceMember, }; use std::collections::HashMap; @@ -273,6 +274,90 @@ impl ObjectWorkspace for StoredProviderCredentialRefreshState { } } +// Implementations for DelegatedIdentityCredential +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 + } +} + +// Implementations for SandboxDelegatedIdentityRecord +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 + } +} + // Implementations for SshSession impl ObjectId for SshSession { fn object_id(&self) -> &str { diff --git a/crates/openshell-core/src/oauth.rs b/crates/openshell-core/src/oauth.rs index c68ecfa6b4..25eff49d4c 100644 --- a/crates/openshell-core/src/oauth.rs +++ b/crates/openshell-core/src/oauth.rs @@ -28,6 +28,15 @@ pub struct OAuthTokenResponse { pub token_type: String, } +/// `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(Debug, Deserialize)] struct RawTokenResponse { access_token: String, @@ -35,6 +44,8 @@ struct RawTokenResponse { expires_in: i64, #[serde(default)] token_type: String, + #[serde(default)] + refresh_token: Option, } #[derive(Debug, Deserialize)] @@ -169,6 +180,65 @@ 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<'_>, +) -> 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 = response + .text() + .await + .unwrap_or_else(|_| "".to_string()); + return Err(miette::miette!("{}", failure_message(status, &body))); + } + + let raw = response + .json::() + .await + .into_diagnostic() + .wrap_err("failed to parse token response as JSON")?; + validate_access_token(&raw.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 @@ -186,9 +256,19 @@ pub fn effective_token_type(token_type: &str) -> &str { } 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 url = reqwest::Url::parse(token_endpoint) .into_diagnostic() .wrap_err("token_endpoint must be an absolute URL")?; + if allow_insecure_http && matches!(url.scheme(), "http" | "https") { + return Ok(url); + } if token_endpoint_transport_allowed(&url) { return Ok(url); } @@ -698,6 +778,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-gateway-interceptors/src/proto_json.rs b/crates/openshell-gateway-interceptors/src/proto_json.rs index f6aecbcf67..f1e1c95e7b 100644 --- a/crates/openshell-gateway-interceptors/src/proto_json.rs +++ b/crates/openshell-gateway-interceptors/src/proto_json.rs @@ -316,6 +316,7 @@ mod tests { labels: HashMap::from([("team".to_string(), "agent".to_string())]), annotations: HashMap::new(), workspace: String::new(), + delegated_identity: None, }; let bytes = request.encode_to_vec(); let json = codec diff --git a/crates/openshell-gateway-interceptors/src/runtime.rs b/crates/openshell-gateway-interceptors/src/runtime.rs index 4e7f5ba613..fe431aceec 100644 --- a/crates/openshell-gateway-interceptors/src/runtime.rs +++ b/crates/openshell-gateway-interceptors/src/runtime.rs @@ -1075,6 +1075,7 @@ mod tests { labels: HashMap::new(), annotations: HashMap::new(), workspace: String::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 cf7c2faa35..df5271ac69 100644 --- a/crates/openshell-providers/src/profiles.rs +++ b/crates/openshell-providers/src/profiles.rs @@ -2384,31 +2384,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 => { diff --git a/crates/openshell-sdk/src/client.rs b/crates/openshell-sdk/src/client.rs index c67e91e219..409ed4f62d 100644 --- a/crates/openshell-sdk/src/client.rs +++ b/crates/openshell-sdk/src/client.rs @@ -819,6 +819,7 @@ fn create_sandbox_request(spec: SandboxSpec) -> proto::CreateSandboxRequest { labels, annotations: HashMap::new(), workspace: String::new(), + delegated_identity: None, } } diff --git a/crates/openshell-sdk/tests/client_mock.rs b/crates/openshell-sdk/tests/client_mock.rs index 90375a2b9d..7b14e82d08 100644 --- a/crates/openshell-sdk/tests/client_mock.rs +++ b/crates/openshell-sdk/tests/client_mock.rs @@ -195,6 +195,55 @@ impl OpenShell for TestOpenShell { })) } + 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/src/auth/oidc.rs b/crates/openshell-server/src/auth/oidc.rs index 475c52ebec..ba99edc715 100644 --- a/crates/openshell-server/src/auth/oidc.rs +++ b/crates/openshell-server/src/auth/oidc.rs @@ -95,6 +95,8 @@ struct JwkKey { pub struct OidcClaims { pub sub: String, #[serde(default)] + pub exp: i64, + #[serde(default)] pub preferred_username: Option, #[serde(default)] #[allow(dead_code)] @@ -287,6 +289,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| { @@ -334,6 +341,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() { @@ -342,16 +353,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 2e86c3a1b5..e96d08276e 100644 --- a/crates/openshell-server/src/cli.rs +++ b/crates/openshell-server/src/cli.rs @@ -429,6 +429,13 @@ fn prepare_server_config(args: &mut RunArgs, matches: &ArgMatches) -> Result, #[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/delegated_identity.rs b/crates/openshell-server/src/delegated_identity.rs new file mode 100644 index 0000000000..916600cf00 --- /dev/null +++ b/crates/openshell-server/src/delegated_identity.rs @@ -0,0 +1,1639 @@ +// 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. + +use crate::ServerState; +use crate::auth::principal::{Principal, UserPrincipal}; +use crate::auth::workspace_authz::{MinWorkspaceRole, authorize_workspace, require_platform_admin}; +use crate::persistence::{ObjectType, WriteCondition, current_time_ms}; +use openshell_core::proto::datamodel::v1::ObjectMeta; +use openshell_core::proto::{ + DelegatedIdentityCredential, DelegatedIdentityCredentialSummary, DelegatedIdentityRequest, + DeleteDelegatedIdentityCredentialRequest, DeleteDelegatedIdentityCredentialResponse, + ExtendSandboxDelegatedIdentityRequest, ExtendSandboxDelegatedIdentityResponse, + GetDelegatedIdentityCredentialStatusRequest, GetDelegatedIdentityCredentialStatusResponse, + GetSandboxDelegatedIdentityStatusRequest, GetSandboxDelegatedIdentityStatusResponse, + ListDelegatedIdentityCredentialsRequest, ListDelegatedIdentityCredentialsResponse, + RevokeDelegatedIdentityCredentialRequest, RevokeDelegatedIdentityCredentialResponse, Sandbox, + SandboxDelegatedIdentity, SandboxDelegatedIdentityRecord, + WithdrawSandboxDelegatedIdentityRequest, WithdrawSandboxDelegatedIdentityResponse, +}; +use openshell_core::{GetResourceVersion, ObjectId, ObjectLabels, ObjectName, ObjectWorkspace}; +use prost::Message; +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 REFRESH_SKEW_MS: i64 = 60_000; +static DELEGATED_IDENTITY_HTTP_CLIENT: LazyLock> = + LazyLock::new(|| { + reqwest::Client::builder() + .timeout(Duration::from_secs(30)) + .connect_timeout(Duration::from_secs(30)) + .build() + .map_err(|err| format!("delegated identity HTTP client configuration failed: {err}")) + }); + +pub struct PreparedSandboxDelegatedIdentity { + pub record: SandboxDelegatedIdentityRecord, + credential_id: String, + credential_resource_version: u64, + credential_created: bool, +} + +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 access_token_expires_at_ms = validate_delegation_request(state, user, &request).await?; + let credential = + upsert_credential(state, user, request.clone(), access_token_expires_at_ms).await?; + let credential_id = credential.credential.object_id().to_string(); + let credential_resource_version = credential.credential.get_resource_version(); + let sandbox_id = sandbox.object_id().to_string(); + let record_id = sandbox_delegated_identity_record_id(&sandbox_id); + Ok(Some(PreparedSandboxDelegatedIdentity { + record: SandboxDelegatedIdentityRecord { + metadata: Some(ObjectMeta { + id: record_id.clone(), + name: record_id, + created_at_ms: current_time_ms(), + labels: HashMap::new(), + resource_version: 0, + annotations: HashMap::new(), + workspace: sandbox.object_workspace().to_string(), + deletion_timestamp_ms: 0, + }), + sandbox_id, + delegated_identity: Some(SandboxDelegatedIdentity { + credential_id: credential_id.clone(), + principal_subject: user.identity.subject.clone(), + delegated_until_ms: request.delegated_until_ms, + withdrawn_at_ms: 0, + }), + }, + credential_id, + credential_resource_version, + credential_created: credential.created, + })) +} + +pub async fn store_prepared_sandbox_delegation( + state: &Arc, + prepared: Option<&PreparedSandboxDelegatedIdentity>, +) -> Result<(), Status> { + let Some(prepared) = prepared else { + return Ok(()); + }; + state + .store + .put_scoped_message(&prepared.record, &prepared.record.sandbox_id) + .await + .map_err(|e| Status::internal(format!("persist sandbox delegated identity failed: {e}"))) +} + +pub async fn delete_prepared_sandbox_delegation( + state: &Arc, + prepared: Option<&PreparedSandboxDelegatedIdentity>, +) -> Result<(), Status> { + let Some(prepared) = prepared else { + return Ok(()); + }; + state + .store + .delete( + SandboxDelegatedIdentityRecord::object_type(), + prepared.record.object_id(), + ) + .await + .map(|_| ()) + .map_err(|e| Status::internal(format!("delete sandbox delegated identity failed: {e}"))) +} + +pub async fn delete_new_prepared_sandbox_credential( + state: &Arc, + prepared: Option<&PreparedSandboxDelegatedIdentity>, +) -> Result<(), Status> { + let Some(prepared) = prepared.filter(|prepared| prepared.credential_created) else { + return Ok(()); + }; + state + .store + .delete_if( + DelegatedIdentityCredential::object_type(), + &prepared.credential_id, + prepared.credential_resource_version, + ) + .await + .map(|_| ()) + .map_err(|e| Status::internal(format!("delete prepared delegated credential failed: {e}"))) +} + +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.withdrawn_at_ms > 0 { + return Err(Status::failed_precondition("delegated identity withdrawn")); + } + let now = current_time_ms(); + if delegation.delegated_until_ms <= 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", + )); + } + if credential.revoked_at_ms > 0 { + return Err(Status::failed_precondition("delegated credential revoked")); + } + let credential = refresh_if_needed(state, credential).await?; + let credential_id = credential.object_id().to_string(); + Ok(( + credential.access_token, + credential.access_token_expires_at_ms, + credential_id, + )) +} + +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, &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(), + now_ms: current_time_ms(), + 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(_) => 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", + )), + } +} + +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, &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.withdrawn_at_ms == 0 + { + delegation.withdrawn_at_ms = 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 material = req + .delegated_identity + .ok_or_else(|| Status::invalid_argument("delegated_identity is required"))?; + let sandbox = authorized_sandbox_by_name(state, &principal, &req.workspace, &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 user = require_user(&principal)?; + let access_token_expires_at_ms = validate_delegation_request(state, user, &material).await?; + let credential = + upsert_credential(state, user, material.clone(), access_token_expires_at_ms).await?; + let update_result = state + .store + .update_message_cas::(record.object_id(), 0, |current| { + if let Some(delegation) = current.delegated_identity.as_mut() { + delegation.credential_id = credential.credential.object_id().to_string(); + delegation.delegated_until_ms = material.delegated_until_ms; + delegation.withdrawn_at_ms = 0; + } + }) + .await; + if let Err(error) = update_result { + cleanup_new_credential_after_prepare_failure(state, &credential).await?; + return Err(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 credentials = state + .store + .list_all_messages::( + crate::grpc::clamp_limit(req.limit, 100, crate::grpc::MAX_PAGE_SIZE), + req.offset, + ) + .await + .map_err(|e| Status::internal(format!("list delegated credentials failed: {e}")))?; + let credentials = credentials + .into_iter() + .map(delegated_credential_summary) + .collect(); + Ok(Response::new(ListDelegatedIdentityCredentialsResponse { + credentials, + })) +} + +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)), + now_ms: 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.refresh_token.is_empty(), + access_token_present: !credential.access_token.is_empty(), + access_token_expires_at_ms: credential.access_token_expires_at_ms, + scopes: credential.scopes, + audience: credential.audience, + last_refresh_at_ms: credential.last_refresh_at_ms, + revoked_at_ms: 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; + } + }, + ) + .await + .map_err(|e| crate::grpc::persistence_error_to_status(e, "revoke delegated credential"))?; + let resource_version = credential + .metadata + .as_ref() + .map(|metadata| metadata.resource_version) + .unwrap_or_default(); + Ok(Response::new(RevokeDelegatedIdentityCredentialResponse { + revoked, + revoked_at_ms: 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 expected_resource_version = + delete_credential_resource_version(state, &req.id, req.expected_resource_version).await?; + let Some(expected_resource_version) = expected_resource_version else { + return Ok(Response::new(DeleteDelegatedIdentityCredentialResponse { + deleted: false, + })); + }; + let deleted = state + .store + .delete_if( + DelegatedIdentityCredential::object_type(), + &req.id, + expected_resource_version, + ) + .await + .map_err(|e| crate::grpc::persistence_error_to_status(e, "delete delegated credential"))?; + Ok(Response::new(DeleteDelegatedIdentityCredentialResponse { + deleted, + })) +} + +async fn delete_credential_resource_version( + state: &Arc, + id: &str, + expected_resource_version: u64, +) -> Result, Status> { + if expected_resource_version != 0 { + return Ok(Some(expected_resource_version)); + } + let credential = state + .store + .get_message::(id) + .await + .map_err(|e| Status::internal(format!("fetch delegated credential failed: {e}")))?; + Ok(effective_delete_credential_resource_version( + credential.as_ref(), + expected_resource_version, + )) +} + +fn effective_delete_credential_resource_version( + credential: Option<&DelegatedIdentityCredential>, + expected_resource_version: u64, +) -> Option { + if expected_resource_version != 0 { + Some(expected_resource_version) + } else { + credential + .and_then(|credential| credential.metadata.as_ref()) + .map(|metadata| metadata.resource_version) + } +} + +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(()) +} + +async fn validate_delegation_request( + state: &Arc, + user: &UserPrincipal, + request: &DelegatedIdentityRequest, +) -> Result { + if request.issuer.trim().is_empty() { + return Err(Status::invalid_argument( + "delegated_identity.issuer is required", + )); + } + let configured_issuer = 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", + ) + })?; + if request.issuer.trim_end_matches('/') != configured_issuer { + return Err(Status::invalid_argument( + "delegated_identity.issuer must match the gateway OIDC issuer", + )); + } + if request.client_id.trim().is_empty() { + return Err(Status::invalid_argument( + "delegated_identity.client_id is required", + )); + } + if request.refresh_token.trim().is_empty() { + return Err(Status::invalid_argument( + "delegated_identity.refresh_token is required", + )); + } + if request.access_token.trim().is_empty() { + return Err(Status::invalid_argument( + "delegated_identity.access_token is required", + )); + } + let access_token_expires_at_ms = + validate_delegated_access_token_subject(state, user, request).await?; + let now = current_time_ms(); + if request.delegated_until_ms <= now { + return Err(Status::invalid_argument( + "delegated_identity.delegated_until_ms must be in the future", + )); + } + let max_ms = i64::try_from(state.config.max_delegated_identity_duration_secs) + .unwrap_or(i64::MAX / 1000) + .saturating_mul(1000); + if request.delegated_until_ms.saturating_sub(now) > max_ms { + return Err(Status::failed_precondition(format!( + "delegated identity duration exceeds gateway maximum of {} seconds", + state.config.max_delegated_identity_duration_secs + ))); + } + Ok(access_token_expires_at_ms) +} + +async fn validate_delegated_access_token_subject( + state: &Arc, + user: &UserPrincipal, + request: &DelegatedIdentityRequest, +) -> Result { + validate_delegated_access_token_subject_value( + state, + &request.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: &str, + 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, + workspace, + 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, + request: DelegatedIdentityRequest, + access_token_expires_at_ms: i64, +) -> Result { + let id = delegated_credential_id(&request.issuer, &request.client_id, &user.identity.subject); + let now = current_time_ms(); + let mut credential = DelegatedIdentityCredential { + metadata: Some(ObjectMeta { + id: id.clone(), + name: id.clone(), + created_at_ms: now, + labels: HashMap::new(), + resource_version: 0, + annotations: HashMap::new(), + workspace: GLOBAL_WORKSPACE.to_string(), + deletion_timestamp_ms: 0, + }), + issuer: request.issuer, + client_id: request.client_id, + principal_subject: user.identity.subject.clone(), + refresh_token: request.refresh_token, + access_token: request.access_token, + access_token_expires_at_ms, + scopes: request.scopes, + audience: request.audience, + last_refresh_at_ms: now, + revoked_at_ms: 0, + }; + + let existing = state + .store + .get_message::(&id) + .await + .map_err(|e| Status::internal(format!("fetch delegated credential failed: {e}")))?; + let (created, write_condition) = if let Some(existing) = existing { + ensure_delegated_credential_not_revoked(&existing)?; + let write_condition = delegated_credential_upsert_condition(&existing); + credential.metadata = existing.metadata; + credential.revoked_at_ms = existing.revoked_at_ms; + (false, write_condition) + } else { + (true, WriteCondition::MustCreate) + }; + let labels = credential + .object_labels() + .filter(|labels| !labels.is_empty()) + .map(|labels| { + serde_json::to_string(&labels) + .map_err(|e| Status::internal(format!("serialize labels failed: {e}"))) + }) + .transpose()?; + let result = state + .store + .put_if( + DelegatedIdentityCredential::object_type(), + credential.object_id(), + credential.object_name(), + credential.object_workspace(), + &credential.encode_to_vec(), + labels.as_deref(), + write_condition, + ) + .await + .map_err(|e| crate::grpc::persistence_error_to_status(e, "persist delegated credential"))?; + if let Some(metadata) = credential.metadata.as_mut() { + metadata.resource_version = result.resource_version; + } + Ok(UpsertedCredential { + credential, + created, + }) +} + +fn delegated_credential_upsert_condition(existing: &DelegatedIdentityCredential) -> WriteCondition { + WriteCondition::MatchResourceVersion(existing.get_resource_version()) +} + +struct UpsertedCredential { + credential: DelegatedIdentityCredential, + created: bool, +} + +async fn cleanup_new_credential_after_prepare_failure( + state: &Arc, + credential: &UpsertedCredential, +) -> Result<(), Status> { + if !credential.created { + return Ok(()); + } + state + .store + .delete_if( + DelegatedIdentityCredential::object_type(), + credential.credential.object_id(), + credential.credential.get_resource_version(), + ) + .await + .map(|_| ()) + .map_err(|e| Status::internal(format!("delete prepared delegated credential failed: {e}"))) +} + +fn ensure_delegated_credential_not_revoked( + credential: &DelegatedIdentityCredential, +) -> Result<(), Status> { + if credential.revoked_at_ms > 0 { + Err(Status::failed_precondition( + "delegated identity credential is revoked", + )) + } else { + Ok(()) + } +} + +fn delegated_credential_id(issuer: &str, client_id: &str, principal_subject: &str) -> String { + let mut hasher = Sha256::new(); + hasher.update(issuer.trim_end_matches('/')); + hasher.update(b"\0"); + hasher.update(client_id); + hasher.update(b"\0"); + hasher.update(principal_subject); + format!("delegated-identity-{:x}", hasher.finalize()) +} + +async fn refresh_if_needed( + state: &Arc, + credential: DelegatedIdentityCredential, +) -> Result { + 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 + { + return Ok(credential); + } + 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::>(); + let refreshed = openshell_core::oauth::post_oauth_refresh_token( + client, + &token_endpoint, + &openshell_core::oauth::RefreshTokenParams { + refresh_token: &credential.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 = validate_delegated_access_token_subject_value( + state, + &refreshed.access_token, + &credential.principal_subject, + ) + .await?; + let refreshed_refresh_token = refreshed.refresh_token; + let refreshed_access_token = refreshed.access_token; + let updated = state + .store + .update_message_cas::( + credential.object_id(), + credential.get_resource_version(), + |current| { + current.access_token.clone_from(&refreshed_access_token); + current.access_token_expires_at_ms = expires_at_ms; + current.last_refresh_at_ms = now; + if let Some(refresh_token) = refreshed_refresh_token.as_ref() { + current.refresh_token.clone_from(refresh_token); + } + }, + ) + .await + .map_err(|e| crate::grpc::persistence_error_to_status(e, "refresh delegated credential"))?; + Ok(updated) +} + +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. \ + Re-authenticate with `openshell gateway logout` followed by `openshell gateway login`, \ + then run `openshell sandbox delegated-identity extend --for=`.", + ); + } + message +} + +fn inactive_refresh_token_error(error: &str) -> bool { + let error = error.to_ascii_lowercase(); + error.contains("invalid_grant") + && (error.contains("session not active") + || error.contains("session inactive") + || error.contains("refresh token")) +} + +#[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 discovery = client + .get(url) + .send() + .await + .map_err(|e| Status::failed_precondition(format!("OIDC discovery failed: {e}")))? + .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::{ + PreparedSandboxDelegatedIdentity, delegated_credential_id, + delegated_credential_status_fields, delegated_credential_summary, + delegated_credential_upsert_condition, delegated_refresh_allows_insecure_http, + delegated_refresh_error_message, delete_new_prepared_sandbox_credential, + effective_delete_credential_resource_version, ensure_delegated_credential_not_revoked, + ensure_delegated_token_subject_matches, sandbox_delegated_identity_record_id, + }; + 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::supervisor_session::SupervisorSessionRegistry; + use crate::tracing_bus::TracingLogBus; + use openshell_core::proto::datamodel::v1::ObjectMeta; + use openshell_core::proto::{ + DelegatedIdentityCredential, DelegatedIdentityRequest, + ExtendSandboxDelegatedIdentityRequest, GetSandboxDelegatedIdentityStatusRequest, Sandbox, + SandboxDelegatedIdentity, SandboxDelegatedIdentityRecord, SandboxSpec, SandboxStatus, + }; + 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"; + + 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()), + } + } + } + + #[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("openshell sandbox delegated-identity extend ")); + } + + #[test] + fn revoked_delegated_credential_rejects_upsert_reactivation() { + let active = DelegatedIdentityCredential { + revoked_at_ms: 0, + ..Default::default() + }; + ensure_delegated_credential_not_revoked(&active).expect("active credential is reusable"); + + let revoked = DelegatedIdentityCredential { + revoked_at_ms: 42, + ..Default::default() + }; + let status = ensure_delegated_credential_not_revoked(&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 delete_credential_resource_version_zero_uses_current_version() { + 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() + }; + + let version = effective_delete_credential_resource_version(Some(&credential), 0); + + assert_eq!(version, Some(7)); + assert_eq!(effective_delete_credential_resource_version(None, 0), None); + assert_eq!( + effective_delete_credential_resource_version(Some(&credential), 42), + Some(42) + ); + } + + #[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(), + refresh_token: "refresh-secret".to_string(), + access_token: "access-secret".to_string(), + 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!(summary.access_token_expires_at_ms, 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 prepared_sandbox_create_cleanup_deletes_only_new_credentials() { + let state = test_server_state().await; + let new = put_test_credential(&state, "delegated-identity-new").await; + let reused = put_test_credential(&state, "delegated-identity-reused").await; + + delete_new_prepared_sandbox_credential(&state, Some(&prepared_test_delegation(&new, true))) + .await + .expect("new credential cleanup should succeed"); + delete_new_prepared_sandbox_credential( + &state, + Some(&prepared_test_delegation(&reused, false)), + ) + .await + .expect("reused credential cleanup should be a no-op"); + + assert!( + state + .store + .get_message::(new.object_id()) + .await + .unwrap() + .is_none() + ); + assert!( + state + .store + .get_message::(reused.object_id()) + .await + .unwrap() + .is_some() + ); + } + + #[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(), + refresh_token: "refresh-token".to_string(), + access_token: original_access_token.clone(), + access_token_expires_at_ms: current_time_ms() - 1, + scopes: "openid profile".to_string(), + ..Default::default() + }; + 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(); + assert_eq!(stored.access_token, original_access_token); + assert_eq!(stored.principal_subject, "alice"); + } + + #[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 request = DelegatedIdentityRequest { + issuer: issuer.clone(), + client_id: TEST_AUDIENCE.to_string(), + refresh_token: "refresh-token".to_string(), + access_token: mint_test_access_token(&issuer, "alice", exp_secs), + delegated_until_ms: current_time_ms() + 600_000, + scopes: "openid profile".to_string(), + audience: TEST_AUDIENCE.to_string(), + }; + + let expires_at_ms = super::validate_delegation_request(&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.credential.access_token_expires_at_ms, + exp_secs.saturating_mul(1000) + ); + } + + #[tokio::test] + async fn extend_cleanup_deletes_new_credential_when_record_update_fails() { + 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 existing_credential = + put_test_credential_for_subject(&state, "delegated-identity-existing", "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(), + delegated_until_ms: current_time_ms() + 600_000, + withdrawn_at_ms: 0, + }), + }; + 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 new_client_id = "openshell-cli-extend"; + let new_credential_id = delegated_credential_id(&issuer, new_client_id, "dev-user"); + let request = authed_request(ExtendSandboxDelegatedIdentityRequest { + name: "delegated".to_string(), + workspace: "default".to_string(), + delegated_identity: Some(DelegatedIdentityRequest { + issuer: issuer.clone(), + client_id: new_client_id.to_string(), + refresh_token: "new-refresh".to_string(), + access_token: mint_test_access_token( + &issuer, + "dev-user", + current_time_secs() + 3600, + ), + delegated_until_ms: current_time_ms() + 600_000, + scopes: "openid profile".to_string(), + audience: TEST_AUDIENCE.to_string(), + }), + }); + + 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::(&new_credential_id) + .await + .unwrap() + .is_none(), + "newly-created credential should be cleaned up" + ); + assert!( + state + .store + .get_message::(existing_credential.object_id()) + .await + .unwrap() + .is_some(), + "pre-existing credential should not be cleaned up" + ); + } + + #[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: "default".to_string(), + }), + ) + .await + .expect("regular sandbox status should report disabled") + .into_inner(); + + assert!(response.delegated_identity.is_none()); + assert!(response.credential_missing); + assert_eq!(response.credential_revoked_at_ms, 0); + assert!(response.now_ms > 0); + } + + #[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(), + delegated_until_ms: 2_000_000, + withdrawn_at_ms: 0, + }), + }, + "sandbox-delegated", + ) + .await + .unwrap(); + + let mut request = Request::new(GetSandboxDelegatedIdentityStatusRequest { + name: "delegated".to_string(), + workspace: "default".to_string(), + }); + 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(), + refresh_token: "refresh".to_string(), + access_token: "access".to_string(), + ..Default::default() + }; + 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()), + }) + .await + .unwrap(); + } + + fn prepared_test_delegation( + credential: &DelegatedIdentityCredential, + credential_created: bool, + ) -> PreparedSandboxDelegatedIdentity { + PreparedSandboxDelegatedIdentity { + record: SandboxDelegatedIdentityRecord { + metadata: Some(ObjectMeta { + id: format!("sandbox-delegated-identity-{credential_created}"), + name: format!("sandbox-delegated-identity-{credential_created}"), + workspace: "default".to_string(), + labels: HashMap::new(), + annotations: HashMap::new(), + ..Default::default() + }), + sandbox_id: "sandbox-test".to_string(), + delegated_identity: Some(SandboxDelegatedIdentity { + credential_id: credential.object_id().to_string(), + principal_subject: credential.principal_subject.clone(), + delegated_until_ms: 2_000_000, + withdrawn_at_ms: 0, + }), + }, + credential_id: credential.object_id().to_string(), + credential_resource_version: credential.get_resource_version(), + credential_created, + } + } + + 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, + 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; + } + + 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/grpc/mod.rs b/crates/openshell-server/src/grpc/mod.rs index 76eafcf5d1..9104f0b77e 100644 --- a/crates/openshell-server/src/grpc/mod.rs +++ b/crates/openshell-server/src/grpc/mod.rs @@ -18,24 +18,30 @@ use openshell_core::proto::{ ClearDraftChunksResponse, ComputeDriverCapabilities, ComputeDriverInfo, ConfigureProviderRefreshRequest, ConfigureProviderRefreshResponse, CreateProviderRequest, CreateSandboxRequest, CreateSshSessionRequest, CreateSshSessionResponse, - CreateWorkspaceRequest, CreateWorkspaceResponse, DeleteProviderProfileRequest, + CreateWorkspaceRequest, CreateWorkspaceResponse, DeleteDelegatedIdentityCredentialRequest, + DeleteDelegatedIdentityCredentialResponse, DeleteProviderProfileRequest, DeleteProviderProfileResponse, DeleteProviderRefreshRequest, DeleteProviderRefreshResponse, DeleteProviderRequest, DeleteProviderResponse, DeleteSandboxRequest, DeleteSandboxResponse, DeleteServiceRequest, DeleteServiceResponse, DeleteWorkspaceRequest, DeleteWorkspaceResponse, DetachSandboxProviderRequest, DetachSandboxProviderResponse, EditDraftChunkRequest, EditDraftChunkResponse, ExchangeProviderSubjectTokenRequest, ExchangeProviderSubjectTokenResponse, ExecSandboxEvent, ExecSandboxInput, ExecSandboxRequest, - ExposeServiceRequest, GatewayMessage, GetCurrentUserRequest, GetCurrentUserResponse, - GetDraftHistoryRequest, GetDraftHistoryResponse, GetDraftPolicyRequest, GetDraftPolicyResponse, - GetGatewayConfigRequest, GetGatewayConfigResponse, GetGatewayInfoRequest, - GetGatewayInfoResponse, GetProviderProfileRequest, GetProviderRefreshStatusRequest, - GetProviderRefreshStatusResponse, GetProviderRequest, GetSandboxConfigRequest, - GetSandboxConfigResponse, GetSandboxLogsRequest, GetSandboxLogsResponse, - GetSandboxPolicyStatusRequest, GetSandboxPolicyStatusResponse, - GetSandboxProviderEnvironmentRequest, GetSandboxProviderEnvironmentResponse, GetSandboxRequest, - GetServiceRequest, GetWorkspaceRequest, GetWorkspaceResponse, HealthRequest, HealthResponse, + ExposeServiceRequest, ExtendSandboxDelegatedIdentityRequest, + ExtendSandboxDelegatedIdentityResponse, GatewayMessage, GetCurrentUserRequest, + GetCurrentUserResponse, GetDelegatedIdentityCredentialStatusRequest, + GetDelegatedIdentityCredentialStatusResponse, GetDraftHistoryRequest, GetDraftHistoryResponse, + GetDraftPolicyRequest, GetDraftPolicyResponse, GetGatewayConfigRequest, + GetGatewayConfigResponse, GetGatewayInfoRequest, GetGatewayInfoResponse, + GetProviderProfileRequest, GetProviderRefreshStatusRequest, GetProviderRefreshStatusResponse, + GetProviderRequest, GetSandboxConfigRequest, GetSandboxConfigResponse, + GetSandboxDelegatedIdentityStatusRequest, GetSandboxDelegatedIdentityStatusResponse, + GetSandboxLogsRequest, GetSandboxLogsResponse, GetSandboxPolicyStatusRequest, + GetSandboxPolicyStatusResponse, GetSandboxProviderEnvironmentRequest, + GetSandboxProviderEnvironmentResponse, GetSandboxRequest, GetServiceRequest, + GetWorkspaceRequest, GetWorkspaceResponse, HealthRequest, HealthResponse, ImportProviderProfilesRequest, ImportProviderProfilesResponse, IssueSandboxTokenRequest, IssueSandboxTokenResponse, LintProviderProfilesRequest, LintProviderProfilesResponse, + ListDelegatedIdentityCredentialsRequest, ListDelegatedIdentityCredentialsResponse, ListProviderProfilesRequest, ListProviderProfilesResponse, ListProvidersRequest, ListProvidersResponse, ListSandboxPoliciesRequest, ListSandboxPoliciesResponse, ListSandboxProvidersRequest, ListSandboxProvidersResponse, ListSandboxesRequest, @@ -45,13 +51,15 @@ use openshell_core::proto::{ RefreshSandboxTokenRequest, RefreshSandboxTokenResponse, RejectDraftChunkRequest, RejectDraftChunkResponse, RelayFrame, RemoveWorkspaceMemberRequest, RemoveWorkspaceMemberResponse, ReportPolicyStatusRequest, ReportPolicyStatusResponse, + RevokeDelegatedIdentityCredentialRequest, RevokeDelegatedIdentityCredentialResponse, RevokeSshSessionRequest, RevokeSshSessionResponse, RotateProviderCredentialRequest, RotateProviderCredentialResponse, SandboxResponse, ServiceEndpointResponse, ServiceStatus, StartSandboxRequest, StopSandboxRequest, SubmitPolicyAnalysisRequest, SubmitPolicyAnalysisResponse, SupervisorMessage, TcpForwardFrame, UndoDraftChunkRequest, UndoDraftChunkResponse, UpdateConfigRequest, UpdateConfigResponse, UpdateProviderProfilesRequest, UpdateProviderProfilesResponse, UpdateProviderRequest, - WatchSandboxRequest, open_shell_server::OpenShell, + WatchSandboxRequest, WithdrawSandboxDelegatedIdentityRequest, + WithdrawSandboxDelegatedIdentityResponse, open_shell_server::OpenShell, }; use serde::{Deserialize, Serialize}; use std::collections::BTreeMap; @@ -274,6 +282,27 @@ impl OpenShell for OpenShellService { sandbox::handle_create_sandbox(&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( @@ -550,6 +579,34 @@ impl OpenShell for OpenShellService { 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( &self, request: Request, diff --git a/crates/openshell-server/src/grpc/policy.rs b/crates/openshell-server/src/grpc/policy.rs index 3b841e66a5..f92b16d0dc 100644 --- a/crates/openshell-server/src/grpc/policy.rs +++ b/crates/openshell-server/src/grpc/policy.rs @@ -2561,6 +2561,8 @@ async fn handle_update_config_inner( .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?; let sandbox_id = sandbox.object_id().to_string(); let mut response_annotations = sandbox_metadata_annotations(&sandbox); @@ -3338,6 +3340,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 @@ -3724,6 +3728,8 @@ async fn handle_approve_draft_chunk_inner( .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?; let sandbox_id = sandbox.object_id().to_string(); let chunk = state @@ -3847,6 +3853,8 @@ async fn handle_reject_draft_chunk_inner( .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?; let sandbox_id = sandbox.object_id().to_string(); let chunk = state @@ -3956,6 +3964,8 @@ async fn handle_approve_all_draft_chunks_inner( .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?; let sandbox_id = sandbox.object_id().to_string(); let pending_chunks = state @@ -4127,6 +4137,8 @@ pub(super) async fn handle_edit_draft_chunk( .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?; let sandbox_id = sandbox.object_id().to_string(); let chunk = state @@ -4200,6 +4212,8 @@ async fn handle_undo_draft_chunk_inner( .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?; let sandbox_id = sandbox.object_id().to_string(); let chunk = state @@ -4294,6 +4308,8 @@ pub(super) async fn handle_clear_draft_chunks( .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?; let sandbox_id = sandbox.object_id().to_string(); let deleted = state diff --git a/crates/openshell-server/src/grpc/provider.rs b/crates/openshell-server/src/grpc/provider.rs index b41db613a3..bfa4d843f0 100644 --- a/crates/openshell-server/src/grpc/provider.rs +++ b/crates/openshell-server/src/grpc/provider.rs @@ -3441,23 +3441,46 @@ pub(super) async fn handle_exchange_provider_subject_token( .subject_token .as_ref() .ok_or_else(|| Status::failed_precondition("token_exchange subject_token is missing"))?; - if subject_token.source != "provider_credential" { - return Err(Status::failed_precondition( - "unsupported subject_token source", - )); - } - if !profile_proto - .credentials - .iter() - .any(|credential| credential.name == subject_token.credential) - { - return Err(Status::failed_precondition( - "subject token credential not declared by provider profile", - )); - } - 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(Status::failed_precondition( + "subject token credential not declared by provider profile", + )); + } + ( + resolve_subject_token_credential( + &state.credentials, + &provider, + &subject_token.credential, + ) + .await?, + provider_credential_expires_at_ms(&provider, &subject_token.credential), + subject_token.credential.clone(), + ) + } + "sandbox_delegated_identity" => { + if !subject_token.credential.trim().is_empty() { + return Err(Status::failed_precondition( + "sandbox_delegated_identity subject_token must not set credential", + )); + } + 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(Status::failed_precondition( + "unsupported subject_token source", + )); + } + }; let jwt_svid_audience = effective_jwt_svid_audience(&token_grant.token_endpoint, &token_grant.jwt_svid_audience); @@ -3474,7 +3497,7 @@ pub(super) async fn handle_exchange_provider_subject_token( 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), @@ -3506,7 +3529,7 @@ pub(super) async fn handle_exchange_provider_subject_token( sandbox_id = %req.sandbox_id, provider = %req.provider, credential_key = %req.credential_key, - subject_credential = %subject_token.credential, + 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, @@ -3522,7 +3545,7 @@ pub(super) async fn handle_exchange_provider_subject_token( let cache_expires_at_ms = intermediate_token_cache_expires_at_ms( &token_response, token_grant.cache_ttl_seconds, - provider_credential_expires_at_ms(&provider, &subject_token.credential), + subject_token_expires_at_ms, supervisor_claims.exp, ); if cache_expires_at_ms > crate::persistence::current_time_ms() { diff --git a/crates/openshell-server/src/grpc/sandbox.rs b/crates/openshell-server/src/grpc/sandbox.rs index 9338956950..2b56203290 100644 --- a/crates/openshell-server/src/grpc/sandbox.rs +++ b/crates/openshell-server/src/grpc/sandbox.rs @@ -11,7 +11,8 @@ use crate::ServerState; use crate::auth::workspace_authz::{ - MinWorkspaceRole, authorize_sandbox_workspace, authorize_workspace, require_platform_admin, + AuthGrant, MinWorkspaceRole, authorize_sandbox_workspace, authorize_workspace, + require_platform_admin, }; use crate::persistence::{ObjectLabels, ObjectType, WriteCondition, generate_name}; use futures::future; @@ -110,9 +111,12 @@ impl Drop for WatchSandboxStream { } } -/// Fetch a sandbox by ID and authorize the caller in one step, returning -/// `NOT_FOUND` for both missing and unauthorized sandboxes so that callers -/// cannot distinguish the two cases (CWE-203). +/// Fetch a sandbox by ID and authorize the caller in one step. +/// +/// Workspace RBAC denials are normalized to `NOT_FOUND`, matching the legacy +/// cross-workspace behavior. Delegated-identity denials remain +/// `PERMISSION_DENIED`: workspace users may see the sandbox, but only the +/// delegating principal may perform delegated-identity-sensitive operations. pub(super) async fn fetch_and_authorize_sandbox( state: &Arc, principal: &crate::auth::principal::Principal, @@ -139,6 +143,8 @@ pub(super) async fn fetch_and_authorize_sandbox( e } })?; + crate::delegated_identity::ensure_delegated_identity_sandbox_user(state, principal, &sandbox) + .await?; Ok(sandbox) } @@ -217,6 +223,7 @@ async fn handle_create_sandbox_inner( ) -> Result, Status> { let principal = super::extract_principal(&request)?; let request = request.into_inner(); + let delegated_identity_request = request.delegated_identity.clone(); let spec = request .spec .ok_or_else(|| Status::invalid_argument("spec is required"))?; @@ -325,6 +332,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?; + // Mint the gateway JWT for singleplayer drivers. K8s sandboxes skip // this mint and bootstrap via `IssueSandboxToken` at supervisor // startup; identifying "is this K8s?" lives in the compute layer, so @@ -345,7 +360,59 @@ async fn handle_create_sandbox_inner( None => None, }; - let sandbox = state.compute.create_sandbox(sandbox, sandbox_token).await?; + if let Err(status) = crate::delegated_identity::store_prepared_sandbox_delegation( + state, + delegated_identity.as_ref(), + ) + .await + { + if let Err(cleanup_status) = + crate::delegated_identity::delete_new_prepared_sandbox_credential( + state, + delegated_identity.as_ref(), + ) + .await + { + warn!( + sandbox_id = %id, + error = %cleanup_status, + "failed to clean up prepared delegated credential after sandbox delegation persist failure" + ); + } + return Err(status); + } + let sandbox = match state.compute.create_sandbox(sandbox, sandbox_token).await { + Ok(sandbox) => sandbox, + Err(status) => { + if let Err(cleanup_status) = + crate::delegated_identity::delete_prepared_sandbox_delegation( + state, + delegated_identity.as_ref(), + ) + .await + { + warn!( + sandbox_id = %id, + error = %cleanup_status, + "failed to clean up prepared delegated identity after sandbox create failure" + ); + } + if let Err(cleanup_status) = + crate::delegated_identity::delete_new_prepared_sandbox_credential( + state, + delegated_identity.as_ref(), + ) + .await + { + warn!( + sandbox_id = %id, + error = %cleanup_status, + "failed to clean up prepared delegated credential after sandbox create failure" + ); + } + return Err(status); + } + }; info!( sandbox_id = %id, @@ -475,6 +542,8 @@ pub(super) async fn handle_list_sandbox_providers( .await? .name; let sandbox = sandbox_by_name(state, &workspace, &req.sandbox_name).await?; + crate::delegated_identity::ensure_delegated_identity_sandbox_user(state, &principal, &sandbox) + .await?; let providers = providers_for_sandbox(state, &sandbox, &workspace).await?; Ok(Response::new(ListSandboxProvidersResponse { providers })) } @@ -525,6 +594,8 @@ pub(super) async fn handle_attach_sandbox_provider( let _sandbox_sync_guard = state.compute.sandbox_sync_guard().await; let sandbox = sandbox_by_name(state, &workspace, &request.sandbox_name).await?; + crate::delegated_identity::ensure_delegated_identity_sandbox_user(state, &principal, &sandbox) + .await?; let sandbox_id = sandbox .metadata .as_ref() @@ -648,6 +719,8 @@ pub(super) async fn handle_detach_sandbox_provider( let _sandbox_sync_guard = state.compute.sandbox_sync_guard().await; let sandbox = sandbox_by_name(state, &workspace, &request.sandbox_name).await?; + crate::delegated_identity::ensure_delegated_identity_sandbox_user(state, &principal, &sandbox) + .await?; let sandbox_id = sandbox .metadata .as_ref() @@ -753,6 +826,16 @@ async fn handle_delete_sandbox_inner( let workspace = super::workspace::resolve_workspace(state.store.as_ref(), &authz.workspace) .await? .name; + let sandbox = sandbox_by_name(state, &workspace, &name).await?; + 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(&workspace, &name).await?; if result.deleted { @@ -1784,6 +1867,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?; let resource_version = session .metadata @@ -2394,10 +2485,15 @@ 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::grpc::test_support::{ authed_request, test_server_state, test_server_state_with_driver, }; use openshell_core::proto::datamodel::v1::ObjectMeta; + use openshell_core::proto::{ + SandboxDelegatedIdentity, SandboxDelegatedIdentityRecord, WorkspaceMember, WorkspaceRole, + }; // ---- shell_escape ---- @@ -2747,6 +2843,100 @@ mod tests { sandbox } + async fn put_delegated_test_sandbox( + state: &Arc, + name: &str, + principal_subject: &str, + ) { + let sandbox = test_sandbox(name, Vec::new()); + 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_at_ms: 1_000_000, + labels: HashMap::new(), + resource_version: 0, + annotations: HashMap::new(), + workspace, + deletion_timestamp_ms: 0, + }), + sandbox_id: sandbox_id.clone(), + delegated_identity: Some(SandboxDelegatedIdentity { + credential_id: "delegated-credential".to_string(), + principal_subject: principal_subject.to_string(), + delegated_until_ms: 2_000_000, + withdrawn_at_ms: 0, + }), + }, + &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_at_ms: 1_000_000, + labels: HashMap::new(), + resource_version: 0, + annotations: HashMap::new(), + workspace: "default".to_string(), + deletion_timestamp_ms: 0, + }), + 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] #[ignore = "flaky under concurrent test execution"] async fn watch_producer_releases_request_span_when_client_disconnects() { @@ -3269,6 +3459,7 @@ mod tests { labels: HashMap::new(), annotations: HashMap::new(), workspace: String::new(), + delegated_identity: None, }), ) .await @@ -3303,6 +3494,7 @@ mod tests { labels: HashMap::new(), annotations: HashMap::new(), workspace: String::new(), + delegated_identity: None, }), ) .await @@ -3327,6 +3519,7 @@ mod tests { labels: HashMap::new(), annotations: HashMap::from([(annotation_key.clone(), annotation_value.clone())]), workspace: String::new(), + delegated_identity: None, }), ) .await @@ -3387,6 +3580,7 @@ mod tests { labels: HashMap::new(), annotations: HashMap::new(), workspace: String::new(), + delegated_identity: None, }), ) .await @@ -3452,6 +3646,7 @@ mod tests { labels: HashMap::new(), annotations: HashMap::new(), workspace: String::new(), + delegated_identity: None, }), ) .await @@ -3482,6 +3677,7 @@ mod tests { labels: HashMap::from([("team".to_string(), "x".repeat(512))]), annotations: HashMap::new(), workspace: String::new(), + delegated_identity: None, }), ) .await @@ -3514,6 +3710,7 @@ mod tests { labels: HashMap::new(), annotations: HashMap::new(), workspace: String::new(), + delegated_identity: None, }), ) .await @@ -3810,6 +4007,213 @@ mod tests { assert!(session2.is_some()); } + #[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_id: "sandbox-work".to_string(), + }), + "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_id: "sandbox-work".to_string(), + }), + "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: "default".to_string(), + }), + "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 { + limit: 100, + offset: 0, + label_selector: String::new(), + workspace: "default".to_string(), + all_workspaces: false, + }), + "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_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: "default".to_string(), + }), + "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: "default".to_string(), + }), + "bob", + ), + ) + .await + .expect("workspace admin should be allowed to delete delegated sandbox") + .into_inner(); + + assert!(response.deleted); + } + + #[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: "default".to_string(), + }), + "bob", + &["openshell-admin"], + ), + ) + .await + .expect("platform admin should be allowed to delete delegated sandbox") + .into_inner(); + + assert!(response.deleted); + } + + #[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_id: "sandbox-work".to_string(), + }), + "alice", + ), + ) + .await + .unwrap() + .into_inner() + .token; + + let err = handle_revoke_ssh_session( + &state, + user_request( + Request::new(RevokeSshSessionRequest { + token: token.clone(), + }), + "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 790e26d618..6da17e1072 100644 --- a/crates/openshell-server/src/grpc/service.rs +++ b/crates/openshell-server/src/grpc/service.rs @@ -51,6 +51,8 @@ pub(super) async fn handle_expose_service( .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?; let now = crate::persistence::current_time_ms(); let key = service_routing::endpoint_key(&req.sandbox, &req.service); @@ -255,6 +257,14 @@ pub(super) async fn handle_delete_service( let Some(endpoint) = endpoint else { return Ok(Response::new(DeleteServiceResponse { deleted: false })); }; + 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?; let key = service_routing::endpoint_key(&req.sandbox, &req.service); let deleted = state diff --git a/crates/openshell-server/src/lib.rs b/crates/openshell-server/src/lib.rs index 107e79e36a..5382b6df7e 100644 --- a/crates/openshell-server/src/lib.rs +++ b/crates/openshell-server/src/lib.rs @@ -30,6 +30,7 @@ mod compute; pub mod config_file; mod credentials; mod defaults; +mod delegated_identity; mod gateway_listener; mod grpc; mod http; diff --git a/crates/openshell-server/tests/common/mod.rs b/crates/openshell-server/tests/common/mod.rs index e5377e3d1f..d250482ec8 100644 --- a/crates/openshell-server/tests/common/mod.rs +++ b/crates/openshell-server/tests/common/mod.rs @@ -98,6 +98,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, @@ -121,6 +145,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 0a7d9fe28d..8e531037d0 100644 --- a/crates/openshell-server/tests/supervisor_relay_integration.rs +++ b/crates/openshell-server/tests/supervisor_relay_integration.rs @@ -85,6 +85,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 15aea6fccf..210c86f9c8 100644 --- a/crates/openshell-supervisor-network/src/token_grant.rs +++ b/crates/openshell-supervisor-network/src/token_grant.rs @@ -60,6 +60,7 @@ static TOKEN_GRANT_HTTP_CLIENT: LazyLock = LazyLock::new(|| { const DEFAULT_TOKEN_CACHE_TTL_SECONDS: i64 = 300; const TOKEN_CACHE_EXPIRY_SKEW_SECONDS: i64 = 30; const MAX_TOKEN_EXPIRES_IN_SECONDS: i64 = 3600; +const MAX_TOKEN_EXCHANGE_CACHE_TTL_SECONDS: i64 = 300; /// Cached access token with expiration metadata. #[derive(Debug, Clone)] @@ -269,8 +270,11 @@ where let token_response = grant(jwt_audience).await?; - let cache_ttl_seconds = - token_cache_ttl_seconds(input.cache_ttl_override, token_response.expires_in); + let cache_ttl_seconds = token_cache_ttl_seconds( + input.cache_ttl_override, + token_response.expires_in, + input.grant_type, + ); let expires_at_ms = current_time_ms().saturating_add(cache_ttl_seconds.saturating_mul(1000)); input.cache.set( @@ -351,7 +355,11 @@ async fn perform_token_exchange( pub use oauth::validate_access_token; -fn token_cache_ttl_seconds(cache_ttl_override: i64, expires_in: i64) -> i64 { +fn token_cache_ttl_seconds( + cache_ttl_override: i64, + expires_in: i64, + grant_type: ProviderCredentialTokenGrantType, +) -> i64 { if cache_ttl_override > 0 { return cache_ttl_override; } @@ -361,10 +369,22 @@ fn token_cache_ttl_seconds(cache_ttl_override: i64, expires_in: i64) -> i64 { } else { DEFAULT_TOKEN_CACHE_TTL_SECONDS }; + let ttl = token_cache_ttl_cap_for_grant_type(ttl, grant_type); ttl.saturating_sub(TOKEN_CACHE_EXPIRY_SKEW_SECONDS).max(1) } +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: @@ -660,6 +680,7 @@ mod tests { scopes: &'a [String], cache_ttl_override: i64, expires_in: i64, + grant_type: ProviderCredentialTokenGrantType, grant_calls: Arc, } @@ -674,7 +695,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 |_| { @@ -692,26 +713,29 @@ 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: i64, - ) -> Result { + 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 { Err(miette::miette!("grant should not be called on cache hit")) }, @@ -850,24 +874,44 @@ mod tests { #[test] fn token_cache_ttl_uses_override_without_endpoint_skew() { - assert_eq!(token_cache_ttl_seconds(120, 10), 120); - assert_eq!(token_cache_ttl_seconds(120, i64::MAX), 120); + assert_eq!( + token_cache_ttl_seconds(120, 10, ProviderCredentialTokenGrantType::ClientCredentials), + 120 + ); + assert_eq!( + token_cache_ttl_seconds( + 120, + i64::MAX, + ProviderCredentialTokenGrantType::ClientCredentials, + ), + 120 + ); } #[test] fn token_cache_ttl_skews_default_and_response_expires_in() { assert_eq!( - token_cache_ttl_seconds(0, 0), + token_cache_ttl_seconds(0, 0, ProviderCredentialTokenGrantType::ClientCredentials), DEFAULT_TOKEN_CACHE_TTL_SECONDS - TOKEN_CACHE_EXPIRY_SKEW_SECONDS ); - assert_eq!(token_cache_ttl_seconds(0, 60), 30); - assert_eq!(token_cache_ttl_seconds(0, 10), 1); + assert_eq!( + token_cache_ttl_seconds(0, 60, ProviderCredentialTokenGrantType::ClientCredentials), + 30 + ); + assert_eq!( + token_cache_ttl_seconds(0, 10, ProviderCredentialTokenGrantType::ClientCredentials), + 1 + ); } #[test] fn token_cache_ttl_clamps_large_response_expires_in() { assert_eq!( - token_cache_ttl_seconds(0, i64::MAX), + token_cache_ttl_seconds( + 0, + i64::MAX, + ProviderCredentialTokenGrantType::ClientCredentials, + ), MAX_TOKEN_EXPIRES_IN_SECONDS - TOKEN_CACHE_EXPIRY_SKEW_SECONDS ); } @@ -887,19 +931,21 @@ mod tests { scopes: &scopes, cache_ttl_override: 0, 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, - 0, - ) + 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: 0, + grant_type: ProviderCredentialTokenGrantType::ClientCredentials, + }) .await .expect("second call should use cache"); @@ -908,6 +954,64 @@ 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: 0, + 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: 0, + 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_seconds(0, i64::MAX, ProviderCredentialTokenGrantType::TokenExchange,), + MAX_TOKEN_EXCHANGE_CACHE_TTL_SECONDS - TOKEN_CACHE_EXPIRY_SKEW_SECONDS + ); + assert_eq!( + token_cache_ttl_seconds( + 120, + i64::MAX, + ProviderCredentialTokenGrantType::TokenExchange, + ), + 120 + ); + assert_eq!( + token_cache_ttl_seconds( + 600, + i64::MAX, + ProviderCredentialTokenGrantType::TokenExchange, + ), + 600 + ); + } + #[tokio::test] async fn obtain_provider_token_separates_cache_by_audience_and_scopes() { let cache = TokenCache::new(); @@ -924,6 +1028,7 @@ mod tests { scopes: &read_scope, cache_ttl_override: 0, expires_in: 60, + grant_type: ProviderCredentialTokenGrantType::ClientCredentials, grant_calls: grant_calls.clone(), }) .await @@ -937,6 +1042,7 @@ mod tests { scopes: &read_scope, cache_ttl_override: 0, expires_in: 60, + grant_type: ProviderCredentialTokenGrantType::ClientCredentials, grant_calls: grant_calls.clone(), }) .await @@ -950,6 +1056,7 @@ mod tests { scopes: &write_scope, cache_ttl_override: 0, expires_in: 60, + grant_type: ProviderCredentialTokenGrantType::ClientCredentials, grant_calls: grant_calls.clone(), }) .await @@ -996,6 +1103,7 @@ mod tests { scopes: &scopes, cache_ttl_override: 0, expires_in: 60, + grant_type: ProviderCredentialTokenGrantType::ClientCredentials, grant_calls: grant_calls.clone(), }) .await @@ -1020,19 +1128,21 @@ mod tests { scopes: &scopes, cache_ttl_override: 60, 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, - 60, - ) + 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: 60, + grant_type: ProviderCredentialTokenGrantType::ClientCredentials, + }) .await .expect("override should keep token cached"); diff --git a/crates/openshell-tui/src/lib.rs b/crates/openshell-tui/src/lib.rs index 1f610015b4..3d5bda3be3 100644 --- a/crates/openshell-tui/src/lib.rs +++ b/crates/openshell-tui/src/lib.rs @@ -1407,6 +1407,7 @@ fn spawn_create_sandbox(app: &mut App, tx: mpsc::UnboundedSender) { labels: HashMap::new(), annotations: HashMap::new(), workspace: workspace.clone(), + delegated_identity: None, }; let sandbox_name = diff --git a/docs/reference/gateway-config.mdx b/docs/reference/gateway-config.mdx index 9c9e7f8d36..b65d9b6cc4 100644 --- a/docs/reference/gateway-config.mdx +++ b/docs/reference/gateway-config.mdx @@ -85,6 +85,8 @@ credential_drivers = ["kubernetes-secrets"] sandbox_namespace = "openshell" ssh_session_ttl_secs = 3600 +# Maximum sandbox delegated identity window, in seconds. +max_delegated_identity_duration_secs = 86400 # Reject invalid policy generations securely by default. Set # "retain_last_valid" only when availability takes priority. diff --git a/docs/sandboxes/providers-v2.mdx b/docs/sandboxes/providers-v2.mdx index c01b9963b8..570605c97c 100644 --- a/docs/sandboxes/providers-v2.mdx +++ b/docs/sandboxes/providers-v2.mdx @@ -516,7 +516,16 @@ openshell provider create \ --runtime-credentials ``` -For `token_exchange` profiles, the provider also stores the user subject token referenced by `token_grant.subject_token.credential`. 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. +Use `source: provider_credential` when the provider record should hold a subject +credential named by `token_grant.subject_token.credential`. 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 \ @@ -530,6 +539,56 @@ 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. +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 +window: + +```shell +openshell provider create \ + --name protected-services \ + --type protected-services-profile \ + --runtime-credentials + +openshell sandbox create \ + --provider protected-services \ + --delegate-identity-for=8h +``` + +Delegation must be enabled when the sandbox is created. A sandbox created +without `--delegate-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 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. + +OpenShell stores the delegated refresh token on the gateway and refreshes the +subject access token automatically while the IdP continues to accept that refresh +token. For long-running delegated identity, configure the gateway OIDC client to +issue non-session-bound refresh tokens, such as with `offline_access` when your +IdP supports it. If the IdP rejects refresh with `invalid_grant` or `Session not +active`, re-authenticate locally with `openshell gateway logout` followed by +`openshell gateway login`, then run `openshell sandbox delegated-identity extend` +to replace the gateway's stored delegated credential. + +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 +``` + Token grant fields: | Field | Required | Behavior | @@ -542,7 +601,7 @@ Token grant fields: | `scopes` | No | OAuth2 scopes sent as a space-separated `scope` parameter. | | `cache_ttl_seconds` | No | Token cache TTL override. When omitted or `0`, OpenShell uses the token response `expires_in` with a 30-second safety margin and one-hour cap, or five minutes minus the margin if the response does not include an expiry. | | `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. | +| `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. | | `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/proto/openshell.proto b/proto/openshell.proto index 655e08e802..43d5aca1d7 100644 --- a/proto/openshell.proto +++ b/proto/openshell.proto @@ -51,6 +51,36 @@ service OpenShell { }; } + // 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) = { @@ -325,6 +355,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) { @@ -798,9 +868,21 @@ message Sandbox { SandboxSpec spec = 2; // Latest user-facing observed status derived by the gateway. SandboxStatus status = 3; + reserved 4, 5, 6; + reserved "phase", "current_policy_version", "delegated_identity"; +} + +message SandboxDelegatedIdentity { + string credential_id = 1; + string principal_subject = 2; + int64 delegated_until_ms = 3; + int64 withdrawn_at_ms = 4; +} - reserved 4, 5; - reserved "phase", "current_policy_version"; +message SandboxDelegatedIdentityRecord { + openshell.datamodel.v1.ObjectMeta metadata = 1; + string sandbox_id = 2; + SandboxDelegatedIdentity delegated_identity = 3; } // Desired sandbox configuration provided through the public API. @@ -948,6 +1030,54 @@ message CreateSandboxRequest { map annotations = 4; // Workspace for the sandbox. Empty defaults to "default". string workspace = 5; + // Optional gateway-owned delegated identity material. The server persists + // this as a gateway-scoped credential and stores only delegation metadata on + // the sandbox. + DelegatedIdentityRequest delegated_identity = 6; +} + +message DelegatedIdentityRequest { + int64 delegated_until_ms = 1; + string issuer = 2; + string client_id = 3; + string refresh_token = 4 [(openshell.options.v1.secret) = true]; + string access_token = 5 [(openshell.options.v1.secret) = true]; + reserved 6; + reserved "access_token_expires_at_ms"; + string scopes = 7; + string audience = 8; +} + +message GetSandboxDelegatedIdentityStatusRequest { + string name = 1; + string workspace = 2; +} + +message GetSandboxDelegatedIdentityStatusResponse { + SandboxDelegatedIdentity delegated_identity = 1; + int64 now_ms = 2; + int64 credential_revoked_at_ms = 3; + bool credential_missing = 4; +} + +message WithdrawSandboxDelegatedIdentityRequest { + string name = 1; + string workspace = 2; +} + +message WithdrawSandboxDelegatedIdentityResponse { + Sandbox sandbox = 1; + bool withdrawn = 2; +} + +message ExtendSandboxDelegatedIdentityRequest { + string name = 1; + string workspace = 2; + DelegatedIdentityRequest delegated_identity = 3; +} + +message ExtendSandboxDelegatedIdentityResponse { + Sandbox sandbox = 1; } // Get sandbox request. @@ -1635,6 +1765,73 @@ message StoredProviderCredentialRefreshState { map additional_output_keys = 17; } +message DelegatedIdentityCredential { + openshell.datamodel.v1.ObjectMeta metadata = 1; + string issuer = 2; + string client_id = 3; + string principal_subject = 4; + string refresh_token = 5 [(openshell.options.v1.secret) = true]; + string access_token = 6 [(openshell.options.v1.secret) = true]; + int64 access_token_expires_at_ms = 7; + string scopes = 8; + string audience = 9; + int64 last_refresh_at_ms = 10; + int64 revoked_at_ms = 11; +} + +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; + int64 access_token_expires_at_ms = 7; + string scopes = 8; + string audience = 9; + int64 last_refresh_at_ms = 10; + int64 revoked_at_ms = 11; +} + +message ListDelegatedIdentityCredentialsRequest { + uint32 limit = 1; + uint32 offset = 2; +} + +message ListDelegatedIdentityCredentialsResponse { + repeated DelegatedIdentityCredentialSummary credentials = 1; +} + +message GetDelegatedIdentityCredentialStatusRequest { + string id = 1; +} + +message GetDelegatedIdentityCredentialStatusResponse { + DelegatedIdentityCredentialSummary credential = 1; + int64 now_ms = 2; +} + +message RevokeDelegatedIdentityCredentialRequest { + string id = 1; + uint64 expected_resource_version = 2; +} + +message RevokeDelegatedIdentityCredentialResponse { + reserved 1; + bool revoked = 2; + int64 revoked_at_ms = 3; + uint64 resource_version = 4; +} + +message DeleteDelegatedIdentityCredentialRequest { + string id = 1; + uint64 expected_resource_version = 2; +} + +message DeleteDelegatedIdentityCredentialResponse { + bool deleted = 1; +} + message GetProviderRefreshStatusRequest { string provider = 1; string credential_key = 2; diff --git a/sdk/go/proto/openshellv1/openshell.pb.go b/sdk/go/proto/openshellv1/openshell.pb.go index 7335718fd5..6d71b56d7e 100644 --- a/sdk/go/proto/openshellv1/openshell.pb.go +++ b/sdk/go/proto/openshellv1/openshell.pb.go @@ -1148,6 +1148,134 @@ func (x *Sandbox) GetStatus() *SandboxStatus { 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"` + DelegatedUntilMs int64 `protobuf:"varint,3,opt,name=delegated_until_ms,json=delegatedUntilMs,proto3" json:"delegated_until_ms,omitempty"` + WithdrawnAtMs int64 `protobuf:"varint,4,opt,name=withdrawn_at_ms,json=withdrawnAtMs,proto3" json:"withdrawn_at_ms,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SandboxDelegatedIdentity) Reset() { + *x = SandboxDelegatedIdentity{} + mi := &file_openshell_proto_msgTypes[13] + 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[13] + 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{13} +} + +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) GetDelegatedUntilMs() int64 { + if x != nil { + return x.DelegatedUntilMs + } + return 0 +} + +func (x *SandboxDelegatedIdentity) GetWithdrawnAtMs() int64 { + if x != nil { + return x.WithdrawnAtMs + } + return 0 +} + +type SandboxDelegatedIdentityRecord struct { + state protoimpl.MessageState `protogen:"open.v1"` + Metadata *datamodelv1.ObjectMeta `protobuf:"bytes,1,opt,name=metadata,proto3" json:"metadata,omitempty"` + SandboxId string `protobuf:"bytes,2,opt,name=sandbox_id,json=sandboxId,proto3" json:"sandbox_id,omitempty"` + DelegatedIdentity *SandboxDelegatedIdentity `protobuf:"bytes,3,opt,name=delegated_identity,json=delegatedIdentity,proto3" json:"delegated_identity,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SandboxDelegatedIdentityRecord) Reset() { + *x = SandboxDelegatedIdentityRecord{} + mi := &file_openshell_proto_msgTypes[14] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SandboxDelegatedIdentityRecord) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SandboxDelegatedIdentityRecord) ProtoMessage() {} + +func (x *SandboxDelegatedIdentityRecord) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[14] + 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 SandboxDelegatedIdentityRecord.ProtoReflect.Descriptor instead. +func (*SandboxDelegatedIdentityRecord) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{14} +} + +func (x *SandboxDelegatedIdentityRecord) GetMetadata() *datamodelv1.ObjectMeta { + if x != nil { + return x.Metadata + } + return nil +} + +func (x *SandboxDelegatedIdentityRecord) GetSandboxId() string { + if x != nil { + return x.SandboxId + } + return "" +} + +func (x *SandboxDelegatedIdentityRecord) GetDelegatedIdentity() *SandboxDelegatedIdentity { + if x != nil { + return x.DelegatedIdentity + } + return nil +} + // Desired sandbox configuration provided through the public API. type SandboxSpec struct { state protoimpl.MessageState `protogen:"open.v1"` @@ -1170,7 +1298,7 @@ type SandboxSpec struct { func (x *SandboxSpec) Reset() { *x = SandboxSpec{} - mi := &file_openshell_proto_msgTypes[13] + mi := &file_openshell_proto_msgTypes[15] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1182,7 +1310,7 @@ func (x *SandboxSpec) String() string { func (*SandboxSpec) ProtoMessage() {} func (x *SandboxSpec) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[13] + mi := &file_openshell_proto_msgTypes[15] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1195,7 +1323,7 @@ func (x *SandboxSpec) ProtoReflect() protoreflect.Message { // Deprecated: Use SandboxSpec.ProtoReflect.Descriptor instead. func (*SandboxSpec) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{13} + return file_openshell_proto_rawDescGZIP(), []int{15} } func (x *SandboxSpec) GetLogLevel() string { @@ -1250,7 +1378,7 @@ type ResourceRequirements struct { func (x *ResourceRequirements) Reset() { *x = ResourceRequirements{} - mi := &file_openshell_proto_msgTypes[14] + mi := &file_openshell_proto_msgTypes[16] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1262,7 +1390,7 @@ func (x *ResourceRequirements) String() string { func (*ResourceRequirements) ProtoMessage() {} func (x *ResourceRequirements) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[14] + mi := &file_openshell_proto_msgTypes[16] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1275,7 +1403,7 @@ func (x *ResourceRequirements) ProtoReflect() protoreflect.Message { // Deprecated: Use ResourceRequirements.ProtoReflect.Descriptor instead. func (*ResourceRequirements) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{14} + return file_openshell_proto_rawDescGZIP(), []int{16} } func (x *ResourceRequirements) GetGpu() *GpuResourceRequirements { @@ -1297,7 +1425,7 @@ type GpuResourceRequirements struct { func (x *GpuResourceRequirements) Reset() { *x = GpuResourceRequirements{} - mi := &file_openshell_proto_msgTypes[15] + mi := &file_openshell_proto_msgTypes[17] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1309,7 +1437,7 @@ func (x *GpuResourceRequirements) String() string { func (*GpuResourceRequirements) ProtoMessage() {} func (x *GpuResourceRequirements) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[15] + mi := &file_openshell_proto_msgTypes[17] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1322,7 +1450,7 @@ func (x *GpuResourceRequirements) ProtoReflect() protoreflect.Message { // Deprecated: Use GpuResourceRequirements.ProtoReflect.Descriptor instead. func (*GpuResourceRequirements) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{15} + return file_openshell_proto_rawDescGZIP(), []int{17} } func (x *GpuResourceRequirements) GetCount() uint32 { @@ -1366,7 +1494,7 @@ type SandboxTemplate struct { func (x *SandboxTemplate) Reset() { *x = SandboxTemplate{} - mi := &file_openshell_proto_msgTypes[16] + mi := &file_openshell_proto_msgTypes[18] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1378,7 +1506,7 @@ func (x *SandboxTemplate) String() string { func (*SandboxTemplate) ProtoMessage() {} func (x *SandboxTemplate) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[16] + mi := &file_openshell_proto_msgTypes[18] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1391,7 +1519,7 @@ func (x *SandboxTemplate) ProtoReflect() protoreflect.Message { // Deprecated: Use SandboxTemplate.ProtoReflect.Descriptor instead. func (*SandboxTemplate) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{16} + return file_openshell_proto_rawDescGZIP(), []int{18} } func (x *SandboxTemplate) GetImage() string { @@ -1482,7 +1610,7 @@ type SandboxStatus struct { func (x *SandboxStatus) Reset() { *x = SandboxStatus{} - mi := &file_openshell_proto_msgTypes[17] + mi := &file_openshell_proto_msgTypes[19] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1494,7 +1622,7 @@ func (x *SandboxStatus) String() string { func (*SandboxStatus) ProtoMessage() {} func (x *SandboxStatus) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[17] + mi := &file_openshell_proto_msgTypes[19] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1507,7 +1635,7 @@ func (x *SandboxStatus) ProtoReflect() protoreflect.Message { // Deprecated: Use SandboxStatus.ProtoReflect.Descriptor instead. func (*SandboxStatus) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{17} + return file_openshell_proto_rawDescGZIP(), []int{19} } func (x *SandboxStatus) GetSandboxName() string { @@ -1578,7 +1706,7 @@ type SandboxCondition struct { func (x *SandboxCondition) Reset() { *x = SandboxCondition{} - mi := &file_openshell_proto_msgTypes[18] + mi := &file_openshell_proto_msgTypes[20] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1590,7 +1718,7 @@ func (x *SandboxCondition) String() string { func (*SandboxCondition) ProtoMessage() {} func (x *SandboxCondition) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[18] + mi := &file_openshell_proto_msgTypes[20] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1603,7 +1731,7 @@ func (x *SandboxCondition) ProtoReflect() protoreflect.Message { // Deprecated: Use SandboxCondition.ProtoReflect.Descriptor instead. func (*SandboxCondition) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{18} + return file_openshell_proto_rawDescGZIP(), []int{20} } func (x *SandboxCondition) GetType() string { @@ -1662,7 +1790,7 @@ type PlatformEvent struct { func (x *PlatformEvent) Reset() { *x = PlatformEvent{} - mi := &file_openshell_proto_msgTypes[19] + mi := &file_openshell_proto_msgTypes[21] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1674,7 +1802,7 @@ func (x *PlatformEvent) String() string { func (*PlatformEvent) ProtoMessage() {} func (x *PlatformEvent) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[19] + mi := &file_openshell_proto_msgTypes[21] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1687,7 +1815,7 @@ func (x *PlatformEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use PlatformEvent.ProtoReflect.Descriptor instead. func (*PlatformEvent) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{19} + return file_openshell_proto_rawDescGZIP(), []int{21} } func (x *PlatformEvent) GetTimestampMs() int64 { @@ -1743,14 +1871,18 @@ type CreateSandboxRequest struct { // Optional annotations for the sandbox (non-selector metadata). Annotations map[string]string `protobuf:"bytes,4,rep,name=annotations,proto3" json:"annotations,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` // Workspace for the sandbox. Empty defaults to "default". - Workspace string `protobuf:"bytes,5,opt,name=workspace,proto3" json:"workspace,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + Workspace string `protobuf:"bytes,5,opt,name=workspace,proto3" json:"workspace,omitempty"` + // Optional gateway-owned delegated identity material. The server persists + // this as a gateway-scoped credential and stores only delegation metadata on + // the sandbox. + DelegatedIdentity *DelegatedIdentityRequest `protobuf:"bytes,6,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[20] + mi := &file_openshell_proto_msgTypes[22] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1762,7 +1894,7 @@ func (x *CreateSandboxRequest) String() string { func (*CreateSandboxRequest) ProtoMessage() {} func (x *CreateSandboxRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[20] + mi := &file_openshell_proto_msgTypes[22] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1775,7 +1907,7 @@ func (x *CreateSandboxRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use CreateSandboxRequest.ProtoReflect.Descriptor instead. func (*CreateSandboxRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{20} + return file_openshell_proto_rawDescGZIP(), []int{22} } func (x *CreateSandboxRequest) GetSpec() *SandboxSpec { @@ -1813,32 +1945,41 @@ func (x *CreateSandboxRequest) GetWorkspace() string { return "" } -// Get sandbox request. -type GetSandboxRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - // Sandbox name (canonical lookup key). - Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` - // Workspace scope. Empty defaults to "default". - Workspace string `protobuf:"bytes,2,opt,name=workspace,proto3" json:"workspace,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache +func (x *CreateSandboxRequest) GetDelegatedIdentity() *DelegatedIdentityRequest { + if x != nil { + return x.DelegatedIdentity + } + return nil } -func (x *GetSandboxRequest) Reset() { - *x = GetSandboxRequest{} - mi := &file_openshell_proto_msgTypes[21] +type DelegatedIdentityRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + DelegatedUntilMs int64 `protobuf:"varint,1,opt,name=delegated_until_ms,json=delegatedUntilMs,proto3" json:"delegated_until_ms,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"` + RefreshToken string `protobuf:"bytes,4,opt,name=refresh_token,json=refreshToken,proto3" json:"refresh_token,omitempty"` + AccessToken string `protobuf:"bytes,5,opt,name=access_token,json=accessToken,proto3" json:"access_token,omitempty"` + Scopes string `protobuf:"bytes,7,opt,name=scopes,proto3" json:"scopes,omitempty"` + Audience string `protobuf:"bytes,8,opt,name=audience,proto3" json:"audience,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DelegatedIdentityRequest) Reset() { + *x = DelegatedIdentityRequest{} + mi := &file_openshell_proto_msgTypes[23] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *GetSandboxRequest) String() string { +func (x *DelegatedIdentityRequest) String() string { return protoimpl.X.MessageStringOf(x) } -func (*GetSandboxRequest) ProtoMessage() {} +func (*DelegatedIdentityRequest) ProtoMessage() {} -func (x *GetSandboxRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[21] +func (x *DelegatedIdentityRequest) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[23] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1849,55 +1990,83 @@ func (x *GetSandboxRequest) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use GetSandboxRequest.ProtoReflect.Descriptor instead. -func (*GetSandboxRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{21} +// Deprecated: Use DelegatedIdentityRequest.ProtoReflect.Descriptor instead. +func (*DelegatedIdentityRequest) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{23} } -func (x *GetSandboxRequest) GetName() string { +func (x *DelegatedIdentityRequest) GetDelegatedUntilMs() int64 { if x != nil { - return x.Name + return x.DelegatedUntilMs + } + return 0 +} + +func (x *DelegatedIdentityRequest) GetIssuer() string { + if x != nil { + return x.Issuer } return "" } -func (x *GetSandboxRequest) GetWorkspace() string { +func (x *DelegatedIdentityRequest) GetClientId() string { if x != nil { - return x.Workspace + return x.ClientId } return "" } -// List sandboxes request. -type ListSandboxesRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - Limit uint32 `protobuf:"varint,1,opt,name=limit,proto3" json:"limit,omitempty"` - Offset uint32 `protobuf:"varint,2,opt,name=offset,proto3" json:"offset,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"` - // Workspace scope. Empty defaults to "default". - Workspace string `protobuf:"bytes,4,opt,name=workspace,proto3" json:"workspace,omitempty"` - // List across all workspaces. Mutually exclusive with workspace. - AllWorkspaces bool `protobuf:"varint,5,opt,name=all_workspaces,json=allWorkspaces,proto3" json:"all_workspaces,omitempty"` +func (x *DelegatedIdentityRequest) GetRefreshToken() string { + if x != nil { + return x.RefreshToken + } + return "" +} + +func (x *DelegatedIdentityRequest) GetAccessToken() string { + if x != nil { + return x.AccessToken + } + return "" +} + +func (x *DelegatedIdentityRequest) GetScopes() string { + if x != nil { + return x.Scopes + } + return "" +} + +func (x *DelegatedIdentityRequest) GetAudience() string { + if x != nil { + return x.Audience + } + return "" +} + +type GetSandboxDelegatedIdentityStatusRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + Workspace string `protobuf:"bytes,2,opt,name=workspace,proto3" json:"workspace,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } -func (x *ListSandboxesRequest) Reset() { - *x = ListSandboxesRequest{} - mi := &file_openshell_proto_msgTypes[22] +func (x *GetSandboxDelegatedIdentityStatusRequest) Reset() { + *x = GetSandboxDelegatedIdentityStatusRequest{} + mi := &file_openshell_proto_msgTypes[24] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *ListSandboxesRequest) String() string { +func (x *GetSandboxDelegatedIdentityStatusRequest) String() string { return protoimpl.X.MessageStringOf(x) } -func (*ListSandboxesRequest) ProtoMessage() {} +func (*GetSandboxDelegatedIdentityStatusRequest) ProtoMessage() {} -func (x *ListSandboxesRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[22] +func (x *GetSandboxDelegatedIdentityStatusRequest) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[24] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1908,72 +2077,50 @@ func (x *ListSandboxesRequest) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use ListSandboxesRequest.ProtoReflect.Descriptor instead. -func (*ListSandboxesRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{22} -} - -func (x *ListSandboxesRequest) GetLimit() uint32 { - if x != nil { - return x.Limit - } - return 0 -} - -func (x *ListSandboxesRequest) GetOffset() uint32 { - if x != nil { - return x.Offset - } - return 0 +// Deprecated: Use GetSandboxDelegatedIdentityStatusRequest.ProtoReflect.Descriptor instead. +func (*GetSandboxDelegatedIdentityStatusRequest) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{24} } -func (x *ListSandboxesRequest) GetLabelSelector() string { +func (x *GetSandboxDelegatedIdentityStatusRequest) GetName() string { if x != nil { - return x.LabelSelector + return x.Name } return "" } -func (x *ListSandboxesRequest) GetWorkspace() string { +func (x *GetSandboxDelegatedIdentityStatusRequest) GetWorkspace() string { if x != nil { return x.Workspace } return "" } -func (x *ListSandboxesRequest) GetAllWorkspaces() bool { - if x != nil { - return x.AllWorkspaces - } - return false -} - -// List providers attached to a sandbox request. -type ListSandboxProvidersRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - // Sandbox name (canonical lookup key). - SandboxName string `protobuf:"bytes,1,opt,name=sandbox_name,json=sandboxName,proto3" json:"sandbox_name,omitempty"` - // Workspace scope. Empty defaults to "default". - Workspace string `protobuf:"bytes,2,opt,name=workspace,proto3" json:"workspace,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"` + NowMs int64 `protobuf:"varint,2,opt,name=now_ms,json=nowMs,proto3" json:"now_ms,omitempty"` + CredentialRevokedAtMs int64 `protobuf:"varint,3,opt,name=credential_revoked_at_ms,json=credentialRevokedAtMs,proto3" json:"credential_revoked_at_ms,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 *ListSandboxProvidersRequest) Reset() { - *x = ListSandboxProvidersRequest{} - mi := &file_openshell_proto_msgTypes[23] +func (x *GetSandboxDelegatedIdentityStatusResponse) Reset() { + *x = GetSandboxDelegatedIdentityStatusResponse{} + mi := &file_openshell_proto_msgTypes[25] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *ListSandboxProvidersRequest) String() string { +func (x *GetSandboxDelegatedIdentityStatusResponse) String() string { return protoimpl.X.MessageStringOf(x) } -func (*ListSandboxProvidersRequest) ProtoMessage() {} +func (*GetSandboxDelegatedIdentityStatusResponse) ProtoMessage() {} -func (x *ListSandboxProvidersRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[23] +func (x *GetSandboxDelegatedIdentityStatusResponse) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[25] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1984,58 +2131,62 @@ func (x *ListSandboxProvidersRequest) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use ListSandboxProvidersRequest.ProtoReflect.Descriptor instead. -func (*ListSandboxProvidersRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{23} +// Deprecated: Use GetSandboxDelegatedIdentityStatusResponse.ProtoReflect.Descriptor instead. +func (*GetSandboxDelegatedIdentityStatusResponse) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{25} } -func (x *ListSandboxProvidersRequest) GetSandboxName() string { +func (x *GetSandboxDelegatedIdentityStatusResponse) GetDelegatedIdentity() *SandboxDelegatedIdentity { if x != nil { - return x.SandboxName + return x.DelegatedIdentity } - return "" + return nil } -func (x *ListSandboxProvidersRequest) GetWorkspace() string { +func (x *GetSandboxDelegatedIdentityStatusResponse) GetNowMs() int64 { if x != nil { - return x.Workspace + return x.NowMs } - return "" + return 0 } -// Attach provider to sandbox request. -type AttachSandboxProviderRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - // Sandbox name (canonical lookup key). - SandboxName string `protobuf:"bytes,1,opt,name=sandbox_name,json=sandboxName,proto3" json:"sandbox_name,omitempty"` - // Provider name to attach. - ProviderName string `protobuf:"bytes,2,opt,name=provider_name,json=providerName,proto3" json:"provider_name,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"` - // Workspace scope. Empty defaults to "default". - Workspace string `protobuf:"bytes,4,opt,name=workspace,proto3" json:"workspace,omitempty"` +func (x *GetSandboxDelegatedIdentityStatusResponse) GetCredentialRevokedAtMs() int64 { + if x != nil { + return x.CredentialRevokedAtMs + } + return 0 +} + +func (x *GetSandboxDelegatedIdentityStatusResponse) GetCredentialMissing() bool { + if x != nil { + return x.CredentialMissing + } + return false +} + +type WithdrawSandboxDelegatedIdentityRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + Workspace string `protobuf:"bytes,2,opt,name=workspace,proto3" json:"workspace,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } -func (x *AttachSandboxProviderRequest) Reset() { - *x = AttachSandboxProviderRequest{} - mi := &file_openshell_proto_msgTypes[24] +func (x *WithdrawSandboxDelegatedIdentityRequest) Reset() { + *x = WithdrawSandboxDelegatedIdentityRequest{} + mi := &file_openshell_proto_msgTypes[26] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *AttachSandboxProviderRequest) String() string { +func (x *WithdrawSandboxDelegatedIdentityRequest) String() string { return protoimpl.X.MessageStringOf(x) } -func (*AttachSandboxProviderRequest) ProtoMessage() {} +func (*WithdrawSandboxDelegatedIdentityRequest) ProtoMessage() {} -func (x *AttachSandboxProviderRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[24] +func (x *WithdrawSandboxDelegatedIdentityRequest) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[26] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2046,72 +2197,48 @@ func (x *AttachSandboxProviderRequest) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use AttachSandboxProviderRequest.ProtoReflect.Descriptor instead. -func (*AttachSandboxProviderRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{24} -} - -func (x *AttachSandboxProviderRequest) GetSandboxName() string { - if x != nil { - return x.SandboxName - } - return "" +// Deprecated: Use WithdrawSandboxDelegatedIdentityRequest.ProtoReflect.Descriptor instead. +func (*WithdrawSandboxDelegatedIdentityRequest) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{26} } -func (x *AttachSandboxProviderRequest) GetProviderName() string { +func (x *WithdrawSandboxDelegatedIdentityRequest) GetName() string { if x != nil { - return x.ProviderName + return x.Name } return "" } -func (x *AttachSandboxProviderRequest) GetExpectedResourceVersion() uint64 { - if x != nil { - return x.ExpectedResourceVersion - } - return 0 -} - -func (x *AttachSandboxProviderRequest) GetWorkspace() string { +func (x *WithdrawSandboxDelegatedIdentityRequest) GetWorkspace() string { if x != nil { return x.Workspace } return "" } -// Detach provider from sandbox request. -type DetachSandboxProviderRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - // Sandbox name (canonical lookup key). - SandboxName string `protobuf:"bytes,1,opt,name=sandbox_name,json=sandboxName,proto3" json:"sandbox_name,omitempty"` - // Provider name to detach. - ProviderName string `protobuf:"bytes,2,opt,name=provider_name,json=providerName,proto3" json:"provider_name,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"` - // Workspace scope. Empty defaults to "default". - Workspace string `protobuf:"bytes,4,opt,name=workspace,proto3" json:"workspace,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 *DetachSandboxProviderRequest) Reset() { - *x = DetachSandboxProviderRequest{} - mi := &file_openshell_proto_msgTypes[25] +func (x *WithdrawSandboxDelegatedIdentityResponse) Reset() { + *x = WithdrawSandboxDelegatedIdentityResponse{} + mi := &file_openshell_proto_msgTypes[27] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *DetachSandboxProviderRequest) String() string { +func (x *WithdrawSandboxDelegatedIdentityResponse) String() string { return protoimpl.X.MessageStringOf(x) } -func (*DetachSandboxProviderRequest) ProtoMessage() {} +func (*WithdrawSandboxDelegatedIdentityResponse) ProtoMessage() {} -func (x *DetachSandboxProviderRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[25] +func (x *WithdrawSandboxDelegatedIdentityResponse) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[27] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2122,65 +2249,49 @@ func (x *DetachSandboxProviderRequest) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use DetachSandboxProviderRequest.ProtoReflect.Descriptor instead. -func (*DetachSandboxProviderRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{25} -} - -func (x *DetachSandboxProviderRequest) GetSandboxName() string { - if x != nil { - return x.SandboxName - } - return "" -} - -func (x *DetachSandboxProviderRequest) GetProviderName() string { - if x != nil { - return x.ProviderName - } - return "" +// Deprecated: Use WithdrawSandboxDelegatedIdentityResponse.ProtoReflect.Descriptor instead. +func (*WithdrawSandboxDelegatedIdentityResponse) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{27} } -func (x *DetachSandboxProviderRequest) GetExpectedResourceVersion() uint64 { +func (x *WithdrawSandboxDelegatedIdentityResponse) GetSandbox() *Sandbox { if x != nil { - return x.ExpectedResourceVersion + return x.Sandbox } - return 0 + return nil } -func (x *DetachSandboxProviderRequest) GetWorkspace() string { +func (x *WithdrawSandboxDelegatedIdentityResponse) GetWithdrawn() bool { if x != nil { - return x.Workspace + return x.Withdrawn } - return "" + return false } -// Delete sandbox request. -type DeleteSandboxRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - // Sandbox name (canonical lookup key). - Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` - // Workspace scope. Empty defaults to "default". - Workspace string `protobuf:"bytes,2,opt,name=workspace,proto3" json:"workspace,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"` + Workspace string `protobuf:"bytes,2,opt,name=workspace,proto3" json:"workspace,omitempty"` + DelegatedIdentity *DelegatedIdentityRequest `protobuf:"bytes,3,opt,name=delegated_identity,json=delegatedIdentity,proto3" json:"delegated_identity,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } -func (x *DeleteSandboxRequest) Reset() { - *x = DeleteSandboxRequest{} - mi := &file_openshell_proto_msgTypes[26] +func (x *ExtendSandboxDelegatedIdentityRequest) Reset() { + *x = ExtendSandboxDelegatedIdentityRequest{} + mi := &file_openshell_proto_msgTypes[28] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *DeleteSandboxRequest) String() string { +func (x *ExtendSandboxDelegatedIdentityRequest) String() string { return protoimpl.X.MessageStringOf(x) } -func (*DeleteSandboxRequest) ProtoMessage() {} +func (*ExtendSandboxDelegatedIdentityRequest) ProtoMessage() {} -func (x *DeleteSandboxRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[26] +func (x *ExtendSandboxDelegatedIdentityRequest) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[28] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2191,51 +2302,54 @@ func (x *DeleteSandboxRequest) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use DeleteSandboxRequest.ProtoReflect.Descriptor instead. -func (*DeleteSandboxRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{26} +// Deprecated: Use ExtendSandboxDelegatedIdentityRequest.ProtoReflect.Descriptor instead. +func (*ExtendSandboxDelegatedIdentityRequest) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{28} } -func (x *DeleteSandboxRequest) GetName() string { +func (x *ExtendSandboxDelegatedIdentityRequest) GetName() string { if x != nil { return x.Name } return "" } -func (x *DeleteSandboxRequest) GetWorkspace() string { +func (x *ExtendSandboxDelegatedIdentityRequest) GetWorkspace() string { if x != nil { return x.Workspace } return "" } -// Stop sandbox request. -type StopSandboxRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - // Sandbox name (canonical lookup key). - Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` - // Workspace scope. Empty defaults to "default". - Workspace string `protobuf:"bytes,2,opt,name=workspace,proto3" json:"workspace,omitempty"` +func (x *ExtendSandboxDelegatedIdentityRequest) GetDelegatedIdentity() *DelegatedIdentityRequest { + if x != nil { + return x.DelegatedIdentity + } + return nil +} + +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 *StopSandboxRequest) Reset() { - *x = StopSandboxRequest{} - mi := &file_openshell_proto_msgTypes[27] +func (x *ExtendSandboxDelegatedIdentityResponse) Reset() { + *x = ExtendSandboxDelegatedIdentityResponse{} + mi := &file_openshell_proto_msgTypes[29] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *StopSandboxRequest) String() string { +func (x *ExtendSandboxDelegatedIdentityResponse) String() string { return protoimpl.X.MessageStringOf(x) } -func (*StopSandboxRequest) ProtoMessage() {} +func (*ExtendSandboxDelegatedIdentityResponse) ProtoMessage() {} -func (x *StopSandboxRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[27] +func (x *ExtendSandboxDelegatedIdentityResponse) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[29] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2246,27 +2360,20 @@ func (x *StopSandboxRequest) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use StopSandboxRequest.ProtoReflect.Descriptor instead. -func (*StopSandboxRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{27} -} - -func (x *StopSandboxRequest) GetName() string { - if x != nil { - return x.Name - } - return "" +// Deprecated: Use ExtendSandboxDelegatedIdentityResponse.ProtoReflect.Descriptor instead. +func (*ExtendSandboxDelegatedIdentityResponse) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{29} } -func (x *StopSandboxRequest) GetWorkspace() string { +func (x *ExtendSandboxDelegatedIdentityResponse) GetSandbox() *Sandbox { if x != nil { - return x.Workspace + return x.Sandbox } - return "" + return nil } -// Start sandbox request. -type StartSandboxRequest struct { +// Get sandbox request. +type GetSandboxRequest struct { state protoimpl.MessageState `protogen:"open.v1"` // Sandbox name (canonical lookup key). Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` @@ -2276,21 +2383,21 @@ type StartSandboxRequest struct { sizeCache protoimpl.SizeCache } -func (x *StartSandboxRequest) Reset() { - *x = StartSandboxRequest{} - mi := &file_openshell_proto_msgTypes[28] +func (x *GetSandboxRequest) Reset() { + *x = GetSandboxRequest{} + mi := &file_openshell_proto_msgTypes[30] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *StartSandboxRequest) String() string { +func (x *GetSandboxRequest) String() string { return protoimpl.X.MessageStringOf(x) } -func (*StartSandboxRequest) ProtoMessage() {} +func (*GetSandboxRequest) ProtoMessage() {} -func (x *StartSandboxRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[28] +func (x *GetSandboxRequest) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[30] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2301,48 +2408,55 @@ func (x *StartSandboxRequest) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use StartSandboxRequest.ProtoReflect.Descriptor instead. -func (*StartSandboxRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{28} +// Deprecated: Use GetSandboxRequest.ProtoReflect.Descriptor instead. +func (*GetSandboxRequest) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{30} } -func (x *StartSandboxRequest) GetName() string { +func (x *GetSandboxRequest) GetName() string { if x != nil { return x.Name } return "" } -func (x *StartSandboxRequest) GetWorkspace() string { +func (x *GetSandboxRequest) GetWorkspace() string { if x != nil { return x.Workspace } return "" } -// Sandbox response. -type SandboxResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - Sandbox *Sandbox `protobuf:"bytes,1,opt,name=sandbox,proto3" json:"sandbox,omitempty"` +// List sandboxes request. +type ListSandboxesRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Limit uint32 `protobuf:"varint,1,opt,name=limit,proto3" json:"limit,omitempty"` + Offset uint32 `protobuf:"varint,2,opt,name=offset,proto3" json:"offset,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"` + // Workspace scope. Empty defaults to "default". + Workspace string `protobuf:"bytes,4,opt,name=workspace,proto3" json:"workspace,omitempty"` + // List across all workspaces. Mutually exclusive with workspace. + AllWorkspaces bool `protobuf:"varint,5,opt,name=all_workspaces,json=allWorkspaces,proto3" json:"all_workspaces,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } -func (x *SandboxResponse) Reset() { - *x = SandboxResponse{} - mi := &file_openshell_proto_msgTypes[29] +func (x *ListSandboxesRequest) Reset() { + *x = ListSandboxesRequest{} + mi := &file_openshell_proto_msgTypes[31] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *SandboxResponse) String() string { +func (x *ListSandboxesRequest) String() string { return protoimpl.X.MessageStringOf(x) } -func (*SandboxResponse) ProtoMessage() {} +func (*ListSandboxesRequest) ProtoMessage() {} -func (x *SandboxResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[29] +func (x *ListSandboxesRequest) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[31] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2353,132 +2467,71 @@ func (x *SandboxResponse) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use SandboxResponse.ProtoReflect.Descriptor instead. -func (*SandboxResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{29} +// Deprecated: Use ListSandboxesRequest.ProtoReflect.Descriptor instead. +func (*ListSandboxesRequest) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{31} } -func (x *SandboxResponse) GetSandbox() *Sandbox { +func (x *ListSandboxesRequest) GetLimit() uint32 { if x != nil { - return x.Sandbox + return x.Limit } - 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"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *ListSandboxesResponse) Reset() { - *x = ListSandboxesResponse{} - mi := &file_openshell_proto_msgTypes[30] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *ListSandboxesResponse) String() string { - return protoimpl.X.MessageStringOf(x) + return 0 } -func (*ListSandboxesResponse) ProtoMessage() {} - -func (x *ListSandboxesResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[30] +func (x *ListSandboxesRequest) GetOffset() uint32 { if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms + return x.Offset } - return mi.MessageOf(x) -} - -// Deprecated: Use ListSandboxesResponse.ProtoReflect.Descriptor instead. -func (*ListSandboxesResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{30} + return 0 } -func (x *ListSandboxesResponse) GetSandboxes() []*Sandbox { +func (x *ListSandboxesRequest) GetLabelSelector() string { if x != nil { - return x.Sandboxes + return x.LabelSelector } - return nil -} - -// 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"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *ListSandboxProvidersResponse) Reset() { - *x = ListSandboxProvidersResponse{} - mi := &file_openshell_proto_msgTypes[31] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *ListSandboxProvidersResponse) String() string { - return protoimpl.X.MessageStringOf(x) + return "" } -func (*ListSandboxProvidersResponse) ProtoMessage() {} - -func (x *ListSandboxProvidersResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[31] +func (x *ListSandboxesRequest) GetWorkspace() string { if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms + return x.Workspace } - return mi.MessageOf(x) -} - -// Deprecated: Use ListSandboxProvidersResponse.ProtoReflect.Descriptor instead. -func (*ListSandboxProvidersResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{31} + return "" } -func (x *ListSandboxProvidersResponse) GetProviders() []*datamodelv1.Provider { +func (x *ListSandboxesRequest) GetAllWorkspaces() bool { if x != nil { - return x.Providers + return x.AllWorkspaces } - return nil + return false } -// 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"` +// List providers attached to a sandbox request. +type ListSandboxProvidersRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Sandbox name (canonical lookup key). + SandboxName string `protobuf:"bytes,1,opt,name=sandbox_name,json=sandboxName,proto3" json:"sandbox_name,omitempty"` + // Workspace scope. Empty defaults to "default". + Workspace string `protobuf:"bytes,2,opt,name=workspace,proto3" json:"workspace,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } -func (x *AttachSandboxProviderResponse) Reset() { - *x = AttachSandboxProviderResponse{} +func (x *ListSandboxProvidersRequest) Reset() { + *x = ListSandboxProvidersRequest{} mi := &file_openshell_proto_msgTypes[32] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *AttachSandboxProviderResponse) String() string { +func (x *ListSandboxProvidersRequest) String() string { return protoimpl.X.MessageStringOf(x) } -func (*AttachSandboxProviderResponse) ProtoMessage() {} +func (*ListSandboxProvidersRequest) ProtoMessage() {} -func (x *AttachSandboxProviderResponse) ProtoReflect() protoreflect.Message { +func (x *ListSandboxProvidersRequest) ProtoReflect() protoreflect.Message { mi := &file_openshell_proto_msgTypes[32] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -2490,49 +2543,57 @@ func (x *AttachSandboxProviderResponse) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use AttachSandboxProviderResponse.ProtoReflect.Descriptor instead. -func (*AttachSandboxProviderResponse) Descriptor() ([]byte, []int) { +// Deprecated: Use ListSandboxProvidersRequest.ProtoReflect.Descriptor instead. +func (*ListSandboxProvidersRequest) Descriptor() ([]byte, []int) { return file_openshell_proto_rawDescGZIP(), []int{32} } -func (x *AttachSandboxProviderResponse) GetSandbox() *Sandbox { +func (x *ListSandboxProvidersRequest) GetSandboxName() string { if x != nil { - return x.Sandbox + return x.SandboxName } - return nil + return "" } -func (x *AttachSandboxProviderResponse) GetAttached() bool { +func (x *ListSandboxProvidersRequest) GetWorkspace() string { if x != nil { - return x.Attached + return x.Workspace } - return false + return "" } -// 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"` +// Attach provider to sandbox request. +type AttachSandboxProviderRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Sandbox name (canonical lookup key). + SandboxName string `protobuf:"bytes,1,opt,name=sandbox_name,json=sandboxName,proto3" json:"sandbox_name,omitempty"` + // Provider name to attach. + ProviderName string `protobuf:"bytes,2,opt,name=provider_name,json=providerName,proto3" json:"provider_name,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"` + // Workspace scope. Empty defaults to "default". + Workspace string `protobuf:"bytes,4,opt,name=workspace,proto3" json:"workspace,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } -func (x *DetachSandboxProviderResponse) Reset() { - *x = DetachSandboxProviderResponse{} +func (x *AttachSandboxProviderRequest) Reset() { + *x = AttachSandboxProviderRequest{} mi := &file_openshell_proto_msgTypes[33] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *DetachSandboxProviderResponse) String() string { +func (x *AttachSandboxProviderRequest) String() string { return protoimpl.X.MessageStringOf(x) } -func (*DetachSandboxProviderResponse) ProtoMessage() {} +func (*AttachSandboxProviderRequest) ProtoMessage() {} -func (x *DetachSandboxProviderResponse) ProtoReflect() protoreflect.Message { +func (x *AttachSandboxProviderRequest) ProtoReflect() protoreflect.Message { mi := &file_openshell_proto_msgTypes[33] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -2544,47 +2605,71 @@ func (x *DetachSandboxProviderResponse) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use DetachSandboxProviderResponse.ProtoReflect.Descriptor instead. -func (*DetachSandboxProviderResponse) Descriptor() ([]byte, []int) { +// Deprecated: Use AttachSandboxProviderRequest.ProtoReflect.Descriptor instead. +func (*AttachSandboxProviderRequest) Descriptor() ([]byte, []int) { return file_openshell_proto_rawDescGZIP(), []int{33} } -func (x *DetachSandboxProviderResponse) GetSandbox() *Sandbox { +func (x *AttachSandboxProviderRequest) GetSandboxName() string { if x != nil { - return x.Sandbox + return x.SandboxName } - return nil + return "" } -func (x *DetachSandboxProviderResponse) GetDetached() bool { +func (x *AttachSandboxProviderRequest) GetProviderName() string { if x != nil { - return x.Detached + return x.ProviderName } - return false + return "" } -// Delete sandbox response. -type DeleteSandboxResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - Deleted bool `protobuf:"varint,1,opt,name=deleted,proto3" json:"deleted,omitempty"` +func (x *AttachSandboxProviderRequest) GetExpectedResourceVersion() uint64 { + if x != nil { + return x.ExpectedResourceVersion + } + return 0 +} + +func (x *AttachSandboxProviderRequest) GetWorkspace() string { + if x != nil { + return x.Workspace + } + return "" +} + +// Detach provider from sandbox request. +type DetachSandboxProviderRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Sandbox name (canonical lookup key). + SandboxName string `protobuf:"bytes,1,opt,name=sandbox_name,json=sandboxName,proto3" json:"sandbox_name,omitempty"` + // Provider name to detach. + ProviderName string `protobuf:"bytes,2,opt,name=provider_name,json=providerName,proto3" json:"provider_name,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"` + // Workspace scope. Empty defaults to "default". + Workspace string `protobuf:"bytes,4,opt,name=workspace,proto3" json:"workspace,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } -func (x *DeleteSandboxResponse) Reset() { - *x = DeleteSandboxResponse{} +func (x *DetachSandboxProviderRequest) Reset() { + *x = DetachSandboxProviderRequest{} mi := &file_openshell_proto_msgTypes[34] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *DeleteSandboxResponse) String() string { +func (x *DetachSandboxProviderRequest) String() string { return protoimpl.X.MessageStringOf(x) } -func (*DeleteSandboxResponse) ProtoMessage() {} +func (*DetachSandboxProviderRequest) ProtoMessage() {} -func (x *DeleteSandboxResponse) ProtoReflect() protoreflect.Message { +func (x *DetachSandboxProviderRequest) ProtoReflect() protoreflect.Message { mi := &file_openshell_proto_msgTypes[34] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -2596,41 +2681,64 @@ func (x *DeleteSandboxResponse) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use DeleteSandboxResponse.ProtoReflect.Descriptor instead. -func (*DeleteSandboxResponse) Descriptor() ([]byte, []int) { +// Deprecated: Use DetachSandboxProviderRequest.ProtoReflect.Descriptor instead. +func (*DetachSandboxProviderRequest) Descriptor() ([]byte, []int) { return file_openshell_proto_rawDescGZIP(), []int{34} } -func (x *DeleteSandboxResponse) GetDeleted() bool { +func (x *DetachSandboxProviderRequest) GetSandboxName() string { if x != nil { - return x.Deleted + return x.SandboxName } - return false + return "" } -// Create SSH session request. -type CreateSshSessionRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - // Sandbox id. - SandboxId string `protobuf:"bytes,1,opt,name=sandbox_id,json=sandboxId,proto3" json:"sandbox_id,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache +func (x *DetachSandboxProviderRequest) GetProviderName() string { + if x != nil { + return x.ProviderName + } + return "" } -func (x *CreateSshSessionRequest) Reset() { - *x = CreateSshSessionRequest{} +func (x *DetachSandboxProviderRequest) GetExpectedResourceVersion() uint64 { + if x != nil { + return x.ExpectedResourceVersion + } + return 0 +} + +func (x *DetachSandboxProviderRequest) GetWorkspace() string { + if x != nil { + return x.Workspace + } + return "" +} + +// Delete sandbox request. +type DeleteSandboxRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Sandbox name (canonical lookup key). + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + // Workspace scope. Empty defaults to "default". + Workspace string `protobuf:"bytes,2,opt,name=workspace,proto3" json:"workspace,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DeleteSandboxRequest) Reset() { + *x = DeleteSandboxRequest{} mi := &file_openshell_proto_msgTypes[35] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *CreateSshSessionRequest) String() string { +func (x *DeleteSandboxRequest) String() string { return protoimpl.X.MessageStringOf(x) } -func (*CreateSshSessionRequest) ProtoMessage() {} +func (*DeleteSandboxRequest) ProtoMessage() {} -func (x *CreateSshSessionRequest) ProtoReflect() protoreflect.Message { +func (x *DeleteSandboxRequest) ProtoReflect() protoreflect.Message { mi := &file_openshell_proto_msgTypes[35] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -2642,63 +2750,50 @@ func (x *CreateSshSessionRequest) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use CreateSshSessionRequest.ProtoReflect.Descriptor instead. -func (*CreateSshSessionRequest) Descriptor() ([]byte, []int) { +// Deprecated: Use DeleteSandboxRequest.ProtoReflect.Descriptor instead. +func (*DeleteSandboxRequest) Descriptor() ([]byte, []int) { return file_openshell_proto_rawDescGZIP(), []int{35} } -func (x *CreateSshSessionRequest) GetSandboxId() string { +func (x *DeleteSandboxRequest) GetName() string { if x != nil { - return x.SandboxId + return x.Name } return "" } -// 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 { +func (x *DeleteSandboxRequest) GetWorkspace() string { + if x != nil { + return x.Workspace + } + return "" +} + +// Stop sandbox request. +type StopSandboxRequest 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"` - // Optional host key fingerprint. If non-empty, [A-Za-z0-9:+/=-] only. - HostKeyFingerprint string `protobuf:"bytes,7,opt,name=host_key_fingerprint,json=hostKeyFingerprint,proto3" json:"host_key_fingerprint,omitempty"` - // Expiry timestamp in milliseconds since epoch. 0 means no expiry. - ExpiresAtMs int64 `protobuf:"varint,8,opt,name=expires_at_ms,json=expiresAtMs,proto3" json:"expires_at_ms,omitempty"` + // Sandbox name (canonical lookup key). + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + // Workspace scope. Empty defaults to "default". + Workspace string `protobuf:"bytes,2,opt,name=workspace,proto3" json:"workspace,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } -func (x *CreateSshSessionResponse) Reset() { - *x = CreateSshSessionResponse{} +func (x *StopSandboxRequest) Reset() { + *x = StopSandboxRequest{} mi := &file_openshell_proto_msgTypes[36] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *CreateSshSessionResponse) String() string { +func (x *StopSandboxRequest) String() string { return protoimpl.X.MessageStringOf(x) } -func (*CreateSshSessionResponse) ProtoMessage() {} +func (*StopSandboxRequest) ProtoMessage() {} -func (x *CreateSshSessionResponse) ProtoReflect() protoreflect.Message { +func (x *StopSandboxRequest) ProtoReflect() protoreflect.Message { mi := &file_openshell_proto_msgTypes[36] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -2710,91 +2805,50 @@ func (x *CreateSshSessionResponse) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use CreateSshSessionResponse.ProtoReflect.Descriptor instead. -func (*CreateSshSessionResponse) Descriptor() ([]byte, []int) { +// Deprecated: Use StopSandboxRequest.ProtoReflect.Descriptor instead. +func (*StopSandboxRequest) Descriptor() ([]byte, []int) { return file_openshell_proto_rawDescGZIP(), []int{36} } -func (x *CreateSshSessionResponse) GetSandboxId() string { - if x != nil { - return x.SandboxId - } - return "" -} - -func (x *CreateSshSessionResponse) GetToken() string { - if x != nil { - return x.Token - } - return "" -} - -func (x *CreateSshSessionResponse) GetGatewayHost() string { - if x != nil { - return x.GatewayHost - } - return "" -} - -func (x *CreateSshSessionResponse) GetGatewayPort() uint32 { - if x != nil { - return x.GatewayPort - } - return 0 -} - -func (x *CreateSshSessionResponse) GetGatewayScheme() string { +func (x *StopSandboxRequest) GetName() string { if x != nil { - return x.GatewayScheme + return x.Name } return "" } -func (x *CreateSshSessionResponse) GetHostKeyFingerprint() string { +func (x *StopSandboxRequest) GetWorkspace() string { if x != nil { - return x.HostKeyFingerprint + return x.Workspace } return "" } -func (x *CreateSshSessionResponse) GetExpiresAtMs() int64 { - if x != nil { - return x.ExpiresAtMs - } - return 0 -} - -// Request to expose an HTTP service running inside a sandbox. -type ExposeServiceRequest struct { +// Start sandbox request. +type StartSandboxRequest struct { state protoimpl.MessageState `protogen:"open.v1"` - // Sandbox name. - Sandbox string `protobuf:"bytes,1,opt,name=sandbox,proto3" json:"sandbox,omitempty"` - // Service name within the sandbox. - Service string `protobuf:"bytes,2,opt,name=service,proto3" json:"service,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 name (canonical lookup key). + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` // Workspace scope. Empty defaults to "default". - Workspace string `protobuf:"bytes,5,opt,name=workspace,proto3" json:"workspace,omitempty"` + Workspace string `protobuf:"bytes,2,opt,name=workspace,proto3" json:"workspace,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } -func (x *ExposeServiceRequest) Reset() { - *x = ExposeServiceRequest{} +func (x *StartSandboxRequest) Reset() { + *x = StartSandboxRequest{} mi := &file_openshell_proto_msgTypes[37] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *ExposeServiceRequest) String() string { +func (x *StartSandboxRequest) String() string { return protoimpl.X.MessageStringOf(x) } -func (*ExposeServiceRequest) ProtoMessage() {} +func (*StartSandboxRequest) ProtoMessage() {} -func (x *ExposeServiceRequest) ProtoReflect() protoreflect.Message { +func (x *StartSandboxRequest) ProtoReflect() protoreflect.Message { mi := &file_openshell_proto_msgTypes[37] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -2806,73 +2860,47 @@ func (x *ExposeServiceRequest) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use ExposeServiceRequest.ProtoReflect.Descriptor instead. -func (*ExposeServiceRequest) Descriptor() ([]byte, []int) { +// Deprecated: Use StartSandboxRequest.ProtoReflect.Descriptor instead. +func (*StartSandboxRequest) Descriptor() ([]byte, []int) { return file_openshell_proto_rawDescGZIP(), []int{37} } -func (x *ExposeServiceRequest) GetSandbox() string { - if x != nil { - return x.Sandbox - } - return "" -} - -func (x *ExposeServiceRequest) GetService() string { +func (x *StartSandboxRequest) GetName() string { if x != nil { - return x.Service + return x.Name } return "" } -func (x *ExposeServiceRequest) GetTargetPort() uint32 { - if x != nil { - return x.TargetPort - } - return 0 -} - -func (x *ExposeServiceRequest) GetDomain() bool { - if x != nil { - return x.Domain - } - return false -} - -func (x *ExposeServiceRequest) GetWorkspace() string { +func (x *StartSandboxRequest) GetWorkspace() string { if x != nil { return x.Workspace } return "" } -// Request to fetch an exposed sandbox service endpoint. -type GetServiceRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - // Sandbox name. - Sandbox string `protobuf:"bytes,1,opt,name=sandbox,proto3" json:"sandbox,omitempty"` - // Service name within the sandbox. Empty selects the unnamed endpoint. - Service string `protobuf:"bytes,2,opt,name=service,proto3" json:"service,omitempty"` - // Workspace scope. Empty defaults to "default". - Workspace string `protobuf:"bytes,3,opt,name=workspace,proto3" json:"workspace,omitempty"` +// Sandbox response. +type SandboxResponse 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 *GetServiceRequest) Reset() { - *x = GetServiceRequest{} +func (x *SandboxResponse) Reset() { + *x = SandboxResponse{} mi := &file_openshell_proto_msgTypes[38] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *GetServiceRequest) String() string { +func (x *SandboxResponse) String() string { return protoimpl.X.MessageStringOf(x) } -func (*GetServiceRequest) ProtoMessage() {} +func (*SandboxResponse) ProtoMessage() {} -func (x *GetServiceRequest) ProtoReflect() protoreflect.Message { +func (x *SandboxResponse) ProtoReflect() protoreflect.Message { mi := &file_openshell_proto_msgTypes[38] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -2884,63 +2912,40 @@ func (x *GetServiceRequest) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use GetServiceRequest.ProtoReflect.Descriptor instead. -func (*GetServiceRequest) Descriptor() ([]byte, []int) { +// Deprecated: Use SandboxResponse.ProtoReflect.Descriptor instead. +func (*SandboxResponse) Descriptor() ([]byte, []int) { return file_openshell_proto_rawDescGZIP(), []int{38} } -func (x *GetServiceRequest) GetSandbox() string { +func (x *SandboxResponse) GetSandbox() *Sandbox { if x != nil { return x.Sandbox } - return "" + return nil } -func (x *GetServiceRequest) GetService() string { - if x != nil { - return x.Service - } - return "" +// List sandboxes response. +type ListSandboxesResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Sandboxes []*Sandbox `protobuf:"bytes,1,rep,name=sandboxes,proto3" json:"sandboxes,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } -func (x *GetServiceRequest) GetWorkspace() string { - if x != nil { - return x.Workspace - } - return "" +func (x *ListSandboxesResponse) Reset() { + *x = ListSandboxesResponse{} + mi := &file_openshell_proto_msgTypes[39] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } -// Request to list exposed sandbox service endpoints. -type ListServicesRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - // Optional sandbox name. Empty lists endpoints for all sandboxes. - Sandbox string `protobuf:"bytes,1,opt,name=sandbox,proto3" json:"sandbox,omitempty"` - // Page size. Zero uses the server default. - Limit uint32 `protobuf:"varint,2,opt,name=limit,proto3" json:"limit,omitempty"` - // Page offset. - Offset uint32 `protobuf:"varint,3,opt,name=offset,proto3" json:"offset,omitempty"` - // Workspace scope. Empty defaults to "default". - Workspace string `protobuf:"bytes,4,opt,name=workspace,proto3" json:"workspace,omitempty"` - // List across all workspaces. Mutually exclusive with workspace. - AllWorkspaces bool `protobuf:"varint,5,opt,name=all_workspaces,json=allWorkspaces,proto3" json:"all_workspaces,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *ListServicesRequest) Reset() { - *x = ListServicesRequest{} - mi := &file_openshell_proto_msgTypes[39] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *ListServicesRequest) String() string { +func (x *ListSandboxesResponse) String() string { return protoimpl.X.MessageStringOf(x) } -func (*ListServicesRequest) ProtoMessage() {} +func (*ListSandboxesResponse) ProtoMessage() {} -func (x *ListServicesRequest) ProtoReflect() protoreflect.Message { +func (x *ListSandboxesResponse) ProtoReflect() protoreflect.Message { mi := &file_openshell_proto_msgTypes[39] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -2952,68 +2957,40 @@ func (x *ListServicesRequest) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use ListServicesRequest.ProtoReflect.Descriptor instead. -func (*ListServicesRequest) Descriptor() ([]byte, []int) { +// Deprecated: Use ListSandboxesResponse.ProtoReflect.Descriptor instead. +func (*ListSandboxesResponse) Descriptor() ([]byte, []int) { return file_openshell_proto_rawDescGZIP(), []int{39} } -func (x *ListServicesRequest) GetSandbox() string { - if x != nil { - return x.Sandbox - } - return "" -} - -func (x *ListServicesRequest) GetLimit() uint32 { - if x != nil { - return x.Limit - } - return 0 -} - -func (x *ListServicesRequest) GetOffset() uint32 { - if x != nil { - return x.Offset - } - return 0 -} - -func (x *ListServicesRequest) GetWorkspace() string { - if x != nil { - return x.Workspace - } - return "" -} - -func (x *ListServicesRequest) GetAllWorkspaces() bool { +func (x *ListSandboxesResponse) GetSandboxes() []*Sandbox { if x != nil { - return x.AllWorkspaces + return x.Sandboxes } - return false + 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"` +// 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"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } -func (x *ListServicesResponse) Reset() { - *x = ListServicesResponse{} +func (x *ListSandboxProvidersResponse) Reset() { + *x = ListSandboxProvidersResponse{} mi := &file_openshell_proto_msgTypes[40] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *ListServicesResponse) String() string { +func (x *ListSandboxProvidersResponse) String() string { return protoimpl.X.MessageStringOf(x) } -func (*ListServicesResponse) ProtoMessage() {} +func (*ListSandboxProvidersResponse) ProtoMessage() {} -func (x *ListServicesResponse) ProtoReflect() protoreflect.Message { +func (x *ListSandboxProvidersResponse) ProtoReflect() protoreflect.Message { mi := &file_openshell_proto_msgTypes[40] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -3025,45 +3002,42 @@ func (x *ListServicesResponse) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use ListServicesResponse.ProtoReflect.Descriptor instead. -func (*ListServicesResponse) Descriptor() ([]byte, []int) { +// Deprecated: Use ListSandboxProvidersResponse.ProtoReflect.Descriptor instead. +func (*ListSandboxProvidersResponse) Descriptor() ([]byte, []int) { return file_openshell_proto_rawDescGZIP(), []int{40} } -func (x *ListServicesResponse) GetServices() []*ServiceEndpointResponse { +func (x *ListSandboxProvidersResponse) GetProviders() []*datamodelv1.Provider { if x != nil { - return x.Services + return x.Providers } return nil } -// Request to delete an exposed sandbox service endpoint. -type DeleteServiceRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - // Sandbox name. - Sandbox string `protobuf:"bytes,1,opt,name=sandbox,proto3" json:"sandbox,omitempty"` - // Service name within the sandbox. Empty selects the unnamed endpoint. - Service string `protobuf:"bytes,2,opt,name=service,proto3" json:"service,omitempty"` - // Workspace scope. Empty defaults to "default". - Workspace string `protobuf:"bytes,3,opt,name=workspace,proto3" json:"workspace,omitempty"` +// 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"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } -func (x *DeleteServiceRequest) Reset() { - *x = DeleteServiceRequest{} +func (x *AttachSandboxProviderResponse) Reset() { + *x = AttachSandboxProviderResponse{} mi := &file_openshell_proto_msgTypes[41] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *DeleteServiceRequest) String() string { +func (x *AttachSandboxProviderResponse) String() string { return protoimpl.X.MessageStringOf(x) } -func (*DeleteServiceRequest) ProtoMessage() {} +func (*AttachSandboxProviderResponse) ProtoMessage() {} -func (x *DeleteServiceRequest) ProtoReflect() protoreflect.Message { +func (x *AttachSandboxProviderResponse) ProtoReflect() protoreflect.Message { mi := &file_openshell_proto_msgTypes[41] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -3075,55 +3049,49 @@ func (x *DeleteServiceRequest) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use DeleteServiceRequest.ProtoReflect.Descriptor instead. -func (*DeleteServiceRequest) Descriptor() ([]byte, []int) { +// Deprecated: Use AttachSandboxProviderResponse.ProtoReflect.Descriptor instead. +func (*AttachSandboxProviderResponse) Descriptor() ([]byte, []int) { return file_openshell_proto_rawDescGZIP(), []int{41} } -func (x *DeleteServiceRequest) GetSandbox() string { +func (x *AttachSandboxProviderResponse) GetSandbox() *Sandbox { if x != nil { return x.Sandbox } - return "" -} - -func (x *DeleteServiceRequest) GetService() string { - if x != nil { - return x.Service - } - return "" + return nil } -func (x *DeleteServiceRequest) GetWorkspace() string { +func (x *AttachSandboxProviderResponse) GetAttached() bool { if x != nil { - return x.Workspace + return x.Attached } - return "" + return false } -// Response for deleting an exposed sandbox service endpoint. -type DeleteServiceResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - // True when an endpoint existed and was deleted. - Deleted bool `protobuf:"varint,1,opt,name=deleted,proto3" json:"deleted,omitempty"` +// 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"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } -func (x *DeleteServiceResponse) Reset() { - *x = DeleteServiceResponse{} +func (x *DetachSandboxProviderResponse) Reset() { + *x = DetachSandboxProviderResponse{} mi := &file_openshell_proto_msgTypes[42] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *DeleteServiceResponse) String() string { +func (x *DetachSandboxProviderResponse) String() string { return protoimpl.X.MessageStringOf(x) } -func (*DeleteServiceResponse) ProtoMessage() {} +func (*DetachSandboxProviderResponse) ProtoMessage() {} -func (x *DeleteServiceResponse) ProtoReflect() protoreflect.Message { +func (x *DetachSandboxProviderResponse) ProtoReflect() protoreflect.Message { mi := &file_openshell_proto_msgTypes[42] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -3135,51 +3103,47 @@ func (x *DeleteServiceResponse) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use DeleteServiceResponse.ProtoReflect.Descriptor instead. -func (*DeleteServiceResponse) Descriptor() ([]byte, []int) { +// Deprecated: Use DetachSandboxProviderResponse.ProtoReflect.Descriptor instead. +func (*DetachSandboxProviderResponse) Descriptor() ([]byte, []int) { return file_openshell_proto_rawDescGZIP(), []int{42} } -func (x *DeleteServiceResponse) GetDeleted() bool { +func (x *DetachSandboxProviderResponse) GetSandbox() *Sandbox { if x != nil { - return x.Deleted + return x.Sandbox + } + return nil +} + +func (x *DetachSandboxProviderResponse) GetDetached() bool { + if x != nil { + return x.Detached } return false } -// 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. - SandboxName string `protobuf:"bytes,3,opt,name=sandbox_name,json=sandboxName,proto3" json:"sandbox_name,omitempty"` - // Service name within the sandbox. - ServiceName string `protobuf:"bytes,4,opt,name=service_name,json=serviceName,proto3" json:"service_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"` +// Delete sandbox response. +type DeleteSandboxResponse 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 *ServiceEndpoint) Reset() { - *x = ServiceEndpoint{} +func (x *DeleteSandboxResponse) Reset() { + *x = DeleteSandboxResponse{} mi := &file_openshell_proto_msgTypes[43] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *ServiceEndpoint) String() string { +func (x *DeleteSandboxResponse) String() string { return protoimpl.X.MessageStringOf(x) } -func (*ServiceEndpoint) ProtoMessage() {} +func (*DeleteSandboxResponse) ProtoMessage() {} -func (x *ServiceEndpoint) ProtoReflect() protoreflect.Message { +func (x *DeleteSandboxResponse) ProtoReflect() protoreflect.Message { mi := &file_openshell_proto_msgTypes[43] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -3191,76 +3155,41 @@ func (x *ServiceEndpoint) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use ServiceEndpoint.ProtoReflect.Descriptor instead. -func (*ServiceEndpoint) Descriptor() ([]byte, []int) { +// Deprecated: Use DeleteSandboxResponse.ProtoReflect.Descriptor instead. +func (*DeleteSandboxResponse) Descriptor() ([]byte, []int) { return file_openshell_proto_rawDescGZIP(), []int{43} } -func (x *ServiceEndpoint) GetMetadata() *datamodelv1.ObjectMeta { - if x != nil { - return x.Metadata - } - return nil -} - -func (x *ServiceEndpoint) GetSandboxId() string { - if x != nil { - return x.SandboxId - } - return "" -} - -func (x *ServiceEndpoint) GetSandboxName() string { - if x != nil { - return x.SandboxName - } - return "" -} - -func (x *ServiceEndpoint) GetServiceName() string { - if x != nil { - return x.ServiceName - } - return "" -} - -func (x *ServiceEndpoint) GetTargetPort() uint32 { - if x != nil { - return x.TargetPort - } - return 0 -} - -func (x *ServiceEndpoint) GetDomain() bool { +func (x *DeleteSandboxResponse) GetDeleted() bool { if x != nil { - return x.Domain + return x.Deleted } return false } -// 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"` +// Create SSH session request. +type CreateSshSessionRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Sandbox id. + SandboxId string `protobuf:"bytes,1,opt,name=sandbox_id,json=sandboxId,proto3" json:"sandbox_id,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } -func (x *ServiceEndpointResponse) Reset() { - *x = ServiceEndpointResponse{} +func (x *CreateSshSessionRequest) Reset() { + *x = CreateSshSessionRequest{} mi := &file_openshell_proto_msgTypes[44] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *ServiceEndpointResponse) String() string { +func (x *CreateSshSessionRequest) String() string { return protoimpl.X.MessageStringOf(x) } -func (*ServiceEndpointResponse) ProtoMessage() {} +func (*CreateSshSessionRequest) ProtoMessage() {} -func (x *ServiceEndpointResponse) ProtoReflect() protoreflect.Message { +func (x *CreateSshSessionRequest) ProtoReflect() protoreflect.Message { mi := &file_openshell_proto_msgTypes[44] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -3272,48 +3201,63 @@ func (x *ServiceEndpointResponse) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use ServiceEndpointResponse.ProtoReflect.Descriptor instead. -func (*ServiceEndpointResponse) Descriptor() ([]byte, []int) { +// Deprecated: Use CreateSshSessionRequest.ProtoReflect.Descriptor instead. +func (*CreateSshSessionRequest) Descriptor() ([]byte, []int) { return file_openshell_proto_rawDescGZIP(), []int{44} } -func (x *ServiceEndpointResponse) GetEndpoint() *ServiceEndpoint { - if x != nil { - return x.Endpoint - } - return nil -} - -func (x *ServiceEndpointResponse) GetUrl() string { +func (x *CreateSshSessionRequest) GetSandboxId() string { if x != nil { - return x.Url + return x.SandboxId } return "" } -// Revoke SSH session request. -type RevokeSshSessionRequest struct { +// 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"` - // Session token to revoke. - Token string `protobuf:"bytes,1,opt,name=token,proto3" json:"token,omitempty"` + // 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"` + // Optional host key fingerprint. If non-empty, [A-Za-z0-9:+/=-] only. + HostKeyFingerprint string `protobuf:"bytes,7,opt,name=host_key_fingerprint,json=hostKeyFingerprint,proto3" json:"host_key_fingerprint,omitempty"` + // Expiry timestamp in milliseconds since epoch. 0 means no expiry. + ExpiresAtMs int64 `protobuf:"varint,8,opt,name=expires_at_ms,json=expiresAtMs,proto3" json:"expires_at_ms,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } -func (x *RevokeSshSessionRequest) Reset() { - *x = RevokeSshSessionRequest{} +func (x *CreateSshSessionResponse) Reset() { + *x = CreateSshSessionResponse{} mi := &file_openshell_proto_msgTypes[45] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *RevokeSshSessionRequest) String() string { +func (x *CreateSshSessionResponse) String() string { return protoimpl.X.MessageStringOf(x) } -func (*RevokeSshSessionRequest) ProtoMessage() {} +func (*CreateSshSessionResponse) ProtoMessage() {} -func (x *RevokeSshSessionRequest) ProtoReflect() protoreflect.Message { +func (x *CreateSshSessionResponse) ProtoReflect() protoreflect.Message { mi := &file_openshell_proto_msgTypes[45] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -3325,104 +3269,92 @@ func (x *RevokeSshSessionRequest) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use RevokeSshSessionRequest.ProtoReflect.Descriptor instead. -func (*RevokeSshSessionRequest) Descriptor() ([]byte, []int) { +// Deprecated: Use CreateSshSessionResponse.ProtoReflect.Descriptor instead. +func (*CreateSshSessionResponse) Descriptor() ([]byte, []int) { return file_openshell_proto_rawDescGZIP(), []int{45} } -func (x *RevokeSshSessionRequest) GetToken() string { +func (x *CreateSshSessionResponse) GetSandboxId() string { if x != nil { - return x.Token + return x.SandboxId } return "" } -// Revoke SSH session response. -type RevokeSshSessionResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - // True when a session was revoked. - Revoked bool `protobuf:"varint,1,opt,name=revoked,proto3" json:"revoked,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache +func (x *CreateSshSessionResponse) GetToken() string { + if x != nil { + return x.Token + } + return "" } -func (x *RevokeSshSessionResponse) Reset() { - *x = RevokeSshSessionResponse{} - mi := &file_openshell_proto_msgTypes[46] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) +func (x *CreateSshSessionResponse) GetGatewayHost() string { + if x != nil { + return x.GatewayHost + } + return "" } -func (x *RevokeSshSessionResponse) String() string { - return protoimpl.X.MessageStringOf(x) +func (x *CreateSshSessionResponse) GetGatewayPort() uint32 { + if x != nil { + return x.GatewayPort + } + return 0 } -func (*RevokeSshSessionResponse) ProtoMessage() {} - -func (x *RevokeSshSessionResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[46] +func (x *CreateSshSessionResponse) GetGatewayScheme() string { if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms + return x.GatewayScheme } - return mi.MessageOf(x) + return "" } -// Deprecated: Use RevokeSshSessionResponse.ProtoReflect.Descriptor instead. -func (*RevokeSshSessionResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{46} +func (x *CreateSshSessionResponse) GetHostKeyFingerprint() string { + if x != nil { + return x.HostKeyFingerprint + } + return "" } -func (x *RevokeSshSessionResponse) GetRevoked() bool { +func (x *CreateSshSessionResponse) GetExpiresAtMs() int64 { if x != nil { - return x.Revoked + return x.ExpiresAtMs } - return false + return 0 } -// Execute command request. -type ExecSandboxRequest struct { +// Request to expose an HTTP service running inside a sandbox. +type ExposeServiceRequest struct { state protoimpl.MessageState `protogen:"open.v1"` - // Sandbox id. - SandboxId string `protobuf:"bytes,1,opt,name=sandbox_id,json=sandboxId,proto3" json:"sandbox_id,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 timeout in seconds. 0 means no timeout. - TimeoutSeconds uint32 `protobuf:"varint,5,opt,name=timeout_seconds,json=timeoutSeconds,proto3" json:"timeout_seconds,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"` + // Sandbox name. + Sandbox string `protobuf:"bytes,1,opt,name=sandbox,proto3" json:"sandbox,omitempty"` + // Service name within the sandbox. + Service string `protobuf:"bytes,2,opt,name=service,proto3" json:"service,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"` + // Workspace scope. Empty defaults to "default". + Workspace string `protobuf:"bytes,5,opt,name=workspace,proto3" json:"workspace,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } -func (x *ExecSandboxRequest) Reset() { - *x = ExecSandboxRequest{} - mi := &file_openshell_proto_msgTypes[47] +func (x *ExposeServiceRequest) Reset() { + *x = ExposeServiceRequest{} + mi := &file_openshell_proto_msgTypes[46] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *ExecSandboxRequest) String() string { +func (x *ExposeServiceRequest) String() string { return protoimpl.X.MessageStringOf(x) } -func (*ExecSandboxRequest) ProtoMessage() {} +func (*ExposeServiceRequest) ProtoMessage() {} -func (x *ExecSandboxRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[47] +func (x *ExposeServiceRequest) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[46] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3433,97 +3365,74 @@ 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{47} +// Deprecated: Use ExposeServiceRequest.ProtoReflect.Descriptor instead. +func (*ExposeServiceRequest) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{46} } -func (x *ExecSandboxRequest) GetSandboxId() string { +func (x *ExposeServiceRequest) GetSandbox() string { if x != nil { - return x.SandboxId + return x.Sandbox } return "" } -func (x *ExecSandboxRequest) GetCommand() []string { - if x != nil { - return x.Command - } - return nil -} - -func (x *ExecSandboxRequest) GetWorkdir() string { +func (x *ExposeServiceRequest) GetService() string { if x != nil { - return x.Workdir + return x.Service } return "" } -func (x *ExecSandboxRequest) GetEnvironment() map[string]string { - if x != nil { - return x.Environment - } - return nil -} - -func (x *ExecSandboxRequest) GetTimeoutSeconds() uint32 { +func (x *ExposeServiceRequest) GetTargetPort() uint32 { if x != nil { - return x.TimeoutSeconds + return x.TargetPort } return 0 } -func (x *ExecSandboxRequest) GetStdin() []byte { - if x != nil { - return x.Stdin - } - return nil -} - -func (x *ExecSandboxRequest) GetTty() bool { +func (x *ExposeServiceRequest) GetDomain() bool { if x != nil { - return x.Tty + return x.Domain } return false } -func (x *ExecSandboxRequest) GetCols() uint32 { - if x != nil { - return x.Cols - } - return 0 -} - -func (x *ExecSandboxRequest) GetRows() uint32 { +func (x *ExposeServiceRequest) GetWorkspace() string { if x != nil { - return x.Rows + return x.Workspace } - return 0 + return "" } -// 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"` +// Request to fetch an exposed sandbox service endpoint. +type GetServiceRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Sandbox name. + Sandbox string `protobuf:"bytes,1,opt,name=sandbox,proto3" json:"sandbox,omitempty"` + // Service name within the sandbox. Empty selects the unnamed endpoint. + Service string `protobuf:"bytes,2,opt,name=service,proto3" json:"service,omitempty"` + // Workspace scope. Empty defaults to "default". + Workspace string `protobuf:"bytes,3,opt,name=workspace,proto3" json:"workspace,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } -func (x *ExecSandboxStdout) Reset() { - *x = ExecSandboxStdout{} - mi := &file_openshell_proto_msgTypes[48] +func (x *GetServiceRequest) Reset() { + *x = GetServiceRequest{} + mi := &file_openshell_proto_msgTypes[47] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *ExecSandboxStdout) String() string { +func (x *GetServiceRequest) String() string { return protoimpl.X.MessageStringOf(x) } -func (*ExecSandboxStdout) ProtoMessage() {} +func (*GetServiceRequest) ProtoMessage() {} -func (x *ExecSandboxStdout) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[48] +func (x *GetServiceRequest) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[47] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3534,41 +3443,64 @@ 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{48} -} - -func (x *ExecSandboxStdout) GetData() []byte { +// Deprecated: Use GetServiceRequest.ProtoReflect.Descriptor instead. +func (*GetServiceRequest) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{47} +} + +func (x *GetServiceRequest) GetSandbox() string { if x != nil { - return x.Data + return x.Sandbox } - return nil + return "" } -// 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"` +func (x *GetServiceRequest) GetService() string { + if x != nil { + return x.Service + } + return "" +} + +func (x *GetServiceRequest) GetWorkspace() string { + if x != nil { + return x.Workspace + } + return "" +} + +// Request to list exposed sandbox service endpoints. +type ListServicesRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Optional sandbox name. Empty lists endpoints for all sandboxes. + Sandbox string `protobuf:"bytes,1,opt,name=sandbox,proto3" json:"sandbox,omitempty"` + // Page size. Zero uses the server default. + Limit uint32 `protobuf:"varint,2,opt,name=limit,proto3" json:"limit,omitempty"` + // Page offset. + Offset uint32 `protobuf:"varint,3,opt,name=offset,proto3" json:"offset,omitempty"` + // Workspace scope. Empty defaults to "default". + Workspace string `protobuf:"bytes,4,opt,name=workspace,proto3" json:"workspace,omitempty"` + // List across all workspaces. Mutually exclusive with workspace. + AllWorkspaces bool `protobuf:"varint,5,opt,name=all_workspaces,json=allWorkspaces,proto3" json:"all_workspaces,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } -func (x *ExecSandboxStderr) Reset() { - *x = ExecSandboxStderr{} - mi := &file_openshell_proto_msgTypes[49] +func (x *ListServicesRequest) Reset() { + *x = ListServicesRequest{} + mi := &file_openshell_proto_msgTypes[48] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *ExecSandboxStderr) String() string { +func (x *ListServicesRequest) String() string { return protoimpl.X.MessageStringOf(x) } -func (*ExecSandboxStderr) ProtoMessage() {} +func (*ListServicesRequest) ProtoMessage() {} -func (x *ExecSandboxStderr) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[49] +func (x *ListServicesRequest) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[48] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3579,41 +3511,69 @@ 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{49} +// Deprecated: Use ListServicesRequest.ProtoReflect.Descriptor instead. +func (*ListServicesRequest) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{48} } -func (x *ExecSandboxStderr) GetData() []byte { +func (x *ListServicesRequest) GetSandbox() string { if x != nil { - return x.Data + return x.Sandbox } - return nil + return "" } -// 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 *ListServicesRequest) GetLimit() uint32 { + if x != nil { + return x.Limit + } + return 0 +} + +func (x *ListServicesRequest) GetOffset() uint32 { + if x != nil { + return x.Offset + } + return 0 +} + +func (x *ListServicesRequest) GetWorkspace() string { + if x != nil { + return x.Workspace + } + return "" +} + +func (x *ListServicesRequest) GetAllWorkspaces() bool { + if x != nil { + return x.AllWorkspaces + } + return false +} + +// 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"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } -func (x *ExecSandboxExit) Reset() { - *x = ExecSandboxExit{} - mi := &file_openshell_proto_msgTypes[50] +func (x *ListServicesResponse) Reset() { + *x = ListServicesResponse{} + mi := &file_openshell_proto_msgTypes[49] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *ExecSandboxExit) String() string { +func (x *ListServicesResponse) String() string { return protoimpl.X.MessageStringOf(x) } -func (*ExecSandboxExit) ProtoMessage() {} +func (*ListServicesResponse) ProtoMessage() {} -func (x *ExecSandboxExit) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[50] +func (x *ListServicesResponse) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[49] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3624,46 +3584,46 @@ 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{50} +// Deprecated: Use ListServicesResponse.ProtoReflect.Descriptor instead. +func (*ListServicesResponse) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{49} } -func (x *ExecSandboxExit) GetExitCode() int32 { +func (x *ListServicesResponse) GetServices() []*ServiceEndpointResponse { if x != nil { - return x.ExitCode + return x.Services } - return 0 + return nil } -// One event in a sandbox exec stream. -type ExecSandboxEvent struct { +// Request to delete an exposed sandbox service endpoint. +type DeleteServiceRequest 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"` + // Sandbox name. + Sandbox string `protobuf:"bytes,1,opt,name=sandbox,proto3" json:"sandbox,omitempty"` + // Service name within the sandbox. Empty selects the unnamed endpoint. + Service string `protobuf:"bytes,2,opt,name=service,proto3" json:"service,omitempty"` + // Workspace scope. Empty defaults to "default". + Workspace string `protobuf:"bytes,3,opt,name=workspace,proto3" json:"workspace,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } -func (x *ExecSandboxEvent) Reset() { - *x = ExecSandboxEvent{} - mi := &file_openshell_proto_msgTypes[51] +func (x *DeleteServiceRequest) Reset() { + *x = DeleteServiceRequest{} + mi := &file_openshell_proto_msgTypes[50] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *ExecSandboxEvent) String() string { +func (x *DeleteServiceRequest) String() string { return protoimpl.X.MessageStringOf(x) } -func (*ExecSandboxEvent) ProtoMessage() {} +func (*DeleteServiceRequest) ProtoMessage() {} -func (x *ExecSandboxEvent) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[51] +func (x *DeleteServiceRequest) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[50] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3674,102 +3634,111 @@ 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{51} +// Deprecated: Use DeleteServiceRequest.ProtoReflect.Descriptor instead. +func (*DeleteServiceRequest) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{50} } -func (x *ExecSandboxEvent) GetPayload() isExecSandboxEvent_Payload { +func (x *DeleteServiceRequest) GetSandbox() string { if x != nil { - return x.Payload + return x.Sandbox } - return nil + return "" } -func (x *ExecSandboxEvent) GetStdout() *ExecSandboxStdout { +func (x *DeleteServiceRequest) GetService() string { if x != nil { - if x, ok := x.Payload.(*ExecSandboxEvent_Stdout); ok { - return x.Stdout - } + return x.Service } - return nil + return "" } -func (x *ExecSandboxEvent) GetStderr() *ExecSandboxStderr { +func (x *DeleteServiceRequest) GetWorkspace() string { if x != nil { - if x, ok := x.Payload.(*ExecSandboxEvent_Stderr); ok { - return x.Stderr - } + return x.Workspace } - return nil + return "" } -func (x *ExecSandboxEvent) GetExit() *ExecSandboxExit { - if x != nil { - if x, ok := x.Payload.(*ExecSandboxEvent_Exit); ok { - return x.Exit - } - } - return nil +// Response for deleting an exposed sandbox service endpoint. +type DeleteServiceResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + // True when an endpoint existed and was deleted. + Deleted bool `protobuf:"varint,1,opt,name=deleted,proto3" json:"deleted,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } -type isExecSandboxEvent_Payload interface { - isExecSandboxEvent_Payload() +func (x *DeleteServiceResponse) Reset() { + *x = DeleteServiceResponse{} + mi := &file_openshell_proto_msgTypes[51] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } -type ExecSandboxEvent_Stdout struct { - Stdout *ExecSandboxStdout `protobuf:"bytes,1,opt,name=stdout,proto3,oneof"` +func (x *DeleteServiceResponse) String() string { + return protoimpl.X.MessageStringOf(x) } -type ExecSandboxEvent_Stderr struct { - Stderr *ExecSandboxStderr `protobuf:"bytes,2,opt,name=stderr,proto3,oneof"` -} +func (*DeleteServiceResponse) ProtoMessage() {} -type ExecSandboxEvent_Exit struct { - Exit *ExecSandboxExit `protobuf:"bytes,3,opt,name=exit,proto3,oneof"` +func (x *DeleteServiceResponse) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[51] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) } -func (*ExecSandboxEvent_Stdout) isExecSandboxEvent_Payload() {} - -func (*ExecSandboxEvent_Stderr) isExecSandboxEvent_Payload() {} +// Deprecated: Use DeleteServiceResponse.ProtoReflect.Descriptor instead. +func (*DeleteServiceResponse) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{51} +} -func (*ExecSandboxEvent_Exit) isExecSandboxEvent_Payload() {} +func (x *DeleteServiceResponse) GetDeleted() bool { + if x != nil { + return x.Deleted + } + return false +} -// Initial frame for one TCP forward stream. -type TcpForwardInit struct { +// Persisted sandbox service endpoint. +type ServiceEndpoint struct { state protoimpl.MessageState `protogen:"open.v1"` - // Sandbox id. - SandboxId string `protobuf:"bytes,1,opt,name=sandbox_id,json=sandboxId,proto3" json:"sandbox_id,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 + // 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. + SandboxName string `protobuf:"bytes,3,opt,name=sandbox_name,json=sandboxName,proto3" json:"sandbox_name,omitempty"` + // Service name within the sandbox. + ServiceName string `protobuf:"bytes,4,opt,name=service_name,json=serviceName,proto3" json:"service_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"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } -func (x *TcpForwardInit) Reset() { - *x = TcpForwardInit{} +func (x *ServiceEndpoint) Reset() { + *x = ServiceEndpoint{} mi := &file_openshell_proto_msgTypes[52] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *TcpForwardInit) String() string { +func (x *ServiceEndpoint) String() string { return protoimpl.X.MessageStringOf(x) } -func (*TcpForwardInit) ProtoMessage() {} +func (*ServiceEndpoint) ProtoMessage() {} -func (x *TcpForwardInit) ProtoReflect() protoreflect.Message { +func (x *ServiceEndpoint) ProtoReflect() protoreflect.Message { mi := &file_openshell_proto_msgTypes[52] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -3781,99 +3750,76 @@ func (x *TcpForwardInit) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use TcpForwardInit.ProtoReflect.Descriptor instead. -func (*TcpForwardInit) Descriptor() ([]byte, []int) { +// Deprecated: Use ServiceEndpoint.ProtoReflect.Descriptor instead. +func (*ServiceEndpoint) Descriptor() ([]byte, []int) { return file_openshell_proto_rawDescGZIP(), []int{52} } -func (x *TcpForwardInit) GetSandboxId() string { +func (x *ServiceEndpoint) GetMetadata() *datamodelv1.ObjectMeta { if x != nil { - return x.SandboxId + return x.Metadata } - return "" + return nil } -func (x *TcpForwardInit) GetServiceId() string { +func (x *ServiceEndpoint) GetSandboxId() string { if x != nil { - return x.ServiceId + return x.SandboxId } return "" } -func (x *TcpForwardInit) GetTarget() isTcpForwardInit_Target { +func (x *ServiceEndpoint) GetSandboxName() string { if x != nil { - return x.Target + return x.SandboxName } - return nil + return "" } -func (x *TcpForwardInit) GetSsh() *SshRelayTarget { +func (x *ServiceEndpoint) GetServiceName() string { if x != nil { - if x, ok := x.Target.(*TcpForwardInit_Ssh); ok { - return x.Ssh - } + return x.ServiceName } - return nil + return "" } -func (x *TcpForwardInit) GetTcp() *TcpRelayTarget { +func (x *ServiceEndpoint) GetTargetPort() uint32 { if x != nil { - if x, ok := x.Target.(*TcpForwardInit_Tcp); ok { - return x.Tcp - } + return x.TargetPort } - return nil + return 0 } -func (x *TcpForwardInit) GetAuthorizationToken() string { +func (x *ServiceEndpoint) GetDomain() bool { if x != nil { - return x.AuthorizationToken + return x.Domain } - 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 false } -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"` +// 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 *TcpForwardFrame) Reset() { - *x = TcpForwardFrame{} +func (x *ServiceEndpointResponse) Reset() { + *x = ServiceEndpointResponse{} mi := &file_openshell_proto_msgTypes[53] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *TcpForwardFrame) String() string { +func (x *ServiceEndpointResponse) String() string { return protoimpl.X.MessageStringOf(x) } -func (*TcpForwardFrame) ProtoMessage() {} +func (*ServiceEndpointResponse) ProtoMessage() {} -func (x *TcpForwardFrame) ProtoReflect() protoreflect.Message { +func (x *ServiceEndpointResponse) ProtoReflect() protoreflect.Message { mi := &file_openshell_proto_msgTypes[53] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -3885,79 +3831,48 @@ func (x *TcpForwardFrame) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use TcpForwardFrame.ProtoReflect.Descriptor instead. -func (*TcpForwardFrame) Descriptor() ([]byte, []int) { +// Deprecated: Use ServiceEndpointResponse.ProtoReflect.Descriptor instead. +func (*ServiceEndpointResponse) Descriptor() ([]byte, []int) { return file_openshell_proto_rawDescGZIP(), []int{53} } -func (x *TcpForwardFrame) GetPayload() isTcpForwardFrame_Payload { - if x != nil { - return x.Payload - } - return nil -} - -func (x *TcpForwardFrame) GetInit() *TcpForwardInit { +func (x *ServiceEndpointResponse) GetEndpoint() *ServiceEndpoint { if x != nil { - if x, ok := x.Payload.(*TcpForwardFrame_Init); ok { - return x.Init - } + return x.Endpoint } return nil } -func (x *TcpForwardFrame) GetData() []byte { +func (x *ServiceEndpointResponse) GetUrl() string { if x != nil { - if x, ok := x.Payload.(*TcpForwardFrame_Data); ok { - return x.Data - } + return x.Url } - 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"` + return "" } -func (*TcpForwardFrame_Init) isTcpForwardFrame_Payload() {} - -func (*TcpForwardFrame_Data) isTcpForwardFrame_Payload() {} - -// Client-to-server message for interactive exec. -type ExecSandboxInput struct { +// Revoke SSH session request. +type RevokeSshSessionRequest 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"` + // Session token to revoke. + Token string `protobuf:"bytes,1,opt,name=token,proto3" json:"token,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } -func (x *ExecSandboxInput) Reset() { - *x = ExecSandboxInput{} +func (x *RevokeSshSessionRequest) Reset() { + *x = RevokeSshSessionRequest{} mi := &file_openshell_proto_msgTypes[54] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *ExecSandboxInput) String() string { +func (x *RevokeSshSessionRequest) String() string { return protoimpl.X.MessageStringOf(x) } -func (*ExecSandboxInput) ProtoMessage() {} +func (*RevokeSshSessionRequest) ProtoMessage() {} -func (x *ExecSandboxInput) ProtoReflect() protoreflect.Message { +func (x *RevokeSshSessionRequest) ProtoReflect() protoreflect.Message { mi := &file_openshell_proto_msgTypes[54] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -3969,93 +3884,41 @@ func (x *ExecSandboxInput) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use ExecSandboxInput.ProtoReflect.Descriptor instead. -func (*ExecSandboxInput) Descriptor() ([]byte, []int) { +// Deprecated: Use RevokeSshSessionRequest.ProtoReflect.Descriptor instead. +func (*RevokeSshSessionRequest) Descriptor() ([]byte, []int) { return file_openshell_proto_rawDescGZIP(), []int{54} } -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 { +func (x *RevokeSshSessionRequest) GetToken() string { if x != nil { - if x, ok := x.Payload.(*ExecSandboxInput_Resize); ok { - return x.Resize - } + return x.Token } - 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"` + return "" } -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"` +// Revoke SSH session response. +type RevokeSshSessionResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + // True when a session was revoked. + Revoked bool `protobuf:"varint,1,opt,name=revoked,proto3" json:"revoked,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } -func (x *ExecSandboxWindowResize) Reset() { - *x = ExecSandboxWindowResize{} +func (x *RevokeSshSessionResponse) Reset() { + *x = RevokeSshSessionResponse{} mi := &file_openshell_proto_msgTypes[55] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *ExecSandboxWindowResize) String() string { +func (x *RevokeSshSessionResponse) String() string { return protoimpl.X.MessageStringOf(x) } -func (*ExecSandboxWindowResize) ProtoMessage() {} +func (*RevokeSshSessionResponse) ProtoMessage() {} -func (x *ExecSandboxWindowResize) ProtoReflect() protoreflect.Message { +func (x *RevokeSshSessionResponse) ProtoReflect() protoreflect.Message { mi := &file_openshell_proto_msgTypes[55] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -4067,57 +3930,57 @@ func (x *ExecSandboxWindowResize) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use ExecSandboxWindowResize.ProtoReflect.Descriptor instead. -func (*ExecSandboxWindowResize) Descriptor() ([]byte, []int) { +// Deprecated: Use RevokeSshSessionResponse.ProtoReflect.Descriptor instead. +func (*RevokeSshSessionResponse) Descriptor() ([]byte, []int) { return file_openshell_proto_rawDescGZIP(), []int{55} } -func (x *ExecSandboxWindowResize) GetCols() uint32 { - if x != nil { - return x.Cols - } - return 0 -} - -func (x *ExecSandboxWindowResize) GetRows() uint32 { +func (x *RevokeSshSessionResponse) GetRevoked() bool { if x != nil { - return x.Rows + return x.Revoked } - return 0 + return false } -// SSH session record stored in persistence. -type SshSession struct { +// Execute command request. +type ExecSandboxRequest 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"` - // Expiry timestamp in milliseconds since epoch. 0 means no expiry - // (backward-compatible default for sessions created before this field existed). - ExpiresAtMs int64 `protobuf:"varint,4,opt,name=expires_at_ms,json=expiresAtMs,proto3" json:"expires_at_ms,omitempty"` - // Revoked flag. - Revoked bool `protobuf:"varint,5,opt,name=revoked,proto3" json:"revoked,omitempty"` + SandboxId string `protobuf:"bytes,1,opt,name=sandbox_id,json=sandboxId,proto3" json:"sandbox_id,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 timeout in seconds. 0 means no timeout. + TimeoutSeconds uint32 `protobuf:"varint,5,opt,name=timeout_seconds,json=timeoutSeconds,proto3" json:"timeout_seconds,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"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } -func (x *SshSession) Reset() { - *x = SshSession{} +func (x *ExecSandboxRequest) Reset() { + *x = ExecSandboxRequest{} mi := &file_openshell_proto_msgTypes[56] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *SshSession) String() string { +func (x *ExecSandboxRequest) String() string { return protoimpl.X.MessageStringOf(x) } -func (*SshSession) ProtoMessage() {} +func (*ExecSandboxRequest) ProtoMessage() {} -func (x *SshSession) ProtoReflect() protoreflect.Message { +func (x *ExecSandboxRequest) ProtoReflect() protoreflect.Message { mi := &file_openshell_proto_msgTypes[56] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -4129,88 +3992,96 @@ func (x *SshSession) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use SshSession.ProtoReflect.Descriptor instead. -func (*SshSession) Descriptor() ([]byte, []int) { +// Deprecated: Use ExecSandboxRequest.ProtoReflect.Descriptor instead. +func (*ExecSandboxRequest) Descriptor() ([]byte, []int) { return file_openshell_proto_rawDescGZIP(), []int{56} } -func (x *SshSession) GetMetadata() *datamodelv1.ObjectMeta { +func (x *ExecSandboxRequest) GetSandboxId() string { if x != nil { - return x.Metadata + return x.SandboxId + } + return "" +} + +func (x *ExecSandboxRequest) GetCommand() []string { + if x != nil { + return x.Command } return nil } -func (x *SshSession) GetSandboxId() string { +func (x *ExecSandboxRequest) GetWorkdir() string { if x != nil { - return x.SandboxId + return x.Workdir } return "" } -func (x *SshSession) GetToken() string { +func (x *ExecSandboxRequest) GetEnvironment() map[string]string { if x != nil { - return x.Token + return x.Environment } - return "" + return nil } -func (x *SshSession) GetExpiresAtMs() int64 { +func (x *ExecSandboxRequest) GetTimeoutSeconds() uint32 { if x != nil { - return x.ExpiresAtMs + return x.TimeoutSeconds } return 0 } -func (x *SshSession) GetRevoked() bool { +func (x *ExecSandboxRequest) GetStdin() []byte { if x != nil { - return x.Revoked + return x.Stdin + } + return nil +} + +func (x *ExecSandboxRequest) GetTty() bool { + if x != nil { + return x.Tty } return false } -// Watch sandbox request. -type WatchSandboxRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - // Sandbox id. - Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,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 a terminal phase (READY or ERROR). - StopOnTerminal bool `protobuf:"varint,7,opt,name=stop_on_terminal,json=stopOnTerminal,proto3" json:"stop_on_terminal,omitempty"` - // Only include log lines with timestamp >= this value (milliseconds since epoch). - // 0 means no time filter. Applies to both tail replay and live streaming. - LogSinceMs int64 `protobuf:"varint,8,opt,name=log_since_ms,json=logSinceMs,proto3" json:"log_since_ms,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"` +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 +} + +// 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"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } -func (x *WatchSandboxRequest) Reset() { - *x = WatchSandboxRequest{} +func (x *ExecSandboxStdout) Reset() { + *x = ExecSandboxStdout{} mi := &file_openshell_proto_msgTypes[57] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *WatchSandboxRequest) String() string { +func (x *ExecSandboxStdout) String() string { return protoimpl.X.MessageStringOf(x) } -func (*WatchSandboxRequest) ProtoMessage() {} +func (*ExecSandboxStdout) ProtoMessage() {} -func (x *WatchSandboxRequest) ProtoReflect() protoreflect.Message { +func (x *ExecSandboxStdout) ProtoReflect() protoreflect.Message { mi := &file_openshell_proto_msgTypes[57] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -4222,110 +4093,40 @@ func (x *WatchSandboxRequest) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use WatchSandboxRequest.ProtoReflect.Descriptor instead. -func (*WatchSandboxRequest) Descriptor() ([]byte, []int) { +// Deprecated: Use ExecSandboxStdout.ProtoReflect.Descriptor instead. +func (*ExecSandboxStdout) Descriptor() ([]byte, []int) { return file_openshell_proto_rawDescGZIP(), []int{57} } -func (x *WatchSandboxRequest) GetId() string { - if x != nil { - return x.Id - } - 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) GetLogSinceMs() int64 { - if x != nil { - return x.LogSinceMs - } - return 0 -} - -func (x *WatchSandboxRequest) GetLogSources() []string { +func (x *ExecSandboxStdout) GetData() []byte { if x != nil { - return x.LogSources + return x.Data } return nil } -func (x *WatchSandboxRequest) GetLogMinLevel() string { - if x != nil { - return x.LogMinLevel - } - 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"` +// 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 *SandboxStreamEvent) Reset() { - *x = SandboxStreamEvent{} +func (x *ExecSandboxStderr) Reset() { + *x = ExecSandboxStderr{} mi := &file_openshell_proto_msgTypes[58] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *SandboxStreamEvent) String() string { +func (x *ExecSandboxStderr) String() string { return protoimpl.X.MessageStringOf(x) } -func (*SandboxStreamEvent) ProtoMessage() {} +func (*ExecSandboxStderr) ProtoMessage() {} -func (x *SandboxStreamEvent) ProtoReflect() protoreflect.Message { +func (x *ExecSandboxStderr) ProtoReflect() protoreflect.Message { mi := &file_openshell_proto_msgTypes[58] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -4337,134 +4138,1477 @@ func (x *SandboxStreamEvent) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use SandboxStreamEvent.ProtoReflect.Descriptor instead. -func (*SandboxStreamEvent) Descriptor() ([]byte, []int) { +// Deprecated: Use ExecSandboxStderr.ProtoReflect.Descriptor instead. +func (*ExecSandboxStderr) Descriptor() ([]byte, []int) { return file_openshell_proto_rawDescGZIP(), []int{58} } -func (x *SandboxStreamEvent) GetPayload() isSandboxStreamEvent_Payload { +func (x *ExecSandboxStderr) GetData() []byte { if x != nil { - return x.Payload + return x.Data } return nil } -func (x *SandboxStreamEvent) GetSandbox() *Sandbox { +// 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[59] + 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[59] if x != nil { - if x, ok := x.Payload.(*SandboxStreamEvent_Sandbox); ok { - return x.Sandbox + 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{59} +} + +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[60] + 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[60] + 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{60} +} + +func (x *ExecSandboxEvent) GetPayload() isExecSandboxEvent_Payload { + if x != nil { + return x.Payload } return nil } -func (x *SandboxStreamEvent) GetLog() *SandboxLogLine { +func (x *ExecSandboxEvent) GetStdout() *ExecSandboxStdout { if x != nil { - if x, ok := x.Payload.(*SandboxStreamEvent_Log); ok { - return x.Log + if x, ok := x.Payload.(*ExecSandboxEvent_Stdout); ok { + return x.Stdout } } return nil } -func (x *SandboxStreamEvent) GetEvent() *PlatformEvent { +func (x *ExecSandboxEvent) GetStderr() *ExecSandboxStderr { if x != nil { - if x, ok := x.Payload.(*SandboxStreamEvent_Event); ok { - return x.Event + if x, ok := x.Payload.(*ExecSandboxEvent_Stderr); ok { + return x.Stderr } } return nil } -func (x *SandboxStreamEvent) GetWarning() *SandboxStreamWarning { +func (x *ExecSandboxEvent) GetExit() *ExecSandboxExit { if x != nil { - if x, ok := x.Payload.(*SandboxStreamEvent_Warning); ok { - return x.Warning + 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 id. + SandboxId string `protobuf:"bytes,1,opt,name=sandbox_id,json=sandboxId,proto3" json:"sandbox_id,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[61] + 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[61] + 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{61} +} + +func (x *TcpForwardInit) GetSandboxId() string { + if x != nil { + return x.SandboxId + } + 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[62] + 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[62] + 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{62} +} + +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[63] + 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[63] + 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{63} +} + +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[64] + 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[64] + 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{64} +} + +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"` + // Expiry timestamp in milliseconds since epoch. 0 means no expiry + // (backward-compatible default for sessions created before this field existed). + ExpiresAtMs int64 `protobuf:"varint,4,opt,name=expires_at_ms,json=expiresAtMs,proto3" json:"expires_at_ms,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[65] + 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[65] + 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{65} +} + +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) GetExpiresAtMs() int64 { + if x != nil { + return x.ExpiresAtMs + } + return 0 +} + +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"` + // Sandbox id. + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,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 a terminal phase (READY or ERROR). + StopOnTerminal bool `protobuf:"varint,7,opt,name=stop_on_terminal,json=stopOnTerminal,proto3" json:"stop_on_terminal,omitempty"` + // Only include log lines with timestamp >= this value (milliseconds since epoch). + // 0 means no time filter. Applies to both tail replay and live streaming. + LogSinceMs int64 `protobuf:"varint,8,opt,name=log_since_ms,json=logSinceMs,proto3" json:"log_since_ms,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"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *WatchSandboxRequest) Reset() { + *x = WatchSandboxRequest{} + mi := &file_openshell_proto_msgTypes[66] + 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[66] + 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{66} +} + +func (x *WatchSandboxRequest) GetId() string { + if x != nil { + return x.Id + } + 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) GetLogSinceMs() int64 { + if x != nil { + return x.LogSinceMs + } + return 0 +} + +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 "" +} + +// 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"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SandboxStreamEvent) Reset() { + *x = SandboxStreamEvent{} + mi := &file_openshell_proto_msgTypes[67] + 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[67] + 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{67} +} + +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 +} + +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 { + // Warning from the server (e.g. missed messages due to lag). + 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"` + TimestampMs int64 `protobuf:"varint,2,opt,name=timestamp_ms,json=timestampMs,proto3" json:"timestamp_ms,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[68] + 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[68] + 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{68} +} + +func (x *SandboxLogLine) GetSandboxId() string { + if x != nil { + return x.SandboxId + } + return "" +} + +func (x *SandboxLogLine) GetTimestampMs() int64 { + if x != nil { + return x.TimestampMs + } + return 0 +} + +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 +} + +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[69] + 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[69] + 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{69} +} + +func (x *SandboxStreamWarning) GetMessage() string { + if x != nil { + return x.Message + } + return "" +} + +// Create provider request. +type CreateProviderRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Provider *datamodelv1.Provider `protobuf:"bytes,1,opt,name=provider,proto3" json:"provider,omitempty"` + // Workspace for the provider. Empty defaults to "default". + Workspace string `protobuf:"bytes,2,opt,name=workspace,proto3" json:"workspace,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *CreateProviderRequest) Reset() { + *x = CreateProviderRequest{} + mi := &file_openshell_proto_msgTypes[70] + 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[70] + 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{70} +} + +func (x *CreateProviderRequest) GetProvider() *datamodelv1.Provider { + if x != nil { + return x.Provider + } + return nil +} + +func (x *CreateProviderRequest) GetWorkspace() string { + if x != nil { + return x.Workspace + } + return "" +} + +// Get provider request. +type GetProviderRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + // Workspace scope. Empty defaults to "default". + Workspace string `protobuf:"bytes,2,opt,name=workspace,proto3" json:"workspace,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetProviderRequest) Reset() { + *x = GetProviderRequest{} + mi := &file_openshell_proto_msgTypes[71] + 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[71] + 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{71} +} + +func (x *GetProviderRequest) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *GetProviderRequest) GetWorkspace() string { + if x != nil { + return x.Workspace + } + return "" +} + +// List providers request. +type ListProvidersRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Limit uint32 `protobuf:"varint,1,opt,name=limit,proto3" json:"limit,omitempty"` + Offset uint32 `protobuf:"varint,2,opt,name=offset,proto3" json:"offset,omitempty"` + // Workspace scope. Empty defaults to "default". + Workspace string `protobuf:"bytes,3,opt,name=workspace,proto3" json:"workspace,omitempty"` + // List across all workspaces. Mutually exclusive with workspace. + AllWorkspaces bool `protobuf:"varint,4,opt,name=all_workspaces,json=allWorkspaces,proto3" json:"all_workspaces,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListProvidersRequest) Reset() { + *x = ListProvidersRequest{} + mi := &file_openshell_proto_msgTypes[72] + 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[72] + 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{72} +} + +func (x *ListProvidersRequest) GetLimit() uint32 { + if x != nil { + return x.Limit + } + return 0 +} + +func (x *ListProvidersRequest) GetOffset() uint32 { + if x != nil { + return x.Offset + } + return 0 +} + +func (x *ListProvidersRequest) GetWorkspace() string { + if x != nil { + return x.Workspace + } + return "" +} + +func (x *ListProvidersRequest) GetAllWorkspaces() bool { + if x != nil { + return x.AllWorkspaces + } + return false +} + +// Update provider request. +type UpdateProviderRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Provider *datamodelv1.Provider `protobuf:"bytes,1,opt,name=provider,proto3" json:"provider,omitempty"` + // Optional per-credential expiry timestamps to merge into the provider. + // A zero value removes the expiry for that credential. + CredentialExpiresAtMs map[string]int64 `protobuf:"bytes,2,rep,name=credential_expires_at_ms,json=credentialExpiresAtMs,proto3" json:"credential_expires_at_ms,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"varint,2,opt,name=value"` + // Workspace scope. Empty defaults to "default". + Workspace string `protobuf:"bytes,3,opt,name=workspace,proto3" json:"workspace,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *UpdateProviderRequest) Reset() { + *x = UpdateProviderRequest{} + mi := &file_openshell_proto_msgTypes[73] + 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[73] + 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{73} +} + +func (x *UpdateProviderRequest) GetProvider() *datamodelv1.Provider { + if x != nil { + return x.Provider + } + return nil +} + +func (x *UpdateProviderRequest) GetCredentialExpiresAtMs() map[string]int64 { + if x != nil { + return x.CredentialExpiresAtMs + } + return nil +} + +func (x *UpdateProviderRequest) GetWorkspace() string { + if x != nil { + return x.Workspace + } + return "" +} + +// Delete provider request. +type DeleteProviderRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + // Workspace scope. Empty defaults to "default". + Workspace string `protobuf:"bytes,2,opt,name=workspace,proto3" json:"workspace,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DeleteProviderRequest) Reset() { + *x = DeleteProviderRequest{} + mi := &file_openshell_proto_msgTypes[74] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DeleteProviderRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteProviderRequest) ProtoMessage() {} + +func (x *DeleteProviderRequest) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[74] + 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 DeleteProviderRequest.ProtoReflect.Descriptor instead. +func (*DeleteProviderRequest) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{74} +} + +func (x *DeleteProviderRequest) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *DeleteProviderRequest) GetWorkspace() string { + if x != nil { + return x.Workspace + } + 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"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ProviderResponse) Reset() { + *x = ProviderResponse{} + mi := &file_openshell_proto_msgTypes[75] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ProviderResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ProviderResponse) ProtoMessage() {} + +func (x *ProviderResponse) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[75] + 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 ProviderResponse.ProtoReflect.Descriptor instead. +func (*ProviderResponse) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{75} +} + +func (x *ProviderResponse) GetProvider() *datamodelv1.Provider { + if x != nil { + return x.Provider + } + return nil +} + +// 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"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListProvidersResponse) Reset() { + *x = ListProvidersResponse{} + mi := &file_openshell_proto_msgTypes[76] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListProvidersResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListProvidersResponse) ProtoMessage() {} + +func (x *ListProvidersResponse) 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 nil + return mi.MessageOf(x) } -func (x *SandboxStreamEvent) GetDraftPolicyUpdate() *DraftPolicyUpdate { +// Deprecated: Use ListProvidersResponse.ProtoReflect.Descriptor instead. +func (*ListProvidersResponse) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{76} +} + +func (x *ListProvidersResponse) GetProviders() []*datamodelv1.Provider { if x != nil { - if x, ok := x.Payload.(*SandboxStreamEvent_DraftPolicyUpdate); ok { - return x.DraftPolicyUpdate - } + return x.Providers } return nil } -type isSandboxStreamEvent_Payload interface { - isSandboxStreamEvent_Payload() +// List provider type profiles request. +type ListProviderProfilesRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Limit uint32 `protobuf:"varint,1,opt,name=limit,proto3" json:"limit,omitempty"` + Offset uint32 `protobuf:"varint,2,opt,name=offset,proto3" json:"offset,omitempty"` + // Workspace scope. When set, returns workspace-scoped + built-in profiles. + // When empty, returns platform-scoped + built-in only. + Workspace string `protobuf:"bytes,3,opt,name=workspace,proto3" json:"workspace,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } -type SandboxStreamEvent_Sandbox struct { - // Latest sandbox snapshot. - Sandbox *Sandbox `protobuf:"bytes,1,opt,name=sandbox,proto3,oneof"` +func (x *ListProviderProfilesRequest) Reset() { + *x = ListProviderProfilesRequest{} + mi := &file_openshell_proto_msgTypes[77] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } -type SandboxStreamEvent_Log struct { - // One server log line/event. - Log *SandboxLogLine `protobuf:"bytes,2,opt,name=log,proto3,oneof"` +func (x *ListProviderProfilesRequest) String() string { + return protoimpl.X.MessageStringOf(x) } -type SandboxStreamEvent_Event struct { - // One platform event. - Event *PlatformEvent `protobuf:"bytes,3,opt,name=event,proto3,oneof"` -} +func (*ListProviderProfilesRequest) ProtoMessage() {} -type SandboxStreamEvent_Warning struct { - // Warning from the server (e.g. missed messages due to lag). - Warning *SandboxStreamWarning `protobuf:"bytes,4,opt,name=warning,proto3,oneof"` +func (x *ListProviderProfilesRequest) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[77] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) } -type SandboxStreamEvent_DraftPolicyUpdate struct { - // Draft policy update notification. - DraftPolicyUpdate *DraftPolicyUpdate `protobuf:"bytes,5,opt,name=draft_policy_update,json=draftPolicyUpdate,proto3,oneof"` +// Deprecated: Use ListProviderProfilesRequest.ProtoReflect.Descriptor instead. +func (*ListProviderProfilesRequest) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{77} } -func (*SandboxStreamEvent_Sandbox) isSandboxStreamEvent_Payload() {} - -func (*SandboxStreamEvent_Log) isSandboxStreamEvent_Payload() {} - -func (*SandboxStreamEvent_Event) isSandboxStreamEvent_Payload() {} +func (x *ListProviderProfilesRequest) GetLimit() uint32 { + if x != nil { + return x.Limit + } + return 0 +} -func (*SandboxStreamEvent_Warning) isSandboxStreamEvent_Payload() {} +func (x *ListProviderProfilesRequest) GetOffset() uint32 { + if x != nil { + return x.Offset + } + return 0 +} -func (*SandboxStreamEvent_DraftPolicyUpdate) isSandboxStreamEvent_Payload() {} +func (x *ListProviderProfilesRequest) GetWorkspace() string { + if x != nil { + return x.Workspace + } + return "" +} -// 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"` - TimestampMs int64 `protobuf:"varint,2,opt,name=timestamp_ms,json=timestampMs,proto3" json:"timestamp_ms,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"` +// Fetch provider type profile request. +type GetProviderProfileRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + // Workspace scope for two-tier profile resolution. When set, checks + // workspace-scoped profiles first, then platform-scoped, then built-in. + // When empty, checks platform-scoped then built-in only. + Workspace string `protobuf:"bytes,2,opt,name=workspace,proto3" json:"workspace,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } -func (x *SandboxLogLine) Reset() { - *x = SandboxLogLine{} - mi := &file_openshell_proto_msgTypes[59] +func (x *GetProviderProfileRequest) Reset() { + *x = GetProviderProfileRequest{} + mi := &file_openshell_proto_msgTypes[78] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *SandboxLogLine) String() string { +func (x *GetProviderProfileRequest) String() string { return protoimpl.X.MessageStringOf(x) } -func (*SandboxLogLine) ProtoMessage() {} +func (*GetProviderProfileRequest) ProtoMessage() {} -func (x *SandboxLogLine) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[59] +func (x *GetProviderProfileRequest) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[78] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4475,82 +5619,105 @@ 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{59} +// Deprecated: Use GetProviderProfileRequest.ProtoReflect.Descriptor instead. +func (*GetProviderProfileRequest) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{78} } -func (x *SandboxLogLine) GetSandboxId() string { +func (x *GetProviderProfileRequest) GetId() string { if x != nil { - return x.SandboxId + return x.Id } return "" } -func (x *SandboxLogLine) GetTimestampMs() int64 { +func (x *GetProviderProfileRequest) GetWorkspace() string { if x != nil { - return x.TimestampMs + return x.Workspace } - return 0 + return "" } -func (x *SandboxLogLine) GetLevel() string { - if x != nil { - return x.Level - } - 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 *SandboxLogLine) GetTarget() string { - if x != nil { - return x.Target - } - return "" +func (x *ProviderProfileImportItem) Reset() { + *x = ProviderProfileImportItem{} + mi := &file_openshell_proto_msgTypes[79] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } -func (x *SandboxLogLine) GetMessage() string { +func (x *ProviderProfileImportItem) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ProviderProfileImportItem) ProtoMessage() {} + +func (x *ProviderProfileImportItem) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[79] if x != nil { - return x.Message + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms } - return "" + return mi.MessageOf(x) } -func (x *SandboxLogLine) GetSource() string { +// Deprecated: Use ProviderProfileImportItem.ProtoReflect.Descriptor instead. +func (*ProviderProfileImportItem) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{79} +} + +func (x *ProviderProfileImportItem) GetProfile() *ProviderProfile { if x != nil { - return x.Source + return x.Profile } - return "" + return nil } -func (x *SandboxLogLine) GetFields() map[string]string { +func (x *ProviderProfileImportItem) GetSource() string { if x != nil { - return x.Fields + return x.Source } - return nil + return "" } -type SandboxStreamWarning struct { +// Provider profile validation diagnostic. +type ProviderProfileDiagnostic struct { state protoimpl.MessageState `protogen:"open.v1"` - Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"` + 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 *SandboxStreamWarning) Reset() { - *x = SandboxStreamWarning{} - mi := &file_openshell_proto_msgTypes[60] +func (x *ProviderProfileDiagnostic) Reset() { + *x = ProviderProfileDiagnostic{} + mi := &file_openshell_proto_msgTypes[80] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *SandboxStreamWarning) String() string { +func (x *ProviderProfileDiagnostic) String() string { return protoimpl.X.MessageStringOf(x) } -func (*SandboxStreamWarning) ProtoMessage() {} +func (*ProviderProfileDiagnostic) ProtoMessage() {} -func (x *SandboxStreamWarning) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[60] +func (x *ProviderProfileDiagnostic) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[80] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4561,43 +5728,78 @@ func (x *SandboxStreamWarning) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use SandboxStreamWarning.ProtoReflect.Descriptor instead. -func (*SandboxStreamWarning) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{60} +// Deprecated: Use ProviderProfileDiagnostic.ProtoReflect.Descriptor instead. +func (*ProviderProfileDiagnostic) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{80} } -func (x *SandboxStreamWarning) GetMessage() string { +func (x *ProviderProfileDiagnostic) GetSource() string { + if x != nil { + return x.Source + } + return "" +} + +func (x *ProviderProfileDiagnostic) GetProfileId() string { + if x != nil { + return x.ProfileId + } + return "" +} + +func (x *ProviderProfileDiagnostic) GetField() string { + if x != nil { + return x.Field + } + return "" +} + +func (x *ProviderProfileDiagnostic) GetMessage() string { if x != nil { return x.Message } return "" } -// Create provider request. -type CreateProviderRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - Provider *datamodelv1.Provider `protobuf:"bytes,1,opt,name=provider,proto3" json:"provider,omitempty"` - // Workspace for the provider. Empty defaults to "default". - Workspace string `protobuf:"bytes,2,opt,name=workspace,proto3" json:"workspace,omitempty"` +func (x *ProviderProfileDiagnostic) GetSeverity() string { + if x != nil { + return x.Severity + } + return "" +} + +// 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"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } -func (x *CreateProviderRequest) Reset() { - *x = CreateProviderRequest{} - mi := &file_openshell_proto_msgTypes[61] +func (x *ProviderCredentialTokenGrantAudienceOverride) Reset() { + *x = ProviderCredentialTokenGrantAudienceOverride{} + mi := &file_openshell_proto_msgTypes[81] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *CreateProviderRequest) String() string { +func (x *ProviderCredentialTokenGrantAudienceOverride) String() string { return protoimpl.X.MessageStringOf(x) } -func (*CreateProviderRequest) ProtoMessage() {} +func (*ProviderCredentialTokenGrantAudienceOverride) ProtoMessage() {} -func (x *CreateProviderRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[61] +func (x *ProviderCredentialTokenGrantAudienceOverride) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[81] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4608,50 +5810,75 @@ 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{61} +// Deprecated: Use ProviderCredentialTokenGrantAudienceOverride.ProtoReflect.Descriptor instead. +func (*ProviderCredentialTokenGrantAudienceOverride) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{81} } -func (x *CreateProviderRequest) GetProvider() *datamodelv1.Provider { +func (x *ProviderCredentialTokenGrantAudienceOverride) GetHost() string { if x != nil { - return x.Provider + return x.Host } - return nil + return "" } -func (x *CreateProviderRequest) GetWorkspace() string { +func (x *ProviderCredentialTokenGrantAudienceOverride) GetPort() uint32 { if x != nil { - return x.Workspace + return x.Port + } + return 0 +} + +func (x *ProviderCredentialTokenGrantAudienceOverride) GetPath() string { + if x != nil { + return x.Path } return "" } -// Get provider request. -type GetProviderRequest struct { +func (x *ProviderCredentialTokenGrantAudienceOverride) GetAudience() string { + if x != nil { + return x.Audience + } + return "" +} + +func (x *ProviderCredentialTokenGrantAudienceOverride) GetScopes() []string { + if x != nil { + return x.Scopes + } + return nil +} + +type ProviderCredentialTokenGrantSubjectToken struct { state protoimpl.MessageState `protogen:"open.v1"` - Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` - // Workspace scope. Empty defaults to "default". - Workspace string `protobuf:"bytes,2,opt,name=workspace,proto3" json:"workspace,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + // 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 *GetProviderRequest) Reset() { - *x = GetProviderRequest{} - mi := &file_openshell_proto_msgTypes[62] +func (x *ProviderCredentialTokenGrantSubjectToken) Reset() { + *x = ProviderCredentialTokenGrantSubjectToken{} + mi := &file_openshell_proto_msgTypes[82] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *GetProviderRequest) String() string { +func (x *ProviderCredentialTokenGrantSubjectToken) String() string { return protoimpl.X.MessageStringOf(x) } -func (*GetProviderRequest) ProtoMessage() {} +func (*ProviderCredentialTokenGrantSubjectToken) ProtoMessage() {} -func (x *GetProviderRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[62] +func (x *ProviderCredentialTokenGrantSubjectToken) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[82] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4662,53 +5889,78 @@ 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{62} +// Deprecated: Use ProviderCredentialTokenGrantSubjectToken.ProtoReflect.Descriptor instead. +func (*ProviderCredentialTokenGrantSubjectToken) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{82} } -func (x *GetProviderRequest) GetName() string { +func (x *ProviderCredentialTokenGrantSubjectToken) GetSource() string { if x != nil { - return x.Name + return x.Source } return "" } -func (x *GetProviderRequest) GetWorkspace() string { +func (x *ProviderCredentialTokenGrantSubjectToken) GetCredential() string { if x != nil { - return x.Workspace + return x.Credential } return "" } -// List providers request. -type ListProvidersRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - Limit uint32 `protobuf:"varint,1,opt,name=limit,proto3" json:"limit,omitempty"` - Offset uint32 `protobuf:"varint,2,opt,name=offset,proto3" json:"offset,omitempty"` - // Workspace scope. Empty defaults to "default". - Workspace string `protobuf:"bytes,3,opt,name=workspace,proto3" json:"workspace,omitempty"` - // List across all workspaces. Mutually exclusive with workspace. - AllWorkspaces bool `protobuf:"varint,4,opt,name=all_workspaces,json=allWorkspaces,proto3" json:"all_workspaces,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache +func (x *ProviderCredentialTokenGrantSubjectToken) GetSubjectTokenType() string { + if x != nil { + return x.SubjectTokenType + } + return "" } -func (x *ListProvidersRequest) Reset() { - *x = ListProvidersRequest{} - mi := &file_openshell_proto_msgTypes[63] +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: override token cache TTL (seconds) + // If 0 or omitted, use expires_in from token response + CacheTtlSeconds int64 `protobuf:"varint,4,opt,name=cache_ttl_seconds,json=cacheTtlSeconds,proto3" json:"cache_ttl_seconds,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 *ProviderCredentialTokenGrant) Reset() { + *x = ProviderCredentialTokenGrant{} + mi := &file_openshell_proto_msgTypes[83] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *ListProvidersRequest) String() string { +func (x *ProviderCredentialTokenGrant) String() string { return protoimpl.X.MessageStringOf(x) } -func (*ListProvidersRequest) ProtoMessage() {} +func (*ProviderCredentialTokenGrant) ProtoMessage() {} -func (x *ListProvidersRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[63] +func (x *ProviderCredentialTokenGrant) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[83] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4719,128 +5971,113 @@ 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{63} +// Deprecated: Use ProviderCredentialTokenGrant.ProtoReflect.Descriptor instead. +func (*ProviderCredentialTokenGrant) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{83} } -func (x *ListProvidersRequest) GetLimit() uint32 { +func (x *ProviderCredentialTokenGrant) GetTokenEndpoint() string { if x != nil { - return x.Limit + return x.TokenEndpoint } - return 0 + return "" } -func (x *ListProvidersRequest) GetOffset() uint32 { +func (x *ProviderCredentialTokenGrant) GetAudience() string { if x != nil { - return x.Offset + return x.Audience } - return 0 + return "" } -func (x *ListProvidersRequest) GetWorkspace() string { +func (x *ProviderCredentialTokenGrant) GetJwtSvidAudience() string { if x != nil { - return x.Workspace + return x.JwtSvidAudience } return "" } -func (x *ListProvidersRequest) GetAllWorkspaces() bool { +func (x *ProviderCredentialTokenGrant) GetScopes() []string { if x != nil { - return x.AllWorkspaces + return x.Scopes } - return false -} - -// Update provider request. -type UpdateProviderRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - Provider *datamodelv1.Provider `protobuf:"bytes,1,opt,name=provider,proto3" json:"provider,omitempty"` - // Optional per-credential expiry timestamps to merge into the provider. - // A zero value removes the expiry for that credential. - CredentialExpiresAtMs map[string]int64 `protobuf:"bytes,2,rep,name=credential_expires_at_ms,json=credentialExpiresAtMs,proto3" json:"credential_expires_at_ms,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"varint,2,opt,name=value"` - // Workspace scope. Empty defaults to "default". - Workspace string `protobuf:"bytes,3,opt,name=workspace,proto3" json:"workspace,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *UpdateProviderRequest) Reset() { - *x = UpdateProviderRequest{} - mi := &file_openshell_proto_msgTypes[64] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) + return nil } -func (x *UpdateProviderRequest) String() string { - return protoimpl.X.MessageStringOf(x) +func (x *ProviderCredentialTokenGrant) GetCacheTtlSeconds() int64 { + if x != nil { + return x.CacheTtlSeconds + } + return 0 } -func (*UpdateProviderRequest) ProtoMessage() {} - -func (x *UpdateProviderRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[64] +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 UpdateProviderRequest.ProtoReflect.Descriptor instead. -func (*UpdateProviderRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{64} +func (x *ProviderCredentialTokenGrant) GetClientAssertionType() string { + if x != nil { + return x.ClientAssertionType + } + return "" } -func (x *UpdateProviderRequest) GetProvider() *datamodelv1.Provider { +func (x *ProviderCredentialTokenGrant) GetGrantType() ProviderCredentialTokenGrantType { if x != nil { - return x.Provider + return x.GrantType } - return nil + return ProviderCredentialTokenGrantType_PROVIDER_CREDENTIAL_TOKEN_GRANT_TYPE_UNSPECIFIED } -func (x *UpdateProviderRequest) GetCredentialExpiresAtMs() map[string]int64 { +func (x *ProviderCredentialTokenGrant) GetSubjectToken() *ProviderCredentialTokenGrantSubjectToken { if x != nil { - return x.CredentialExpiresAtMs + return x.SubjectToken } return nil } -func (x *UpdateProviderRequest) GetWorkspace() string { +func (x *ProviderCredentialTokenGrant) GetRequestedTokenType() string { if x != nil { - return x.Workspace + return x.RequestedTokenType } return "" } -// Delete provider request. -type DeleteProviderRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` - // Workspace scope. Empty defaults to "default". - Workspace string `protobuf:"bytes,2,opt,name=workspace,proto3" json:"workspace,omitempty"` +// 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"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } -func (x *DeleteProviderRequest) Reset() { - *x = DeleteProviderRequest{} - mi := &file_openshell_proto_msgTypes[65] +func (x *ProviderProfileCredential) Reset() { + *x = ProviderProfileCredential{} + mi := &file_openshell_proto_msgTypes[84] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *DeleteProviderRequest) String() string { +func (x *ProviderProfileCredential) String() string { return protoimpl.X.MessageStringOf(x) } -func (*DeleteProviderRequest) ProtoMessage() {} +func (*ProviderProfileCredential) ProtoMessage() {} -func (x *DeleteProviderRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[65] +func (x *ProviderProfileCredential) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[84] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4851,93 +6088,106 @@ 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{65} +// Deprecated: Use ProviderProfileCredential.ProtoReflect.Descriptor instead. +func (*ProviderProfileCredential) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{84} } -func (x *DeleteProviderRequest) GetName() string { +func (x *ProviderProfileCredential) GetName() string { if x != nil { return x.Name } return "" } -func (x *DeleteProviderRequest) GetWorkspace() string { +func (x *ProviderProfileCredential) GetDescription() string { if x != nil { - return x.Workspace + return x.Description } 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"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache +func (x *ProviderProfileCredential) GetEnvVars() []string { + if x != nil { + return x.EnvVars + } + return nil } -func (x *ProviderResponse) Reset() { - *x = ProviderResponse{} - mi := &file_openshell_proto_msgTypes[66] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) +func (x *ProviderProfileCredential) GetRequired() bool { + if x != nil { + return x.Required + } + return false } -func (x *ProviderResponse) String() string { - return protoimpl.X.MessageStringOf(x) +func (x *ProviderProfileCredential) GetAuthStyle() string { + if x != nil { + return x.AuthStyle + } + return "" } -func (*ProviderResponse) ProtoMessage() {} +func (x *ProviderProfileCredential) GetHeaderName() string { + if x != nil { + return x.HeaderName + } + return "" +} -func (x *ProviderResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[66] +func (x *ProviderProfileCredential) GetQueryParam() string { if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms + return x.QueryParam } - return mi.MessageOf(x) + return "" } -// Deprecated: Use ProviderResponse.ProtoReflect.Descriptor instead. -func (*ProviderResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{66} +func (x *ProviderProfileCredential) GetRefresh() *ProviderCredentialRefresh { + if x != nil { + return x.Refresh + } + return nil } -func (x *ProviderResponse) GetProvider() *datamodelv1.Provider { +func (x *ProviderProfileCredential) GetPathTemplate() string { if x != nil { - return x.Provider + return x.PathTemplate + } + return "" +} + +func (x *ProviderProfileCredential) GetTokenGrant() *ProviderCredentialTokenGrant { + if x != nil { + return x.TokenGrant } return nil } -// 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"` +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"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } -func (x *ListProvidersResponse) Reset() { - *x = ListProvidersResponse{} - mi := &file_openshell_proto_msgTypes[67] +func (x *ProviderCredentialRefreshMaterial) Reset() { + *x = ProviderCredentialRefreshMaterial{} + mi := &file_openshell_proto_msgTypes[85] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *ListProvidersResponse) String() string { +func (x *ProviderCredentialRefreshMaterial) String() string { return protoimpl.X.MessageStringOf(x) } -func (*ListProvidersResponse) ProtoMessage() {} +func (*ProviderCredentialRefreshMaterial) ProtoMessage() {} -func (x *ListProvidersResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[67] +func (x *ProviderCredentialRefreshMaterial) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[85] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4948,45 +6198,66 @@ 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{67} +// Deprecated: Use ProviderCredentialRefreshMaterial.ProtoReflect.Descriptor instead. +func (*ProviderCredentialRefreshMaterial) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{85} } -func (x *ListProvidersResponse) GetProviders() []*datamodelv1.Provider { +func (x *ProviderCredentialRefreshMaterial) GetName() string { if x != nil { - return x.Providers + return x.Name } - return nil + return "" } -// List provider type profiles request. -type ListProviderProfilesRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - Limit uint32 `protobuf:"varint,1,opt,name=limit,proto3" json:"limit,omitempty"` - Offset uint32 `protobuf:"varint,2,opt,name=offset,proto3" json:"offset,omitempty"` - // Workspace scope. When set, returns workspace-scoped + built-in profiles. - // When empty, returns platform-scoped + built-in only. - Workspace string `protobuf:"bytes,3,opt,name=workspace,proto3" json:"workspace,omitempty"` +func (x *ProviderCredentialRefreshMaterial) GetDescription() string { + if x != nil { + return x.Description + } + 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 unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } -func (x *ListProviderProfilesRequest) Reset() { - *x = ListProviderProfilesRequest{} - mi := &file_openshell_proto_msgTypes[68] +func (x *ProviderCredentialRefreshOutput) Reset() { + *x = ProviderCredentialRefreshOutput{} + mi := &file_openshell_proto_msgTypes[86] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *ListProviderProfilesRequest) String() string { +func (x *ProviderCredentialRefreshOutput) String() string { return protoimpl.X.MessageStringOf(x) } -func (*ListProviderProfilesRequest) ProtoMessage() {} +func (*ProviderCredentialRefreshOutput) ProtoMessage() {} -func (x *ListProviderProfilesRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[68] +func (x *ProviderCredentialRefreshOutput) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[86] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4997,59 +6268,53 @@ func (x *ListProviderProfilesRequest) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use ListProviderProfilesRequest.ProtoReflect.Descriptor instead. -func (*ListProviderProfilesRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{68} -} - -func (x *ListProviderProfilesRequest) GetLimit() uint32 { - if x != nil { - return x.Limit - } - return 0 +// Deprecated: Use ProviderCredentialRefreshOutput.ProtoReflect.Descriptor instead. +func (*ProviderCredentialRefreshOutput) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{86} } -func (x *ListProviderProfilesRequest) GetOffset() uint32 { +func (x *ProviderCredentialRefreshOutput) GetOutput() string { if x != nil { - return x.Offset + return x.Output } - return 0 + return "" } -func (x *ListProviderProfilesRequest) GetWorkspace() string { +func (x *ProviderCredentialRefreshOutput) GetCredential() string { if x != nil { - return x.Workspace + return x.Credential } return "" } -// Fetch provider type profile request. -type GetProviderProfileRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` - // Workspace scope for two-tier profile resolution. When set, checks - // workspace-scoped profiles first, then platform-scoped, then built-in. - // When empty, checks platform-scoped then built-in only. - Workspace string `protobuf:"bytes,2,opt,name=workspace,proto3" json:"workspace,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"` + RefreshBeforeSeconds int64 `protobuf:"varint,4,opt,name=refresh_before_seconds,json=refreshBeforeSeconds,proto3" json:"refresh_before_seconds,omitempty"` + MaxLifetimeSeconds int64 `protobuf:"varint,5,opt,name=max_lifetime_seconds,json=maxLifetimeSeconds,proto3" json:"max_lifetime_seconds,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 *GetProviderProfileRequest) Reset() { - *x = GetProviderProfileRequest{} - mi := &file_openshell_proto_msgTypes[69] +func (x *ProviderCredentialRefresh) Reset() { + *x = ProviderCredentialRefresh{} + mi := &file_openshell_proto_msgTypes[87] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *GetProviderProfileRequest) String() string { +func (x *ProviderCredentialRefresh) String() string { return protoimpl.X.MessageStringOf(x) } -func (*GetProviderProfileRequest) ProtoMessage() {} +func (*ProviderCredentialRefresh) ProtoMessage() {} -func (x *GetProviderProfileRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[69] +func (x *ProviderCredentialRefresh) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[87] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5060,105 +6325,90 @@ 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{69} +// Deprecated: Use ProviderCredentialRefresh.ProtoReflect.Descriptor instead. +func (*ProviderCredentialRefresh) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{87} } -func (x *GetProviderProfileRequest) GetId() string { +func (x *ProviderCredentialRefresh) GetStrategy() ProviderCredentialRefreshStrategy { if x != nil { - return x.Id + return x.Strategy } - return "" + return ProviderCredentialRefreshStrategy_PROVIDER_CREDENTIAL_REFRESH_STRATEGY_UNSPECIFIED } -func (x *GetProviderProfileRequest) GetWorkspace() string { +func (x *ProviderCredentialRefresh) GetTokenUrl() string { if x != nil { - return x.Workspace + return x.TokenUrl } 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 *ProviderProfileImportItem) Reset() { - *x = ProviderProfileImportItem{} - mi := &file_openshell_proto_msgTypes[70] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *ProviderProfileImportItem) String() string { - return protoimpl.X.MessageStringOf(x) +func (x *ProviderCredentialRefresh) GetScopes() []string { + if x != nil { + return x.Scopes + } + return nil } -func (*ProviderProfileImportItem) ProtoMessage() {} - -func (x *ProviderProfileImportItem) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[70] +func (x *ProviderCredentialRefresh) GetRefreshBeforeSeconds() int64 { if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms + return x.RefreshBeforeSeconds } - return mi.MessageOf(x) + return 0 } -// Deprecated: Use ProviderProfileImportItem.ProtoReflect.Descriptor instead. -func (*ProviderProfileImportItem) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{70} +func (x *ProviderCredentialRefresh) GetMaxLifetimeSeconds() int64 { + if x != nil { + return x.MaxLifetimeSeconds + } + return 0 } -func (x *ProviderProfileImportItem) GetProfile() *ProviderProfile { +func (x *ProviderCredentialRefresh) GetMaterial() []*ProviderCredentialRefreshMaterial { if x != nil { - return x.Profile + return x.Material } return nil } -func (x *ProviderProfileImportItem) GetSource() string { +func (x *ProviderCredentialRefresh) GetAdditionalOutputs() []*ProviderCredentialRefreshOutput { if x != nil { - return x.Source + return x.AdditionalOutputs } - return "" + return nil } -// 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 +type ProviderCredentialRefreshStatus struct { + state protoimpl.MessageState `protogen:"open.v1"` + ProviderName string `protobuf:"bytes,1,opt,name=provider_name,json=providerName,proto3" json:"provider_name,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"` + ExpiresAtMs int64 `protobuf:"varint,6,opt,name=expires_at_ms,json=expiresAtMs,proto3" json:"expires_at_ms,omitempty"` + NextRefreshAtMs int64 `protobuf:"varint,7,opt,name=next_refresh_at_ms,json=nextRefreshAtMs,proto3" json:"next_refresh_at_ms,omitempty"` + LastRefreshAtMs int64 `protobuf:"varint,8,opt,name=last_refresh_at_ms,json=lastRefreshAtMs,proto3" json:"last_refresh_at_ms,omitempty"` + LastError string `protobuf:"bytes,9,opt,name=last_error,json=lastError,proto3" json:"last_error,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } -func (x *ProviderProfileDiagnostic) Reset() { - *x = ProviderProfileDiagnostic{} - mi := &file_openshell_proto_msgTypes[71] +func (x *ProviderCredentialRefreshStatus) Reset() { + *x = ProviderCredentialRefreshStatus{} + mi := &file_openshell_proto_msgTypes[88] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *ProviderProfileDiagnostic) String() string { +func (x *ProviderCredentialRefreshStatus) String() string { return protoimpl.X.MessageStringOf(x) } -func (*ProviderProfileDiagnostic) ProtoMessage() {} +func (*ProviderCredentialRefreshStatus) ProtoMessage() {} -func (x *ProviderProfileDiagnostic) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[71] +func (x *ProviderCredentialRefreshStatus) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[88] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5169,78 +6419,98 @@ 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{71} +// Deprecated: Use ProviderCredentialRefreshStatus.ProtoReflect.Descriptor instead. +func (*ProviderCredentialRefreshStatus) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{88} } -func (x *ProviderProfileDiagnostic) GetSource() string { +func (x *ProviderCredentialRefreshStatus) GetProviderName() string { if x != nil { - return x.Source + return x.ProviderName } return "" } -func (x *ProviderProfileDiagnostic) GetProfileId() string { +func (x *ProviderCredentialRefreshStatus) GetProviderId() string { if x != nil { - return x.ProfileId + return x.ProviderId } return "" } -func (x *ProviderProfileDiagnostic) GetField() string { +func (x *ProviderCredentialRefreshStatus) GetCredentialKey() string { if x != nil { - return x.Field + return x.CredentialKey } return "" } -func (x *ProviderProfileDiagnostic) GetMessage() string { +func (x *ProviderCredentialRefreshStatus) GetStrategy() ProviderCredentialRefreshStrategy { if x != nil { - return x.Message + return x.Strategy + } + return ProviderCredentialRefreshStrategy_PROVIDER_CREDENTIAL_REFRESH_STRATEGY_UNSPECIFIED +} + +func (x *ProviderCredentialRefreshStatus) GetStatus() string { + if x != nil { + return x.Status } return "" } -func (x *ProviderProfileDiagnostic) GetSeverity() string { +func (x *ProviderCredentialRefreshStatus) GetExpiresAtMs() int64 { if x != nil { - return x.Severity + return x.ExpiresAtMs + } + return 0 +} + +func (x *ProviderCredentialRefreshStatus) GetNextRefreshAtMs() int64 { + if x != nil { + return x.NextRefreshAtMs + } + return 0 +} + +func (x *ProviderCredentialRefreshStatus) GetLastRefreshAtMs() int64 { + if x != nil { + return x.LastRefreshAtMs + } + return 0 +} + +func (x *ProviderCredentialRefreshStatus) GetLastError() string { + if x != nil { + return x.LastError } return "" } -// Endpoint selector for token grant audience overrides. -type ProviderCredentialTokenGrantAudienceOverride struct { +// Provider profile local discovery declaration. +type ProviderProfileDiscovery 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"` + // 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 *ProviderCredentialTokenGrantAudienceOverride) Reset() { - *x = ProviderCredentialTokenGrantAudienceOverride{} - mi := &file_openshell_proto_msgTypes[72] +func (x *ProviderProfileDiscovery) Reset() { + *x = ProviderProfileDiscovery{} + mi := &file_openshell_proto_msgTypes[89] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *ProviderCredentialTokenGrantAudienceOverride) String() string { +func (x *ProviderProfileDiscovery) String() string { return protoimpl.X.MessageStringOf(x) } -func (*ProviderCredentialTokenGrantAudienceOverride) ProtoMessage() {} +func (*ProviderProfileDiscovery) ProtoMessage() {} -func (x *ProviderCredentialTokenGrantAudienceOverride) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[72] +func (x *ProviderProfileDiscovery) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[89] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5251,75 +6521,60 @@ 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{72} -} - -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 -} - -func (x *ProviderCredentialTokenGrantAudienceOverride) GetPath() string { - if x != nil { - return x.Path - } - return "" -} - -func (x *ProviderCredentialTokenGrantAudienceOverride) GetAudience() string { - if x != nil { - return x.Audience - } - return "" +// Deprecated: Use ProviderProfileDiscovery.ProtoReflect.Descriptor instead. +func (*ProviderProfileDiscovery) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{89} } -func (x *ProviderCredentialTokenGrantAudienceOverride) GetScopes() []string { +func (x *ProviderProfileDiscovery) GetCredentials() []string { if x != nil { - return x.Scopes + return x.Credentials } 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 StoredProviderCredentialRefreshState struct { + state protoimpl.MessageState `protogen:"open.v1"` + Metadata *datamodelv1.ObjectMeta `protobuf:"bytes,1,opt,name=metadata,proto3" json:"metadata,omitempty"` + ProviderId string `protobuf:"bytes,2,opt,name=provider_id,json=providerId,proto3" json:"provider_id,omitempty"` + ProviderName string `protobuf:"bytes,3,opt,name=provider_name,json=providerName,proto3" json:"provider_name,omitempty"` + CredentialKey string `protobuf:"bytes,4,opt,name=credential_key,json=credentialKey,proto3" json:"credential_key,omitempty"` + Strategy ProviderCredentialRefreshStrategy `protobuf:"varint,5,opt,name=strategy,proto3,enum=openshell.v1.ProviderCredentialRefreshStrategy" json:"strategy,omitempty"` + Material map[string]string `protobuf:"bytes,6,rep,name=material,proto3" json:"material,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + SecretMaterialKeys []string `protobuf:"bytes,7,rep,name=secret_material_keys,json=secretMaterialKeys,proto3" json:"secret_material_keys,omitempty"` + ExpiresAtMs int64 `protobuf:"varint,8,opt,name=expires_at_ms,json=expiresAtMs,proto3" json:"expires_at_ms,omitempty"` + NextRefreshAtMs int64 `protobuf:"varint,9,opt,name=next_refresh_at_ms,json=nextRefreshAtMs,proto3" json:"next_refresh_at_ms,omitempty"` + LastRefreshAtMs int64 `protobuf:"varint,10,opt,name=last_refresh_at_ms,json=lastRefreshAtMs,proto3" json:"last_refresh_at_ms,omitempty"` + Status string `protobuf:"bytes,11,opt,name=status,proto3" json:"status,omitempty"` + LastError string `protobuf:"bytes,12,opt,name=last_error,json=lastError,proto3" json:"last_error,omitempty"` + TokenUrl string `protobuf:"bytes,13,opt,name=token_url,json=tokenUrl,proto3" json:"token_url,omitempty"` + Scopes []string `protobuf:"bytes,14,rep,name=scopes,proto3" json:"scopes,omitempty"` + RefreshBeforeSeconds int64 `protobuf:"varint,15,opt,name=refresh_before_seconds,json=refreshBeforeSeconds,proto3" json:"refresh_before_seconds,omitempty"` + MaxLifetimeSeconds int64 `protobuf:"varint,16,opt,name=max_lifetime_seconds,json=maxLifetimeSeconds,proto3" json:"max_lifetime_seconds,omitempty"` + // Resolved mapping of strategy-defined output id -> concrete env key, pinned + // at configure time from the profile's additional_outputs. Read by minting, + // collision reservation, and env-key surfacing so later profile edits cannot + // silently redirect writes. + AdditionalOutputKeys map[string]string `protobuf:"bytes,17,rep,name=additional_output_keys,json=additionalOutputKeys,proto3" json:"additional_output_keys,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } -func (x *ProviderCredentialTokenGrantSubjectToken) Reset() { - *x = ProviderCredentialTokenGrantSubjectToken{} - mi := &file_openshell_proto_msgTypes[73] +func (x *StoredProviderCredentialRefreshState) Reset() { + *x = StoredProviderCredentialRefreshState{} + mi := &file_openshell_proto_msgTypes[90] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *ProviderCredentialTokenGrantSubjectToken) String() string { +func (x *StoredProviderCredentialRefreshState) String() string { return protoimpl.X.MessageStringOf(x) } -func (*ProviderCredentialTokenGrantSubjectToken) ProtoMessage() {} +func (*StoredProviderCredentialRefreshState) ProtoMessage() {} -func (x *ProviderCredentialTokenGrantSubjectToken) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[73] +func (x *StoredProviderCredentialRefreshState) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[90] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5330,195 +6585,162 @@ func (x *ProviderCredentialTokenGrantSubjectToken) ProtoReflect() protoreflect.M return mi.MessageOf(x) } -// Deprecated: Use ProviderCredentialTokenGrantSubjectToken.ProtoReflect.Descriptor instead. -func (*ProviderCredentialTokenGrantSubjectToken) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{73} +// Deprecated: Use StoredProviderCredentialRefreshState.ProtoReflect.Descriptor instead. +func (*StoredProviderCredentialRefreshState) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{90} } -func (x *ProviderCredentialTokenGrantSubjectToken) GetSource() string { +func (x *StoredProviderCredentialRefreshState) GetMetadata() *datamodelv1.ObjectMeta { if x != nil { - return x.Source + return x.Metadata } - return "" + return nil } -func (x *ProviderCredentialTokenGrantSubjectToken) GetCredential() string { +func (x *StoredProviderCredentialRefreshState) GetProviderId() string { if x != nil { - return x.Credential + return x.ProviderId } return "" } -func (x *ProviderCredentialTokenGrantSubjectToken) GetSubjectTokenType() string { +func (x *StoredProviderCredentialRefreshState) GetProviderName() string { if x != nil { - return x.SubjectTokenType + return x.ProviderName } return "" } -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: override token cache TTL (seconds) - // If 0 or omitted, use expires_in from token response - CacheTtlSeconds int64 `protobuf:"varint,4,opt,name=cache_ttl_seconds,json=cacheTtlSeconds,proto3" json:"cache_ttl_seconds,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 *ProviderCredentialTokenGrant) Reset() { - *x = ProviderCredentialTokenGrant{} - mi := &file_openshell_proto_msgTypes[74] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *ProviderCredentialTokenGrant) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*ProviderCredentialTokenGrant) ProtoMessage() {} - -func (x *ProviderCredentialTokenGrant) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[74] +func (x *StoredProviderCredentialRefreshState) GetCredentialKey() string { if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms + return x.CredentialKey } - return mi.MessageOf(x) + return "" } -// Deprecated: Use ProviderCredentialTokenGrant.ProtoReflect.Descriptor instead. -func (*ProviderCredentialTokenGrant) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{74} +func (x *StoredProviderCredentialRefreshState) GetStrategy() ProviderCredentialRefreshStrategy { + if x != nil { + return x.Strategy + } + return ProviderCredentialRefreshStrategy_PROVIDER_CREDENTIAL_REFRESH_STRATEGY_UNSPECIFIED } -func (x *ProviderCredentialTokenGrant) GetTokenEndpoint() string { +func (x *StoredProviderCredentialRefreshState) GetMaterial() map[string]string { if x != nil { - return x.TokenEndpoint + return x.Material } - return "" + return nil } -func (x *ProviderCredentialTokenGrant) GetAudience() string { +func (x *StoredProviderCredentialRefreshState) GetSecretMaterialKeys() []string { if x != nil { - return x.Audience + return x.SecretMaterialKeys } - return "" + return nil } -func (x *ProviderCredentialTokenGrant) GetJwtSvidAudience() string { +func (x *StoredProviderCredentialRefreshState) GetExpiresAtMs() int64 { if x != nil { - return x.JwtSvidAudience + return x.ExpiresAtMs } - return "" + return 0 } -func (x *ProviderCredentialTokenGrant) GetScopes() []string { +func (x *StoredProviderCredentialRefreshState) GetNextRefreshAtMs() int64 { if x != nil { - return x.Scopes + return x.NextRefreshAtMs } - return nil + return 0 } -func (x *ProviderCredentialTokenGrant) GetCacheTtlSeconds() int64 { +func (x *StoredProviderCredentialRefreshState) GetLastRefreshAtMs() int64 { if x != nil { - return x.CacheTtlSeconds + return x.LastRefreshAtMs } return 0 } -func (x *ProviderCredentialTokenGrant) GetAudienceOverrides() []*ProviderCredentialTokenGrantAudienceOverride { +func (x *StoredProviderCredentialRefreshState) GetStatus() string { if x != nil { - return x.AudienceOverrides + return x.Status } - return nil + return "" } -func (x *ProviderCredentialTokenGrant) GetClientAssertionType() string { +func (x *StoredProviderCredentialRefreshState) GetLastError() string { if x != nil { - return x.ClientAssertionType + return x.LastError } return "" } -func (x *ProviderCredentialTokenGrant) GetGrantType() ProviderCredentialTokenGrantType { +func (x *StoredProviderCredentialRefreshState) GetTokenUrl() string { if x != nil { - return x.GrantType + return x.TokenUrl } - return ProviderCredentialTokenGrantType_PROVIDER_CREDENTIAL_TOKEN_GRANT_TYPE_UNSPECIFIED + return "" } -func (x *ProviderCredentialTokenGrant) GetSubjectToken() *ProviderCredentialTokenGrantSubjectToken { +func (x *StoredProviderCredentialRefreshState) GetScopes() []string { if x != nil { - return x.SubjectToken + return x.Scopes } return nil } -func (x *ProviderCredentialTokenGrant) GetRequestedTokenType() string { +func (x *StoredProviderCredentialRefreshState) GetRefreshBeforeSeconds() int64 { if x != nil { - return x.RequestedTokenType + return x.RefreshBeforeSeconds } - return "" + return 0 } -// 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"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache +func (x *StoredProviderCredentialRefreshState) GetMaxLifetimeSeconds() int64 { + if x != nil { + return x.MaxLifetimeSeconds + } + return 0 } -func (x *ProviderProfileCredential) Reset() { - *x = ProviderProfileCredential{} - mi := &file_openshell_proto_msgTypes[75] +func (x *StoredProviderCredentialRefreshState) GetAdditionalOutputKeys() map[string]string { + if x != nil { + return x.AdditionalOutputKeys + } + return nil +} + +type DelegatedIdentityCredential 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"` + RefreshToken string `protobuf:"bytes,5,opt,name=refresh_token,json=refreshToken,proto3" json:"refresh_token,omitempty"` + AccessToken string `protobuf:"bytes,6,opt,name=access_token,json=accessToken,proto3" json:"access_token,omitempty"` + AccessTokenExpiresAtMs int64 `protobuf:"varint,7,opt,name=access_token_expires_at_ms,json=accessTokenExpiresAtMs,proto3" json:"access_token_expires_at_ms,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"` + LastRefreshAtMs int64 `protobuf:"varint,10,opt,name=last_refresh_at_ms,json=lastRefreshAtMs,proto3" json:"last_refresh_at_ms,omitempty"` + RevokedAtMs int64 `protobuf:"varint,11,opt,name=revoked_at_ms,json=revokedAtMs,proto3" json:"revoked_at_ms,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DelegatedIdentityCredential) Reset() { + *x = DelegatedIdentityCredential{} + mi := &file_openshell_proto_msgTypes[91] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *ProviderProfileCredential) String() string { +func (x *DelegatedIdentityCredential) String() string { return protoimpl.X.MessageStringOf(x) } -func (*ProviderProfileCredential) ProtoMessage() {} +func (*DelegatedIdentityCredential) ProtoMessage() {} -func (x *ProviderProfileCredential) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[75] +func (x *DelegatedIdentityCredential) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[91] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5529,106 +6751,120 @@ 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{75} +// Deprecated: Use DelegatedIdentityCredential.ProtoReflect.Descriptor instead. +func (*DelegatedIdentityCredential) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{91} } -func (x *ProviderProfileCredential) GetName() string { +func (x *DelegatedIdentityCredential) GetMetadata() *datamodelv1.ObjectMeta { if x != nil { - return x.Name + return x.Metadata } - return "" + return nil } -func (x *ProviderProfileCredential) GetDescription() string { +func (x *DelegatedIdentityCredential) GetIssuer() string { if x != nil { - return x.Description + return x.Issuer } return "" } -func (x *ProviderProfileCredential) GetEnvVars() []string { +func (x *DelegatedIdentityCredential) GetClientId() string { if x != nil { - return x.EnvVars + return x.ClientId } - return nil + return "" } -func (x *ProviderProfileCredential) GetRequired() bool { +func (x *DelegatedIdentityCredential) GetPrincipalSubject() string { if x != nil { - return x.Required + return x.PrincipalSubject } - return false + return "" } -func (x *ProviderProfileCredential) GetAuthStyle() string { +func (x *DelegatedIdentityCredential) GetRefreshToken() string { if x != nil { - return x.AuthStyle + return x.RefreshToken } return "" } -func (x *ProviderProfileCredential) GetHeaderName() string { +func (x *DelegatedIdentityCredential) GetAccessToken() string { if x != nil { - return x.HeaderName + return x.AccessToken } return "" } -func (x *ProviderProfileCredential) GetQueryParam() string { +func (x *DelegatedIdentityCredential) GetAccessTokenExpiresAtMs() int64 { if x != nil { - return x.QueryParam + return x.AccessTokenExpiresAtMs } - return "" + return 0 } -func (x *ProviderProfileCredential) GetRefresh() *ProviderCredentialRefresh { +func (x *DelegatedIdentityCredential) GetScopes() string { if x != nil { - return x.Refresh + return x.Scopes } - return nil + return "" } -func (x *ProviderProfileCredential) GetPathTemplate() string { +func (x *DelegatedIdentityCredential) GetAudience() string { if x != nil { - return x.PathTemplate + return x.Audience } return "" } -func (x *ProviderProfileCredential) GetTokenGrant() *ProviderCredentialTokenGrant { +func (x *DelegatedIdentityCredential) GetLastRefreshAtMs() int64 { if x != nil { - return x.TokenGrant + return x.LastRefreshAtMs } - return nil + return 0 } -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"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache +func (x *DelegatedIdentityCredential) GetRevokedAtMs() int64 { + if x != nil { + return x.RevokedAtMs + } + return 0 } -func (x *ProviderCredentialRefreshMaterial) Reset() { - *x = ProviderCredentialRefreshMaterial{} - mi := &file_openshell_proto_msgTypes[76] +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"` + AccessTokenExpiresAtMs int64 `protobuf:"varint,7,opt,name=access_token_expires_at_ms,json=accessTokenExpiresAtMs,proto3" json:"access_token_expires_at_ms,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"` + LastRefreshAtMs int64 `protobuf:"varint,10,opt,name=last_refresh_at_ms,json=lastRefreshAtMs,proto3" json:"last_refresh_at_ms,omitempty"` + RevokedAtMs int64 `protobuf:"varint,11,opt,name=revoked_at_ms,json=revokedAtMs,proto3" json:"revoked_at_ms,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DelegatedIdentityCredentialSummary) Reset() { + *x = DelegatedIdentityCredentialSummary{} + mi := &file_openshell_proto_msgTypes[92] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *ProviderCredentialRefreshMaterial) String() string { +func (x *DelegatedIdentityCredentialSummary) String() string { return protoimpl.X.MessageStringOf(x) } -func (*ProviderCredentialRefreshMaterial) ProtoMessage() {} +func (*DelegatedIdentityCredentialSummary) ProtoMessage() {} -func (x *ProviderCredentialRefreshMaterial) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[76] +func (x *DelegatedIdentityCredentialSummary) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[92] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5639,123 +6875,111 @@ 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{76} +// Deprecated: Use DelegatedIdentityCredentialSummary.ProtoReflect.Descriptor instead. +func (*DelegatedIdentityCredentialSummary) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{92} } -func (x *ProviderCredentialRefreshMaterial) GetName() string { +func (x *DelegatedIdentityCredentialSummary) GetMetadata() *datamodelv1.ObjectMeta { if x != nil { - return x.Name + return x.Metadata } - return "" + return nil } -func (x *ProviderCredentialRefreshMaterial) GetDescription() string { +func (x *DelegatedIdentityCredentialSummary) GetIssuer() string { if x != nil { - return x.Description + return x.Issuer } return "" } -func (x *ProviderCredentialRefreshMaterial) GetRequired() bool { +func (x *DelegatedIdentityCredentialSummary) GetClientId() string { if x != nil { - return x.Required + return x.ClientId } - return false + return "" } -func (x *ProviderCredentialRefreshMaterial) GetSecret() bool { +func (x *DelegatedIdentityCredentialSummary) GetPrincipalSubject() string { if x != nil { - return x.Secret + return x.PrincipalSubject } - return false + return "" } -// 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 *DelegatedIdentityCredentialSummary) GetRefreshTokenPresent() bool { + if x != nil { + return x.RefreshTokenPresent + } + return false } -func (x *ProviderCredentialRefreshOutput) Reset() { - *x = ProviderCredentialRefreshOutput{} - mi := &file_openshell_proto_msgTypes[77] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) +func (x *DelegatedIdentityCredentialSummary) GetAccessTokenPresent() bool { + if x != nil { + return x.AccessTokenPresent + } + return false } -func (x *ProviderCredentialRefreshOutput) String() string { - return protoimpl.X.MessageStringOf(x) +func (x *DelegatedIdentityCredentialSummary) GetAccessTokenExpiresAtMs() int64 { + if x != nil { + return x.AccessTokenExpiresAtMs + } + return 0 } -func (*ProviderCredentialRefreshOutput) ProtoMessage() {} - -func (x *ProviderCredentialRefreshOutput) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[77] +func (x *DelegatedIdentityCredentialSummary) GetScopes() string { if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms + return x.Scopes } - return mi.MessageOf(x) + return "" } -// Deprecated: Use ProviderCredentialRefreshOutput.ProtoReflect.Descriptor instead. -func (*ProviderCredentialRefreshOutput) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{77} +func (x *DelegatedIdentityCredentialSummary) GetAudience() string { + if x != nil { + return x.Audience + } + return "" } -func (x *ProviderCredentialRefreshOutput) GetOutput() string { +func (x *DelegatedIdentityCredentialSummary) GetLastRefreshAtMs() int64 { if x != nil { - return x.Output + return x.LastRefreshAtMs } - return "" + return 0 } -func (x *ProviderCredentialRefreshOutput) GetCredential() string { +func (x *DelegatedIdentityCredentialSummary) GetRevokedAtMs() int64 { if x != nil { - return x.Credential + return x.RevokedAtMs } - return "" + return 0 } -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"` - RefreshBeforeSeconds int64 `protobuf:"varint,4,opt,name=refresh_before_seconds,json=refreshBeforeSeconds,proto3" json:"refresh_before_seconds,omitempty"` - MaxLifetimeSeconds int64 `protobuf:"varint,5,opt,name=max_lifetime_seconds,json=maxLifetimeSeconds,proto3" json:"max_lifetime_seconds,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 ListDelegatedIdentityCredentialsRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Limit uint32 `protobuf:"varint,1,opt,name=limit,proto3" json:"limit,omitempty"` + Offset uint32 `protobuf:"varint,2,opt,name=offset,proto3" json:"offset,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } -func (x *ProviderCredentialRefresh) Reset() { - *x = ProviderCredentialRefresh{} - mi := &file_openshell_proto_msgTypes[78] +func (x *ListDelegatedIdentityCredentialsRequest) Reset() { + *x = ListDelegatedIdentityCredentialsRequest{} + mi := &file_openshell_proto_msgTypes[93] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *ProviderCredentialRefresh) String() string { +func (x *ListDelegatedIdentityCredentialsRequest) String() string { return protoimpl.X.MessageStringOf(x) } -func (*ProviderCredentialRefresh) ProtoMessage() {} +func (*ListDelegatedIdentityCredentialsRequest) ProtoMessage() {} -func (x *ProviderCredentialRefresh) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[78] +func (x *ListDelegatedIdentityCredentialsRequest) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[93] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5766,90 +6990,91 @@ 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{78} +// Deprecated: Use ListDelegatedIdentityCredentialsRequest.ProtoReflect.Descriptor instead. +func (*ListDelegatedIdentityCredentialsRequest) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{93} } -func (x *ProviderCredentialRefresh) GetStrategy() ProviderCredentialRefreshStrategy { +func (x *ListDelegatedIdentityCredentialsRequest) GetLimit() uint32 { if x != nil { - return x.Strategy + return x.Limit } - return ProviderCredentialRefreshStrategy_PROVIDER_CREDENTIAL_REFRESH_STRATEGY_UNSPECIFIED + return 0 } -func (x *ProviderCredentialRefresh) GetTokenUrl() string { +func (x *ListDelegatedIdentityCredentialsRequest) GetOffset() uint32 { if x != nil { - return x.TokenUrl + return x.Offset } - return "" + return 0 } -func (x *ProviderCredentialRefresh) GetScopes() []string { - if x != nil { - return x.Scopes - } - return nil +type ListDelegatedIdentityCredentialsResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Credentials []*DelegatedIdentityCredentialSummary `protobuf:"bytes,1,rep,name=credentials,proto3" json:"credentials,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } -func (x *ProviderCredentialRefresh) GetRefreshBeforeSeconds() int64 { - if x != nil { - return x.RefreshBeforeSeconds - } - return 0 +func (x *ListDelegatedIdentityCredentialsResponse) Reset() { + *x = ListDelegatedIdentityCredentialsResponse{} + mi := &file_openshell_proto_msgTypes[94] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } -func (x *ProviderCredentialRefresh) GetMaxLifetimeSeconds() int64 { - if x != nil { - return x.MaxLifetimeSeconds - } - return 0 +func (x *ListDelegatedIdentityCredentialsResponse) String() string { + return protoimpl.X.MessageStringOf(x) } -func (x *ProviderCredentialRefresh) GetMaterial() []*ProviderCredentialRefreshMaterial { +func (*ListDelegatedIdentityCredentialsResponse) ProtoMessage() {} + +func (x *ListDelegatedIdentityCredentialsResponse) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[94] if x != nil { - return x.Material + 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) GetAdditionalOutputs() []*ProviderCredentialRefreshOutput { +// Deprecated: Use ListDelegatedIdentityCredentialsResponse.ProtoReflect.Descriptor instead. +func (*ListDelegatedIdentityCredentialsResponse) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{94} +} + +func (x *ListDelegatedIdentityCredentialsResponse) GetCredentials() []*DelegatedIdentityCredentialSummary { if x != nil { - return x.AdditionalOutputs + return x.Credentials } return nil } -type ProviderCredentialRefreshStatus struct { - state protoimpl.MessageState `protogen:"open.v1"` - ProviderName string `protobuf:"bytes,1,opt,name=provider_name,json=providerName,proto3" json:"provider_name,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"` - ExpiresAtMs int64 `protobuf:"varint,6,opt,name=expires_at_ms,json=expiresAtMs,proto3" json:"expires_at_ms,omitempty"` - NextRefreshAtMs int64 `protobuf:"varint,7,opt,name=next_refresh_at_ms,json=nextRefreshAtMs,proto3" json:"next_refresh_at_ms,omitempty"` - LastRefreshAtMs int64 `protobuf:"varint,8,opt,name=last_refresh_at_ms,json=lastRefreshAtMs,proto3" json:"last_refresh_at_ms,omitempty"` - LastError string `protobuf:"bytes,9,opt,name=last_error,json=lastError,proto3" json:"last_error,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 *ProviderCredentialRefreshStatus) Reset() { - *x = ProviderCredentialRefreshStatus{} - mi := &file_openshell_proto_msgTypes[79] +func (x *GetDelegatedIdentityCredentialStatusRequest) Reset() { + *x = GetDelegatedIdentityCredentialStatusRequest{} + mi := &file_openshell_proto_msgTypes[95] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *ProviderCredentialRefreshStatus) String() string { +func (x *GetDelegatedIdentityCredentialStatusRequest) String() string { return protoimpl.X.MessageStringOf(x) } -func (*ProviderCredentialRefreshStatus) ProtoMessage() {} +func (*GetDelegatedIdentityCredentialStatusRequest) ProtoMessage() {} -func (x *ProviderCredentialRefreshStatus) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[79] +func (x *GetDelegatedIdentityCredentialStatusRequest) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[95] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5860,98 +7085,93 @@ 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{79} -} - -func (x *ProviderCredentialRefreshStatus) GetProviderName() string { - if x != nil { - return x.ProviderName - } - return "" +// Deprecated: Use GetDelegatedIdentityCredentialStatusRequest.ProtoReflect.Descriptor instead. +func (*GetDelegatedIdentityCredentialStatusRequest) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{95} } -func (x *ProviderCredentialRefreshStatus) GetProviderId() string { +func (x *GetDelegatedIdentityCredentialStatusRequest) GetId() string { if x != nil { - return x.ProviderId + return x.Id } return "" } -func (x *ProviderCredentialRefreshStatus) GetCredentialKey() string { - if x != nil { - return x.CredentialKey - } - return "" +type GetDelegatedIdentityCredentialStatusResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Credential *DelegatedIdentityCredentialSummary `protobuf:"bytes,1,opt,name=credential,proto3" json:"credential,omitempty"` + NowMs int64 `protobuf:"varint,2,opt,name=now_ms,json=nowMs,proto3" json:"now_ms,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } -func (x *ProviderCredentialRefreshStatus) GetStrategy() ProviderCredentialRefreshStrategy { - if x != nil { - return x.Strategy - } - return ProviderCredentialRefreshStrategy_PROVIDER_CREDENTIAL_REFRESH_STRATEGY_UNSPECIFIED +func (x *GetDelegatedIdentityCredentialStatusResponse) Reset() { + *x = GetDelegatedIdentityCredentialStatusResponse{} + mi := &file_openshell_proto_msgTypes[96] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } -func (x *ProviderCredentialRefreshStatus) GetStatus() string { - if x != nil { - return x.Status - } - return "" +func (x *GetDelegatedIdentityCredentialStatusResponse) String() string { + return protoimpl.X.MessageStringOf(x) } -func (x *ProviderCredentialRefreshStatus) GetExpiresAtMs() int64 { - if x != nil { - return x.ExpiresAtMs - } - return 0 -} +func (*GetDelegatedIdentityCredentialStatusResponse) ProtoMessage() {} -func (x *ProviderCredentialRefreshStatus) GetNextRefreshAtMs() int64 { +func (x *GetDelegatedIdentityCredentialStatusResponse) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[96] if x != nil { - return x.NextRefreshAtMs + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms } - return 0 + return mi.MessageOf(x) } -func (x *ProviderCredentialRefreshStatus) GetLastRefreshAtMs() int64 { - if x != nil { - return x.LastRefreshAtMs - } - return 0 +// Deprecated: Use GetDelegatedIdentityCredentialStatusResponse.ProtoReflect.Descriptor instead. +func (*GetDelegatedIdentityCredentialStatusResponse) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{96} } -func (x *ProviderCredentialRefreshStatus) GetLastError() string { +func (x *GetDelegatedIdentityCredentialStatusResponse) GetCredential() *DelegatedIdentityCredentialSummary { if x != nil { - return x.LastError + return x.Credential } - return "" + return nil } -// 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 *GetDelegatedIdentityCredentialStatusResponse) GetNowMs() int64 { + if x != nil { + return x.NowMs + } + return 0 } -func (x *ProviderProfileDiscovery) Reset() { - *x = ProviderProfileDiscovery{} - mi := &file_openshell_proto_msgTypes[80] +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 *RevokeDelegatedIdentityCredentialRequest) Reset() { + *x = RevokeDelegatedIdentityCredentialRequest{} + mi := &file_openshell_proto_msgTypes[97] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *ProviderProfileDiscovery) String() string { +func (x *RevokeDelegatedIdentityCredentialRequest) String() string { return protoimpl.X.MessageStringOf(x) } -func (*ProviderProfileDiscovery) ProtoMessage() {} +func (*RevokeDelegatedIdentityCredentialRequest) ProtoMessage() {} -func (x *ProviderProfileDiscovery) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[80] +func (x *RevokeDelegatedIdentityCredentialRequest) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[97] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5962,60 +7182,49 @@ 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{80} +// Deprecated: Use RevokeDelegatedIdentityCredentialRequest.ProtoReflect.Descriptor instead. +func (*RevokeDelegatedIdentityCredentialRequest) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{97} } -func (x *ProviderProfileDiscovery) GetCredentials() []string { +func (x *RevokeDelegatedIdentityCredentialRequest) GetId() string { if x != nil { - return x.Credentials + return x.Id } - return nil + return "" } -type StoredProviderCredentialRefreshState struct { - state protoimpl.MessageState `protogen:"open.v1"` - Metadata *datamodelv1.ObjectMeta `protobuf:"bytes,1,opt,name=metadata,proto3" json:"metadata,omitempty"` - ProviderId string `protobuf:"bytes,2,opt,name=provider_id,json=providerId,proto3" json:"provider_id,omitempty"` - ProviderName string `protobuf:"bytes,3,opt,name=provider_name,json=providerName,proto3" json:"provider_name,omitempty"` - CredentialKey string `protobuf:"bytes,4,opt,name=credential_key,json=credentialKey,proto3" json:"credential_key,omitempty"` - Strategy ProviderCredentialRefreshStrategy `protobuf:"varint,5,opt,name=strategy,proto3,enum=openshell.v1.ProviderCredentialRefreshStrategy" json:"strategy,omitempty"` - Material map[string]string `protobuf:"bytes,6,rep,name=material,proto3" json:"material,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` - SecretMaterialKeys []string `protobuf:"bytes,7,rep,name=secret_material_keys,json=secretMaterialKeys,proto3" json:"secret_material_keys,omitempty"` - ExpiresAtMs int64 `protobuf:"varint,8,opt,name=expires_at_ms,json=expiresAtMs,proto3" json:"expires_at_ms,omitempty"` - NextRefreshAtMs int64 `protobuf:"varint,9,opt,name=next_refresh_at_ms,json=nextRefreshAtMs,proto3" json:"next_refresh_at_ms,omitempty"` - LastRefreshAtMs int64 `protobuf:"varint,10,opt,name=last_refresh_at_ms,json=lastRefreshAtMs,proto3" json:"last_refresh_at_ms,omitempty"` - Status string `protobuf:"bytes,11,opt,name=status,proto3" json:"status,omitempty"` - LastError string `protobuf:"bytes,12,opt,name=last_error,json=lastError,proto3" json:"last_error,omitempty"` - TokenUrl string `protobuf:"bytes,13,opt,name=token_url,json=tokenUrl,proto3" json:"token_url,omitempty"` - Scopes []string `protobuf:"bytes,14,rep,name=scopes,proto3" json:"scopes,omitempty"` - RefreshBeforeSeconds int64 `protobuf:"varint,15,opt,name=refresh_before_seconds,json=refreshBeforeSeconds,proto3" json:"refresh_before_seconds,omitempty"` - MaxLifetimeSeconds int64 `protobuf:"varint,16,opt,name=max_lifetime_seconds,json=maxLifetimeSeconds,proto3" json:"max_lifetime_seconds,omitempty"` - // Resolved mapping of strategy-defined output id -> concrete env key, pinned - // at configure time from the profile's additional_outputs. Read by minting, - // collision reservation, and env-key surfacing so later profile edits cannot - // silently redirect writes. - AdditionalOutputKeys map[string]string `protobuf:"bytes,17,rep,name=additional_output_keys,json=additionalOutputKeys,proto3" json:"additional_output_keys,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache +func (x *RevokeDelegatedIdentityCredentialRequest) GetExpectedResourceVersion() uint64 { + if x != nil { + return x.ExpectedResourceVersion + } + return 0 } -func (x *StoredProviderCredentialRefreshState) Reset() { - *x = StoredProviderCredentialRefreshState{} - mi := &file_openshell_proto_msgTypes[81] +type RevokeDelegatedIdentityCredentialResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Revoked bool `protobuf:"varint,2,opt,name=revoked,proto3" json:"revoked,omitempty"` + RevokedAtMs int64 `protobuf:"varint,3,opt,name=revoked_at_ms,json=revokedAtMs,proto3" json:"revoked_at_ms,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[98] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *StoredProviderCredentialRefreshState) String() string { +func (x *RevokeDelegatedIdentityCredentialResponse) String() string { return protoimpl.X.MessageStringOf(x) } -func (*StoredProviderCredentialRefreshState) ProtoMessage() {} +func (*RevokeDelegatedIdentityCredentialResponse) ProtoMessage() {} -func (x *StoredProviderCredentialRefreshState) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[81] +func (x *RevokeDelegatedIdentityCredentialResponse) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[98] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6026,128 +7235,126 @@ func (x *StoredProviderCredentialRefreshState) ProtoReflect() protoreflect.Messa return mi.MessageOf(x) } -// Deprecated: Use StoredProviderCredentialRefreshState.ProtoReflect.Descriptor instead. -func (*StoredProviderCredentialRefreshState) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{81} +// Deprecated: Use RevokeDelegatedIdentityCredentialResponse.ProtoReflect.Descriptor instead. +func (*RevokeDelegatedIdentityCredentialResponse) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{98} } -func (x *StoredProviderCredentialRefreshState) GetMetadata() *datamodelv1.ObjectMeta { +func (x *RevokeDelegatedIdentityCredentialResponse) GetRevoked() bool { if x != nil { - return x.Metadata + return x.Revoked } - return nil + return false } -func (x *StoredProviderCredentialRefreshState) GetProviderId() string { +func (x *RevokeDelegatedIdentityCredentialResponse) GetRevokedAtMs() int64 { if x != nil { - return x.ProviderId + return x.RevokedAtMs } - return "" + return 0 } -func (x *StoredProviderCredentialRefreshState) GetProviderName() string { +func (x *RevokeDelegatedIdentityCredentialResponse) GetResourceVersion() uint64 { if x != nil { - return x.ProviderName + return x.ResourceVersion } - return "" + return 0 } -func (x *StoredProviderCredentialRefreshState) GetCredentialKey() string { - if x != nil { - return x.CredentialKey - } - return "" +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 *StoredProviderCredentialRefreshState) GetStrategy() ProviderCredentialRefreshStrategy { - if x != nil { - return x.Strategy - } - return ProviderCredentialRefreshStrategy_PROVIDER_CREDENTIAL_REFRESH_STRATEGY_UNSPECIFIED +func (x *DeleteDelegatedIdentityCredentialRequest) Reset() { + *x = DeleteDelegatedIdentityCredentialRequest{} + mi := &file_openshell_proto_msgTypes[99] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } -func (x *StoredProviderCredentialRefreshState) GetMaterial() map[string]string { - if x != nil { - return x.Material - } - return nil +func (x *DeleteDelegatedIdentityCredentialRequest) String() string { + return protoimpl.X.MessageStringOf(x) } -func (x *StoredProviderCredentialRefreshState) GetSecretMaterialKeys() []string { +func (*DeleteDelegatedIdentityCredentialRequest) ProtoMessage() {} + +func (x *DeleteDelegatedIdentityCredentialRequest) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[99] if x != nil { - return x.SecretMaterialKeys + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms } - return nil + return mi.MessageOf(x) } -func (x *StoredProviderCredentialRefreshState) GetExpiresAtMs() int64 { - if x != nil { - return x.ExpiresAtMs - } - return 0 +// Deprecated: Use DeleteDelegatedIdentityCredentialRequest.ProtoReflect.Descriptor instead. +func (*DeleteDelegatedIdentityCredentialRequest) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{99} } -func (x *StoredProviderCredentialRefreshState) GetNextRefreshAtMs() int64 { +func (x *DeleteDelegatedIdentityCredentialRequest) GetId() string { if x != nil { - return x.NextRefreshAtMs + return x.Id } - return 0 + return "" } -func (x *StoredProviderCredentialRefreshState) GetLastRefreshAtMs() int64 { +func (x *DeleteDelegatedIdentityCredentialRequest) GetExpectedResourceVersion() uint64 { if x != nil { - return x.LastRefreshAtMs + return x.ExpectedResourceVersion } return 0 } -func (x *StoredProviderCredentialRefreshState) GetStatus() string { - if x != nil { - return x.Status - } - return "" +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 *StoredProviderCredentialRefreshState) GetLastError() string { - if x != nil { - return x.LastError - } - return "" +func (x *DeleteDelegatedIdentityCredentialResponse) Reset() { + *x = DeleteDelegatedIdentityCredentialResponse{} + mi := &file_openshell_proto_msgTypes[100] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } -func (x *StoredProviderCredentialRefreshState) GetTokenUrl() string { - if x != nil { - return x.TokenUrl - } - return "" +func (x *DeleteDelegatedIdentityCredentialResponse) String() string { + return protoimpl.X.MessageStringOf(x) } -func (x *StoredProviderCredentialRefreshState) GetScopes() []string { - if x != nil { - return x.Scopes - } - return nil -} +func (*DeleteDelegatedIdentityCredentialResponse) ProtoMessage() {} -func (x *StoredProviderCredentialRefreshState) GetRefreshBeforeSeconds() int64 { +func (x *DeleteDelegatedIdentityCredentialResponse) ProtoReflect() protoreflect.Message { + mi := &file_openshell_proto_msgTypes[100] if x != nil { - return x.RefreshBeforeSeconds + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms } - return 0 + return mi.MessageOf(x) } -func (x *StoredProviderCredentialRefreshState) GetMaxLifetimeSeconds() int64 { - if x != nil { - return x.MaxLifetimeSeconds - } - return 0 +// Deprecated: Use DeleteDelegatedIdentityCredentialResponse.ProtoReflect.Descriptor instead. +func (*DeleteDelegatedIdentityCredentialResponse) Descriptor() ([]byte, []int) { + return file_openshell_proto_rawDescGZIP(), []int{100} } -func (x *StoredProviderCredentialRefreshState) GetAdditionalOutputKeys() map[string]string { +func (x *DeleteDelegatedIdentityCredentialResponse) GetDeleted() bool { if x != nil { - return x.AdditionalOutputKeys + return x.Deleted } - return nil + return false } type GetProviderRefreshStatusRequest struct { @@ -6162,7 +7369,7 @@ type GetProviderRefreshStatusRequest struct { func (x *GetProviderRefreshStatusRequest) Reset() { *x = GetProviderRefreshStatusRequest{} - mi := &file_openshell_proto_msgTypes[82] + mi := &file_openshell_proto_msgTypes[101] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6174,7 +7381,7 @@ func (x *GetProviderRefreshStatusRequest) String() string { func (*GetProviderRefreshStatusRequest) ProtoMessage() {} func (x *GetProviderRefreshStatusRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[82] + mi := &file_openshell_proto_msgTypes[101] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6187,7 +7394,7 @@ func (x *GetProviderRefreshStatusRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetProviderRefreshStatusRequest.ProtoReflect.Descriptor instead. func (*GetProviderRefreshStatusRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{82} + return file_openshell_proto_rawDescGZIP(), []int{101} } func (x *GetProviderRefreshStatusRequest) GetProvider() string { @@ -6220,7 +7427,7 @@ type GetProviderRefreshStatusResponse struct { func (x *GetProviderRefreshStatusResponse) Reset() { *x = GetProviderRefreshStatusResponse{} - mi := &file_openshell_proto_msgTypes[83] + mi := &file_openshell_proto_msgTypes[102] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6232,7 +7439,7 @@ func (x *GetProviderRefreshStatusResponse) String() string { func (*GetProviderRefreshStatusResponse) ProtoMessage() {} func (x *GetProviderRefreshStatusResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[83] + mi := &file_openshell_proto_msgTypes[102] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6245,7 +7452,7 @@ func (x *GetProviderRefreshStatusResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetProviderRefreshStatusResponse.ProtoReflect.Descriptor instead. func (*GetProviderRefreshStatusResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{83} + return file_openshell_proto_rawDescGZIP(), []int{102} } func (x *GetProviderRefreshStatusResponse) GetCredentials() []*ProviderCredentialRefreshStatus { @@ -6271,7 +7478,7 @@ type ConfigureProviderRefreshRequest struct { func (x *ConfigureProviderRefreshRequest) Reset() { *x = ConfigureProviderRefreshRequest{} - mi := &file_openshell_proto_msgTypes[84] + mi := &file_openshell_proto_msgTypes[103] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6283,7 +7490,7 @@ func (x *ConfigureProviderRefreshRequest) String() string { func (*ConfigureProviderRefreshRequest) ProtoMessage() {} func (x *ConfigureProviderRefreshRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[84] + mi := &file_openshell_proto_msgTypes[103] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6296,7 +7503,7 @@ func (x *ConfigureProviderRefreshRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ConfigureProviderRefreshRequest.ProtoReflect.Descriptor instead. func (*ConfigureProviderRefreshRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{84} + return file_openshell_proto_rawDescGZIP(), []int{103} } func (x *ConfigureProviderRefreshRequest) GetProvider() string { @@ -6357,7 +7564,7 @@ type ConfigureProviderRefreshResponse struct { func (x *ConfigureProviderRefreshResponse) Reset() { *x = ConfigureProviderRefreshResponse{} - mi := &file_openshell_proto_msgTypes[85] + mi := &file_openshell_proto_msgTypes[104] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6369,7 +7576,7 @@ func (x *ConfigureProviderRefreshResponse) String() string { func (*ConfigureProviderRefreshResponse) ProtoMessage() {} func (x *ConfigureProviderRefreshResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[85] + mi := &file_openshell_proto_msgTypes[104] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6382,7 +7589,7 @@ func (x *ConfigureProviderRefreshResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ConfigureProviderRefreshResponse.ProtoReflect.Descriptor instead. func (*ConfigureProviderRefreshResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{85} + return file_openshell_proto_rawDescGZIP(), []int{104} } func (x *ConfigureProviderRefreshResponse) GetStatus() *ProviderCredentialRefreshStatus { @@ -6404,7 +7611,7 @@ type RotateProviderCredentialRequest struct { func (x *RotateProviderCredentialRequest) Reset() { *x = RotateProviderCredentialRequest{} - mi := &file_openshell_proto_msgTypes[86] + mi := &file_openshell_proto_msgTypes[105] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6416,7 +7623,7 @@ func (x *RotateProviderCredentialRequest) String() string { func (*RotateProviderCredentialRequest) ProtoMessage() {} func (x *RotateProviderCredentialRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[86] + mi := &file_openshell_proto_msgTypes[105] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6429,7 +7636,7 @@ func (x *RotateProviderCredentialRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use RotateProviderCredentialRequest.ProtoReflect.Descriptor instead. func (*RotateProviderCredentialRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{86} + return file_openshell_proto_rawDescGZIP(), []int{105} } func (x *RotateProviderCredentialRequest) GetProvider() string { @@ -6462,7 +7669,7 @@ type RotateProviderCredentialResponse struct { func (x *RotateProviderCredentialResponse) Reset() { *x = RotateProviderCredentialResponse{} - mi := &file_openshell_proto_msgTypes[87] + mi := &file_openshell_proto_msgTypes[106] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6474,7 +7681,7 @@ func (x *RotateProviderCredentialResponse) String() string { func (*RotateProviderCredentialResponse) ProtoMessage() {} func (x *RotateProviderCredentialResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[87] + mi := &file_openshell_proto_msgTypes[106] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6487,7 +7694,7 @@ func (x *RotateProviderCredentialResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use RotateProviderCredentialResponse.ProtoReflect.Descriptor instead. func (*RotateProviderCredentialResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{87} + return file_openshell_proto_rawDescGZIP(), []int{106} } func (x *RotateProviderCredentialResponse) GetStatus() *ProviderCredentialRefreshStatus { @@ -6509,7 +7716,7 @@ type DeleteProviderRefreshRequest struct { func (x *DeleteProviderRefreshRequest) Reset() { *x = DeleteProviderRefreshRequest{} - mi := &file_openshell_proto_msgTypes[88] + mi := &file_openshell_proto_msgTypes[107] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6521,7 +7728,7 @@ func (x *DeleteProviderRefreshRequest) String() string { func (*DeleteProviderRefreshRequest) ProtoMessage() {} func (x *DeleteProviderRefreshRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[88] + mi := &file_openshell_proto_msgTypes[107] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6534,7 +7741,7 @@ func (x *DeleteProviderRefreshRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteProviderRefreshRequest.ProtoReflect.Descriptor instead. func (*DeleteProviderRefreshRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{88} + return file_openshell_proto_rawDescGZIP(), []int{107} } func (x *DeleteProviderRefreshRequest) GetProvider() string { @@ -6567,7 +7774,7 @@ type DeleteProviderRefreshResponse struct { func (x *DeleteProviderRefreshResponse) Reset() { *x = DeleteProviderRefreshResponse{} - mi := &file_openshell_proto_msgTypes[89] + mi := &file_openshell_proto_msgTypes[108] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6579,7 +7786,7 @@ func (x *DeleteProviderRefreshResponse) String() string { func (*DeleteProviderRefreshResponse) ProtoMessage() {} func (x *DeleteProviderRefreshResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[89] + mi := &file_openshell_proto_msgTypes[108] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6592,7 +7799,7 @@ func (x *DeleteProviderRefreshResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteProviderRefreshResponse.ProtoReflect.Descriptor instead. func (*DeleteProviderRefreshResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{89} + return file_openshell_proto_rawDescGZIP(), []int{108} } func (x *DeleteProviderRefreshResponse) GetDeleted() bool { @@ -6632,7 +7839,7 @@ type ProviderProfile struct { func (x *ProviderProfile) Reset() { *x = ProviderProfile{} - mi := &file_openshell_proto_msgTypes[90] + mi := &file_openshell_proto_msgTypes[109] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6644,7 +7851,7 @@ func (x *ProviderProfile) String() string { func (*ProviderProfile) ProtoMessage() {} func (x *ProviderProfile) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[90] + mi := &file_openshell_proto_msgTypes[109] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6657,7 +7864,7 @@ func (x *ProviderProfile) ProtoReflect() protoreflect.Message { // Deprecated: Use ProviderProfile.ProtoReflect.Descriptor instead. func (*ProviderProfile) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{90} + return file_openshell_proto_rawDescGZIP(), []int{109} } func (x *ProviderProfile) GetId() string { @@ -6762,7 +7969,7 @@ type StoredProviderProfile struct { func (x *StoredProviderProfile) Reset() { *x = StoredProviderProfile{} - mi := &file_openshell_proto_msgTypes[91] + mi := &file_openshell_proto_msgTypes[110] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6774,7 +7981,7 @@ func (x *StoredProviderProfile) String() string { func (*StoredProviderProfile) ProtoMessage() {} func (x *StoredProviderProfile) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[91] + mi := &file_openshell_proto_msgTypes[110] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6787,7 +7994,7 @@ func (x *StoredProviderProfile) ProtoReflect() protoreflect.Message { // Deprecated: Use StoredProviderProfile.ProtoReflect.Descriptor instead. func (*StoredProviderProfile) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{91} + return file_openshell_proto_rawDescGZIP(), []int{110} } func (x *StoredProviderProfile) GetMetadata() *datamodelv1.ObjectMeta { @@ -6814,7 +8021,7 @@ type ProviderProfileResponse struct { func (x *ProviderProfileResponse) Reset() { *x = ProviderProfileResponse{} - mi := &file_openshell_proto_msgTypes[92] + mi := &file_openshell_proto_msgTypes[111] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6826,7 +8033,7 @@ func (x *ProviderProfileResponse) String() string { func (*ProviderProfileResponse) ProtoMessage() {} func (x *ProviderProfileResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[92] + mi := &file_openshell_proto_msgTypes[111] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6839,7 +8046,7 @@ func (x *ProviderProfileResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ProviderProfileResponse.ProtoReflect.Descriptor instead. func (*ProviderProfileResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{92} + return file_openshell_proto_rawDescGZIP(), []int{111} } func (x *ProviderProfileResponse) GetProfile() *ProviderProfile { @@ -6859,7 +8066,7 @@ type ListProviderProfilesResponse struct { func (x *ListProviderProfilesResponse) Reset() { *x = ListProviderProfilesResponse{} - mi := &file_openshell_proto_msgTypes[93] + mi := &file_openshell_proto_msgTypes[112] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6871,7 +8078,7 @@ func (x *ListProviderProfilesResponse) String() string { func (*ListProviderProfilesResponse) ProtoMessage() {} func (x *ListProviderProfilesResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[93] + mi := &file_openshell_proto_msgTypes[112] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6884,7 +8091,7 @@ func (x *ListProviderProfilesResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListProviderProfilesResponse.ProtoReflect.Descriptor instead. func (*ListProviderProfilesResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{93} + return file_openshell_proto_rawDescGZIP(), []int{112} } func (x *ListProviderProfilesResponse) GetProfiles() []*ProviderProfile { @@ -6907,7 +8114,7 @@ type ImportProviderProfilesRequest struct { func (x *ImportProviderProfilesRequest) Reset() { *x = ImportProviderProfilesRequest{} - mi := &file_openshell_proto_msgTypes[94] + mi := &file_openshell_proto_msgTypes[113] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6919,7 +8126,7 @@ func (x *ImportProviderProfilesRequest) String() string { func (*ImportProviderProfilesRequest) ProtoMessage() {} func (x *ImportProviderProfilesRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[94] + mi := &file_openshell_proto_msgTypes[113] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6932,7 +8139,7 @@ func (x *ImportProviderProfilesRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ImportProviderProfilesRequest.ProtoReflect.Descriptor instead. func (*ImportProviderProfilesRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{94} + return file_openshell_proto_rawDescGZIP(), []int{113} } func (x *ImportProviderProfilesRequest) GetProfiles() []*ProviderProfileImportItem { @@ -6961,7 +8168,7 @@ type ImportProviderProfilesResponse struct { func (x *ImportProviderProfilesResponse) Reset() { *x = ImportProviderProfilesResponse{} - mi := &file_openshell_proto_msgTypes[95] + mi := &file_openshell_proto_msgTypes[114] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6973,7 +8180,7 @@ func (x *ImportProviderProfilesResponse) String() string { func (*ImportProviderProfilesResponse) ProtoMessage() {} func (x *ImportProviderProfilesResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[95] + mi := &file_openshell_proto_msgTypes[114] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6986,7 +8193,7 @@ func (x *ImportProviderProfilesResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ImportProviderProfilesResponse.ProtoReflect.Descriptor instead. func (*ImportProviderProfilesResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{95} + return file_openshell_proto_rawDescGZIP(), []int{114} } func (x *ImportProviderProfilesResponse) GetDiagnostics() []*ProviderProfileDiagnostic { @@ -7030,7 +8237,7 @@ type UpdateProviderProfilesRequest struct { func (x *UpdateProviderProfilesRequest) Reset() { *x = UpdateProviderProfilesRequest{} - mi := &file_openshell_proto_msgTypes[96] + mi := &file_openshell_proto_msgTypes[115] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7042,7 +8249,7 @@ func (x *UpdateProviderProfilesRequest) String() string { func (*UpdateProviderProfilesRequest) ProtoMessage() {} func (x *UpdateProviderProfilesRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[96] + mi := &file_openshell_proto_msgTypes[115] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7055,7 +8262,7 @@ func (x *UpdateProviderProfilesRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateProviderProfilesRequest.ProtoReflect.Descriptor instead. func (*UpdateProviderProfilesRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{96} + return file_openshell_proto_rawDescGZIP(), []int{115} } func (x *UpdateProviderProfilesRequest) GetProfile() *ProviderProfileImportItem { @@ -7098,7 +8305,7 @@ type UpdateProviderProfilesResponse struct { func (x *UpdateProviderProfilesResponse) Reset() { *x = UpdateProviderProfilesResponse{} - mi := &file_openshell_proto_msgTypes[97] + mi := &file_openshell_proto_msgTypes[116] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7110,7 +8317,7 @@ func (x *UpdateProviderProfilesResponse) String() string { func (*UpdateProviderProfilesResponse) ProtoMessage() {} func (x *UpdateProviderProfilesResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[97] + mi := &file_openshell_proto_msgTypes[116] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7123,7 +8330,7 @@ func (x *UpdateProviderProfilesResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateProviderProfilesResponse.ProtoReflect.Descriptor instead. func (*UpdateProviderProfilesResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{97} + return file_openshell_proto_rawDescGZIP(), []int{116} } func (x *UpdateProviderProfilesResponse) GetDiagnostics() []*ProviderProfileDiagnostic { @@ -7160,7 +8367,7 @@ type LintProviderProfilesRequest struct { func (x *LintProviderProfilesRequest) Reset() { *x = LintProviderProfilesRequest{} - mi := &file_openshell_proto_msgTypes[98] + mi := &file_openshell_proto_msgTypes[117] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7172,7 +8379,7 @@ func (x *LintProviderProfilesRequest) String() string { func (*LintProviderProfilesRequest) ProtoMessage() {} func (x *LintProviderProfilesRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[98] + mi := &file_openshell_proto_msgTypes[117] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7185,7 +8392,7 @@ func (x *LintProviderProfilesRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use LintProviderProfilesRequest.ProtoReflect.Descriptor instead. func (*LintProviderProfilesRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{98} + return file_openshell_proto_rawDescGZIP(), []int{117} } func (x *LintProviderProfilesRequest) GetProfiles() []*ProviderProfileImportItem { @@ -7213,7 +8420,7 @@ type LintProviderProfilesResponse struct { func (x *LintProviderProfilesResponse) Reset() { *x = LintProviderProfilesResponse{} - mi := &file_openshell_proto_msgTypes[99] + mi := &file_openshell_proto_msgTypes[118] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7225,7 +8432,7 @@ func (x *LintProviderProfilesResponse) String() string { func (*LintProviderProfilesResponse) ProtoMessage() {} func (x *LintProviderProfilesResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[99] + mi := &file_openshell_proto_msgTypes[118] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7238,7 +8445,7 @@ func (x *LintProviderProfilesResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use LintProviderProfilesResponse.ProtoReflect.Descriptor instead. func (*LintProviderProfilesResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{99} + return file_openshell_proto_rawDescGZIP(), []int{118} } func (x *LintProviderProfilesResponse) GetDiagnostics() []*ProviderProfileDiagnostic { @@ -7265,7 +8472,7 @@ type DeleteProviderResponse struct { func (x *DeleteProviderResponse) Reset() { *x = DeleteProviderResponse{} - mi := &file_openshell_proto_msgTypes[100] + mi := &file_openshell_proto_msgTypes[119] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7277,7 +8484,7 @@ func (x *DeleteProviderResponse) String() string { func (*DeleteProviderResponse) ProtoMessage() {} func (x *DeleteProviderResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[100] + mi := &file_openshell_proto_msgTypes[119] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7290,7 +8497,7 @@ func (x *DeleteProviderResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteProviderResponse.ProtoReflect.Descriptor instead. func (*DeleteProviderResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{100} + return file_openshell_proto_rawDescGZIP(), []int{119} } func (x *DeleteProviderResponse) GetDeleted() bool { @@ -7313,7 +8520,7 @@ type DeleteProviderProfileRequest struct { func (x *DeleteProviderProfileRequest) Reset() { *x = DeleteProviderProfileRequest{} - mi := &file_openshell_proto_msgTypes[101] + mi := &file_openshell_proto_msgTypes[120] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7325,7 +8532,7 @@ func (x *DeleteProviderProfileRequest) String() string { func (*DeleteProviderProfileRequest) ProtoMessage() {} func (x *DeleteProviderProfileRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[101] + mi := &file_openshell_proto_msgTypes[120] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7338,7 +8545,7 @@ func (x *DeleteProviderProfileRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteProviderProfileRequest.ProtoReflect.Descriptor instead. func (*DeleteProviderProfileRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{101} + return file_openshell_proto_rawDescGZIP(), []int{120} } func (x *DeleteProviderProfileRequest) GetId() string { @@ -7365,7 +8572,7 @@ type DeleteProviderProfileResponse struct { func (x *DeleteProviderProfileResponse) Reset() { *x = DeleteProviderProfileResponse{} - mi := &file_openshell_proto_msgTypes[102] + mi := &file_openshell_proto_msgTypes[121] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7377,7 +8584,7 @@ func (x *DeleteProviderProfileResponse) String() string { func (*DeleteProviderProfileResponse) ProtoMessage() {} func (x *DeleteProviderProfileResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[102] + mi := &file_openshell_proto_msgTypes[121] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7390,7 +8597,7 @@ func (x *DeleteProviderProfileResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteProviderProfileResponse.ProtoReflect.Descriptor instead. func (*DeleteProviderProfileResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{102} + return file_openshell_proto_rawDescGZIP(), []int{121} } func (x *DeleteProviderProfileResponse) GetDeleted() bool { @@ -7415,7 +8622,7 @@ type GetSandboxProviderEnvironmentRequest struct { func (x *GetSandboxProviderEnvironmentRequest) Reset() { *x = GetSandboxProviderEnvironmentRequest{} - mi := &file_openshell_proto_msgTypes[103] + mi := &file_openshell_proto_msgTypes[122] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7427,7 +8634,7 @@ func (x *GetSandboxProviderEnvironmentRequest) String() string { func (*GetSandboxProviderEnvironmentRequest) ProtoMessage() {} func (x *GetSandboxProviderEnvironmentRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[103] + mi := &file_openshell_proto_msgTypes[122] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7440,7 +8647,7 @@ func (x *GetSandboxProviderEnvironmentRequest) ProtoReflect() protoreflect.Messa // Deprecated: Use GetSandboxProviderEnvironmentRequest.ProtoReflect.Descriptor instead. func (*GetSandboxProviderEnvironmentRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{103} + return file_openshell_proto_rawDescGZIP(), []int{122} } func (x *GetSandboxProviderEnvironmentRequest) GetSandboxId() string { @@ -7469,7 +8676,7 @@ type StaticCredentialEndpointBinding struct { func (x *StaticCredentialEndpointBinding) Reset() { *x = StaticCredentialEndpointBinding{} - mi := &file_openshell_proto_msgTypes[104] + mi := &file_openshell_proto_msgTypes[123] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7481,7 +8688,7 @@ func (x *StaticCredentialEndpointBinding) String() string { func (*StaticCredentialEndpointBinding) ProtoMessage() {} func (x *StaticCredentialEndpointBinding) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[104] + mi := &file_openshell_proto_msgTypes[123] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7494,7 +8701,7 @@ func (x *StaticCredentialEndpointBinding) ProtoReflect() protoreflect.Message { // Deprecated: Use StaticCredentialEndpointBinding.ProtoReflect.Descriptor instead. func (*StaticCredentialEndpointBinding) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{104} + return file_openshell_proto_rawDescGZIP(), []int{123} } func (x *StaticCredentialEndpointBinding) GetHost() string { @@ -7532,7 +8739,7 @@ type StaticCredentialBinding struct { func (x *StaticCredentialBinding) Reset() { *x = StaticCredentialBinding{} - mi := &file_openshell_proto_msgTypes[105] + mi := &file_openshell_proto_msgTypes[124] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7544,7 +8751,7 @@ func (x *StaticCredentialBinding) String() string { func (*StaticCredentialBinding) ProtoMessage() {} func (x *StaticCredentialBinding) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[105] + mi := &file_openshell_proto_msgTypes[124] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7557,7 +8764,7 @@ func (x *StaticCredentialBinding) ProtoReflect() protoreflect.Message { // Deprecated: Use StaticCredentialBinding.ProtoReflect.Descriptor instead. func (*StaticCredentialBinding) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{105} + return file_openshell_proto_rawDescGZIP(), []int{124} } func (x *StaticCredentialBinding) GetEndpoints() []*StaticCredentialEndpointBinding { @@ -7601,7 +8808,7 @@ type GetSandboxProviderEnvironmentResponse struct { func (x *GetSandboxProviderEnvironmentResponse) Reset() { *x = GetSandboxProviderEnvironmentResponse{} - mi := &file_openshell_proto_msgTypes[106] + mi := &file_openshell_proto_msgTypes[125] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7613,7 +8820,7 @@ func (x *GetSandboxProviderEnvironmentResponse) String() string { func (*GetSandboxProviderEnvironmentResponse) ProtoMessage() {} func (x *GetSandboxProviderEnvironmentResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[106] + mi := &file_openshell_proto_msgTypes[125] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7626,7 +8833,7 @@ func (x *GetSandboxProviderEnvironmentResponse) ProtoReflect() protoreflect.Mess // Deprecated: Use GetSandboxProviderEnvironmentResponse.ProtoReflect.Descriptor instead. func (*GetSandboxProviderEnvironmentResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{106} + return file_openshell_proto_rawDescGZIP(), []int{125} } func (x *GetSandboxProviderEnvironmentResponse) GetEnvironment() map[string]string { @@ -7688,7 +8895,7 @@ type ExchangeProviderSubjectTokenRequest struct { func (x *ExchangeProviderSubjectTokenRequest) Reset() { *x = ExchangeProviderSubjectTokenRequest{} - mi := &file_openshell_proto_msgTypes[107] + mi := &file_openshell_proto_msgTypes[126] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7700,7 +8907,7 @@ func (x *ExchangeProviderSubjectTokenRequest) String() string { func (*ExchangeProviderSubjectTokenRequest) ProtoMessage() {} func (x *ExchangeProviderSubjectTokenRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[107] + mi := &file_openshell_proto_msgTypes[126] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7713,7 +8920,7 @@ func (x *ExchangeProviderSubjectTokenRequest) ProtoReflect() protoreflect.Messag // Deprecated: Use ExchangeProviderSubjectTokenRequest.ProtoReflect.Descriptor instead. func (*ExchangeProviderSubjectTokenRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{107} + return file_openshell_proto_rawDescGZIP(), []int{126} } func (x *ExchangeProviderSubjectTokenRequest) GetSandboxId() string { @@ -7755,7 +8962,7 @@ type ExchangeProviderSubjectTokenResponse struct { func (x *ExchangeProviderSubjectTokenResponse) Reset() { *x = ExchangeProviderSubjectTokenResponse{} - mi := &file_openshell_proto_msgTypes[108] + mi := &file_openshell_proto_msgTypes[127] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7767,7 +8974,7 @@ func (x *ExchangeProviderSubjectTokenResponse) String() string { func (*ExchangeProviderSubjectTokenResponse) ProtoMessage() {} func (x *ExchangeProviderSubjectTokenResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[108] + mi := &file_openshell_proto_msgTypes[127] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7780,7 +8987,7 @@ func (x *ExchangeProviderSubjectTokenResponse) ProtoReflect() protoreflect.Messa // Deprecated: Use ExchangeProviderSubjectTokenResponse.ProtoReflect.Descriptor instead. func (*ExchangeProviderSubjectTokenResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{108} + return file_openshell_proto_rawDescGZIP(), []int{127} } func (x *ExchangeProviderSubjectTokenResponse) GetAccessToken() string { @@ -7851,7 +9058,7 @@ type UpdateConfigRequest struct { func (x *UpdateConfigRequest) Reset() { *x = UpdateConfigRequest{} - mi := &file_openshell_proto_msgTypes[109] + mi := &file_openshell_proto_msgTypes[128] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7863,7 +9070,7 @@ func (x *UpdateConfigRequest) String() string { func (*UpdateConfigRequest) ProtoMessage() {} func (x *UpdateConfigRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[109] + mi := &file_openshell_proto_msgTypes[128] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7876,7 +9083,7 @@ func (x *UpdateConfigRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateConfigRequest.ProtoReflect.Descriptor instead. func (*UpdateConfigRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{109} + return file_openshell_proto_rawDescGZIP(), []int{128} } func (x *UpdateConfigRequest) GetName() string { @@ -7966,7 +9173,7 @@ type PolicyMergeOperation struct { func (x *PolicyMergeOperation) Reset() { *x = PolicyMergeOperation{} - mi := &file_openshell_proto_msgTypes[110] + mi := &file_openshell_proto_msgTypes[129] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7978,7 +9185,7 @@ func (x *PolicyMergeOperation) String() string { func (*PolicyMergeOperation) ProtoMessage() {} func (x *PolicyMergeOperation) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[110] + mi := &file_openshell_proto_msgTypes[129] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7991,7 +9198,7 @@ func (x *PolicyMergeOperation) ProtoReflect() protoreflect.Message { // Deprecated: Use PolicyMergeOperation.ProtoReflect.Descriptor instead. func (*PolicyMergeOperation) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{110} + return file_openshell_proto_rawDescGZIP(), []int{129} } func (x *PolicyMergeOperation) GetOperation() isPolicyMergeOperation_Operation { @@ -8105,7 +9312,7 @@ type AddNetworkRule struct { func (x *AddNetworkRule) Reset() { *x = AddNetworkRule{} - mi := &file_openshell_proto_msgTypes[111] + mi := &file_openshell_proto_msgTypes[130] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8117,7 +9324,7 @@ func (x *AddNetworkRule) String() string { func (*AddNetworkRule) ProtoMessage() {} func (x *AddNetworkRule) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[111] + mi := &file_openshell_proto_msgTypes[130] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8130,7 +9337,7 @@ func (x *AddNetworkRule) ProtoReflect() protoreflect.Message { // Deprecated: Use AddNetworkRule.ProtoReflect.Descriptor instead. func (*AddNetworkRule) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{111} + return file_openshell_proto_rawDescGZIP(), []int{130} } func (x *AddNetworkRule) GetRuleName() string { @@ -8158,7 +9365,7 @@ type RemoveNetworkEndpoint struct { func (x *RemoveNetworkEndpoint) Reset() { *x = RemoveNetworkEndpoint{} - mi := &file_openshell_proto_msgTypes[112] + mi := &file_openshell_proto_msgTypes[131] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8170,7 +9377,7 @@ func (x *RemoveNetworkEndpoint) String() string { func (*RemoveNetworkEndpoint) ProtoMessage() {} func (x *RemoveNetworkEndpoint) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[112] + mi := &file_openshell_proto_msgTypes[131] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8183,7 +9390,7 @@ func (x *RemoveNetworkEndpoint) ProtoReflect() protoreflect.Message { // Deprecated: Use RemoveNetworkEndpoint.ProtoReflect.Descriptor instead. func (*RemoveNetworkEndpoint) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{112} + return file_openshell_proto_rawDescGZIP(), []int{131} } func (x *RemoveNetworkEndpoint) GetRuleName() string { @@ -8216,7 +9423,7 @@ type RemoveNetworkRule struct { func (x *RemoveNetworkRule) Reset() { *x = RemoveNetworkRule{} - mi := &file_openshell_proto_msgTypes[113] + mi := &file_openshell_proto_msgTypes[132] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8228,7 +9435,7 @@ func (x *RemoveNetworkRule) String() string { func (*RemoveNetworkRule) ProtoMessage() {} func (x *RemoveNetworkRule) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[113] + mi := &file_openshell_proto_msgTypes[132] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8241,7 +9448,7 @@ func (x *RemoveNetworkRule) ProtoReflect() protoreflect.Message { // Deprecated: Use RemoveNetworkRule.ProtoReflect.Descriptor instead. func (*RemoveNetworkRule) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{113} + return file_openshell_proto_rawDescGZIP(), []int{132} } func (x *RemoveNetworkRule) GetRuleName() string { @@ -8262,7 +9469,7 @@ type AddDenyRules struct { func (x *AddDenyRules) Reset() { *x = AddDenyRules{} - mi := &file_openshell_proto_msgTypes[114] + mi := &file_openshell_proto_msgTypes[133] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8274,7 +9481,7 @@ func (x *AddDenyRules) String() string { func (*AddDenyRules) ProtoMessage() {} func (x *AddDenyRules) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[114] + mi := &file_openshell_proto_msgTypes[133] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8287,7 +9494,7 @@ func (x *AddDenyRules) ProtoReflect() protoreflect.Message { // Deprecated: Use AddDenyRules.ProtoReflect.Descriptor instead. func (*AddDenyRules) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{114} + return file_openshell_proto_rawDescGZIP(), []int{133} } func (x *AddDenyRules) GetHost() string { @@ -8322,7 +9529,7 @@ type AddAllowRules struct { func (x *AddAllowRules) Reset() { *x = AddAllowRules{} - mi := &file_openshell_proto_msgTypes[115] + mi := &file_openshell_proto_msgTypes[134] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8334,7 +9541,7 @@ func (x *AddAllowRules) String() string { func (*AddAllowRules) ProtoMessage() {} func (x *AddAllowRules) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[115] + mi := &file_openshell_proto_msgTypes[134] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8347,7 +9554,7 @@ func (x *AddAllowRules) ProtoReflect() protoreflect.Message { // Deprecated: Use AddAllowRules.ProtoReflect.Descriptor instead. func (*AddAllowRules) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{115} + return file_openshell_proto_rawDescGZIP(), []int{134} } func (x *AddAllowRules) GetHost() string { @@ -8381,7 +9588,7 @@ type RemoveNetworkBinary struct { func (x *RemoveNetworkBinary) Reset() { *x = RemoveNetworkBinary{} - mi := &file_openshell_proto_msgTypes[116] + mi := &file_openshell_proto_msgTypes[135] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8393,7 +9600,7 @@ func (x *RemoveNetworkBinary) String() string { func (*RemoveNetworkBinary) ProtoMessage() {} func (x *RemoveNetworkBinary) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[116] + mi := &file_openshell_proto_msgTypes[135] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8406,7 +9613,7 @@ func (x *RemoveNetworkBinary) ProtoReflect() protoreflect.Message { // Deprecated: Use RemoveNetworkBinary.ProtoReflect.Descriptor instead. func (*RemoveNetworkBinary) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{116} + return file_openshell_proto_rawDescGZIP(), []int{135} } func (x *RemoveNetworkBinary) GetRuleName() string { @@ -8442,7 +9649,7 @@ type UpdateConfigResponse struct { func (x *UpdateConfigResponse) Reset() { *x = UpdateConfigResponse{} - mi := &file_openshell_proto_msgTypes[117] + mi := &file_openshell_proto_msgTypes[136] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8454,7 +9661,7 @@ func (x *UpdateConfigResponse) String() string { func (*UpdateConfigResponse) ProtoMessage() {} func (x *UpdateConfigResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[117] + mi := &file_openshell_proto_msgTypes[136] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8467,7 +9674,7 @@ func (x *UpdateConfigResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateConfigResponse.ProtoReflect.Descriptor instead. func (*UpdateConfigResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{117} + return file_openshell_proto_rawDescGZIP(), []int{136} } func (x *UpdateConfigResponse) GetVersion() uint32 { @@ -8522,7 +9729,7 @@ type GetSandboxPolicyStatusRequest struct { func (x *GetSandboxPolicyStatusRequest) Reset() { *x = GetSandboxPolicyStatusRequest{} - mi := &file_openshell_proto_msgTypes[118] + mi := &file_openshell_proto_msgTypes[137] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8534,7 +9741,7 @@ func (x *GetSandboxPolicyStatusRequest) String() string { func (*GetSandboxPolicyStatusRequest) ProtoMessage() {} func (x *GetSandboxPolicyStatusRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[118] + mi := &file_openshell_proto_msgTypes[137] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8547,7 +9754,7 @@ func (x *GetSandboxPolicyStatusRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetSandboxPolicyStatusRequest.ProtoReflect.Descriptor instead. func (*GetSandboxPolicyStatusRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{118} + return file_openshell_proto_rawDescGZIP(), []int{137} } func (x *GetSandboxPolicyStatusRequest) GetName() string { @@ -8591,7 +9798,7 @@ type GetSandboxPolicyStatusResponse struct { func (x *GetSandboxPolicyStatusResponse) Reset() { *x = GetSandboxPolicyStatusResponse{} - mi := &file_openshell_proto_msgTypes[119] + mi := &file_openshell_proto_msgTypes[138] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8603,7 +9810,7 @@ func (x *GetSandboxPolicyStatusResponse) String() string { func (*GetSandboxPolicyStatusResponse) ProtoMessage() {} func (x *GetSandboxPolicyStatusResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[119] + mi := &file_openshell_proto_msgTypes[138] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8616,7 +9823,7 @@ func (x *GetSandboxPolicyStatusResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetSandboxPolicyStatusResponse.ProtoReflect.Descriptor instead. func (*GetSandboxPolicyStatusResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{119} + return file_openshell_proto_rawDescGZIP(), []int{138} } func (x *GetSandboxPolicyStatusResponse) GetRevision() *SandboxPolicyRevision { @@ -8650,7 +9857,7 @@ type ListSandboxPoliciesRequest struct { func (x *ListSandboxPoliciesRequest) Reset() { *x = ListSandboxPoliciesRequest{} - mi := &file_openshell_proto_msgTypes[120] + mi := &file_openshell_proto_msgTypes[139] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8662,7 +9869,7 @@ func (x *ListSandboxPoliciesRequest) String() string { func (*ListSandboxPoliciesRequest) ProtoMessage() {} func (x *ListSandboxPoliciesRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[120] + mi := &file_openshell_proto_msgTypes[139] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8675,7 +9882,7 @@ func (x *ListSandboxPoliciesRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListSandboxPoliciesRequest.ProtoReflect.Descriptor instead. func (*ListSandboxPoliciesRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{120} + return file_openshell_proto_rawDescGZIP(), []int{139} } func (x *ListSandboxPoliciesRequest) GetName() string { @@ -8723,7 +9930,7 @@ type ListSandboxPoliciesResponse struct { func (x *ListSandboxPoliciesResponse) Reset() { *x = ListSandboxPoliciesResponse{} - mi := &file_openshell_proto_msgTypes[121] + mi := &file_openshell_proto_msgTypes[140] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8735,7 +9942,7 @@ func (x *ListSandboxPoliciesResponse) String() string { func (*ListSandboxPoliciesResponse) ProtoMessage() {} func (x *ListSandboxPoliciesResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[121] + mi := &file_openshell_proto_msgTypes[140] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8748,7 +9955,7 @@ func (x *ListSandboxPoliciesResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListSandboxPoliciesResponse.ProtoReflect.Descriptor instead. func (*ListSandboxPoliciesResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{121} + return file_openshell_proto_rawDescGZIP(), []int{140} } func (x *ListSandboxPoliciesResponse) GetRevisions() []*SandboxPolicyRevision { @@ -8775,7 +9982,7 @@ type ReportPolicyStatusRequest struct { func (x *ReportPolicyStatusRequest) Reset() { *x = ReportPolicyStatusRequest{} - mi := &file_openshell_proto_msgTypes[122] + mi := &file_openshell_proto_msgTypes[141] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8787,7 +9994,7 @@ func (x *ReportPolicyStatusRequest) String() string { func (*ReportPolicyStatusRequest) ProtoMessage() {} func (x *ReportPolicyStatusRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[122] + mi := &file_openshell_proto_msgTypes[141] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8800,7 +10007,7 @@ func (x *ReportPolicyStatusRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ReportPolicyStatusRequest.ProtoReflect.Descriptor instead. func (*ReportPolicyStatusRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{122} + return file_openshell_proto_rawDescGZIP(), []int{141} } func (x *ReportPolicyStatusRequest) GetSandboxId() string { @@ -8840,7 +10047,7 @@ type ReportPolicyStatusResponse struct { func (x *ReportPolicyStatusResponse) Reset() { *x = ReportPolicyStatusResponse{} - mi := &file_openshell_proto_msgTypes[123] + mi := &file_openshell_proto_msgTypes[142] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8852,7 +10059,7 @@ func (x *ReportPolicyStatusResponse) String() string { func (*ReportPolicyStatusResponse) ProtoMessage() {} func (x *ReportPolicyStatusResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[123] + mi := &file_openshell_proto_msgTypes[142] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8865,7 +10072,7 @@ func (x *ReportPolicyStatusResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ReportPolicyStatusResponse.ProtoReflect.Descriptor instead. func (*ReportPolicyStatusResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{123} + return file_openshell_proto_rawDescGZIP(), []int{142} } // A versioned policy revision with metadata. @@ -8893,7 +10100,7 @@ type SandboxPolicyRevision struct { func (x *SandboxPolicyRevision) Reset() { *x = SandboxPolicyRevision{} - mi := &file_openshell_proto_msgTypes[124] + mi := &file_openshell_proto_msgTypes[143] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8905,7 +10112,7 @@ func (x *SandboxPolicyRevision) String() string { func (*SandboxPolicyRevision) ProtoMessage() {} func (x *SandboxPolicyRevision) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[124] + mi := &file_openshell_proto_msgTypes[143] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8918,7 +10125,7 @@ func (x *SandboxPolicyRevision) ProtoReflect() protoreflect.Message { // Deprecated: Use SandboxPolicyRevision.ProtoReflect.Descriptor instead. func (*SandboxPolicyRevision) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{124} + return file_openshell_proto_rawDescGZIP(), []int{143} } func (x *SandboxPolicyRevision) GetVersion() uint32 { @@ -8998,7 +10205,7 @@ type GetSandboxLogsRequest struct { func (x *GetSandboxLogsRequest) Reset() { *x = GetSandboxLogsRequest{} - mi := &file_openshell_proto_msgTypes[125] + mi := &file_openshell_proto_msgTypes[144] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9010,7 +10217,7 @@ func (x *GetSandboxLogsRequest) String() string { func (*GetSandboxLogsRequest) ProtoMessage() {} func (x *GetSandboxLogsRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[125] + mi := &file_openshell_proto_msgTypes[144] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9023,7 +10230,7 @@ func (x *GetSandboxLogsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetSandboxLogsRequest.ProtoReflect.Descriptor instead. func (*GetSandboxLogsRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{125} + return file_openshell_proto_rawDescGZIP(), []int{144} } func (x *GetSandboxLogsRequest) GetSandboxId() string { @@ -9081,7 +10288,7 @@ type PushSandboxLogsRequest struct { func (x *PushSandboxLogsRequest) Reset() { *x = PushSandboxLogsRequest{} - mi := &file_openshell_proto_msgTypes[126] + mi := &file_openshell_proto_msgTypes[145] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9093,7 +10300,7 @@ func (x *PushSandboxLogsRequest) String() string { func (*PushSandboxLogsRequest) ProtoMessage() {} func (x *PushSandboxLogsRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[126] + mi := &file_openshell_proto_msgTypes[145] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9106,7 +10313,7 @@ func (x *PushSandboxLogsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use PushSandboxLogsRequest.ProtoReflect.Descriptor instead. func (*PushSandboxLogsRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{126} + return file_openshell_proto_rawDescGZIP(), []int{145} } func (x *PushSandboxLogsRequest) GetSandboxId() string { @@ -9132,7 +10339,7 @@ type PushSandboxLogsResponse struct { func (x *PushSandboxLogsResponse) Reset() { *x = PushSandboxLogsResponse{} - mi := &file_openshell_proto_msgTypes[127] + mi := &file_openshell_proto_msgTypes[146] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9144,7 +10351,7 @@ func (x *PushSandboxLogsResponse) String() string { func (*PushSandboxLogsResponse) ProtoMessage() {} func (x *PushSandboxLogsResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[127] + mi := &file_openshell_proto_msgTypes[146] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9157,7 +10364,7 @@ func (x *PushSandboxLogsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use PushSandboxLogsResponse.ProtoReflect.Descriptor instead. func (*PushSandboxLogsResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{127} + return file_openshell_proto_rawDescGZIP(), []int{146} } // Get sandbox logs response. @@ -9173,7 +10380,7 @@ type GetSandboxLogsResponse struct { func (x *GetSandboxLogsResponse) Reset() { *x = GetSandboxLogsResponse{} - mi := &file_openshell_proto_msgTypes[128] + mi := &file_openshell_proto_msgTypes[147] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9185,7 +10392,7 @@ func (x *GetSandboxLogsResponse) String() string { func (*GetSandboxLogsResponse) ProtoMessage() {} func (x *GetSandboxLogsResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[128] + mi := &file_openshell_proto_msgTypes[147] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9198,7 +10405,7 @@ func (x *GetSandboxLogsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetSandboxLogsResponse.ProtoReflect.Descriptor instead. func (*GetSandboxLogsResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{128} + return file_openshell_proto_rawDescGZIP(), []int{147} } func (x *GetSandboxLogsResponse) GetLogs() []*SandboxLogLine { @@ -9231,7 +10438,7 @@ type SupervisorMessage struct { func (x *SupervisorMessage) Reset() { *x = SupervisorMessage{} - mi := &file_openshell_proto_msgTypes[129] + mi := &file_openshell_proto_msgTypes[148] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9243,7 +10450,7 @@ func (x *SupervisorMessage) String() string { func (*SupervisorMessage) ProtoMessage() {} func (x *SupervisorMessage) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[129] + mi := &file_openshell_proto_msgTypes[148] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9256,7 +10463,7 @@ func (x *SupervisorMessage) ProtoReflect() protoreflect.Message { // Deprecated: Use SupervisorMessage.ProtoReflect.Descriptor instead. func (*SupervisorMessage) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{129} + return file_openshell_proto_rawDescGZIP(), []int{148} } func (x *SupervisorMessage) GetPayload() isSupervisorMessage_Payload { @@ -9347,7 +10554,7 @@ type GatewayMessage struct { func (x *GatewayMessage) Reset() { *x = GatewayMessage{} - mi := &file_openshell_proto_msgTypes[130] + mi := &file_openshell_proto_msgTypes[149] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9359,7 +10566,7 @@ func (x *GatewayMessage) String() string { func (*GatewayMessage) ProtoMessage() {} func (x *GatewayMessage) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[130] + mi := &file_openshell_proto_msgTypes[149] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9372,7 +10579,7 @@ func (x *GatewayMessage) ProtoReflect() protoreflect.Message { // Deprecated: Use GatewayMessage.ProtoReflect.Descriptor instead. func (*GatewayMessage) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{130} + return file_openshell_proto_rawDescGZIP(), []int{149} } func (x *GatewayMessage) GetPayload() isGatewayMessage_Payload { @@ -9474,7 +10681,7 @@ type SupervisorHello struct { func (x *SupervisorHello) Reset() { *x = SupervisorHello{} - mi := &file_openshell_proto_msgTypes[131] + mi := &file_openshell_proto_msgTypes[150] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9486,7 +10693,7 @@ func (x *SupervisorHello) String() string { func (*SupervisorHello) ProtoMessage() {} func (x *SupervisorHello) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[131] + mi := &file_openshell_proto_msgTypes[150] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9499,7 +10706,7 @@ func (x *SupervisorHello) ProtoReflect() protoreflect.Message { // Deprecated: Use SupervisorHello.ProtoReflect.Descriptor instead. func (*SupervisorHello) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{131} + return file_openshell_proto_rawDescGZIP(), []int{150} } func (x *SupervisorHello) GetSandboxId() string { @@ -9529,7 +10736,7 @@ type SessionAccepted struct { func (x *SessionAccepted) Reset() { *x = SessionAccepted{} - mi := &file_openshell_proto_msgTypes[132] + mi := &file_openshell_proto_msgTypes[151] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9541,7 +10748,7 @@ func (x *SessionAccepted) String() string { func (*SessionAccepted) ProtoMessage() {} func (x *SessionAccepted) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[132] + mi := &file_openshell_proto_msgTypes[151] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9554,7 +10761,7 @@ func (x *SessionAccepted) ProtoReflect() protoreflect.Message { // Deprecated: Use SessionAccepted.ProtoReflect.Descriptor instead. func (*SessionAccepted) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{132} + return file_openshell_proto_rawDescGZIP(), []int{151} } func (x *SessionAccepted) GetSessionId() string { @@ -9582,7 +10789,7 @@ type SessionRejected struct { func (x *SessionRejected) Reset() { *x = SessionRejected{} - mi := &file_openshell_proto_msgTypes[133] + mi := &file_openshell_proto_msgTypes[152] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9594,7 +10801,7 @@ func (x *SessionRejected) String() string { func (*SessionRejected) ProtoMessage() {} func (x *SessionRejected) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[133] + mi := &file_openshell_proto_msgTypes[152] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9607,7 +10814,7 @@ func (x *SessionRejected) ProtoReflect() protoreflect.Message { // Deprecated: Use SessionRejected.ProtoReflect.Descriptor instead. func (*SessionRejected) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{133} + return file_openshell_proto_rawDescGZIP(), []int{152} } func (x *SessionRejected) GetReason() string { @@ -9626,7 +10833,7 @@ type SupervisorHeartbeat struct { func (x *SupervisorHeartbeat) Reset() { *x = SupervisorHeartbeat{} - mi := &file_openshell_proto_msgTypes[134] + mi := &file_openshell_proto_msgTypes[153] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9638,7 +10845,7 @@ func (x *SupervisorHeartbeat) String() string { func (*SupervisorHeartbeat) ProtoMessage() {} func (x *SupervisorHeartbeat) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[134] + mi := &file_openshell_proto_msgTypes[153] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9651,7 +10858,7 @@ func (x *SupervisorHeartbeat) ProtoReflect() protoreflect.Message { // Deprecated: Use SupervisorHeartbeat.ProtoReflect.Descriptor instead. func (*SupervisorHeartbeat) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{134} + return file_openshell_proto_rawDescGZIP(), []int{153} } // Gateway heartbeat. @@ -9663,7 +10870,7 @@ type GatewayHeartbeat struct { func (x *GatewayHeartbeat) Reset() { *x = GatewayHeartbeat{} - mi := &file_openshell_proto_msgTypes[135] + mi := &file_openshell_proto_msgTypes[154] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9675,7 +10882,7 @@ func (x *GatewayHeartbeat) String() string { func (*GatewayHeartbeat) ProtoMessage() {} func (x *GatewayHeartbeat) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[135] + mi := &file_openshell_proto_msgTypes[154] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9688,7 +10895,7 @@ func (x *GatewayHeartbeat) ProtoReflect() protoreflect.Message { // Deprecated: Use GatewayHeartbeat.ProtoReflect.Descriptor instead. func (*GatewayHeartbeat) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{135} + return file_openshell_proto_rawDescGZIP(), []int{154} } // Gateway requests the supervisor to open a relay channel. @@ -9717,7 +10924,7 @@ type RelayOpen struct { func (x *RelayOpen) Reset() { *x = RelayOpen{} - mi := &file_openshell_proto_msgTypes[136] + mi := &file_openshell_proto_msgTypes[155] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9729,7 +10936,7 @@ func (x *RelayOpen) String() string { func (*RelayOpen) ProtoMessage() {} func (x *RelayOpen) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[136] + mi := &file_openshell_proto_msgTypes[155] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9742,7 +10949,7 @@ func (x *RelayOpen) ProtoReflect() protoreflect.Message { // Deprecated: Use RelayOpen.ProtoReflect.Descriptor instead. func (*RelayOpen) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{136} + return file_openshell_proto_rawDescGZIP(), []int{155} } func (x *RelayOpen) GetChannelId() string { @@ -9809,7 +11016,7 @@ type SshRelayTarget struct { func (x *SshRelayTarget) Reset() { *x = SshRelayTarget{} - mi := &file_openshell_proto_msgTypes[137] + mi := &file_openshell_proto_msgTypes[156] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9821,7 +11028,7 @@ func (x *SshRelayTarget) String() string { func (*SshRelayTarget) ProtoMessage() {} func (x *SshRelayTarget) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[137] + mi := &file_openshell_proto_msgTypes[156] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9834,7 +11041,7 @@ func (x *SshRelayTarget) ProtoReflect() protoreflect.Message { // Deprecated: Use SshRelayTarget.ProtoReflect.Descriptor instead. func (*SshRelayTarget) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{137} + return file_openshell_proto_rawDescGZIP(), []int{156} } // TCP target dialed by the supervisor from inside the sandbox. @@ -9850,7 +11057,7 @@ type TcpRelayTarget struct { func (x *TcpRelayTarget) Reset() { *x = TcpRelayTarget{} - mi := &file_openshell_proto_msgTypes[138] + mi := &file_openshell_proto_msgTypes[157] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9862,7 +11069,7 @@ func (x *TcpRelayTarget) String() string { func (*TcpRelayTarget) ProtoMessage() {} func (x *TcpRelayTarget) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[138] + mi := &file_openshell_proto_msgTypes[157] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9875,7 +11082,7 @@ func (x *TcpRelayTarget) ProtoReflect() protoreflect.Message { // Deprecated: Use TcpRelayTarget.ProtoReflect.Descriptor instead. func (*TcpRelayTarget) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{138} + return file_openshell_proto_rawDescGZIP(), []int{157} } func (x *TcpRelayTarget) GetHost() string { @@ -9903,7 +11110,7 @@ type RelayInit struct { func (x *RelayInit) Reset() { *x = RelayInit{} - mi := &file_openshell_proto_msgTypes[139] + mi := &file_openshell_proto_msgTypes[158] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9915,7 +11122,7 @@ func (x *RelayInit) String() string { func (*RelayInit) ProtoMessage() {} func (x *RelayInit) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[139] + mi := &file_openshell_proto_msgTypes[158] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9928,7 +11135,7 @@ func (x *RelayInit) ProtoReflect() protoreflect.Message { // Deprecated: Use RelayInit.ProtoReflect.Descriptor instead. func (*RelayInit) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{139} + return file_openshell_proto_rawDescGZIP(), []int{158} } func (x *RelayInit) GetChannelId() string { @@ -9955,7 +11162,7 @@ type RelayFrame struct { func (x *RelayFrame) Reset() { *x = RelayFrame{} - mi := &file_openshell_proto_msgTypes[140] + mi := &file_openshell_proto_msgTypes[159] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9967,7 +11174,7 @@ func (x *RelayFrame) String() string { func (*RelayFrame) ProtoMessage() {} func (x *RelayFrame) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[140] + mi := &file_openshell_proto_msgTypes[159] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9980,7 +11187,7 @@ func (x *RelayFrame) ProtoReflect() protoreflect.Message { // Deprecated: Use RelayFrame.ProtoReflect.Descriptor instead. func (*RelayFrame) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{140} + return file_openshell_proto_rawDescGZIP(), []int{159} } func (x *RelayFrame) GetPayload() isRelayFrame_Payload { @@ -10039,7 +11246,7 @@ type RelayOpenResult struct { func (x *RelayOpenResult) Reset() { *x = RelayOpenResult{} - mi := &file_openshell_proto_msgTypes[141] + mi := &file_openshell_proto_msgTypes[160] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10051,7 +11258,7 @@ func (x *RelayOpenResult) String() string { func (*RelayOpenResult) ProtoMessage() {} func (x *RelayOpenResult) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[141] + mi := &file_openshell_proto_msgTypes[160] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10064,7 +11271,7 @@ func (x *RelayOpenResult) ProtoReflect() protoreflect.Message { // Deprecated: Use RelayOpenResult.ProtoReflect.Descriptor instead. func (*RelayOpenResult) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{141} + return file_openshell_proto_rawDescGZIP(), []int{160} } func (x *RelayOpenResult) GetChannelId() string { @@ -10101,7 +11308,7 @@ type RelayClose struct { func (x *RelayClose) Reset() { *x = RelayClose{} - mi := &file_openshell_proto_msgTypes[142] + mi := &file_openshell_proto_msgTypes[161] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10113,7 +11320,7 @@ func (x *RelayClose) String() string { func (*RelayClose) ProtoMessage() {} func (x *RelayClose) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[142] + mi := &file_openshell_proto_msgTypes[161] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10126,7 +11333,7 @@ func (x *RelayClose) ProtoReflect() protoreflect.Message { // Deprecated: Use RelayClose.ProtoReflect.Descriptor instead. func (*RelayClose) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{142} + return file_openshell_proto_rawDescGZIP(), []int{161} } func (x *RelayClose) GetChannelId() string { @@ -10160,7 +11367,7 @@ type L7RequestSample struct { func (x *L7RequestSample) Reset() { *x = L7RequestSample{} - mi := &file_openshell_proto_msgTypes[143] + mi := &file_openshell_proto_msgTypes[162] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10172,7 +11379,7 @@ func (x *L7RequestSample) String() string { func (*L7RequestSample) ProtoMessage() {} func (x *L7RequestSample) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[143] + mi := &file_openshell_proto_msgTypes[162] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10185,7 +11392,7 @@ func (x *L7RequestSample) ProtoReflect() protoreflect.Message { // Deprecated: Use L7RequestSample.ProtoReflect.Descriptor instead. func (*L7RequestSample) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{143} + return file_openshell_proto_rawDescGZIP(), []int{162} } func (x *L7RequestSample) GetMethod() string { @@ -10259,7 +11466,7 @@ type DenialSummary struct { func (x *DenialSummary) Reset() { *x = DenialSummary{} - mi := &file_openshell_proto_msgTypes[144] + mi := &file_openshell_proto_msgTypes[163] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10271,7 +11478,7 @@ func (x *DenialSummary) String() string { func (*DenialSummary) ProtoMessage() {} func (x *DenialSummary) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[144] + mi := &file_openshell_proto_msgTypes[163] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10284,7 +11491,7 @@ func (x *DenialSummary) ProtoReflect() protoreflect.Message { // Deprecated: Use DenialSummary.ProtoReflect.Descriptor instead. func (*DenialSummary) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{144} + return file_openshell_proto_rawDescGZIP(), []int{163} } func (x *DenialSummary) GetSandboxId() string { @@ -10419,7 +11626,7 @@ type DenialGroupCount struct { func (x *DenialGroupCount) Reset() { *x = DenialGroupCount{} - mi := &file_openshell_proto_msgTypes[145] + mi := &file_openshell_proto_msgTypes[164] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10431,7 +11638,7 @@ func (x *DenialGroupCount) String() string { func (*DenialGroupCount) ProtoMessage() {} func (x *DenialGroupCount) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[145] + mi := &file_openshell_proto_msgTypes[164] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10444,7 +11651,7 @@ func (x *DenialGroupCount) ProtoReflect() protoreflect.Message { // Deprecated: Use DenialGroupCount.ProtoReflect.Descriptor instead. func (*DenialGroupCount) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{145} + return file_openshell_proto_rawDescGZIP(), []int{164} } func (x *DenialGroupCount) GetDenyGroup() string { @@ -10477,7 +11684,7 @@ type NetworkActivitySummary struct { func (x *NetworkActivitySummary) Reset() { *x = NetworkActivitySummary{} - mi := &file_openshell_proto_msgTypes[146] + mi := &file_openshell_proto_msgTypes[165] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10489,7 +11696,7 @@ func (x *NetworkActivitySummary) String() string { func (*NetworkActivitySummary) ProtoMessage() {} func (x *NetworkActivitySummary) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[146] + mi := &file_openshell_proto_msgTypes[165] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10502,7 +11709,7 @@ func (x *NetworkActivitySummary) ProtoReflect() protoreflect.Message { // Deprecated: Use NetworkActivitySummary.ProtoReflect.Descriptor instead. func (*NetworkActivitySummary) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{146} + return file_openshell_proto_rawDescGZIP(), []int{165} } func (x *NetworkActivitySummary) GetNetworkActivityCount() uint32 { @@ -10576,7 +11783,7 @@ type PolicyChunk struct { func (x *PolicyChunk) Reset() { *x = PolicyChunk{} - mi := &file_openshell_proto_msgTypes[147] + mi := &file_openshell_proto_msgTypes[166] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10588,7 +11795,7 @@ func (x *PolicyChunk) String() string { func (*PolicyChunk) ProtoMessage() {} func (x *PolicyChunk) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[147] + mi := &file_openshell_proto_msgTypes[166] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10601,7 +11808,7 @@ func (x *PolicyChunk) ProtoReflect() protoreflect.Message { // Deprecated: Use PolicyChunk.ProtoReflect.Descriptor instead. func (*PolicyChunk) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{147} + return file_openshell_proto_rawDescGZIP(), []int{166} } func (x *PolicyChunk) GetId() string { @@ -10747,7 +11954,7 @@ type DraftPolicyUpdate struct { func (x *DraftPolicyUpdate) Reset() { *x = DraftPolicyUpdate{} - mi := &file_openshell_proto_msgTypes[148] + mi := &file_openshell_proto_msgTypes[167] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10759,7 +11966,7 @@ func (x *DraftPolicyUpdate) String() string { func (*DraftPolicyUpdate) ProtoMessage() {} func (x *DraftPolicyUpdate) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[148] + mi := &file_openshell_proto_msgTypes[167] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10772,7 +11979,7 @@ func (x *DraftPolicyUpdate) ProtoReflect() protoreflect.Message { // Deprecated: Use DraftPolicyUpdate.ProtoReflect.Descriptor instead. func (*DraftPolicyUpdate) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{148} + return file_openshell_proto_rawDescGZIP(), []int{167} } func (x *DraftPolicyUpdate) GetDraftVersion() uint64 { @@ -10830,7 +12037,7 @@ type SubmitPolicyAnalysisRequest struct { func (x *SubmitPolicyAnalysisRequest) Reset() { *x = SubmitPolicyAnalysisRequest{} - mi := &file_openshell_proto_msgTypes[149] + mi := &file_openshell_proto_msgTypes[168] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10842,7 +12049,7 @@ func (x *SubmitPolicyAnalysisRequest) String() string { func (*SubmitPolicyAnalysisRequest) ProtoMessage() {} func (x *SubmitPolicyAnalysisRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[149] + mi := &file_openshell_proto_msgTypes[168] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10855,7 +12062,7 @@ func (x *SubmitPolicyAnalysisRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use SubmitPolicyAnalysisRequest.ProtoReflect.Descriptor instead. func (*SubmitPolicyAnalysisRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{149} + return file_openshell_proto_rawDescGZIP(), []int{168} } func (x *SubmitPolicyAnalysisRequest) GetSummaries() []*DenialSummary { @@ -10918,7 +12125,7 @@ type SubmitPolicyAnalysisResponse struct { func (x *SubmitPolicyAnalysisResponse) Reset() { *x = SubmitPolicyAnalysisResponse{} - mi := &file_openshell_proto_msgTypes[150] + mi := &file_openshell_proto_msgTypes[169] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10930,7 +12137,7 @@ func (x *SubmitPolicyAnalysisResponse) String() string { func (*SubmitPolicyAnalysisResponse) ProtoMessage() {} func (x *SubmitPolicyAnalysisResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[150] + mi := &file_openshell_proto_msgTypes[169] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10943,7 +12150,7 @@ func (x *SubmitPolicyAnalysisResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use SubmitPolicyAnalysisResponse.ProtoReflect.Descriptor instead. func (*SubmitPolicyAnalysisResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{150} + return file_openshell_proto_rawDescGZIP(), []int{169} } func (x *SubmitPolicyAnalysisResponse) GetAcceptedChunks() uint32 { @@ -10989,7 +12196,7 @@ type GetDraftPolicyRequest struct { func (x *GetDraftPolicyRequest) Reset() { *x = GetDraftPolicyRequest{} - mi := &file_openshell_proto_msgTypes[151] + mi := &file_openshell_proto_msgTypes[170] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11001,7 +12208,7 @@ func (x *GetDraftPolicyRequest) String() string { func (*GetDraftPolicyRequest) ProtoMessage() {} func (x *GetDraftPolicyRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[151] + mi := &file_openshell_proto_msgTypes[170] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11014,7 +12221,7 @@ func (x *GetDraftPolicyRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetDraftPolicyRequest.ProtoReflect.Descriptor instead. func (*GetDraftPolicyRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{151} + return file_openshell_proto_rawDescGZIP(), []int{170} } func (x *GetDraftPolicyRequest) GetName() string { @@ -11054,7 +12261,7 @@ type GetDraftPolicyResponse struct { func (x *GetDraftPolicyResponse) Reset() { *x = GetDraftPolicyResponse{} - mi := &file_openshell_proto_msgTypes[152] + mi := &file_openshell_proto_msgTypes[171] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11066,7 +12273,7 @@ func (x *GetDraftPolicyResponse) String() string { func (*GetDraftPolicyResponse) ProtoMessage() {} func (x *GetDraftPolicyResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[152] + mi := &file_openshell_proto_msgTypes[171] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11079,7 +12286,7 @@ func (x *GetDraftPolicyResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetDraftPolicyResponse.ProtoReflect.Descriptor instead. func (*GetDraftPolicyResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{152} + return file_openshell_proto_rawDescGZIP(), []int{171} } func (x *GetDraftPolicyResponse) GetChunks() []*PolicyChunk { @@ -11125,7 +12332,7 @@ type ApproveDraftChunkRequest struct { func (x *ApproveDraftChunkRequest) Reset() { *x = ApproveDraftChunkRequest{} - mi := &file_openshell_proto_msgTypes[153] + mi := &file_openshell_proto_msgTypes[172] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11137,7 +12344,7 @@ func (x *ApproveDraftChunkRequest) String() string { func (*ApproveDraftChunkRequest) ProtoMessage() {} func (x *ApproveDraftChunkRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[153] + mi := &file_openshell_proto_msgTypes[172] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11150,7 +12357,7 @@ func (x *ApproveDraftChunkRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ApproveDraftChunkRequest.ProtoReflect.Descriptor instead. func (*ApproveDraftChunkRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{153} + return file_openshell_proto_rawDescGZIP(), []int{172} } func (x *ApproveDraftChunkRequest) GetName() string { @@ -11186,7 +12393,7 @@ type ApproveDraftChunkResponse struct { func (x *ApproveDraftChunkResponse) Reset() { *x = ApproveDraftChunkResponse{} - mi := &file_openshell_proto_msgTypes[154] + mi := &file_openshell_proto_msgTypes[173] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11198,7 +12405,7 @@ func (x *ApproveDraftChunkResponse) String() string { func (*ApproveDraftChunkResponse) ProtoMessage() {} func (x *ApproveDraftChunkResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[154] + mi := &file_openshell_proto_msgTypes[173] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11211,7 +12418,7 @@ func (x *ApproveDraftChunkResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ApproveDraftChunkResponse.ProtoReflect.Descriptor instead. func (*ApproveDraftChunkResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{154} + return file_openshell_proto_rawDescGZIP(), []int{173} } func (x *ApproveDraftChunkResponse) GetPolicyVersion() uint32 { @@ -11245,7 +12452,7 @@ type RejectDraftChunkRequest struct { func (x *RejectDraftChunkRequest) Reset() { *x = RejectDraftChunkRequest{} - mi := &file_openshell_proto_msgTypes[155] + mi := &file_openshell_proto_msgTypes[174] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11257,7 +12464,7 @@ func (x *RejectDraftChunkRequest) String() string { func (*RejectDraftChunkRequest) ProtoMessage() {} func (x *RejectDraftChunkRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[155] + mi := &file_openshell_proto_msgTypes[174] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11270,7 +12477,7 @@ func (x *RejectDraftChunkRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use RejectDraftChunkRequest.ProtoReflect.Descriptor instead. func (*RejectDraftChunkRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{155} + return file_openshell_proto_rawDescGZIP(), []int{174} } func (x *RejectDraftChunkRequest) GetName() string { @@ -11309,7 +12516,7 @@ type RejectDraftChunkResponse struct { func (x *RejectDraftChunkResponse) Reset() { *x = RejectDraftChunkResponse{} - mi := &file_openshell_proto_msgTypes[156] + mi := &file_openshell_proto_msgTypes[175] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11321,7 +12528,7 @@ func (x *RejectDraftChunkResponse) String() string { func (*RejectDraftChunkResponse) ProtoMessage() {} func (x *RejectDraftChunkResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[156] + mi := &file_openshell_proto_msgTypes[175] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11334,7 +12541,7 @@ func (x *RejectDraftChunkResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use RejectDraftChunkResponse.ProtoReflect.Descriptor instead. func (*RejectDraftChunkResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{156} + return file_openshell_proto_rawDescGZIP(), []int{175} } // Approve all pending chunks. @@ -11352,7 +12559,7 @@ type ApproveAllDraftChunksRequest struct { func (x *ApproveAllDraftChunksRequest) Reset() { *x = ApproveAllDraftChunksRequest{} - mi := &file_openshell_proto_msgTypes[157] + mi := &file_openshell_proto_msgTypes[176] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11364,7 +12571,7 @@ func (x *ApproveAllDraftChunksRequest) String() string { func (*ApproveAllDraftChunksRequest) ProtoMessage() {} func (x *ApproveAllDraftChunksRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[157] + mi := &file_openshell_proto_msgTypes[176] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11377,7 +12584,7 @@ func (x *ApproveAllDraftChunksRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ApproveAllDraftChunksRequest.ProtoReflect.Descriptor instead. func (*ApproveAllDraftChunksRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{157} + return file_openshell_proto_rawDescGZIP(), []int{176} } func (x *ApproveAllDraftChunksRequest) GetName() string { @@ -11417,7 +12624,7 @@ type ApproveAllDraftChunksResponse struct { func (x *ApproveAllDraftChunksResponse) Reset() { *x = ApproveAllDraftChunksResponse{} - mi := &file_openshell_proto_msgTypes[158] + mi := &file_openshell_proto_msgTypes[177] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11429,7 +12636,7 @@ func (x *ApproveAllDraftChunksResponse) String() string { func (*ApproveAllDraftChunksResponse) ProtoMessage() {} func (x *ApproveAllDraftChunksResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[158] + mi := &file_openshell_proto_msgTypes[177] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11442,7 +12649,7 @@ func (x *ApproveAllDraftChunksResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ApproveAllDraftChunksResponse.ProtoReflect.Descriptor instead. func (*ApproveAllDraftChunksResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{158} + return file_openshell_proto_rawDescGZIP(), []int{177} } func (x *ApproveAllDraftChunksResponse) GetPolicyVersion() uint32 { @@ -11490,7 +12697,7 @@ type EditDraftChunkRequest struct { func (x *EditDraftChunkRequest) Reset() { *x = EditDraftChunkRequest{} - mi := &file_openshell_proto_msgTypes[159] + mi := &file_openshell_proto_msgTypes[178] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11502,7 +12709,7 @@ func (x *EditDraftChunkRequest) String() string { func (*EditDraftChunkRequest) ProtoMessage() {} func (x *EditDraftChunkRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[159] + mi := &file_openshell_proto_msgTypes[178] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11515,7 +12722,7 @@ func (x *EditDraftChunkRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use EditDraftChunkRequest.ProtoReflect.Descriptor instead. func (*EditDraftChunkRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{159} + return file_openshell_proto_rawDescGZIP(), []int{178} } func (x *EditDraftChunkRequest) GetName() string { @@ -11554,7 +12761,7 @@ type EditDraftChunkResponse struct { func (x *EditDraftChunkResponse) Reset() { *x = EditDraftChunkResponse{} - mi := &file_openshell_proto_msgTypes[160] + mi := &file_openshell_proto_msgTypes[179] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11566,7 +12773,7 @@ func (x *EditDraftChunkResponse) String() string { func (*EditDraftChunkResponse) ProtoMessage() {} func (x *EditDraftChunkResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[160] + mi := &file_openshell_proto_msgTypes[179] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11579,7 +12786,7 @@ func (x *EditDraftChunkResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use EditDraftChunkResponse.ProtoReflect.Descriptor instead. func (*EditDraftChunkResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{160} + return file_openshell_proto_rawDescGZIP(), []int{179} } // Reverse an approval (remove merged rule from active policy). @@ -11597,7 +12804,7 @@ type UndoDraftChunkRequest struct { func (x *UndoDraftChunkRequest) Reset() { *x = UndoDraftChunkRequest{} - mi := &file_openshell_proto_msgTypes[161] + mi := &file_openshell_proto_msgTypes[180] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11609,7 +12816,7 @@ func (x *UndoDraftChunkRequest) String() string { func (*UndoDraftChunkRequest) ProtoMessage() {} func (x *UndoDraftChunkRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[161] + mi := &file_openshell_proto_msgTypes[180] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11622,7 +12829,7 @@ func (x *UndoDraftChunkRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UndoDraftChunkRequest.ProtoReflect.Descriptor instead. func (*UndoDraftChunkRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{161} + return file_openshell_proto_rawDescGZIP(), []int{180} } func (x *UndoDraftChunkRequest) GetName() string { @@ -11658,7 +12865,7 @@ type UndoDraftChunkResponse struct { func (x *UndoDraftChunkResponse) Reset() { *x = UndoDraftChunkResponse{} - mi := &file_openshell_proto_msgTypes[162] + mi := &file_openshell_proto_msgTypes[181] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11670,7 +12877,7 @@ func (x *UndoDraftChunkResponse) String() string { func (*UndoDraftChunkResponse) ProtoMessage() {} func (x *UndoDraftChunkResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[162] + mi := &file_openshell_proto_msgTypes[181] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11683,7 +12890,7 @@ func (x *UndoDraftChunkResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use UndoDraftChunkResponse.ProtoReflect.Descriptor instead. func (*UndoDraftChunkResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{162} + return file_openshell_proto_rawDescGZIP(), []int{181} } func (x *UndoDraftChunkResponse) GetPolicyVersion() uint32 { @@ -11713,7 +12920,7 @@ type ClearDraftChunksRequest struct { func (x *ClearDraftChunksRequest) Reset() { *x = ClearDraftChunksRequest{} - mi := &file_openshell_proto_msgTypes[163] + mi := &file_openshell_proto_msgTypes[182] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11725,7 +12932,7 @@ func (x *ClearDraftChunksRequest) String() string { func (*ClearDraftChunksRequest) ProtoMessage() {} func (x *ClearDraftChunksRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[163] + mi := &file_openshell_proto_msgTypes[182] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11738,7 +12945,7 @@ func (x *ClearDraftChunksRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ClearDraftChunksRequest.ProtoReflect.Descriptor instead. func (*ClearDraftChunksRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{163} + return file_openshell_proto_rawDescGZIP(), []int{182} } func (x *ClearDraftChunksRequest) GetName() string { @@ -11765,7 +12972,7 @@ type ClearDraftChunksResponse struct { func (x *ClearDraftChunksResponse) Reset() { *x = ClearDraftChunksResponse{} - mi := &file_openshell_proto_msgTypes[164] + mi := &file_openshell_proto_msgTypes[183] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11777,7 +12984,7 @@ func (x *ClearDraftChunksResponse) String() string { func (*ClearDraftChunksResponse) ProtoMessage() {} func (x *ClearDraftChunksResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[164] + mi := &file_openshell_proto_msgTypes[183] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11790,7 +12997,7 @@ func (x *ClearDraftChunksResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ClearDraftChunksResponse.ProtoReflect.Descriptor instead. func (*ClearDraftChunksResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{164} + return file_openshell_proto_rawDescGZIP(), []int{183} } func (x *ClearDraftChunksResponse) GetChunksCleared() uint32 { @@ -11813,7 +13020,7 @@ type GetDraftHistoryRequest struct { func (x *GetDraftHistoryRequest) Reset() { *x = GetDraftHistoryRequest{} - mi := &file_openshell_proto_msgTypes[165] + mi := &file_openshell_proto_msgTypes[184] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11825,7 +13032,7 @@ func (x *GetDraftHistoryRequest) String() string { func (*GetDraftHistoryRequest) ProtoMessage() {} func (x *GetDraftHistoryRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[165] + mi := &file_openshell_proto_msgTypes[184] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11838,7 +13045,7 @@ func (x *GetDraftHistoryRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetDraftHistoryRequest.ProtoReflect.Descriptor instead. func (*GetDraftHistoryRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{165} + return file_openshell_proto_rawDescGZIP(), []int{184} } func (x *GetDraftHistoryRequest) GetName() string { @@ -11872,7 +13079,7 @@ type DraftHistoryEntry struct { func (x *DraftHistoryEntry) Reset() { *x = DraftHistoryEntry{} - mi := &file_openshell_proto_msgTypes[166] + mi := &file_openshell_proto_msgTypes[185] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11884,7 +13091,7 @@ func (x *DraftHistoryEntry) String() string { func (*DraftHistoryEntry) ProtoMessage() {} func (x *DraftHistoryEntry) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[166] + mi := &file_openshell_proto_msgTypes[185] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11897,7 +13104,7 @@ func (x *DraftHistoryEntry) ProtoReflect() protoreflect.Message { // Deprecated: Use DraftHistoryEntry.ProtoReflect.Descriptor instead. func (*DraftHistoryEntry) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{166} + return file_openshell_proto_rawDescGZIP(), []int{185} } func (x *DraftHistoryEntry) GetTimestampMs() int64 { @@ -11938,7 +13145,7 @@ type GetDraftHistoryResponse struct { func (x *GetDraftHistoryResponse) Reset() { *x = GetDraftHistoryResponse{} - mi := &file_openshell_proto_msgTypes[167] + mi := &file_openshell_proto_msgTypes[186] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11950,7 +13157,7 @@ func (x *GetDraftHistoryResponse) String() string { func (*GetDraftHistoryResponse) ProtoMessage() {} func (x *GetDraftHistoryResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[167] + mi := &file_openshell_proto_msgTypes[186] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11963,7 +13170,7 @@ func (x *GetDraftHistoryResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetDraftHistoryResponse.ProtoReflect.Descriptor instead. func (*GetDraftHistoryResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{167} + return file_openshell_proto_rawDescGZIP(), []int{186} } func (x *GetDraftHistoryResponse) GetEntries() []*DraftHistoryEntry { @@ -11992,7 +13199,7 @@ type PolicyRevisionPayload struct { func (x *PolicyRevisionPayload) Reset() { *x = PolicyRevisionPayload{} - mi := &file_openshell_proto_msgTypes[168] + mi := &file_openshell_proto_msgTypes[187] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12004,7 +13211,7 @@ func (x *PolicyRevisionPayload) String() string { func (*PolicyRevisionPayload) ProtoMessage() {} func (x *PolicyRevisionPayload) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[168] + mi := &file_openshell_proto_msgTypes[187] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12017,7 +13224,7 @@ func (x *PolicyRevisionPayload) ProtoReflect() protoreflect.Message { // Deprecated: Use PolicyRevisionPayload.ProtoReflect.Descriptor instead. func (*PolicyRevisionPayload) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{168} + return file_openshell_proto_rawDescGZIP(), []int{187} } func (x *PolicyRevisionPayload) GetPolicy() *sandboxv1.SandboxPolicy { @@ -12090,7 +13297,7 @@ type DraftChunkPayload struct { func (x *DraftChunkPayload) Reset() { *x = DraftChunkPayload{} - mi := &file_openshell_proto_msgTypes[169] + mi := &file_openshell_proto_msgTypes[188] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12102,7 +13309,7 @@ func (x *DraftChunkPayload) String() string { func (*DraftChunkPayload) ProtoMessage() {} func (x *DraftChunkPayload) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[169] + mi := &file_openshell_proto_msgTypes[188] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12115,7 +13322,7 @@ func (x *DraftChunkPayload) ProtoReflect() protoreflect.Message { // Deprecated: Use DraftChunkPayload.ProtoReflect.Descriptor instead. func (*DraftChunkPayload) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{169} + return file_openshell_proto_rawDescGZIP(), []int{188} } func (x *DraftChunkPayload) GetRuleName() string { @@ -12221,7 +13428,7 @@ type StoredPolicyRevision struct { func (x *StoredPolicyRevision) Reset() { *x = StoredPolicyRevision{} - mi := &file_openshell_proto_msgTypes[170] + mi := &file_openshell_proto_msgTypes[189] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12233,7 +13440,7 @@ func (x *StoredPolicyRevision) String() string { func (*StoredPolicyRevision) ProtoMessage() {} func (x *StoredPolicyRevision) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[170] + mi := &file_openshell_proto_msgTypes[189] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12246,7 +13453,7 @@ func (x *StoredPolicyRevision) ProtoReflect() protoreflect.Message { // Deprecated: Use StoredPolicyRevision.ProtoReflect.Descriptor instead. func (*StoredPolicyRevision) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{170} + return file_openshell_proto_rawDescGZIP(), []int{189} } func (x *StoredPolicyRevision) GetId() string { @@ -12349,7 +13556,7 @@ type StoredDraftChunk struct { func (x *StoredDraftChunk) Reset() { *x = StoredDraftChunk{} - mi := &file_openshell_proto_msgTypes[171] + mi := &file_openshell_proto_msgTypes[190] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12361,7 +13568,7 @@ func (x *StoredDraftChunk) String() string { func (*StoredDraftChunk) ProtoMessage() {} func (x *StoredDraftChunk) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[171] + mi := &file_openshell_proto_msgTypes[190] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12374,7 +13581,7 @@ func (x *StoredDraftChunk) ProtoReflect() protoreflect.Message { // Deprecated: Use StoredDraftChunk.ProtoReflect.Descriptor instead. func (*StoredDraftChunk) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{171} + return file_openshell_proto_rawDescGZIP(), []int{190} } func (x *StoredDraftChunk) GetId() string { @@ -12523,7 +13730,7 @@ type CreateWorkspaceRequest struct { func (x *CreateWorkspaceRequest) Reset() { *x = CreateWorkspaceRequest{} - mi := &file_openshell_proto_msgTypes[172] + mi := &file_openshell_proto_msgTypes[191] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12535,7 +13742,7 @@ func (x *CreateWorkspaceRequest) String() string { func (*CreateWorkspaceRequest) ProtoMessage() {} func (x *CreateWorkspaceRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[172] + mi := &file_openshell_proto_msgTypes[191] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12548,7 +13755,7 @@ func (x *CreateWorkspaceRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use CreateWorkspaceRequest.ProtoReflect.Descriptor instead. func (*CreateWorkspaceRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{172} + return file_openshell_proto_rawDescGZIP(), []int{191} } func (x *CreateWorkspaceRequest) GetName() string { @@ -12575,7 +13782,7 @@ type CreateWorkspaceResponse struct { func (x *CreateWorkspaceResponse) Reset() { *x = CreateWorkspaceResponse{} - mi := &file_openshell_proto_msgTypes[173] + mi := &file_openshell_proto_msgTypes[192] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12587,7 +13794,7 @@ func (x *CreateWorkspaceResponse) String() string { func (*CreateWorkspaceResponse) ProtoMessage() {} func (x *CreateWorkspaceResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[173] + mi := &file_openshell_proto_msgTypes[192] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12600,7 +13807,7 @@ func (x *CreateWorkspaceResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use CreateWorkspaceResponse.ProtoReflect.Descriptor instead. func (*CreateWorkspaceResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{173} + return file_openshell_proto_rawDescGZIP(), []int{192} } func (x *CreateWorkspaceResponse) GetWorkspace() *datamodelv1.Workspace { @@ -12621,7 +13828,7 @@ type GetWorkspaceRequest struct { func (x *GetWorkspaceRequest) Reset() { *x = GetWorkspaceRequest{} - mi := &file_openshell_proto_msgTypes[174] + mi := &file_openshell_proto_msgTypes[193] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12633,7 +13840,7 @@ func (x *GetWorkspaceRequest) String() string { func (*GetWorkspaceRequest) ProtoMessage() {} func (x *GetWorkspaceRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[174] + mi := &file_openshell_proto_msgTypes[193] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12646,7 +13853,7 @@ func (x *GetWorkspaceRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetWorkspaceRequest.ProtoReflect.Descriptor instead. func (*GetWorkspaceRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{174} + return file_openshell_proto_rawDescGZIP(), []int{193} } func (x *GetWorkspaceRequest) GetName() string { @@ -12666,7 +13873,7 @@ type GetWorkspaceResponse struct { func (x *GetWorkspaceResponse) Reset() { *x = GetWorkspaceResponse{} - mi := &file_openshell_proto_msgTypes[175] + mi := &file_openshell_proto_msgTypes[194] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12678,7 +13885,7 @@ func (x *GetWorkspaceResponse) String() string { func (*GetWorkspaceResponse) ProtoMessage() {} func (x *GetWorkspaceResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[175] + mi := &file_openshell_proto_msgTypes[194] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12691,7 +13898,7 @@ func (x *GetWorkspaceResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetWorkspaceResponse.ProtoReflect.Descriptor instead. func (*GetWorkspaceResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{175} + return file_openshell_proto_rawDescGZIP(), []int{194} } func (x *GetWorkspaceResponse) GetWorkspace() *datamodelv1.Workspace { @@ -12714,7 +13921,7 @@ type ListWorkspacesRequest struct { func (x *ListWorkspacesRequest) Reset() { *x = ListWorkspacesRequest{} - mi := &file_openshell_proto_msgTypes[176] + mi := &file_openshell_proto_msgTypes[195] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12726,7 +13933,7 @@ func (x *ListWorkspacesRequest) String() string { func (*ListWorkspacesRequest) ProtoMessage() {} func (x *ListWorkspacesRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[176] + mi := &file_openshell_proto_msgTypes[195] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12739,7 +13946,7 @@ func (x *ListWorkspacesRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListWorkspacesRequest.ProtoReflect.Descriptor instead. func (*ListWorkspacesRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{176} + return file_openshell_proto_rawDescGZIP(), []int{195} } func (x *ListWorkspacesRequest) GetLimit() uint32 { @@ -12773,7 +13980,7 @@ type ListWorkspacesResponse struct { func (x *ListWorkspacesResponse) Reset() { *x = ListWorkspacesResponse{} - mi := &file_openshell_proto_msgTypes[177] + mi := &file_openshell_proto_msgTypes[196] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12785,7 +13992,7 @@ func (x *ListWorkspacesResponse) String() string { func (*ListWorkspacesResponse) ProtoMessage() {} func (x *ListWorkspacesResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[177] + mi := &file_openshell_proto_msgTypes[196] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12798,7 +14005,7 @@ func (x *ListWorkspacesResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListWorkspacesResponse.ProtoReflect.Descriptor instead. func (*ListWorkspacesResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{177} + return file_openshell_proto_rawDescGZIP(), []int{196} } func (x *ListWorkspacesResponse) GetWorkspaces() []*datamodelv1.Workspace { @@ -12819,7 +14026,7 @@ type DeleteWorkspaceRequest struct { func (x *DeleteWorkspaceRequest) Reset() { *x = DeleteWorkspaceRequest{} - mi := &file_openshell_proto_msgTypes[178] + mi := &file_openshell_proto_msgTypes[197] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12831,7 +14038,7 @@ func (x *DeleteWorkspaceRequest) String() string { func (*DeleteWorkspaceRequest) ProtoMessage() {} func (x *DeleteWorkspaceRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[178] + mi := &file_openshell_proto_msgTypes[197] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12844,7 +14051,7 @@ func (x *DeleteWorkspaceRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteWorkspaceRequest.ProtoReflect.Descriptor instead. func (*DeleteWorkspaceRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{178} + return file_openshell_proto_rawDescGZIP(), []int{197} } func (x *DeleteWorkspaceRequest) GetName() string { @@ -12864,7 +14071,7 @@ type DeleteWorkspaceResponse struct { func (x *DeleteWorkspaceResponse) Reset() { *x = DeleteWorkspaceResponse{} - mi := &file_openshell_proto_msgTypes[179] + mi := &file_openshell_proto_msgTypes[198] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12876,7 +14083,7 @@ func (x *DeleteWorkspaceResponse) String() string { func (*DeleteWorkspaceResponse) ProtoMessage() {} func (x *DeleteWorkspaceResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[179] + mi := &file_openshell_proto_msgTypes[198] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12889,7 +14096,7 @@ func (x *DeleteWorkspaceResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteWorkspaceResponse.ProtoReflect.Descriptor instead. func (*DeleteWorkspaceResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{179} + return file_openshell_proto_rawDescGZIP(), []int{198} } func (x *DeleteWorkspaceResponse) GetDeleted() bool { @@ -12913,7 +14120,7 @@ type WorkspaceMember struct { func (x *WorkspaceMember) Reset() { *x = WorkspaceMember{} - mi := &file_openshell_proto_msgTypes[180] + mi := &file_openshell_proto_msgTypes[199] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12925,7 +14132,7 @@ func (x *WorkspaceMember) String() string { func (*WorkspaceMember) ProtoMessage() {} func (x *WorkspaceMember) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[180] + mi := &file_openshell_proto_msgTypes[199] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12938,7 +14145,7 @@ func (x *WorkspaceMember) ProtoReflect() protoreflect.Message { // Deprecated: Use WorkspaceMember.ProtoReflect.Descriptor instead. func (*WorkspaceMember) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{180} + return file_openshell_proto_rawDescGZIP(), []int{199} } func (x *WorkspaceMember) GetMetadata() *datamodelv1.ObjectMeta { @@ -12977,7 +14184,7 @@ type AddWorkspaceMemberRequest struct { func (x *AddWorkspaceMemberRequest) Reset() { *x = AddWorkspaceMemberRequest{} - mi := &file_openshell_proto_msgTypes[181] + mi := &file_openshell_proto_msgTypes[200] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12989,7 +14196,7 @@ func (x *AddWorkspaceMemberRequest) String() string { func (*AddWorkspaceMemberRequest) ProtoMessage() {} func (x *AddWorkspaceMemberRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[181] + mi := &file_openshell_proto_msgTypes[200] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13002,7 +14209,7 @@ func (x *AddWorkspaceMemberRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use AddWorkspaceMemberRequest.ProtoReflect.Descriptor instead. func (*AddWorkspaceMemberRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{181} + return file_openshell_proto_rawDescGZIP(), []int{200} } func (x *AddWorkspaceMemberRequest) GetWorkspace() string { @@ -13036,7 +14243,7 @@ type AddWorkspaceMemberResponse struct { func (x *AddWorkspaceMemberResponse) Reset() { *x = AddWorkspaceMemberResponse{} - mi := &file_openshell_proto_msgTypes[182] + mi := &file_openshell_proto_msgTypes[201] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13048,7 +14255,7 @@ func (x *AddWorkspaceMemberResponse) String() string { func (*AddWorkspaceMemberResponse) ProtoMessage() {} func (x *AddWorkspaceMemberResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[182] + mi := &file_openshell_proto_msgTypes[201] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13061,7 +14268,7 @@ func (x *AddWorkspaceMemberResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use AddWorkspaceMemberResponse.ProtoReflect.Descriptor instead. func (*AddWorkspaceMemberResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{182} + return file_openshell_proto_rawDescGZIP(), []int{201} } func (x *AddWorkspaceMemberResponse) GetMember() *WorkspaceMember { @@ -13084,7 +14291,7 @@ type RemoveWorkspaceMemberRequest struct { func (x *RemoveWorkspaceMemberRequest) Reset() { *x = RemoveWorkspaceMemberRequest{} - mi := &file_openshell_proto_msgTypes[183] + mi := &file_openshell_proto_msgTypes[202] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13096,7 +14303,7 @@ func (x *RemoveWorkspaceMemberRequest) String() string { func (*RemoveWorkspaceMemberRequest) ProtoMessage() {} func (x *RemoveWorkspaceMemberRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[183] + mi := &file_openshell_proto_msgTypes[202] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13109,7 +14316,7 @@ func (x *RemoveWorkspaceMemberRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use RemoveWorkspaceMemberRequest.ProtoReflect.Descriptor instead. func (*RemoveWorkspaceMemberRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{183} + return file_openshell_proto_rawDescGZIP(), []int{202} } func (x *RemoveWorkspaceMemberRequest) GetWorkspace() string { @@ -13136,7 +14343,7 @@ type RemoveWorkspaceMemberResponse struct { func (x *RemoveWorkspaceMemberResponse) Reset() { *x = RemoveWorkspaceMemberResponse{} - mi := &file_openshell_proto_msgTypes[184] + mi := &file_openshell_proto_msgTypes[203] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13148,7 +14355,7 @@ func (x *RemoveWorkspaceMemberResponse) String() string { func (*RemoveWorkspaceMemberResponse) ProtoMessage() {} func (x *RemoveWorkspaceMemberResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[184] + mi := &file_openshell_proto_msgTypes[203] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13161,7 +14368,7 @@ func (x *RemoveWorkspaceMemberResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use RemoveWorkspaceMemberResponse.ProtoReflect.Descriptor instead. func (*RemoveWorkspaceMemberResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{184} + return file_openshell_proto_rawDescGZIP(), []int{203} } func (x *RemoveWorkspaceMemberResponse) GetRemoved() bool { @@ -13184,7 +14391,7 @@ type ListWorkspaceMembersRequest struct { func (x *ListWorkspaceMembersRequest) Reset() { *x = ListWorkspaceMembersRequest{} - mi := &file_openshell_proto_msgTypes[185] + mi := &file_openshell_proto_msgTypes[204] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13196,7 +14403,7 @@ func (x *ListWorkspaceMembersRequest) String() string { func (*ListWorkspaceMembersRequest) ProtoMessage() {} func (x *ListWorkspaceMembersRequest) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[185] + mi := &file_openshell_proto_msgTypes[204] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13209,7 +14416,7 @@ func (x *ListWorkspaceMembersRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListWorkspaceMembersRequest.ProtoReflect.Descriptor instead. func (*ListWorkspaceMembersRequest) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{185} + return file_openshell_proto_rawDescGZIP(), []int{204} } func (x *ListWorkspaceMembersRequest) GetWorkspace() string { @@ -13243,7 +14450,7 @@ type ListWorkspaceMembersResponse struct { func (x *ListWorkspaceMembersResponse) Reset() { *x = ListWorkspaceMembersResponse{} - mi := &file_openshell_proto_msgTypes[186] + mi := &file_openshell_proto_msgTypes[205] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13255,7 +14462,7 @@ func (x *ListWorkspaceMembersResponse) String() string { func (*ListWorkspaceMembersResponse) ProtoMessage() {} func (x *ListWorkspaceMembersResponse) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[186] + mi := &file_openshell_proto_msgTypes[205] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13268,7 +14475,7 @@ func (x *ListWorkspaceMembersResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListWorkspaceMembersResponse.ProtoReflect.Descriptor instead. func (*ListWorkspaceMembersResponse) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{186} + return file_openshell_proto_rawDescGZIP(), []int{205} } func (x *ListWorkspaceMembersResponse) GetMembers() []*WorkspaceMember { @@ -13296,7 +14503,7 @@ type ExtensionServiceCredential struct { func (x *ExtensionServiceCredential) Reset() { *x = ExtensionServiceCredential{} - mi := &file_openshell_proto_msgTypes[187] + mi := &file_openshell_proto_msgTypes[206] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13308,7 +14515,7 @@ func (x *ExtensionServiceCredential) String() string { func (*ExtensionServiceCredential) ProtoMessage() {} func (x *ExtensionServiceCredential) ProtoReflect() protoreflect.Message { - mi := &file_openshell_proto_msgTypes[187] + mi := &file_openshell_proto_msgTypes[206] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13321,7 +14528,7 @@ func (x *ExtensionServiceCredential) ProtoReflect() protoreflect.Message { // Deprecated: Use ExtensionServiceCredential.ProtoReflect.Descriptor instead. func (*ExtensionServiceCredential) Descriptor() ([]byte, []int) { - return file_openshell_proto_rawDescGZIP(), []int{187} + return file_openshell_proto_rawDescGZIP(), []int{206} } func (x *ExtensionServiceCredential) GetServiceName() string { @@ -13382,11 +14589,21 @@ const file_openshell_proto_rawDesc = "" + "\x19ComputeDriverCapabilities\x12\x1f\n" + "\vdriver_name\x18\x01 \x01(\tR\n" + "driverName\x12%\n" + - "\x0edriver_version\x18\x02 \x01(\tR\rdriverVersion\"\xd8\x01\n" + + "\x0edriver_version\x18\x02 \x01(\tR\rdriverVersion\"\xf2\x01\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\x06statusJ\x04\b\x04\x10\x05J\x04\b\x05\x10\x06R\x05phaseR\x16current_policy_version\"\xd7\x03\n" + + "\x06status\x18\x03 \x01(\v2\x1b.openshell.v1.SandboxStatusR\x06statusJ\x04\b\x04\x10\x05J\x04\b\x05\x10\x06J\x04\b\x06\x10\aR\x05phaseR\x16current_policy_versionR\x12delegated_identity\"\xc2\x01\n" + + "\x18SandboxDelegatedIdentity\x12#\n" + + "\rcredential_id\x18\x01 \x01(\tR\fcredentialId\x12+\n" + + "\x11principal_subject\x18\x02 \x01(\tR\x10principalSubject\x12,\n" + + "\x12delegated_until_ms\x18\x03 \x01(\x03R\x10delegatedUntilMs\x12&\n" + + "\x0fwithdrawn_at_ms\x18\x04 \x01(\x03R\rwithdrawnAtMs\"\xd6\x01\n" + + "\x1eSandboxDelegatedIdentityRecord\x12>\n" + + "\bmetadata\x18\x01 \x01(\v2\".openshell.datamodel.v1.ObjectMetaR\bmetadata\x12\x1d\n" + + "\n" + + "sandbox_id\x18\x02 \x01(\tR\tsandboxId\x12U\n" + + "\x12delegated_identity\x18\x03 \x01(\v2&.openshell.v1.SandboxDelegatedIdentityR\x11delegatedIdentity\"\xd7\x03\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" + @@ -13452,19 +14669,48 @@ 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\x01\"\x91\x03\n" + + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\xe8\x03\n" + "\x14CreateSandboxRequest\x12-\n" + "\x04spec\x18\x01 \x01(\v2\x19.openshell.v1.SandboxSpecR\x04spec\x12\x12\n" + "\x04name\x18\x02 \x01(\tR\x04name\x12F\n" + "\x06labels\x18\x03 \x03(\v2..openshell.v1.CreateSandboxRequest.LabelsEntryR\x06labels\x12U\n" + "\vannotations\x18\x04 \x03(\v23.openshell.v1.CreateSandboxRequest.AnnotationsEntryR\vannotations\x12\x1c\n" + - "\tworkspace\x18\x05 \x01(\tR\tworkspace\x1a9\n" + + "\tworkspace\x18\x05 \x01(\tR\tworkspace\x12U\n" + + "\x12delegated_identity\x18\x06 \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\"E\n" + + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\xa7\x02\n" + + "\x18DelegatedIdentityRequest\x12,\n" + + "\x12delegated_until_ms\x18\x01 \x01(\x03R\x10delegatedUntilMs\x12\x16\n" + + "\x06issuer\x18\x02 \x01(\tR\x06issuer\x12\x1b\n" + + "\tclient_id\x18\x03 \x01(\tR\bclientId\x12)\n" + + "\rrefresh_token\x18\x04 \x01(\tB\x04\x88\xb5\x18\x01R\frefreshToken\x12'\n" + + "\faccess_token\x18\x05 \x01(\tB\x04\x88\xb5\x18\x01R\vaccessToken\x12\x16\n" + + "\x06scopes\x18\a \x01(\tR\x06scopes\x12\x1a\n" + + "\baudience\x18\b \x01(\tR\baudienceJ\x04\b\x06\x10\aR\x1aaccess_token_expires_at_ms\"\\\n" + + "(GetSandboxDelegatedIdentityStatusRequest\x12\x12\n" + + "\x04name\x18\x01 \x01(\tR\x04name\x12\x1c\n" + + "\tworkspace\x18\x02 \x01(\tR\tworkspace\"\x81\x02\n" + + ")GetSandboxDelegatedIdentityStatusResponse\x12U\n" + + "\x12delegated_identity\x18\x01 \x01(\v2&.openshell.v1.SandboxDelegatedIdentityR\x11delegatedIdentity\x12\x15\n" + + "\x06now_ms\x18\x02 \x01(\x03R\x05nowMs\x127\n" + + "\x18credential_revoked_at_ms\x18\x03 \x01(\x03R\x15credentialRevokedAtMs\x12-\n" + + "\x12credential_missing\x18\x04 \x01(\bR\x11credentialMissing\"[\n" + + "'WithdrawSandboxDelegatedIdentityRequest\x12\x12\n" + + "\x04name\x18\x01 \x01(\tR\x04name\x12\x1c\n" + + "\tworkspace\x18\x02 \x01(\tR\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\"\xb0\x01\n" + + "%ExtendSandboxDelegatedIdentityRequest\x12\x12\n" + + "\x04name\x18\x01 \x01(\tR\x04name\x12\x1c\n" + + "\tworkspace\x18\x02 \x01(\tR\tworkspace\x12U\n" + + "\x12delegated_identity\x18\x03 \x01(\v2&.openshell.v1.DelegatedIdentityRequestR\x11delegatedIdentity\"Y\n" + + "&ExtendSandboxDelegatedIdentityResponse\x12/\n" + + "\asandbox\x18\x01 \x01(\v2\x15.openshell.v1.SandboxR\asandbox\"E\n" + "\x11GetSandboxRequest\x12\x12\n" + "\x04name\x18\x01 \x01(\tR\x04name\x12\x1c\n" + "\tworkspace\x18\x02 \x01(\tR\tworkspace\"\xb0\x01\n" + @@ -13795,7 +15041,57 @@ const file_openshell_proto_rawDesc = "" + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\x1aG\n" + "\x19AdditionalOutputKeysEntry\x12\x10\n" + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + - "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\x82\x01\n" + + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\xd4\x03\n" + + "\x1bDelegatedIdentityCredential\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\x12)\n" + + "\rrefresh_token\x18\x05 \x01(\tB\x04\x88\xb5\x18\x01R\frefreshToken\x12'\n" + + "\faccess_token\x18\x06 \x01(\tB\x04\x88\xb5\x18\x01R\vaccessToken\x12:\n" + + "\x1aaccess_token_expires_at_ms\x18\a \x01(\x03R\x16accessTokenExpiresAtMs\x12\x16\n" + + "\x06scopes\x18\b \x01(\tR\x06scopes\x12\x1a\n" + + "\baudience\x18\t \x01(\tR\baudience\x12+\n" + + "\x12last_refresh_at_ms\x18\n" + + " \x01(\x03R\x0flastRefreshAtMs\x12\"\n" + + "\rrevoked_at_ms\x18\v \x01(\x03R\vrevokedAtMs\"\xed\x03\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" + + "\x1aaccess_token_expires_at_ms\x18\a \x01(\x03R\x16accessTokenExpiresAtMs\x12\x16\n" + + "\x06scopes\x18\b \x01(\tR\x06scopes\x12\x1a\n" + + "\baudience\x18\t \x01(\tR\baudience\x12+\n" + + "\x12last_refresh_at_ms\x18\n" + + " \x01(\x03R\x0flastRefreshAtMs\x12\"\n" + + "\rrevoked_at_ms\x18\v \x01(\x03R\vrevokedAtMs\"W\n" + + "'ListDelegatedIdentityCredentialsRequest\x12\x14\n" + + "\x05limit\x18\x01 \x01(\rR\x05limit\x12\x16\n" + + "\x06offset\x18\x02 \x01(\rR\x06offset\"~\n" + + "(ListDelegatedIdentityCredentialsResponse\x12R\n" + + "\vcredentials\x18\x01 \x03(\v20.openshell.v1.DelegatedIdentityCredentialSummaryR\vcredentials\"=\n" + + "+GetDelegatedIdentityCredentialStatusRequest\x12\x0e\n" + + "\x02id\x18\x01 \x01(\tR\x02id\"\x97\x01\n" + + ",GetDelegatedIdentityCredentialStatusResponse\x12P\n" + + "\n" + + "credential\x18\x01 \x01(\v20.openshell.v1.DelegatedIdentityCredentialSummaryR\n" + + "credential\x12\x15\n" + + "\x06now_ms\x18\x02 \x01(\x03R\x05nowMs\"v\n" + + "(RevokeDelegatedIdentityCredentialRequest\x12\x0e\n" + + "\x02id\x18\x01 \x01(\tR\x02id\x12:\n" + + "\x19expected_resource_version\x18\x02 \x01(\x04R\x17expectedResourceVersion\"\x9a\x01\n" + + ")RevokeDelegatedIdentityCredentialResponse\x12\x18\n" + + "\arevoked\x18\x02 \x01(\bR\arevoked\x12\"\n" + + "\rrevoked_at_ms\x18\x03 \x01(\x03R\vrevokedAtMs\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\"\x82\x01\n" + "\x1fGetProviderRefreshStatusRequest\x12\x1a\n" + "\bprovider\x18\x01 \x01(\tR\bprovider\x12%\n" + "\x0ecredential_key\x18\x02 \x01(\tR\rcredentialKey\x12\x1c\n" + @@ -14411,7 +15707,7 @@ const file_openshell_proto_rawDesc = "" + "\rWorkspaceRole\x12\x1e\n" + "\x1aWORKSPACE_ROLE_UNSPECIFIED\x10\x00\x12\x17\n" + "\x13WORKSPACE_ROLE_USER\x10\x01\x12\x18\n" + - "\x14WORKSPACE_ROLE_ADMIN\x10\x022\xabE\n" + + "\x14WORKSPACE_ROLE_ADMIN\x10\x022\xe4O\n" + "\tOpenShell\x12Z\n" + "\x06Health\x12\x1b.openshell.v1.HealthRequest\x1a\x1c.openshell.v1.HealthResponse\"\x15\x82\xb5\x18\x11\n" + "\x0funauthenticated\x12i\n" + @@ -14420,6 +15716,12 @@ const file_openshell_proto_rawDesc = "" + "\x0eGetGatewayInfo\x12#.openshell.v1.GetGatewayInfoRequest\x1a$.openshell.v1.GetGatewayInfoResponse\")\x82\xb5\x18%\n" + "\x06bearer\x1a\x0eplatform_admin\"\vconfig:read\x12u\n" + "\rCreateSandbox\x12\".openshell.v1.CreateSandboxRequest\x1a\x1d.openshell.v1.SandboxResponse\"!\x82\xb5\x18\x1d\n" + + "\x06bearer\x12\x04user\"\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" + @@ -14481,7 +15783,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" + @@ -14564,7 +15874,7 @@ func file_openshell_proto_rawDescGZIP() []byte { } var file_openshell_proto_enumTypes = make([]protoimpl.EnumInfo, 7) -var file_openshell_proto_msgTypes = make([]protoimpl.MessageInfo, 212) +var file_openshell_proto_msgTypes = make([]protoimpl.MessageInfo, 231) var file_openshell_proto_goTypes = []any{ (SandboxPhase)(0), // 0: openshell.v1.SandboxPhase (ProviderCredentialTokenGrantType)(0), // 1: openshell.v1.ProviderCredentialTokenGrantType @@ -14586,514 +15896,558 @@ var file_openshell_proto_goTypes = []any{ (*ComputeDriverInfo)(nil), // 17: openshell.v1.ComputeDriverInfo (*ComputeDriverCapabilities)(nil), // 18: openshell.v1.ComputeDriverCapabilities (*Sandbox)(nil), // 19: openshell.v1.Sandbox - (*SandboxSpec)(nil), // 20: openshell.v1.SandboxSpec - (*ResourceRequirements)(nil), // 21: openshell.v1.ResourceRequirements - (*GpuResourceRequirements)(nil), // 22: openshell.v1.GpuResourceRequirements - (*SandboxTemplate)(nil), // 23: openshell.v1.SandboxTemplate - (*SandboxStatus)(nil), // 24: openshell.v1.SandboxStatus - (*SandboxCondition)(nil), // 25: openshell.v1.SandboxCondition - (*PlatformEvent)(nil), // 26: openshell.v1.PlatformEvent - (*CreateSandboxRequest)(nil), // 27: openshell.v1.CreateSandboxRequest - (*GetSandboxRequest)(nil), // 28: openshell.v1.GetSandboxRequest - (*ListSandboxesRequest)(nil), // 29: openshell.v1.ListSandboxesRequest - (*ListSandboxProvidersRequest)(nil), // 30: openshell.v1.ListSandboxProvidersRequest - (*AttachSandboxProviderRequest)(nil), // 31: openshell.v1.AttachSandboxProviderRequest - (*DetachSandboxProviderRequest)(nil), // 32: openshell.v1.DetachSandboxProviderRequest - (*DeleteSandboxRequest)(nil), // 33: openshell.v1.DeleteSandboxRequest - (*StopSandboxRequest)(nil), // 34: openshell.v1.StopSandboxRequest - (*StartSandboxRequest)(nil), // 35: openshell.v1.StartSandboxRequest - (*SandboxResponse)(nil), // 36: openshell.v1.SandboxResponse - (*ListSandboxesResponse)(nil), // 37: openshell.v1.ListSandboxesResponse - (*ListSandboxProvidersResponse)(nil), // 38: openshell.v1.ListSandboxProvidersResponse - (*AttachSandboxProviderResponse)(nil), // 39: openshell.v1.AttachSandboxProviderResponse - (*DetachSandboxProviderResponse)(nil), // 40: openshell.v1.DetachSandboxProviderResponse - (*DeleteSandboxResponse)(nil), // 41: openshell.v1.DeleteSandboxResponse - (*CreateSshSessionRequest)(nil), // 42: openshell.v1.CreateSshSessionRequest - (*CreateSshSessionResponse)(nil), // 43: openshell.v1.CreateSshSessionResponse - (*ExposeServiceRequest)(nil), // 44: openshell.v1.ExposeServiceRequest - (*GetServiceRequest)(nil), // 45: openshell.v1.GetServiceRequest - (*ListServicesRequest)(nil), // 46: openshell.v1.ListServicesRequest - (*ListServicesResponse)(nil), // 47: openshell.v1.ListServicesResponse - (*DeleteServiceRequest)(nil), // 48: openshell.v1.DeleteServiceRequest - (*DeleteServiceResponse)(nil), // 49: openshell.v1.DeleteServiceResponse - (*ServiceEndpoint)(nil), // 50: openshell.v1.ServiceEndpoint - (*ServiceEndpointResponse)(nil), // 51: openshell.v1.ServiceEndpointResponse - (*RevokeSshSessionRequest)(nil), // 52: openshell.v1.RevokeSshSessionRequest - (*RevokeSshSessionResponse)(nil), // 53: openshell.v1.RevokeSshSessionResponse - (*ExecSandboxRequest)(nil), // 54: openshell.v1.ExecSandboxRequest - (*ExecSandboxStdout)(nil), // 55: openshell.v1.ExecSandboxStdout - (*ExecSandboxStderr)(nil), // 56: openshell.v1.ExecSandboxStderr - (*ExecSandboxExit)(nil), // 57: openshell.v1.ExecSandboxExit - (*ExecSandboxEvent)(nil), // 58: openshell.v1.ExecSandboxEvent - (*TcpForwardInit)(nil), // 59: openshell.v1.TcpForwardInit - (*TcpForwardFrame)(nil), // 60: openshell.v1.TcpForwardFrame - (*ExecSandboxInput)(nil), // 61: openshell.v1.ExecSandboxInput - (*ExecSandboxWindowResize)(nil), // 62: openshell.v1.ExecSandboxWindowResize - (*SshSession)(nil), // 63: openshell.v1.SshSession - (*WatchSandboxRequest)(nil), // 64: openshell.v1.WatchSandboxRequest - (*SandboxStreamEvent)(nil), // 65: openshell.v1.SandboxStreamEvent - (*SandboxLogLine)(nil), // 66: openshell.v1.SandboxLogLine - (*SandboxStreamWarning)(nil), // 67: openshell.v1.SandboxStreamWarning - (*CreateProviderRequest)(nil), // 68: openshell.v1.CreateProviderRequest - (*GetProviderRequest)(nil), // 69: openshell.v1.GetProviderRequest - (*ListProvidersRequest)(nil), // 70: openshell.v1.ListProvidersRequest - (*UpdateProviderRequest)(nil), // 71: openshell.v1.UpdateProviderRequest - (*DeleteProviderRequest)(nil), // 72: openshell.v1.DeleteProviderRequest - (*ProviderResponse)(nil), // 73: openshell.v1.ProviderResponse - (*ListProvidersResponse)(nil), // 74: openshell.v1.ListProvidersResponse - (*ListProviderProfilesRequest)(nil), // 75: openshell.v1.ListProviderProfilesRequest - (*GetProviderProfileRequest)(nil), // 76: openshell.v1.GetProviderProfileRequest - (*ProviderProfileImportItem)(nil), // 77: openshell.v1.ProviderProfileImportItem - (*ProviderProfileDiagnostic)(nil), // 78: openshell.v1.ProviderProfileDiagnostic - (*ProviderCredentialTokenGrantAudienceOverride)(nil), // 79: openshell.v1.ProviderCredentialTokenGrantAudienceOverride - (*ProviderCredentialTokenGrantSubjectToken)(nil), // 80: openshell.v1.ProviderCredentialTokenGrantSubjectToken - (*ProviderCredentialTokenGrant)(nil), // 81: openshell.v1.ProviderCredentialTokenGrant - (*ProviderProfileCredential)(nil), // 82: openshell.v1.ProviderProfileCredential - (*ProviderCredentialRefreshMaterial)(nil), // 83: openshell.v1.ProviderCredentialRefreshMaterial - (*ProviderCredentialRefreshOutput)(nil), // 84: openshell.v1.ProviderCredentialRefreshOutput - (*ProviderCredentialRefresh)(nil), // 85: openshell.v1.ProviderCredentialRefresh - (*ProviderCredentialRefreshStatus)(nil), // 86: openshell.v1.ProviderCredentialRefreshStatus - (*ProviderProfileDiscovery)(nil), // 87: openshell.v1.ProviderProfileDiscovery - (*StoredProviderCredentialRefreshState)(nil), // 88: openshell.v1.StoredProviderCredentialRefreshState - (*GetProviderRefreshStatusRequest)(nil), // 89: openshell.v1.GetProviderRefreshStatusRequest - (*GetProviderRefreshStatusResponse)(nil), // 90: openshell.v1.GetProviderRefreshStatusResponse - (*ConfigureProviderRefreshRequest)(nil), // 91: openshell.v1.ConfigureProviderRefreshRequest - (*ConfigureProviderRefreshResponse)(nil), // 92: openshell.v1.ConfigureProviderRefreshResponse - (*RotateProviderCredentialRequest)(nil), // 93: openshell.v1.RotateProviderCredentialRequest - (*RotateProviderCredentialResponse)(nil), // 94: openshell.v1.RotateProviderCredentialResponse - (*DeleteProviderRefreshRequest)(nil), // 95: openshell.v1.DeleteProviderRefreshRequest - (*DeleteProviderRefreshResponse)(nil), // 96: openshell.v1.DeleteProviderRefreshResponse - (*ProviderProfile)(nil), // 97: openshell.v1.ProviderProfile - (*StoredProviderProfile)(nil), // 98: openshell.v1.StoredProviderProfile - (*ProviderProfileResponse)(nil), // 99: openshell.v1.ProviderProfileResponse - (*ListProviderProfilesResponse)(nil), // 100: openshell.v1.ListProviderProfilesResponse - (*ImportProviderProfilesRequest)(nil), // 101: openshell.v1.ImportProviderProfilesRequest - (*ImportProviderProfilesResponse)(nil), // 102: openshell.v1.ImportProviderProfilesResponse - (*UpdateProviderProfilesRequest)(nil), // 103: openshell.v1.UpdateProviderProfilesRequest - (*UpdateProviderProfilesResponse)(nil), // 104: openshell.v1.UpdateProviderProfilesResponse - (*LintProviderProfilesRequest)(nil), // 105: openshell.v1.LintProviderProfilesRequest - (*LintProviderProfilesResponse)(nil), // 106: openshell.v1.LintProviderProfilesResponse - (*DeleteProviderResponse)(nil), // 107: openshell.v1.DeleteProviderResponse - (*DeleteProviderProfileRequest)(nil), // 108: openshell.v1.DeleteProviderProfileRequest - (*DeleteProviderProfileResponse)(nil), // 109: openshell.v1.DeleteProviderProfileResponse - (*GetSandboxProviderEnvironmentRequest)(nil), // 110: openshell.v1.GetSandboxProviderEnvironmentRequest - (*StaticCredentialEndpointBinding)(nil), // 111: openshell.v1.StaticCredentialEndpointBinding - (*StaticCredentialBinding)(nil), // 112: openshell.v1.StaticCredentialBinding - (*GetSandboxProviderEnvironmentResponse)(nil), // 113: openshell.v1.GetSandboxProviderEnvironmentResponse - (*ExchangeProviderSubjectTokenRequest)(nil), // 114: openshell.v1.ExchangeProviderSubjectTokenRequest - (*ExchangeProviderSubjectTokenResponse)(nil), // 115: openshell.v1.ExchangeProviderSubjectTokenResponse - (*UpdateConfigRequest)(nil), // 116: openshell.v1.UpdateConfigRequest - (*PolicyMergeOperation)(nil), // 117: openshell.v1.PolicyMergeOperation - (*AddNetworkRule)(nil), // 118: openshell.v1.AddNetworkRule - (*RemoveNetworkEndpoint)(nil), // 119: openshell.v1.RemoveNetworkEndpoint - (*RemoveNetworkRule)(nil), // 120: openshell.v1.RemoveNetworkRule - (*AddDenyRules)(nil), // 121: openshell.v1.AddDenyRules - (*AddAllowRules)(nil), // 122: openshell.v1.AddAllowRules - (*RemoveNetworkBinary)(nil), // 123: openshell.v1.RemoveNetworkBinary - (*UpdateConfigResponse)(nil), // 124: openshell.v1.UpdateConfigResponse - (*GetSandboxPolicyStatusRequest)(nil), // 125: openshell.v1.GetSandboxPolicyStatusRequest - (*GetSandboxPolicyStatusResponse)(nil), // 126: openshell.v1.GetSandboxPolicyStatusResponse - (*ListSandboxPoliciesRequest)(nil), // 127: openshell.v1.ListSandboxPoliciesRequest - (*ListSandboxPoliciesResponse)(nil), // 128: openshell.v1.ListSandboxPoliciesResponse - (*ReportPolicyStatusRequest)(nil), // 129: openshell.v1.ReportPolicyStatusRequest - (*ReportPolicyStatusResponse)(nil), // 130: openshell.v1.ReportPolicyStatusResponse - (*SandboxPolicyRevision)(nil), // 131: openshell.v1.SandboxPolicyRevision - (*GetSandboxLogsRequest)(nil), // 132: openshell.v1.GetSandboxLogsRequest - (*PushSandboxLogsRequest)(nil), // 133: openshell.v1.PushSandboxLogsRequest - (*PushSandboxLogsResponse)(nil), // 134: openshell.v1.PushSandboxLogsResponse - (*GetSandboxLogsResponse)(nil), // 135: openshell.v1.GetSandboxLogsResponse - (*SupervisorMessage)(nil), // 136: openshell.v1.SupervisorMessage - (*GatewayMessage)(nil), // 137: openshell.v1.GatewayMessage - (*SupervisorHello)(nil), // 138: openshell.v1.SupervisorHello - (*SessionAccepted)(nil), // 139: openshell.v1.SessionAccepted - (*SessionRejected)(nil), // 140: openshell.v1.SessionRejected - (*SupervisorHeartbeat)(nil), // 141: openshell.v1.SupervisorHeartbeat - (*GatewayHeartbeat)(nil), // 142: openshell.v1.GatewayHeartbeat - (*RelayOpen)(nil), // 143: openshell.v1.RelayOpen - (*SshRelayTarget)(nil), // 144: openshell.v1.SshRelayTarget - (*TcpRelayTarget)(nil), // 145: openshell.v1.TcpRelayTarget - (*RelayInit)(nil), // 146: openshell.v1.RelayInit - (*RelayFrame)(nil), // 147: openshell.v1.RelayFrame - (*RelayOpenResult)(nil), // 148: openshell.v1.RelayOpenResult - (*RelayClose)(nil), // 149: openshell.v1.RelayClose - (*L7RequestSample)(nil), // 150: openshell.v1.L7RequestSample - (*DenialSummary)(nil), // 151: openshell.v1.DenialSummary - (*DenialGroupCount)(nil), // 152: openshell.v1.DenialGroupCount - (*NetworkActivitySummary)(nil), // 153: openshell.v1.NetworkActivitySummary - (*PolicyChunk)(nil), // 154: openshell.v1.PolicyChunk - (*DraftPolicyUpdate)(nil), // 155: openshell.v1.DraftPolicyUpdate - (*SubmitPolicyAnalysisRequest)(nil), // 156: openshell.v1.SubmitPolicyAnalysisRequest - (*SubmitPolicyAnalysisResponse)(nil), // 157: openshell.v1.SubmitPolicyAnalysisResponse - (*GetDraftPolicyRequest)(nil), // 158: openshell.v1.GetDraftPolicyRequest - (*GetDraftPolicyResponse)(nil), // 159: openshell.v1.GetDraftPolicyResponse - (*ApproveDraftChunkRequest)(nil), // 160: openshell.v1.ApproveDraftChunkRequest - (*ApproveDraftChunkResponse)(nil), // 161: openshell.v1.ApproveDraftChunkResponse - (*RejectDraftChunkRequest)(nil), // 162: openshell.v1.RejectDraftChunkRequest - (*RejectDraftChunkResponse)(nil), // 163: openshell.v1.RejectDraftChunkResponse - (*ApproveAllDraftChunksRequest)(nil), // 164: openshell.v1.ApproveAllDraftChunksRequest - (*ApproveAllDraftChunksResponse)(nil), // 165: openshell.v1.ApproveAllDraftChunksResponse - (*EditDraftChunkRequest)(nil), // 166: openshell.v1.EditDraftChunkRequest - (*EditDraftChunkResponse)(nil), // 167: openshell.v1.EditDraftChunkResponse - (*UndoDraftChunkRequest)(nil), // 168: openshell.v1.UndoDraftChunkRequest - (*UndoDraftChunkResponse)(nil), // 169: openshell.v1.UndoDraftChunkResponse - (*ClearDraftChunksRequest)(nil), // 170: openshell.v1.ClearDraftChunksRequest - (*ClearDraftChunksResponse)(nil), // 171: openshell.v1.ClearDraftChunksResponse - (*GetDraftHistoryRequest)(nil), // 172: openshell.v1.GetDraftHistoryRequest - (*DraftHistoryEntry)(nil), // 173: openshell.v1.DraftHistoryEntry - (*GetDraftHistoryResponse)(nil), // 174: openshell.v1.GetDraftHistoryResponse - (*PolicyRevisionPayload)(nil), // 175: openshell.v1.PolicyRevisionPayload - (*DraftChunkPayload)(nil), // 176: openshell.v1.DraftChunkPayload - (*StoredPolicyRevision)(nil), // 177: openshell.v1.StoredPolicyRevision - (*StoredDraftChunk)(nil), // 178: openshell.v1.StoredDraftChunk - (*CreateWorkspaceRequest)(nil), // 179: openshell.v1.CreateWorkspaceRequest - (*CreateWorkspaceResponse)(nil), // 180: openshell.v1.CreateWorkspaceResponse - (*GetWorkspaceRequest)(nil), // 181: openshell.v1.GetWorkspaceRequest - (*GetWorkspaceResponse)(nil), // 182: openshell.v1.GetWorkspaceResponse - (*ListWorkspacesRequest)(nil), // 183: openshell.v1.ListWorkspacesRequest - (*ListWorkspacesResponse)(nil), // 184: openshell.v1.ListWorkspacesResponse - (*DeleteWorkspaceRequest)(nil), // 185: openshell.v1.DeleteWorkspaceRequest - (*DeleteWorkspaceResponse)(nil), // 186: openshell.v1.DeleteWorkspaceResponse - (*WorkspaceMember)(nil), // 187: openshell.v1.WorkspaceMember - (*AddWorkspaceMemberRequest)(nil), // 188: openshell.v1.AddWorkspaceMemberRequest - (*AddWorkspaceMemberResponse)(nil), // 189: openshell.v1.AddWorkspaceMemberResponse - (*RemoveWorkspaceMemberRequest)(nil), // 190: openshell.v1.RemoveWorkspaceMemberRequest - (*RemoveWorkspaceMemberResponse)(nil), // 191: openshell.v1.RemoveWorkspaceMemberResponse - (*ListWorkspaceMembersRequest)(nil), // 192: openshell.v1.ListWorkspaceMembersRequest - (*ListWorkspaceMembersResponse)(nil), // 193: openshell.v1.ListWorkspaceMembersResponse - (*ExtensionServiceCredential)(nil), // 194: openshell.v1.ExtensionServiceCredential - nil, // 195: openshell.v1.SandboxSpec.EnvironmentEntry - nil, // 196: openshell.v1.SandboxTemplate.LabelsEntry - nil, // 197: openshell.v1.SandboxTemplate.AnnotationsEntry - nil, // 198: openshell.v1.SandboxTemplate.EnvironmentEntry - nil, // 199: openshell.v1.PlatformEvent.MetadataEntry - nil, // 200: openshell.v1.CreateSandboxRequest.LabelsEntry - nil, // 201: openshell.v1.CreateSandboxRequest.AnnotationsEntry - nil, // 202: openshell.v1.ExecSandboxRequest.EnvironmentEntry - nil, // 203: openshell.v1.SandboxLogLine.FieldsEntry - nil, // 204: openshell.v1.UpdateProviderRequest.CredentialExpiresAtMsEntry - nil, // 205: openshell.v1.StoredProviderCredentialRefreshState.MaterialEntry - nil, // 206: openshell.v1.StoredProviderCredentialRefreshState.AdditionalOutputKeysEntry - nil, // 207: openshell.v1.ConfigureProviderRefreshRequest.MaterialEntry - nil, // 208: openshell.v1.ProviderProfile.AnnotationsEntry - nil, // 209: openshell.v1.GetSandboxProviderEnvironmentResponse.EnvironmentEntry - nil, // 210: openshell.v1.GetSandboxProviderEnvironmentResponse.CredentialExpiresAtMsEntry - nil, // 211: openshell.v1.GetSandboxProviderEnvironmentResponse.DynamicCredentialsEntry - nil, // 212: openshell.v1.GetSandboxProviderEnvironmentResponse.StaticCredentialBindingsEntry - nil, // 213: openshell.v1.UpdateConfigRequest.AnnotationsEntry - nil, // 214: openshell.v1.UpdateConfigResponse.AnnotationsEntry - nil, // 215: openshell.v1.SandboxPolicyRevision.ProvenanceEntry - nil, // 216: openshell.v1.PolicyRevisionPayload.ProvenanceEntry - nil, // 217: openshell.v1.StoredPolicyRevision.ProvenanceEntry - nil, // 218: openshell.v1.CreateWorkspaceRequest.LabelsEntry - (*datamodelv1.ObjectMeta)(nil), // 219: openshell.datamodel.v1.ObjectMeta - (*sandboxv1.SandboxPolicy)(nil), // 220: openshell.sandbox.v1.SandboxPolicy - (*structpb.Struct)(nil), // 221: google.protobuf.Struct - (*datamodelv1.Provider)(nil), // 222: openshell.datamodel.v1.Provider - (*sandboxv1.NetworkEndpoint)(nil), // 223: openshell.sandbox.v1.NetworkEndpoint - (*sandboxv1.NetworkBinary)(nil), // 224: openshell.sandbox.v1.NetworkBinary - (*sandboxv1.SettingValue)(nil), // 225: openshell.sandbox.v1.SettingValue - (*sandboxv1.NetworkPolicyRule)(nil), // 226: openshell.sandbox.v1.NetworkPolicyRule - (*sandboxv1.L7DenyRule)(nil), // 227: openshell.sandbox.v1.L7DenyRule - (*sandboxv1.L7Rule)(nil), // 228: openshell.sandbox.v1.L7Rule - (*datamodelv1.Workspace)(nil), // 229: openshell.datamodel.v1.Workspace - (*sandboxv1.GetSandboxConfigRequest)(nil), // 230: openshell.sandbox.v1.GetSandboxConfigRequest - (*sandboxv1.GetGatewayConfigRequest)(nil), // 231: openshell.sandbox.v1.GetGatewayConfigRequest - (*sandboxv1.GetSandboxConfigResponse)(nil), // 232: openshell.sandbox.v1.GetSandboxConfigResponse - (*sandboxv1.GetGatewayConfigResponse)(nil), // 233: openshell.sandbox.v1.GetGatewayConfigResponse + (*SandboxDelegatedIdentity)(nil), // 20: openshell.v1.SandboxDelegatedIdentity + (*SandboxDelegatedIdentityRecord)(nil), // 21: openshell.v1.SandboxDelegatedIdentityRecord + (*SandboxSpec)(nil), // 22: openshell.v1.SandboxSpec + (*ResourceRequirements)(nil), // 23: openshell.v1.ResourceRequirements + (*GpuResourceRequirements)(nil), // 24: openshell.v1.GpuResourceRequirements + (*SandboxTemplate)(nil), // 25: openshell.v1.SandboxTemplate + (*SandboxStatus)(nil), // 26: openshell.v1.SandboxStatus + (*SandboxCondition)(nil), // 27: openshell.v1.SandboxCondition + (*PlatformEvent)(nil), // 28: openshell.v1.PlatformEvent + (*CreateSandboxRequest)(nil), // 29: openshell.v1.CreateSandboxRequest + (*DelegatedIdentityRequest)(nil), // 30: openshell.v1.DelegatedIdentityRequest + (*GetSandboxDelegatedIdentityStatusRequest)(nil), // 31: openshell.v1.GetSandboxDelegatedIdentityStatusRequest + (*GetSandboxDelegatedIdentityStatusResponse)(nil), // 32: openshell.v1.GetSandboxDelegatedIdentityStatusResponse + (*WithdrawSandboxDelegatedIdentityRequest)(nil), // 33: openshell.v1.WithdrawSandboxDelegatedIdentityRequest + (*WithdrawSandboxDelegatedIdentityResponse)(nil), // 34: openshell.v1.WithdrawSandboxDelegatedIdentityResponse + (*ExtendSandboxDelegatedIdentityRequest)(nil), // 35: openshell.v1.ExtendSandboxDelegatedIdentityRequest + (*ExtendSandboxDelegatedIdentityResponse)(nil), // 36: openshell.v1.ExtendSandboxDelegatedIdentityResponse + (*GetSandboxRequest)(nil), // 37: openshell.v1.GetSandboxRequest + (*ListSandboxesRequest)(nil), // 38: openshell.v1.ListSandboxesRequest + (*ListSandboxProvidersRequest)(nil), // 39: openshell.v1.ListSandboxProvidersRequest + (*AttachSandboxProviderRequest)(nil), // 40: openshell.v1.AttachSandboxProviderRequest + (*DetachSandboxProviderRequest)(nil), // 41: openshell.v1.DetachSandboxProviderRequest + (*DeleteSandboxRequest)(nil), // 42: openshell.v1.DeleteSandboxRequest + (*StopSandboxRequest)(nil), // 43: openshell.v1.StopSandboxRequest + (*StartSandboxRequest)(nil), // 44: openshell.v1.StartSandboxRequest + (*SandboxResponse)(nil), // 45: openshell.v1.SandboxResponse + (*ListSandboxesResponse)(nil), // 46: openshell.v1.ListSandboxesResponse + (*ListSandboxProvidersResponse)(nil), // 47: openshell.v1.ListSandboxProvidersResponse + (*AttachSandboxProviderResponse)(nil), // 48: openshell.v1.AttachSandboxProviderResponse + (*DetachSandboxProviderResponse)(nil), // 49: openshell.v1.DetachSandboxProviderResponse + (*DeleteSandboxResponse)(nil), // 50: openshell.v1.DeleteSandboxResponse + (*CreateSshSessionRequest)(nil), // 51: openshell.v1.CreateSshSessionRequest + (*CreateSshSessionResponse)(nil), // 52: openshell.v1.CreateSshSessionResponse + (*ExposeServiceRequest)(nil), // 53: openshell.v1.ExposeServiceRequest + (*GetServiceRequest)(nil), // 54: openshell.v1.GetServiceRequest + (*ListServicesRequest)(nil), // 55: openshell.v1.ListServicesRequest + (*ListServicesResponse)(nil), // 56: openshell.v1.ListServicesResponse + (*DeleteServiceRequest)(nil), // 57: openshell.v1.DeleteServiceRequest + (*DeleteServiceResponse)(nil), // 58: openshell.v1.DeleteServiceResponse + (*ServiceEndpoint)(nil), // 59: openshell.v1.ServiceEndpoint + (*ServiceEndpointResponse)(nil), // 60: openshell.v1.ServiceEndpointResponse + (*RevokeSshSessionRequest)(nil), // 61: openshell.v1.RevokeSshSessionRequest + (*RevokeSshSessionResponse)(nil), // 62: openshell.v1.RevokeSshSessionResponse + (*ExecSandboxRequest)(nil), // 63: openshell.v1.ExecSandboxRequest + (*ExecSandboxStdout)(nil), // 64: openshell.v1.ExecSandboxStdout + (*ExecSandboxStderr)(nil), // 65: openshell.v1.ExecSandboxStderr + (*ExecSandboxExit)(nil), // 66: openshell.v1.ExecSandboxExit + (*ExecSandboxEvent)(nil), // 67: openshell.v1.ExecSandboxEvent + (*TcpForwardInit)(nil), // 68: openshell.v1.TcpForwardInit + (*TcpForwardFrame)(nil), // 69: openshell.v1.TcpForwardFrame + (*ExecSandboxInput)(nil), // 70: openshell.v1.ExecSandboxInput + (*ExecSandboxWindowResize)(nil), // 71: openshell.v1.ExecSandboxWindowResize + (*SshSession)(nil), // 72: openshell.v1.SshSession + (*WatchSandboxRequest)(nil), // 73: openshell.v1.WatchSandboxRequest + (*SandboxStreamEvent)(nil), // 74: openshell.v1.SandboxStreamEvent + (*SandboxLogLine)(nil), // 75: openshell.v1.SandboxLogLine + (*SandboxStreamWarning)(nil), // 76: openshell.v1.SandboxStreamWarning + (*CreateProviderRequest)(nil), // 77: openshell.v1.CreateProviderRequest + (*GetProviderRequest)(nil), // 78: openshell.v1.GetProviderRequest + (*ListProvidersRequest)(nil), // 79: openshell.v1.ListProvidersRequest + (*UpdateProviderRequest)(nil), // 80: openshell.v1.UpdateProviderRequest + (*DeleteProviderRequest)(nil), // 81: openshell.v1.DeleteProviderRequest + (*ProviderResponse)(nil), // 82: openshell.v1.ProviderResponse + (*ListProvidersResponse)(nil), // 83: openshell.v1.ListProvidersResponse + (*ListProviderProfilesRequest)(nil), // 84: openshell.v1.ListProviderProfilesRequest + (*GetProviderProfileRequest)(nil), // 85: openshell.v1.GetProviderProfileRequest + (*ProviderProfileImportItem)(nil), // 86: openshell.v1.ProviderProfileImportItem + (*ProviderProfileDiagnostic)(nil), // 87: openshell.v1.ProviderProfileDiagnostic + (*ProviderCredentialTokenGrantAudienceOverride)(nil), // 88: openshell.v1.ProviderCredentialTokenGrantAudienceOverride + (*ProviderCredentialTokenGrantSubjectToken)(nil), // 89: openshell.v1.ProviderCredentialTokenGrantSubjectToken + (*ProviderCredentialTokenGrant)(nil), // 90: openshell.v1.ProviderCredentialTokenGrant + (*ProviderProfileCredential)(nil), // 91: openshell.v1.ProviderProfileCredential + (*ProviderCredentialRefreshMaterial)(nil), // 92: openshell.v1.ProviderCredentialRefreshMaterial + (*ProviderCredentialRefreshOutput)(nil), // 93: openshell.v1.ProviderCredentialRefreshOutput + (*ProviderCredentialRefresh)(nil), // 94: openshell.v1.ProviderCredentialRefresh + (*ProviderCredentialRefreshStatus)(nil), // 95: openshell.v1.ProviderCredentialRefreshStatus + (*ProviderProfileDiscovery)(nil), // 96: openshell.v1.ProviderProfileDiscovery + (*StoredProviderCredentialRefreshState)(nil), // 97: openshell.v1.StoredProviderCredentialRefreshState + (*DelegatedIdentityCredential)(nil), // 98: openshell.v1.DelegatedIdentityCredential + (*DelegatedIdentityCredentialSummary)(nil), // 99: openshell.v1.DelegatedIdentityCredentialSummary + (*ListDelegatedIdentityCredentialsRequest)(nil), // 100: openshell.v1.ListDelegatedIdentityCredentialsRequest + (*ListDelegatedIdentityCredentialsResponse)(nil), // 101: openshell.v1.ListDelegatedIdentityCredentialsResponse + (*GetDelegatedIdentityCredentialStatusRequest)(nil), // 102: openshell.v1.GetDelegatedIdentityCredentialStatusRequest + (*GetDelegatedIdentityCredentialStatusResponse)(nil), // 103: openshell.v1.GetDelegatedIdentityCredentialStatusResponse + (*RevokeDelegatedIdentityCredentialRequest)(nil), // 104: openshell.v1.RevokeDelegatedIdentityCredentialRequest + (*RevokeDelegatedIdentityCredentialResponse)(nil), // 105: openshell.v1.RevokeDelegatedIdentityCredentialResponse + (*DeleteDelegatedIdentityCredentialRequest)(nil), // 106: openshell.v1.DeleteDelegatedIdentityCredentialRequest + (*DeleteDelegatedIdentityCredentialResponse)(nil), // 107: openshell.v1.DeleteDelegatedIdentityCredentialResponse + (*GetProviderRefreshStatusRequest)(nil), // 108: openshell.v1.GetProviderRefreshStatusRequest + (*GetProviderRefreshStatusResponse)(nil), // 109: openshell.v1.GetProviderRefreshStatusResponse + (*ConfigureProviderRefreshRequest)(nil), // 110: openshell.v1.ConfigureProviderRefreshRequest + (*ConfigureProviderRefreshResponse)(nil), // 111: openshell.v1.ConfigureProviderRefreshResponse + (*RotateProviderCredentialRequest)(nil), // 112: openshell.v1.RotateProviderCredentialRequest + (*RotateProviderCredentialResponse)(nil), // 113: openshell.v1.RotateProviderCredentialResponse + (*DeleteProviderRefreshRequest)(nil), // 114: openshell.v1.DeleteProviderRefreshRequest + (*DeleteProviderRefreshResponse)(nil), // 115: openshell.v1.DeleteProviderRefreshResponse + (*ProviderProfile)(nil), // 116: openshell.v1.ProviderProfile + (*StoredProviderProfile)(nil), // 117: openshell.v1.StoredProviderProfile + (*ProviderProfileResponse)(nil), // 118: openshell.v1.ProviderProfileResponse + (*ListProviderProfilesResponse)(nil), // 119: openshell.v1.ListProviderProfilesResponse + (*ImportProviderProfilesRequest)(nil), // 120: openshell.v1.ImportProviderProfilesRequest + (*ImportProviderProfilesResponse)(nil), // 121: openshell.v1.ImportProviderProfilesResponse + (*UpdateProviderProfilesRequest)(nil), // 122: openshell.v1.UpdateProviderProfilesRequest + (*UpdateProviderProfilesResponse)(nil), // 123: openshell.v1.UpdateProviderProfilesResponse + (*LintProviderProfilesRequest)(nil), // 124: openshell.v1.LintProviderProfilesRequest + (*LintProviderProfilesResponse)(nil), // 125: openshell.v1.LintProviderProfilesResponse + (*DeleteProviderResponse)(nil), // 126: openshell.v1.DeleteProviderResponse + (*DeleteProviderProfileRequest)(nil), // 127: openshell.v1.DeleteProviderProfileRequest + (*DeleteProviderProfileResponse)(nil), // 128: openshell.v1.DeleteProviderProfileResponse + (*GetSandboxProviderEnvironmentRequest)(nil), // 129: openshell.v1.GetSandboxProviderEnvironmentRequest + (*StaticCredentialEndpointBinding)(nil), // 130: openshell.v1.StaticCredentialEndpointBinding + (*StaticCredentialBinding)(nil), // 131: openshell.v1.StaticCredentialBinding + (*GetSandboxProviderEnvironmentResponse)(nil), // 132: openshell.v1.GetSandboxProviderEnvironmentResponse + (*ExchangeProviderSubjectTokenRequest)(nil), // 133: openshell.v1.ExchangeProviderSubjectTokenRequest + (*ExchangeProviderSubjectTokenResponse)(nil), // 134: openshell.v1.ExchangeProviderSubjectTokenResponse + (*UpdateConfigRequest)(nil), // 135: openshell.v1.UpdateConfigRequest + (*PolicyMergeOperation)(nil), // 136: openshell.v1.PolicyMergeOperation + (*AddNetworkRule)(nil), // 137: openshell.v1.AddNetworkRule + (*RemoveNetworkEndpoint)(nil), // 138: openshell.v1.RemoveNetworkEndpoint + (*RemoveNetworkRule)(nil), // 139: openshell.v1.RemoveNetworkRule + (*AddDenyRules)(nil), // 140: openshell.v1.AddDenyRules + (*AddAllowRules)(nil), // 141: openshell.v1.AddAllowRules + (*RemoveNetworkBinary)(nil), // 142: openshell.v1.RemoveNetworkBinary + (*UpdateConfigResponse)(nil), // 143: openshell.v1.UpdateConfigResponse + (*GetSandboxPolicyStatusRequest)(nil), // 144: openshell.v1.GetSandboxPolicyStatusRequest + (*GetSandboxPolicyStatusResponse)(nil), // 145: openshell.v1.GetSandboxPolicyStatusResponse + (*ListSandboxPoliciesRequest)(nil), // 146: openshell.v1.ListSandboxPoliciesRequest + (*ListSandboxPoliciesResponse)(nil), // 147: openshell.v1.ListSandboxPoliciesResponse + (*ReportPolicyStatusRequest)(nil), // 148: openshell.v1.ReportPolicyStatusRequest + (*ReportPolicyStatusResponse)(nil), // 149: openshell.v1.ReportPolicyStatusResponse + (*SandboxPolicyRevision)(nil), // 150: openshell.v1.SandboxPolicyRevision + (*GetSandboxLogsRequest)(nil), // 151: openshell.v1.GetSandboxLogsRequest + (*PushSandboxLogsRequest)(nil), // 152: openshell.v1.PushSandboxLogsRequest + (*PushSandboxLogsResponse)(nil), // 153: openshell.v1.PushSandboxLogsResponse + (*GetSandboxLogsResponse)(nil), // 154: openshell.v1.GetSandboxLogsResponse + (*SupervisorMessage)(nil), // 155: openshell.v1.SupervisorMessage + (*GatewayMessage)(nil), // 156: openshell.v1.GatewayMessage + (*SupervisorHello)(nil), // 157: openshell.v1.SupervisorHello + (*SessionAccepted)(nil), // 158: openshell.v1.SessionAccepted + (*SessionRejected)(nil), // 159: openshell.v1.SessionRejected + (*SupervisorHeartbeat)(nil), // 160: openshell.v1.SupervisorHeartbeat + (*GatewayHeartbeat)(nil), // 161: openshell.v1.GatewayHeartbeat + (*RelayOpen)(nil), // 162: openshell.v1.RelayOpen + (*SshRelayTarget)(nil), // 163: openshell.v1.SshRelayTarget + (*TcpRelayTarget)(nil), // 164: openshell.v1.TcpRelayTarget + (*RelayInit)(nil), // 165: openshell.v1.RelayInit + (*RelayFrame)(nil), // 166: openshell.v1.RelayFrame + (*RelayOpenResult)(nil), // 167: openshell.v1.RelayOpenResult + (*RelayClose)(nil), // 168: openshell.v1.RelayClose + (*L7RequestSample)(nil), // 169: openshell.v1.L7RequestSample + (*DenialSummary)(nil), // 170: openshell.v1.DenialSummary + (*DenialGroupCount)(nil), // 171: openshell.v1.DenialGroupCount + (*NetworkActivitySummary)(nil), // 172: openshell.v1.NetworkActivitySummary + (*PolicyChunk)(nil), // 173: openshell.v1.PolicyChunk + (*DraftPolicyUpdate)(nil), // 174: openshell.v1.DraftPolicyUpdate + (*SubmitPolicyAnalysisRequest)(nil), // 175: openshell.v1.SubmitPolicyAnalysisRequest + (*SubmitPolicyAnalysisResponse)(nil), // 176: openshell.v1.SubmitPolicyAnalysisResponse + (*GetDraftPolicyRequest)(nil), // 177: openshell.v1.GetDraftPolicyRequest + (*GetDraftPolicyResponse)(nil), // 178: openshell.v1.GetDraftPolicyResponse + (*ApproveDraftChunkRequest)(nil), // 179: openshell.v1.ApproveDraftChunkRequest + (*ApproveDraftChunkResponse)(nil), // 180: openshell.v1.ApproveDraftChunkResponse + (*RejectDraftChunkRequest)(nil), // 181: openshell.v1.RejectDraftChunkRequest + (*RejectDraftChunkResponse)(nil), // 182: openshell.v1.RejectDraftChunkResponse + (*ApproveAllDraftChunksRequest)(nil), // 183: openshell.v1.ApproveAllDraftChunksRequest + (*ApproveAllDraftChunksResponse)(nil), // 184: openshell.v1.ApproveAllDraftChunksResponse + (*EditDraftChunkRequest)(nil), // 185: openshell.v1.EditDraftChunkRequest + (*EditDraftChunkResponse)(nil), // 186: openshell.v1.EditDraftChunkResponse + (*UndoDraftChunkRequest)(nil), // 187: openshell.v1.UndoDraftChunkRequest + (*UndoDraftChunkResponse)(nil), // 188: openshell.v1.UndoDraftChunkResponse + (*ClearDraftChunksRequest)(nil), // 189: openshell.v1.ClearDraftChunksRequest + (*ClearDraftChunksResponse)(nil), // 190: openshell.v1.ClearDraftChunksResponse + (*GetDraftHistoryRequest)(nil), // 191: openshell.v1.GetDraftHistoryRequest + (*DraftHistoryEntry)(nil), // 192: openshell.v1.DraftHistoryEntry + (*GetDraftHistoryResponse)(nil), // 193: openshell.v1.GetDraftHistoryResponse + (*PolicyRevisionPayload)(nil), // 194: openshell.v1.PolicyRevisionPayload + (*DraftChunkPayload)(nil), // 195: openshell.v1.DraftChunkPayload + (*StoredPolicyRevision)(nil), // 196: openshell.v1.StoredPolicyRevision + (*StoredDraftChunk)(nil), // 197: openshell.v1.StoredDraftChunk + (*CreateWorkspaceRequest)(nil), // 198: openshell.v1.CreateWorkspaceRequest + (*CreateWorkspaceResponse)(nil), // 199: openshell.v1.CreateWorkspaceResponse + (*GetWorkspaceRequest)(nil), // 200: openshell.v1.GetWorkspaceRequest + (*GetWorkspaceResponse)(nil), // 201: openshell.v1.GetWorkspaceResponse + (*ListWorkspacesRequest)(nil), // 202: openshell.v1.ListWorkspacesRequest + (*ListWorkspacesResponse)(nil), // 203: openshell.v1.ListWorkspacesResponse + (*DeleteWorkspaceRequest)(nil), // 204: openshell.v1.DeleteWorkspaceRequest + (*DeleteWorkspaceResponse)(nil), // 205: openshell.v1.DeleteWorkspaceResponse + (*WorkspaceMember)(nil), // 206: openshell.v1.WorkspaceMember + (*AddWorkspaceMemberRequest)(nil), // 207: openshell.v1.AddWorkspaceMemberRequest + (*AddWorkspaceMemberResponse)(nil), // 208: openshell.v1.AddWorkspaceMemberResponse + (*RemoveWorkspaceMemberRequest)(nil), // 209: openshell.v1.RemoveWorkspaceMemberRequest + (*RemoveWorkspaceMemberResponse)(nil), // 210: openshell.v1.RemoveWorkspaceMemberResponse + (*ListWorkspaceMembersRequest)(nil), // 211: openshell.v1.ListWorkspaceMembersRequest + (*ListWorkspaceMembersResponse)(nil), // 212: openshell.v1.ListWorkspaceMembersResponse + (*ExtensionServiceCredential)(nil), // 213: openshell.v1.ExtensionServiceCredential + nil, // 214: openshell.v1.SandboxSpec.EnvironmentEntry + nil, // 215: openshell.v1.SandboxTemplate.LabelsEntry + nil, // 216: openshell.v1.SandboxTemplate.AnnotationsEntry + nil, // 217: openshell.v1.SandboxTemplate.EnvironmentEntry + nil, // 218: openshell.v1.PlatformEvent.MetadataEntry + nil, // 219: openshell.v1.CreateSandboxRequest.LabelsEntry + nil, // 220: openshell.v1.CreateSandboxRequest.AnnotationsEntry + nil, // 221: openshell.v1.ExecSandboxRequest.EnvironmentEntry + nil, // 222: openshell.v1.SandboxLogLine.FieldsEntry + nil, // 223: openshell.v1.UpdateProviderRequest.CredentialExpiresAtMsEntry + nil, // 224: openshell.v1.StoredProviderCredentialRefreshState.MaterialEntry + nil, // 225: openshell.v1.StoredProviderCredentialRefreshState.AdditionalOutputKeysEntry + nil, // 226: openshell.v1.ConfigureProviderRefreshRequest.MaterialEntry + nil, // 227: openshell.v1.ProviderProfile.AnnotationsEntry + nil, // 228: openshell.v1.GetSandboxProviderEnvironmentResponse.EnvironmentEntry + nil, // 229: openshell.v1.GetSandboxProviderEnvironmentResponse.CredentialExpiresAtMsEntry + nil, // 230: openshell.v1.GetSandboxProviderEnvironmentResponse.DynamicCredentialsEntry + nil, // 231: openshell.v1.GetSandboxProviderEnvironmentResponse.StaticCredentialBindingsEntry + nil, // 232: openshell.v1.UpdateConfigRequest.AnnotationsEntry + nil, // 233: openshell.v1.UpdateConfigResponse.AnnotationsEntry + nil, // 234: openshell.v1.SandboxPolicyRevision.ProvenanceEntry + nil, // 235: openshell.v1.PolicyRevisionPayload.ProvenanceEntry + nil, // 236: openshell.v1.StoredPolicyRevision.ProvenanceEntry + nil, // 237: openshell.v1.CreateWorkspaceRequest.LabelsEntry + (*datamodelv1.ObjectMeta)(nil), // 238: openshell.datamodel.v1.ObjectMeta + (*sandboxv1.SandboxPolicy)(nil), // 239: openshell.sandbox.v1.SandboxPolicy + (*structpb.Struct)(nil), // 240: google.protobuf.Struct + (*datamodelv1.Provider)(nil), // 241: openshell.datamodel.v1.Provider + (*sandboxv1.NetworkEndpoint)(nil), // 242: openshell.sandbox.v1.NetworkEndpoint + (*sandboxv1.NetworkBinary)(nil), // 243: openshell.sandbox.v1.NetworkBinary + (*sandboxv1.SettingValue)(nil), // 244: openshell.sandbox.v1.SettingValue + (*sandboxv1.NetworkPolicyRule)(nil), // 245: openshell.sandbox.v1.NetworkPolicyRule + (*sandboxv1.L7DenyRule)(nil), // 246: openshell.sandbox.v1.L7DenyRule + (*sandboxv1.L7Rule)(nil), // 247: openshell.sandbox.v1.L7Rule + (*datamodelv1.Workspace)(nil), // 248: openshell.datamodel.v1.Workspace + (*sandboxv1.GetSandboxConfigRequest)(nil), // 249: openshell.sandbox.v1.GetSandboxConfigRequest + (*sandboxv1.GetGatewayConfigRequest)(nil), // 250: openshell.sandbox.v1.GetGatewayConfigRequest + (*sandboxv1.GetSandboxConfigResponse)(nil), // 251: openshell.sandbox.v1.GetSandboxConfigResponse + (*sandboxv1.GetGatewayConfigResponse)(nil), // 252: openshell.sandbox.v1.GetGatewayConfigResponse } var file_openshell_proto_depIdxs = []int32{ - 194, // 0: openshell.v1.RefreshSandboxTokenResponse.extension_credentials:type_name -> openshell.v1.ExtensionServiceCredential + 213, // 0: openshell.v1.RefreshSandboxTokenResponse.extension_credentials:type_name -> openshell.v1.ExtensionServiceCredential 5, // 1: openshell.v1.HealthResponse.status:type_name -> openshell.v1.ServiceStatus 5, // 2: openshell.v1.GetGatewayInfoResponse.status:type_name -> openshell.v1.ServiceStatus 17, // 3: openshell.v1.GetGatewayInfoResponse.compute_drivers:type_name -> openshell.v1.ComputeDriverInfo 18, // 4: openshell.v1.ComputeDriverInfo.capabilities:type_name -> openshell.v1.ComputeDriverCapabilities - 219, // 5: openshell.v1.Sandbox.metadata:type_name -> openshell.datamodel.v1.ObjectMeta - 20, // 6: openshell.v1.Sandbox.spec:type_name -> openshell.v1.SandboxSpec - 24, // 7: openshell.v1.Sandbox.status:type_name -> openshell.v1.SandboxStatus - 195, // 8: openshell.v1.SandboxSpec.environment:type_name -> openshell.v1.SandboxSpec.EnvironmentEntry - 23, // 9: openshell.v1.SandboxSpec.template:type_name -> openshell.v1.SandboxTemplate - 220, // 10: openshell.v1.SandboxSpec.policy:type_name -> openshell.sandbox.v1.SandboxPolicy - 21, // 11: openshell.v1.SandboxSpec.resource_requirements:type_name -> openshell.v1.ResourceRequirements - 22, // 12: openshell.v1.ResourceRequirements.gpu:type_name -> openshell.v1.GpuResourceRequirements - 196, // 13: openshell.v1.SandboxTemplate.labels:type_name -> openshell.v1.SandboxTemplate.LabelsEntry - 197, // 14: openshell.v1.SandboxTemplate.annotations:type_name -> openshell.v1.SandboxTemplate.AnnotationsEntry - 198, // 15: openshell.v1.SandboxTemplate.environment:type_name -> openshell.v1.SandboxTemplate.EnvironmentEntry - 221, // 16: openshell.v1.SandboxTemplate.resources:type_name -> google.protobuf.Struct - 221, // 17: openshell.v1.SandboxTemplate.driver_config:type_name -> google.protobuf.Struct - 25, // 18: openshell.v1.SandboxStatus.conditions:type_name -> openshell.v1.SandboxCondition - 0, // 19: openshell.v1.SandboxStatus.phase:type_name -> openshell.v1.SandboxPhase - 199, // 20: openshell.v1.PlatformEvent.metadata:type_name -> openshell.v1.PlatformEvent.MetadataEntry - 20, // 21: openshell.v1.CreateSandboxRequest.spec:type_name -> openshell.v1.SandboxSpec - 200, // 22: openshell.v1.CreateSandboxRequest.labels:type_name -> openshell.v1.CreateSandboxRequest.LabelsEntry - 201, // 23: openshell.v1.CreateSandboxRequest.annotations:type_name -> openshell.v1.CreateSandboxRequest.AnnotationsEntry - 19, // 24: openshell.v1.SandboxResponse.sandbox:type_name -> openshell.v1.Sandbox - 19, // 25: openshell.v1.ListSandboxesResponse.sandboxes:type_name -> openshell.v1.Sandbox - 222, // 26: openshell.v1.ListSandboxProvidersResponse.providers:type_name -> openshell.datamodel.v1.Provider - 19, // 27: openshell.v1.AttachSandboxProviderResponse.sandbox:type_name -> openshell.v1.Sandbox - 19, // 28: openshell.v1.DetachSandboxProviderResponse.sandbox:type_name -> openshell.v1.Sandbox - 51, // 29: openshell.v1.ListServicesResponse.services:type_name -> openshell.v1.ServiceEndpointResponse - 219, // 30: openshell.v1.ServiceEndpoint.metadata:type_name -> openshell.datamodel.v1.ObjectMeta - 50, // 31: openshell.v1.ServiceEndpointResponse.endpoint:type_name -> openshell.v1.ServiceEndpoint - 202, // 32: openshell.v1.ExecSandboxRequest.environment:type_name -> openshell.v1.ExecSandboxRequest.EnvironmentEntry - 55, // 33: openshell.v1.ExecSandboxEvent.stdout:type_name -> openshell.v1.ExecSandboxStdout - 56, // 34: openshell.v1.ExecSandboxEvent.stderr:type_name -> openshell.v1.ExecSandboxStderr - 57, // 35: openshell.v1.ExecSandboxEvent.exit:type_name -> openshell.v1.ExecSandboxExit - 144, // 36: openshell.v1.TcpForwardInit.ssh:type_name -> openshell.v1.SshRelayTarget - 145, // 37: openshell.v1.TcpForwardInit.tcp:type_name -> openshell.v1.TcpRelayTarget - 59, // 38: openshell.v1.TcpForwardFrame.init:type_name -> openshell.v1.TcpForwardInit - 54, // 39: openshell.v1.ExecSandboxInput.start:type_name -> openshell.v1.ExecSandboxRequest - 62, // 40: openshell.v1.ExecSandboxInput.resize:type_name -> openshell.v1.ExecSandboxWindowResize - 219, // 41: openshell.v1.SshSession.metadata:type_name -> openshell.datamodel.v1.ObjectMeta - 19, // 42: openshell.v1.SandboxStreamEvent.sandbox:type_name -> openshell.v1.Sandbox - 66, // 43: openshell.v1.SandboxStreamEvent.log:type_name -> openshell.v1.SandboxLogLine - 26, // 44: openshell.v1.SandboxStreamEvent.event:type_name -> openshell.v1.PlatformEvent - 67, // 45: openshell.v1.SandboxStreamEvent.warning:type_name -> openshell.v1.SandboxStreamWarning - 155, // 46: openshell.v1.SandboxStreamEvent.draft_policy_update:type_name -> openshell.v1.DraftPolicyUpdate - 203, // 47: openshell.v1.SandboxLogLine.fields:type_name -> openshell.v1.SandboxLogLine.FieldsEntry - 222, // 48: openshell.v1.CreateProviderRequest.provider:type_name -> openshell.datamodel.v1.Provider - 222, // 49: openshell.v1.UpdateProviderRequest.provider:type_name -> openshell.datamodel.v1.Provider - 204, // 50: openshell.v1.UpdateProviderRequest.credential_expires_at_ms:type_name -> openshell.v1.UpdateProviderRequest.CredentialExpiresAtMsEntry - 222, // 51: openshell.v1.ProviderResponse.provider:type_name -> openshell.datamodel.v1.Provider - 222, // 52: openshell.v1.ListProvidersResponse.providers:type_name -> openshell.datamodel.v1.Provider - 97, // 53: openshell.v1.ProviderProfileImportItem.profile:type_name -> openshell.v1.ProviderProfile - 79, // 54: openshell.v1.ProviderCredentialTokenGrant.audience_overrides:type_name -> openshell.v1.ProviderCredentialTokenGrantAudienceOverride - 1, // 55: openshell.v1.ProviderCredentialTokenGrant.grant_type:type_name -> openshell.v1.ProviderCredentialTokenGrantType - 80, // 56: openshell.v1.ProviderCredentialTokenGrant.subject_token:type_name -> openshell.v1.ProviderCredentialTokenGrantSubjectToken - 85, // 57: openshell.v1.ProviderProfileCredential.refresh:type_name -> openshell.v1.ProviderCredentialRefresh - 81, // 58: openshell.v1.ProviderProfileCredential.token_grant:type_name -> openshell.v1.ProviderCredentialTokenGrant - 2, // 59: openshell.v1.ProviderCredentialRefresh.strategy:type_name -> openshell.v1.ProviderCredentialRefreshStrategy - 83, // 60: openshell.v1.ProviderCredentialRefresh.material:type_name -> openshell.v1.ProviderCredentialRefreshMaterial - 84, // 61: openshell.v1.ProviderCredentialRefresh.additional_outputs:type_name -> openshell.v1.ProviderCredentialRefreshOutput - 2, // 62: openshell.v1.ProviderCredentialRefreshStatus.strategy:type_name -> openshell.v1.ProviderCredentialRefreshStrategy - 219, // 63: openshell.v1.StoredProviderCredentialRefreshState.metadata:type_name -> openshell.datamodel.v1.ObjectMeta - 2, // 64: openshell.v1.StoredProviderCredentialRefreshState.strategy:type_name -> openshell.v1.ProviderCredentialRefreshStrategy - 205, // 65: openshell.v1.StoredProviderCredentialRefreshState.material:type_name -> openshell.v1.StoredProviderCredentialRefreshState.MaterialEntry - 206, // 66: openshell.v1.StoredProviderCredentialRefreshState.additional_output_keys:type_name -> openshell.v1.StoredProviderCredentialRefreshState.AdditionalOutputKeysEntry - 86, // 67: openshell.v1.GetProviderRefreshStatusResponse.credentials:type_name -> openshell.v1.ProviderCredentialRefreshStatus - 2, // 68: openshell.v1.ConfigureProviderRefreshRequest.strategy:type_name -> openshell.v1.ProviderCredentialRefreshStrategy - 207, // 69: openshell.v1.ConfigureProviderRefreshRequest.material:type_name -> openshell.v1.ConfigureProviderRefreshRequest.MaterialEntry - 86, // 70: openshell.v1.ConfigureProviderRefreshResponse.status:type_name -> openshell.v1.ProviderCredentialRefreshStatus - 86, // 71: openshell.v1.RotateProviderCredentialResponse.status:type_name -> openshell.v1.ProviderCredentialRefreshStatus - 3, // 72: openshell.v1.ProviderProfile.category:type_name -> openshell.v1.ProviderProfileCategory - 82, // 73: openshell.v1.ProviderProfile.credentials:type_name -> openshell.v1.ProviderProfileCredential - 223, // 74: openshell.v1.ProviderProfile.endpoints:type_name -> openshell.sandbox.v1.NetworkEndpoint - 224, // 75: openshell.v1.ProviderProfile.binaries:type_name -> openshell.sandbox.v1.NetworkBinary - 87, // 76: openshell.v1.ProviderProfile.discovery:type_name -> openshell.v1.ProviderProfileDiscovery - 208, // 77: openshell.v1.ProviderProfile.annotations:type_name -> openshell.v1.ProviderProfile.AnnotationsEntry - 219, // 78: openshell.v1.StoredProviderProfile.metadata:type_name -> openshell.datamodel.v1.ObjectMeta - 97, // 79: openshell.v1.StoredProviderProfile.profile:type_name -> openshell.v1.ProviderProfile - 97, // 80: openshell.v1.ProviderProfileResponse.profile:type_name -> openshell.v1.ProviderProfile - 97, // 81: openshell.v1.ListProviderProfilesResponse.profiles:type_name -> openshell.v1.ProviderProfile - 77, // 82: openshell.v1.ImportProviderProfilesRequest.profiles:type_name -> openshell.v1.ProviderProfileImportItem - 78, // 83: openshell.v1.ImportProviderProfilesResponse.diagnostics:type_name -> openshell.v1.ProviderProfileDiagnostic - 97, // 84: openshell.v1.ImportProviderProfilesResponse.profiles:type_name -> openshell.v1.ProviderProfile - 77, // 85: openshell.v1.UpdateProviderProfilesRequest.profile:type_name -> openshell.v1.ProviderProfileImportItem - 78, // 86: openshell.v1.UpdateProviderProfilesResponse.diagnostics:type_name -> openshell.v1.ProviderProfileDiagnostic - 97, // 87: openshell.v1.UpdateProviderProfilesResponse.profile:type_name -> openshell.v1.ProviderProfile - 77, // 88: openshell.v1.LintProviderProfilesRequest.profiles:type_name -> openshell.v1.ProviderProfileImportItem - 78, // 89: openshell.v1.LintProviderProfilesResponse.diagnostics:type_name -> openshell.v1.ProviderProfileDiagnostic - 111, // 90: openshell.v1.StaticCredentialBinding.endpoints:type_name -> openshell.v1.StaticCredentialEndpointBinding - 209, // 91: openshell.v1.GetSandboxProviderEnvironmentResponse.environment:type_name -> openshell.v1.GetSandboxProviderEnvironmentResponse.EnvironmentEntry - 210, // 92: openshell.v1.GetSandboxProviderEnvironmentResponse.credential_expires_at_ms:type_name -> openshell.v1.GetSandboxProviderEnvironmentResponse.CredentialExpiresAtMsEntry - 211, // 93: openshell.v1.GetSandboxProviderEnvironmentResponse.dynamic_credentials:type_name -> openshell.v1.GetSandboxProviderEnvironmentResponse.DynamicCredentialsEntry - 212, // 94: openshell.v1.GetSandboxProviderEnvironmentResponse.static_credential_bindings:type_name -> openshell.v1.GetSandboxProviderEnvironmentResponse.StaticCredentialBindingsEntry - 220, // 95: openshell.v1.UpdateConfigRequest.policy:type_name -> openshell.sandbox.v1.SandboxPolicy - 225, // 96: openshell.v1.UpdateConfigRequest.setting_value:type_name -> openshell.sandbox.v1.SettingValue - 117, // 97: openshell.v1.UpdateConfigRequest.merge_operations:type_name -> openshell.v1.PolicyMergeOperation - 213, // 98: openshell.v1.UpdateConfigRequest.annotations:type_name -> openshell.v1.UpdateConfigRequest.AnnotationsEntry - 118, // 99: openshell.v1.PolicyMergeOperation.add_rule:type_name -> openshell.v1.AddNetworkRule - 119, // 100: openshell.v1.PolicyMergeOperation.remove_endpoint:type_name -> openshell.v1.RemoveNetworkEndpoint - 120, // 101: openshell.v1.PolicyMergeOperation.remove_rule:type_name -> openshell.v1.RemoveNetworkRule - 121, // 102: openshell.v1.PolicyMergeOperation.add_deny_rules:type_name -> openshell.v1.AddDenyRules - 122, // 103: openshell.v1.PolicyMergeOperation.add_allow_rules:type_name -> openshell.v1.AddAllowRules - 123, // 104: openshell.v1.PolicyMergeOperation.remove_binary:type_name -> openshell.v1.RemoveNetworkBinary - 226, // 105: openshell.v1.AddNetworkRule.rule:type_name -> openshell.sandbox.v1.NetworkPolicyRule - 227, // 106: openshell.v1.AddDenyRules.deny_rules:type_name -> openshell.sandbox.v1.L7DenyRule - 228, // 107: openshell.v1.AddAllowRules.rules:type_name -> openshell.sandbox.v1.L7Rule - 214, // 108: openshell.v1.UpdateConfigResponse.annotations:type_name -> openshell.v1.UpdateConfigResponse.AnnotationsEntry - 131, // 109: openshell.v1.GetSandboxPolicyStatusResponse.revision:type_name -> openshell.v1.SandboxPolicyRevision - 131, // 110: openshell.v1.ListSandboxPoliciesResponse.revisions:type_name -> openshell.v1.SandboxPolicyRevision - 4, // 111: openshell.v1.ReportPolicyStatusRequest.status:type_name -> openshell.v1.PolicyStatus - 4, // 112: openshell.v1.SandboxPolicyRevision.status:type_name -> openshell.v1.PolicyStatus - 220, // 113: openshell.v1.SandboxPolicyRevision.policy:type_name -> openshell.sandbox.v1.SandboxPolicy - 215, // 114: openshell.v1.SandboxPolicyRevision.provenance:type_name -> openshell.v1.SandboxPolicyRevision.ProvenanceEntry - 66, // 115: openshell.v1.PushSandboxLogsRequest.logs:type_name -> openshell.v1.SandboxLogLine - 66, // 116: openshell.v1.GetSandboxLogsResponse.logs:type_name -> openshell.v1.SandboxLogLine - 138, // 117: openshell.v1.SupervisorMessage.hello:type_name -> openshell.v1.SupervisorHello - 141, // 118: openshell.v1.SupervisorMessage.heartbeat:type_name -> openshell.v1.SupervisorHeartbeat - 148, // 119: openshell.v1.SupervisorMessage.relay_open_result:type_name -> openshell.v1.RelayOpenResult - 149, // 120: openshell.v1.SupervisorMessage.relay_close:type_name -> openshell.v1.RelayClose - 139, // 121: openshell.v1.GatewayMessage.session_accepted:type_name -> openshell.v1.SessionAccepted - 140, // 122: openshell.v1.GatewayMessage.session_rejected:type_name -> openshell.v1.SessionRejected - 142, // 123: openshell.v1.GatewayMessage.heartbeat:type_name -> openshell.v1.GatewayHeartbeat - 143, // 124: openshell.v1.GatewayMessage.relay_open:type_name -> openshell.v1.RelayOpen - 149, // 125: openshell.v1.GatewayMessage.relay_close:type_name -> openshell.v1.RelayClose - 144, // 126: openshell.v1.RelayOpen.ssh:type_name -> openshell.v1.SshRelayTarget - 145, // 127: openshell.v1.RelayOpen.tcp:type_name -> openshell.v1.TcpRelayTarget - 146, // 128: openshell.v1.RelayFrame.init:type_name -> openshell.v1.RelayInit - 150, // 129: openshell.v1.DenialSummary.l7_request_samples:type_name -> openshell.v1.L7RequestSample - 152, // 130: openshell.v1.NetworkActivitySummary.denials_by_group:type_name -> openshell.v1.DenialGroupCount - 226, // 131: openshell.v1.PolicyChunk.proposed_rule:type_name -> openshell.sandbox.v1.NetworkPolicyRule - 151, // 132: openshell.v1.SubmitPolicyAnalysisRequest.summaries:type_name -> openshell.v1.DenialSummary - 154, // 133: openshell.v1.SubmitPolicyAnalysisRequest.proposed_chunks:type_name -> openshell.v1.PolicyChunk - 153, // 134: openshell.v1.SubmitPolicyAnalysisRequest.network_activity_summaries:type_name -> openshell.v1.NetworkActivitySummary - 154, // 135: openshell.v1.GetDraftPolicyResponse.chunks:type_name -> openshell.v1.PolicyChunk - 226, // 136: openshell.v1.EditDraftChunkRequest.proposed_rule:type_name -> openshell.sandbox.v1.NetworkPolicyRule - 173, // 137: openshell.v1.GetDraftHistoryResponse.entries:type_name -> openshell.v1.DraftHistoryEntry - 220, // 138: openshell.v1.PolicyRevisionPayload.policy:type_name -> openshell.sandbox.v1.SandboxPolicy - 216, // 139: openshell.v1.PolicyRevisionPayload.provenance:type_name -> openshell.v1.PolicyRevisionPayload.ProvenanceEntry - 226, // 140: openshell.v1.DraftChunkPayload.proposed_rule:type_name -> openshell.sandbox.v1.NetworkPolicyRule - 217, // 141: openshell.v1.StoredPolicyRevision.provenance:type_name -> openshell.v1.StoredPolicyRevision.ProvenanceEntry - 218, // 142: openshell.v1.CreateWorkspaceRequest.labels:type_name -> openshell.v1.CreateWorkspaceRequest.LabelsEntry - 229, // 143: openshell.v1.CreateWorkspaceResponse.workspace:type_name -> openshell.datamodel.v1.Workspace - 229, // 144: openshell.v1.GetWorkspaceResponse.workspace:type_name -> openshell.datamodel.v1.Workspace - 229, // 145: openshell.v1.ListWorkspacesResponse.workspaces:type_name -> openshell.datamodel.v1.Workspace - 219, // 146: openshell.v1.WorkspaceMember.metadata:type_name -> openshell.datamodel.v1.ObjectMeta - 6, // 147: openshell.v1.WorkspaceMember.role:type_name -> openshell.v1.WorkspaceRole - 6, // 148: openshell.v1.AddWorkspaceMemberRequest.role:type_name -> openshell.v1.WorkspaceRole - 187, // 149: openshell.v1.AddWorkspaceMemberResponse.member:type_name -> openshell.v1.WorkspaceMember - 187, // 150: openshell.v1.ListWorkspaceMembersResponse.members:type_name -> openshell.v1.WorkspaceMember - 82, // 151: openshell.v1.GetSandboxProviderEnvironmentResponse.DynamicCredentialsEntry.value:type_name -> openshell.v1.ProviderProfileCredential - 112, // 152: openshell.v1.GetSandboxProviderEnvironmentResponse.StaticCredentialBindingsEntry.value:type_name -> openshell.v1.StaticCredentialBinding - 11, // 153: openshell.v1.OpenShell.Health:input_type -> openshell.v1.HealthRequest - 13, // 154: openshell.v1.OpenShell.GetCurrentUser:input_type -> openshell.v1.GetCurrentUserRequest - 15, // 155: openshell.v1.OpenShell.GetGatewayInfo:input_type -> openshell.v1.GetGatewayInfoRequest - 27, // 156: openshell.v1.OpenShell.CreateSandbox:input_type -> openshell.v1.CreateSandboxRequest - 28, // 157: openshell.v1.OpenShell.GetSandbox:input_type -> openshell.v1.GetSandboxRequest - 29, // 158: openshell.v1.OpenShell.ListSandboxes:input_type -> openshell.v1.ListSandboxesRequest - 30, // 159: openshell.v1.OpenShell.ListSandboxProviders:input_type -> openshell.v1.ListSandboxProvidersRequest - 31, // 160: openshell.v1.OpenShell.AttachSandboxProvider:input_type -> openshell.v1.AttachSandboxProviderRequest - 32, // 161: openshell.v1.OpenShell.DetachSandboxProvider:input_type -> openshell.v1.DetachSandboxProviderRequest - 33, // 162: openshell.v1.OpenShell.DeleteSandbox:input_type -> openshell.v1.DeleteSandboxRequest - 34, // 163: openshell.v1.OpenShell.StopSandbox:input_type -> openshell.v1.StopSandboxRequest - 35, // 164: openshell.v1.OpenShell.StartSandbox:input_type -> openshell.v1.StartSandboxRequest - 42, // 165: openshell.v1.OpenShell.CreateSshSession:input_type -> openshell.v1.CreateSshSessionRequest - 44, // 166: openshell.v1.OpenShell.ExposeService:input_type -> openshell.v1.ExposeServiceRequest - 45, // 167: openshell.v1.OpenShell.GetService:input_type -> openshell.v1.GetServiceRequest - 46, // 168: openshell.v1.OpenShell.ListServices:input_type -> openshell.v1.ListServicesRequest - 48, // 169: openshell.v1.OpenShell.DeleteService:input_type -> openshell.v1.DeleteServiceRequest - 52, // 170: openshell.v1.OpenShell.RevokeSshSession:input_type -> openshell.v1.RevokeSshSessionRequest - 54, // 171: openshell.v1.OpenShell.ExecSandbox:input_type -> openshell.v1.ExecSandboxRequest - 60, // 172: openshell.v1.OpenShell.ForwardTcp:input_type -> openshell.v1.TcpForwardFrame - 61, // 173: openshell.v1.OpenShell.ExecSandboxInteractive:input_type -> openshell.v1.ExecSandboxInput - 68, // 174: openshell.v1.OpenShell.CreateProvider:input_type -> openshell.v1.CreateProviderRequest - 69, // 175: openshell.v1.OpenShell.GetProvider:input_type -> openshell.v1.GetProviderRequest - 70, // 176: openshell.v1.OpenShell.ListProviders:input_type -> openshell.v1.ListProvidersRequest - 75, // 177: openshell.v1.OpenShell.ListProviderProfiles:input_type -> openshell.v1.ListProviderProfilesRequest - 76, // 178: openshell.v1.OpenShell.GetProviderProfile:input_type -> openshell.v1.GetProviderProfileRequest - 101, // 179: openshell.v1.OpenShell.ImportProviderProfiles:input_type -> openshell.v1.ImportProviderProfilesRequest - 103, // 180: openshell.v1.OpenShell.UpdateProviderProfiles:input_type -> openshell.v1.UpdateProviderProfilesRequest - 105, // 181: openshell.v1.OpenShell.LintProviderProfiles:input_type -> openshell.v1.LintProviderProfilesRequest - 71, // 182: openshell.v1.OpenShell.UpdateProvider:input_type -> openshell.v1.UpdateProviderRequest - 89, // 183: openshell.v1.OpenShell.GetProviderRefreshStatus:input_type -> openshell.v1.GetProviderRefreshStatusRequest - 91, // 184: openshell.v1.OpenShell.ConfigureProviderRefresh:input_type -> openshell.v1.ConfigureProviderRefreshRequest - 93, // 185: openshell.v1.OpenShell.RotateProviderCredential:input_type -> openshell.v1.RotateProviderCredentialRequest - 95, // 186: openshell.v1.OpenShell.DeleteProviderRefresh:input_type -> openshell.v1.DeleteProviderRefreshRequest - 72, // 187: openshell.v1.OpenShell.DeleteProvider:input_type -> openshell.v1.DeleteProviderRequest - 108, // 188: openshell.v1.OpenShell.DeleteProviderProfile:input_type -> openshell.v1.DeleteProviderProfileRequest - 230, // 189: openshell.v1.OpenShell.GetSandboxConfig:input_type -> openshell.sandbox.v1.GetSandboxConfigRequest - 231, // 190: openshell.v1.OpenShell.GetGatewayConfig:input_type -> openshell.sandbox.v1.GetGatewayConfigRequest - 116, // 191: openshell.v1.OpenShell.UpdateConfig:input_type -> openshell.v1.UpdateConfigRequest - 125, // 192: openshell.v1.OpenShell.GetSandboxPolicyStatus:input_type -> openshell.v1.GetSandboxPolicyStatusRequest - 127, // 193: openshell.v1.OpenShell.ListSandboxPolicies:input_type -> openshell.v1.ListSandboxPoliciesRequest - 129, // 194: openshell.v1.OpenShell.ReportPolicyStatus:input_type -> openshell.v1.ReportPolicyStatusRequest - 110, // 195: openshell.v1.OpenShell.GetSandboxProviderEnvironment:input_type -> openshell.v1.GetSandboxProviderEnvironmentRequest - 114, // 196: openshell.v1.OpenShell.ExchangeProviderSubjectToken:input_type -> openshell.v1.ExchangeProviderSubjectTokenRequest - 132, // 197: openshell.v1.OpenShell.GetSandboxLogs:input_type -> openshell.v1.GetSandboxLogsRequest - 133, // 198: openshell.v1.OpenShell.PushSandboxLogs:input_type -> openshell.v1.PushSandboxLogsRequest - 136, // 199: openshell.v1.OpenShell.ConnectSupervisor:input_type -> openshell.v1.SupervisorMessage - 147, // 200: openshell.v1.OpenShell.RelayStream:input_type -> openshell.v1.RelayFrame - 64, // 201: openshell.v1.OpenShell.WatchSandbox:input_type -> openshell.v1.WatchSandboxRequest - 156, // 202: openshell.v1.OpenShell.SubmitPolicyAnalysis:input_type -> openshell.v1.SubmitPolicyAnalysisRequest - 158, // 203: openshell.v1.OpenShell.GetDraftPolicy:input_type -> openshell.v1.GetDraftPolicyRequest - 160, // 204: openshell.v1.OpenShell.ApproveDraftChunk:input_type -> openshell.v1.ApproveDraftChunkRequest - 162, // 205: openshell.v1.OpenShell.RejectDraftChunk:input_type -> openshell.v1.RejectDraftChunkRequest - 164, // 206: openshell.v1.OpenShell.ApproveAllDraftChunks:input_type -> openshell.v1.ApproveAllDraftChunksRequest - 166, // 207: openshell.v1.OpenShell.EditDraftChunk:input_type -> openshell.v1.EditDraftChunkRequest - 168, // 208: openshell.v1.OpenShell.UndoDraftChunk:input_type -> openshell.v1.UndoDraftChunkRequest - 170, // 209: openshell.v1.OpenShell.ClearDraftChunks:input_type -> openshell.v1.ClearDraftChunksRequest - 172, // 210: openshell.v1.OpenShell.GetDraftHistory:input_type -> openshell.v1.GetDraftHistoryRequest - 7, // 211: openshell.v1.OpenShell.IssueSandboxToken:input_type -> openshell.v1.IssueSandboxTokenRequest - 9, // 212: openshell.v1.OpenShell.RefreshSandboxToken:input_type -> openshell.v1.RefreshSandboxTokenRequest - 179, // 213: openshell.v1.OpenShell.CreateWorkspace:input_type -> openshell.v1.CreateWorkspaceRequest - 181, // 214: openshell.v1.OpenShell.GetWorkspace:input_type -> openshell.v1.GetWorkspaceRequest - 183, // 215: openshell.v1.OpenShell.ListWorkspaces:input_type -> openshell.v1.ListWorkspacesRequest - 185, // 216: openshell.v1.OpenShell.DeleteWorkspace:input_type -> openshell.v1.DeleteWorkspaceRequest - 188, // 217: openshell.v1.OpenShell.AddWorkspaceMember:input_type -> openshell.v1.AddWorkspaceMemberRequest - 190, // 218: openshell.v1.OpenShell.RemoveWorkspaceMember:input_type -> openshell.v1.RemoveWorkspaceMemberRequest - 192, // 219: openshell.v1.OpenShell.ListWorkspaceMembers:input_type -> openshell.v1.ListWorkspaceMembersRequest - 12, // 220: openshell.v1.OpenShell.Health:output_type -> openshell.v1.HealthResponse - 14, // 221: openshell.v1.OpenShell.GetCurrentUser:output_type -> openshell.v1.GetCurrentUserResponse - 16, // 222: openshell.v1.OpenShell.GetGatewayInfo:output_type -> openshell.v1.GetGatewayInfoResponse - 36, // 223: openshell.v1.OpenShell.CreateSandbox:output_type -> openshell.v1.SandboxResponse - 36, // 224: openshell.v1.OpenShell.GetSandbox:output_type -> openshell.v1.SandboxResponse - 37, // 225: openshell.v1.OpenShell.ListSandboxes:output_type -> openshell.v1.ListSandboxesResponse - 38, // 226: openshell.v1.OpenShell.ListSandboxProviders:output_type -> openshell.v1.ListSandboxProvidersResponse - 39, // 227: openshell.v1.OpenShell.AttachSandboxProvider:output_type -> openshell.v1.AttachSandboxProviderResponse - 40, // 228: openshell.v1.OpenShell.DetachSandboxProvider:output_type -> openshell.v1.DetachSandboxProviderResponse - 41, // 229: openshell.v1.OpenShell.DeleteSandbox:output_type -> openshell.v1.DeleteSandboxResponse - 36, // 230: openshell.v1.OpenShell.StopSandbox:output_type -> openshell.v1.SandboxResponse - 36, // 231: openshell.v1.OpenShell.StartSandbox:output_type -> openshell.v1.SandboxResponse - 43, // 232: openshell.v1.OpenShell.CreateSshSession:output_type -> openshell.v1.CreateSshSessionResponse - 51, // 233: openshell.v1.OpenShell.ExposeService:output_type -> openshell.v1.ServiceEndpointResponse - 51, // 234: openshell.v1.OpenShell.GetService:output_type -> openshell.v1.ServiceEndpointResponse - 47, // 235: openshell.v1.OpenShell.ListServices:output_type -> openshell.v1.ListServicesResponse - 49, // 236: openshell.v1.OpenShell.DeleteService:output_type -> openshell.v1.DeleteServiceResponse - 53, // 237: openshell.v1.OpenShell.RevokeSshSession:output_type -> openshell.v1.RevokeSshSessionResponse - 58, // 238: openshell.v1.OpenShell.ExecSandbox:output_type -> openshell.v1.ExecSandboxEvent - 60, // 239: openshell.v1.OpenShell.ForwardTcp:output_type -> openshell.v1.TcpForwardFrame - 58, // 240: openshell.v1.OpenShell.ExecSandboxInteractive:output_type -> openshell.v1.ExecSandboxEvent - 73, // 241: openshell.v1.OpenShell.CreateProvider:output_type -> openshell.v1.ProviderResponse - 73, // 242: openshell.v1.OpenShell.GetProvider:output_type -> openshell.v1.ProviderResponse - 74, // 243: openshell.v1.OpenShell.ListProviders:output_type -> openshell.v1.ListProvidersResponse - 100, // 244: openshell.v1.OpenShell.ListProviderProfiles:output_type -> openshell.v1.ListProviderProfilesResponse - 99, // 245: openshell.v1.OpenShell.GetProviderProfile:output_type -> openshell.v1.ProviderProfileResponse - 102, // 246: openshell.v1.OpenShell.ImportProviderProfiles:output_type -> openshell.v1.ImportProviderProfilesResponse - 104, // 247: openshell.v1.OpenShell.UpdateProviderProfiles:output_type -> openshell.v1.UpdateProviderProfilesResponse - 106, // 248: openshell.v1.OpenShell.LintProviderProfiles:output_type -> openshell.v1.LintProviderProfilesResponse - 73, // 249: openshell.v1.OpenShell.UpdateProvider:output_type -> openshell.v1.ProviderResponse - 90, // 250: openshell.v1.OpenShell.GetProviderRefreshStatus:output_type -> openshell.v1.GetProviderRefreshStatusResponse - 92, // 251: openshell.v1.OpenShell.ConfigureProviderRefresh:output_type -> openshell.v1.ConfigureProviderRefreshResponse - 94, // 252: openshell.v1.OpenShell.RotateProviderCredential:output_type -> openshell.v1.RotateProviderCredentialResponse - 96, // 253: openshell.v1.OpenShell.DeleteProviderRefresh:output_type -> openshell.v1.DeleteProviderRefreshResponse - 107, // 254: openshell.v1.OpenShell.DeleteProvider:output_type -> openshell.v1.DeleteProviderResponse - 109, // 255: openshell.v1.OpenShell.DeleteProviderProfile:output_type -> openshell.v1.DeleteProviderProfileResponse - 232, // 256: openshell.v1.OpenShell.GetSandboxConfig:output_type -> openshell.sandbox.v1.GetSandboxConfigResponse - 233, // 257: openshell.v1.OpenShell.GetGatewayConfig:output_type -> openshell.sandbox.v1.GetGatewayConfigResponse - 124, // 258: openshell.v1.OpenShell.UpdateConfig:output_type -> openshell.v1.UpdateConfigResponse - 126, // 259: openshell.v1.OpenShell.GetSandboxPolicyStatus:output_type -> openshell.v1.GetSandboxPolicyStatusResponse - 128, // 260: openshell.v1.OpenShell.ListSandboxPolicies:output_type -> openshell.v1.ListSandboxPoliciesResponse - 130, // 261: openshell.v1.OpenShell.ReportPolicyStatus:output_type -> openshell.v1.ReportPolicyStatusResponse - 113, // 262: openshell.v1.OpenShell.GetSandboxProviderEnvironment:output_type -> openshell.v1.GetSandboxProviderEnvironmentResponse - 115, // 263: openshell.v1.OpenShell.ExchangeProviderSubjectToken:output_type -> openshell.v1.ExchangeProviderSubjectTokenResponse - 135, // 264: openshell.v1.OpenShell.GetSandboxLogs:output_type -> openshell.v1.GetSandboxLogsResponse - 134, // 265: openshell.v1.OpenShell.PushSandboxLogs:output_type -> openshell.v1.PushSandboxLogsResponse - 137, // 266: openshell.v1.OpenShell.ConnectSupervisor:output_type -> openshell.v1.GatewayMessage - 147, // 267: openshell.v1.OpenShell.RelayStream:output_type -> openshell.v1.RelayFrame - 65, // 268: openshell.v1.OpenShell.WatchSandbox:output_type -> openshell.v1.SandboxStreamEvent - 157, // 269: openshell.v1.OpenShell.SubmitPolicyAnalysis:output_type -> openshell.v1.SubmitPolicyAnalysisResponse - 159, // 270: openshell.v1.OpenShell.GetDraftPolicy:output_type -> openshell.v1.GetDraftPolicyResponse - 161, // 271: openshell.v1.OpenShell.ApproveDraftChunk:output_type -> openshell.v1.ApproveDraftChunkResponse - 163, // 272: openshell.v1.OpenShell.RejectDraftChunk:output_type -> openshell.v1.RejectDraftChunkResponse - 165, // 273: openshell.v1.OpenShell.ApproveAllDraftChunks:output_type -> openshell.v1.ApproveAllDraftChunksResponse - 167, // 274: openshell.v1.OpenShell.EditDraftChunk:output_type -> openshell.v1.EditDraftChunkResponse - 169, // 275: openshell.v1.OpenShell.UndoDraftChunk:output_type -> openshell.v1.UndoDraftChunkResponse - 171, // 276: openshell.v1.OpenShell.ClearDraftChunks:output_type -> openshell.v1.ClearDraftChunksResponse - 174, // 277: openshell.v1.OpenShell.GetDraftHistory:output_type -> openshell.v1.GetDraftHistoryResponse - 8, // 278: openshell.v1.OpenShell.IssueSandboxToken:output_type -> openshell.v1.IssueSandboxTokenResponse - 10, // 279: openshell.v1.OpenShell.RefreshSandboxToken:output_type -> openshell.v1.RefreshSandboxTokenResponse - 180, // 280: openshell.v1.OpenShell.CreateWorkspace:output_type -> openshell.v1.CreateWorkspaceResponse - 182, // 281: openshell.v1.OpenShell.GetWorkspace:output_type -> openshell.v1.GetWorkspaceResponse - 184, // 282: openshell.v1.OpenShell.ListWorkspaces:output_type -> openshell.v1.ListWorkspacesResponse - 186, // 283: openshell.v1.OpenShell.DeleteWorkspace:output_type -> openshell.v1.DeleteWorkspaceResponse - 189, // 284: openshell.v1.OpenShell.AddWorkspaceMember:output_type -> openshell.v1.AddWorkspaceMemberResponse - 191, // 285: openshell.v1.OpenShell.RemoveWorkspaceMember:output_type -> openshell.v1.RemoveWorkspaceMemberResponse - 193, // 286: openshell.v1.OpenShell.ListWorkspaceMembers:output_type -> openshell.v1.ListWorkspaceMembersResponse - 220, // [220:287] is the sub-list for method output_type - 153, // [153:220] is the sub-list for method input_type - 153, // [153:153] is the sub-list for extension type_name - 153, // [153:153] is the sub-list for extension extendee - 0, // [0:153] is the sub-list for field type_name + 238, // 5: openshell.v1.Sandbox.metadata:type_name -> openshell.datamodel.v1.ObjectMeta + 22, // 6: openshell.v1.Sandbox.spec:type_name -> openshell.v1.SandboxSpec + 26, // 7: openshell.v1.Sandbox.status:type_name -> openshell.v1.SandboxStatus + 238, // 8: openshell.v1.SandboxDelegatedIdentityRecord.metadata:type_name -> openshell.datamodel.v1.ObjectMeta + 20, // 9: openshell.v1.SandboxDelegatedIdentityRecord.delegated_identity:type_name -> openshell.v1.SandboxDelegatedIdentity + 214, // 10: openshell.v1.SandboxSpec.environment:type_name -> openshell.v1.SandboxSpec.EnvironmentEntry + 25, // 11: openshell.v1.SandboxSpec.template:type_name -> openshell.v1.SandboxTemplate + 239, // 12: openshell.v1.SandboxSpec.policy:type_name -> openshell.sandbox.v1.SandboxPolicy + 23, // 13: openshell.v1.SandboxSpec.resource_requirements:type_name -> openshell.v1.ResourceRequirements + 24, // 14: openshell.v1.ResourceRequirements.gpu:type_name -> openshell.v1.GpuResourceRequirements + 215, // 15: openshell.v1.SandboxTemplate.labels:type_name -> openshell.v1.SandboxTemplate.LabelsEntry + 216, // 16: openshell.v1.SandboxTemplate.annotations:type_name -> openshell.v1.SandboxTemplate.AnnotationsEntry + 217, // 17: openshell.v1.SandboxTemplate.environment:type_name -> openshell.v1.SandboxTemplate.EnvironmentEntry + 240, // 18: openshell.v1.SandboxTemplate.resources:type_name -> google.protobuf.Struct + 240, // 19: openshell.v1.SandboxTemplate.driver_config:type_name -> google.protobuf.Struct + 27, // 20: openshell.v1.SandboxStatus.conditions:type_name -> openshell.v1.SandboxCondition + 0, // 21: openshell.v1.SandboxStatus.phase:type_name -> openshell.v1.SandboxPhase + 218, // 22: openshell.v1.PlatformEvent.metadata:type_name -> openshell.v1.PlatformEvent.MetadataEntry + 22, // 23: openshell.v1.CreateSandboxRequest.spec:type_name -> openshell.v1.SandboxSpec + 219, // 24: openshell.v1.CreateSandboxRequest.labels:type_name -> openshell.v1.CreateSandboxRequest.LabelsEntry + 220, // 25: openshell.v1.CreateSandboxRequest.annotations:type_name -> openshell.v1.CreateSandboxRequest.AnnotationsEntry + 30, // 26: openshell.v1.CreateSandboxRequest.delegated_identity:type_name -> openshell.v1.DelegatedIdentityRequest + 20, // 27: openshell.v1.GetSandboxDelegatedIdentityStatusResponse.delegated_identity:type_name -> openshell.v1.SandboxDelegatedIdentity + 19, // 28: openshell.v1.WithdrawSandboxDelegatedIdentityResponse.sandbox:type_name -> openshell.v1.Sandbox + 30, // 29: openshell.v1.ExtendSandboxDelegatedIdentityRequest.delegated_identity:type_name -> openshell.v1.DelegatedIdentityRequest + 19, // 30: openshell.v1.ExtendSandboxDelegatedIdentityResponse.sandbox:type_name -> openshell.v1.Sandbox + 19, // 31: openshell.v1.SandboxResponse.sandbox:type_name -> openshell.v1.Sandbox + 19, // 32: openshell.v1.ListSandboxesResponse.sandboxes:type_name -> openshell.v1.Sandbox + 241, // 33: openshell.v1.ListSandboxProvidersResponse.providers:type_name -> openshell.datamodel.v1.Provider + 19, // 34: openshell.v1.AttachSandboxProviderResponse.sandbox:type_name -> openshell.v1.Sandbox + 19, // 35: openshell.v1.DetachSandboxProviderResponse.sandbox:type_name -> openshell.v1.Sandbox + 60, // 36: openshell.v1.ListServicesResponse.services:type_name -> openshell.v1.ServiceEndpointResponse + 238, // 37: openshell.v1.ServiceEndpoint.metadata:type_name -> openshell.datamodel.v1.ObjectMeta + 59, // 38: openshell.v1.ServiceEndpointResponse.endpoint:type_name -> openshell.v1.ServiceEndpoint + 221, // 39: openshell.v1.ExecSandboxRequest.environment:type_name -> openshell.v1.ExecSandboxRequest.EnvironmentEntry + 64, // 40: openshell.v1.ExecSandboxEvent.stdout:type_name -> openshell.v1.ExecSandboxStdout + 65, // 41: openshell.v1.ExecSandboxEvent.stderr:type_name -> openshell.v1.ExecSandboxStderr + 66, // 42: openshell.v1.ExecSandboxEvent.exit:type_name -> openshell.v1.ExecSandboxExit + 163, // 43: openshell.v1.TcpForwardInit.ssh:type_name -> openshell.v1.SshRelayTarget + 164, // 44: openshell.v1.TcpForwardInit.tcp:type_name -> openshell.v1.TcpRelayTarget + 68, // 45: openshell.v1.TcpForwardFrame.init:type_name -> openshell.v1.TcpForwardInit + 63, // 46: openshell.v1.ExecSandboxInput.start:type_name -> openshell.v1.ExecSandboxRequest + 71, // 47: openshell.v1.ExecSandboxInput.resize:type_name -> openshell.v1.ExecSandboxWindowResize + 238, // 48: openshell.v1.SshSession.metadata:type_name -> openshell.datamodel.v1.ObjectMeta + 19, // 49: openshell.v1.SandboxStreamEvent.sandbox:type_name -> openshell.v1.Sandbox + 75, // 50: openshell.v1.SandboxStreamEvent.log:type_name -> openshell.v1.SandboxLogLine + 28, // 51: openshell.v1.SandboxStreamEvent.event:type_name -> openshell.v1.PlatformEvent + 76, // 52: openshell.v1.SandboxStreamEvent.warning:type_name -> openshell.v1.SandboxStreamWarning + 174, // 53: openshell.v1.SandboxStreamEvent.draft_policy_update:type_name -> openshell.v1.DraftPolicyUpdate + 222, // 54: openshell.v1.SandboxLogLine.fields:type_name -> openshell.v1.SandboxLogLine.FieldsEntry + 241, // 55: openshell.v1.CreateProviderRequest.provider:type_name -> openshell.datamodel.v1.Provider + 241, // 56: openshell.v1.UpdateProviderRequest.provider:type_name -> openshell.datamodel.v1.Provider + 223, // 57: openshell.v1.UpdateProviderRequest.credential_expires_at_ms:type_name -> openshell.v1.UpdateProviderRequest.CredentialExpiresAtMsEntry + 241, // 58: openshell.v1.ProviderResponse.provider:type_name -> openshell.datamodel.v1.Provider + 241, // 59: openshell.v1.ListProvidersResponse.providers:type_name -> openshell.datamodel.v1.Provider + 116, // 60: openshell.v1.ProviderProfileImportItem.profile:type_name -> openshell.v1.ProviderProfile + 88, // 61: openshell.v1.ProviderCredentialTokenGrant.audience_overrides:type_name -> openshell.v1.ProviderCredentialTokenGrantAudienceOverride + 1, // 62: openshell.v1.ProviderCredentialTokenGrant.grant_type:type_name -> openshell.v1.ProviderCredentialTokenGrantType + 89, // 63: openshell.v1.ProviderCredentialTokenGrant.subject_token:type_name -> openshell.v1.ProviderCredentialTokenGrantSubjectToken + 94, // 64: openshell.v1.ProviderProfileCredential.refresh:type_name -> openshell.v1.ProviderCredentialRefresh + 90, // 65: openshell.v1.ProviderProfileCredential.token_grant:type_name -> openshell.v1.ProviderCredentialTokenGrant + 2, // 66: openshell.v1.ProviderCredentialRefresh.strategy:type_name -> openshell.v1.ProviderCredentialRefreshStrategy + 92, // 67: openshell.v1.ProviderCredentialRefresh.material:type_name -> openshell.v1.ProviderCredentialRefreshMaterial + 93, // 68: openshell.v1.ProviderCredentialRefresh.additional_outputs:type_name -> openshell.v1.ProviderCredentialRefreshOutput + 2, // 69: openshell.v1.ProviderCredentialRefreshStatus.strategy:type_name -> openshell.v1.ProviderCredentialRefreshStrategy + 238, // 70: openshell.v1.StoredProviderCredentialRefreshState.metadata:type_name -> openshell.datamodel.v1.ObjectMeta + 2, // 71: openshell.v1.StoredProviderCredentialRefreshState.strategy:type_name -> openshell.v1.ProviderCredentialRefreshStrategy + 224, // 72: openshell.v1.StoredProviderCredentialRefreshState.material:type_name -> openshell.v1.StoredProviderCredentialRefreshState.MaterialEntry + 225, // 73: openshell.v1.StoredProviderCredentialRefreshState.additional_output_keys:type_name -> openshell.v1.StoredProviderCredentialRefreshState.AdditionalOutputKeysEntry + 238, // 74: openshell.v1.DelegatedIdentityCredential.metadata:type_name -> openshell.datamodel.v1.ObjectMeta + 238, // 75: openshell.v1.DelegatedIdentityCredentialSummary.metadata:type_name -> openshell.datamodel.v1.ObjectMeta + 99, // 76: openshell.v1.ListDelegatedIdentityCredentialsResponse.credentials:type_name -> openshell.v1.DelegatedIdentityCredentialSummary + 99, // 77: openshell.v1.GetDelegatedIdentityCredentialStatusResponse.credential:type_name -> openshell.v1.DelegatedIdentityCredentialSummary + 95, // 78: openshell.v1.GetProviderRefreshStatusResponse.credentials:type_name -> openshell.v1.ProviderCredentialRefreshStatus + 2, // 79: openshell.v1.ConfigureProviderRefreshRequest.strategy:type_name -> openshell.v1.ProviderCredentialRefreshStrategy + 226, // 80: openshell.v1.ConfigureProviderRefreshRequest.material:type_name -> openshell.v1.ConfigureProviderRefreshRequest.MaterialEntry + 95, // 81: openshell.v1.ConfigureProviderRefreshResponse.status:type_name -> openshell.v1.ProviderCredentialRefreshStatus + 95, // 82: openshell.v1.RotateProviderCredentialResponse.status:type_name -> openshell.v1.ProviderCredentialRefreshStatus + 3, // 83: openshell.v1.ProviderProfile.category:type_name -> openshell.v1.ProviderProfileCategory + 91, // 84: openshell.v1.ProviderProfile.credentials:type_name -> openshell.v1.ProviderProfileCredential + 242, // 85: openshell.v1.ProviderProfile.endpoints:type_name -> openshell.sandbox.v1.NetworkEndpoint + 243, // 86: openshell.v1.ProviderProfile.binaries:type_name -> openshell.sandbox.v1.NetworkBinary + 96, // 87: openshell.v1.ProviderProfile.discovery:type_name -> openshell.v1.ProviderProfileDiscovery + 227, // 88: openshell.v1.ProviderProfile.annotations:type_name -> openshell.v1.ProviderProfile.AnnotationsEntry + 238, // 89: openshell.v1.StoredProviderProfile.metadata:type_name -> openshell.datamodel.v1.ObjectMeta + 116, // 90: openshell.v1.StoredProviderProfile.profile:type_name -> openshell.v1.ProviderProfile + 116, // 91: openshell.v1.ProviderProfileResponse.profile:type_name -> openshell.v1.ProviderProfile + 116, // 92: openshell.v1.ListProviderProfilesResponse.profiles:type_name -> openshell.v1.ProviderProfile + 86, // 93: openshell.v1.ImportProviderProfilesRequest.profiles:type_name -> openshell.v1.ProviderProfileImportItem + 87, // 94: openshell.v1.ImportProviderProfilesResponse.diagnostics:type_name -> openshell.v1.ProviderProfileDiagnostic + 116, // 95: openshell.v1.ImportProviderProfilesResponse.profiles:type_name -> openshell.v1.ProviderProfile + 86, // 96: openshell.v1.UpdateProviderProfilesRequest.profile:type_name -> openshell.v1.ProviderProfileImportItem + 87, // 97: openshell.v1.UpdateProviderProfilesResponse.diagnostics:type_name -> openshell.v1.ProviderProfileDiagnostic + 116, // 98: openshell.v1.UpdateProviderProfilesResponse.profile:type_name -> openshell.v1.ProviderProfile + 86, // 99: openshell.v1.LintProviderProfilesRequest.profiles:type_name -> openshell.v1.ProviderProfileImportItem + 87, // 100: openshell.v1.LintProviderProfilesResponse.diagnostics:type_name -> openshell.v1.ProviderProfileDiagnostic + 130, // 101: openshell.v1.StaticCredentialBinding.endpoints:type_name -> openshell.v1.StaticCredentialEndpointBinding + 228, // 102: openshell.v1.GetSandboxProviderEnvironmentResponse.environment:type_name -> openshell.v1.GetSandboxProviderEnvironmentResponse.EnvironmentEntry + 229, // 103: openshell.v1.GetSandboxProviderEnvironmentResponse.credential_expires_at_ms:type_name -> openshell.v1.GetSandboxProviderEnvironmentResponse.CredentialExpiresAtMsEntry + 230, // 104: openshell.v1.GetSandboxProviderEnvironmentResponse.dynamic_credentials:type_name -> openshell.v1.GetSandboxProviderEnvironmentResponse.DynamicCredentialsEntry + 231, // 105: openshell.v1.GetSandboxProviderEnvironmentResponse.static_credential_bindings:type_name -> openshell.v1.GetSandboxProviderEnvironmentResponse.StaticCredentialBindingsEntry + 239, // 106: openshell.v1.UpdateConfigRequest.policy:type_name -> openshell.sandbox.v1.SandboxPolicy + 244, // 107: openshell.v1.UpdateConfigRequest.setting_value:type_name -> openshell.sandbox.v1.SettingValue + 136, // 108: openshell.v1.UpdateConfigRequest.merge_operations:type_name -> openshell.v1.PolicyMergeOperation + 232, // 109: openshell.v1.UpdateConfigRequest.annotations:type_name -> openshell.v1.UpdateConfigRequest.AnnotationsEntry + 137, // 110: openshell.v1.PolicyMergeOperation.add_rule:type_name -> openshell.v1.AddNetworkRule + 138, // 111: openshell.v1.PolicyMergeOperation.remove_endpoint:type_name -> openshell.v1.RemoveNetworkEndpoint + 139, // 112: openshell.v1.PolicyMergeOperation.remove_rule:type_name -> openshell.v1.RemoveNetworkRule + 140, // 113: openshell.v1.PolicyMergeOperation.add_deny_rules:type_name -> openshell.v1.AddDenyRules + 141, // 114: openshell.v1.PolicyMergeOperation.add_allow_rules:type_name -> openshell.v1.AddAllowRules + 142, // 115: openshell.v1.PolicyMergeOperation.remove_binary:type_name -> openshell.v1.RemoveNetworkBinary + 245, // 116: openshell.v1.AddNetworkRule.rule:type_name -> openshell.sandbox.v1.NetworkPolicyRule + 246, // 117: openshell.v1.AddDenyRules.deny_rules:type_name -> openshell.sandbox.v1.L7DenyRule + 247, // 118: openshell.v1.AddAllowRules.rules:type_name -> openshell.sandbox.v1.L7Rule + 233, // 119: openshell.v1.UpdateConfigResponse.annotations:type_name -> openshell.v1.UpdateConfigResponse.AnnotationsEntry + 150, // 120: openshell.v1.GetSandboxPolicyStatusResponse.revision:type_name -> openshell.v1.SandboxPolicyRevision + 150, // 121: openshell.v1.ListSandboxPoliciesResponse.revisions:type_name -> openshell.v1.SandboxPolicyRevision + 4, // 122: openshell.v1.ReportPolicyStatusRequest.status:type_name -> openshell.v1.PolicyStatus + 4, // 123: openshell.v1.SandboxPolicyRevision.status:type_name -> openshell.v1.PolicyStatus + 239, // 124: openshell.v1.SandboxPolicyRevision.policy:type_name -> openshell.sandbox.v1.SandboxPolicy + 234, // 125: openshell.v1.SandboxPolicyRevision.provenance:type_name -> openshell.v1.SandboxPolicyRevision.ProvenanceEntry + 75, // 126: openshell.v1.PushSandboxLogsRequest.logs:type_name -> openshell.v1.SandboxLogLine + 75, // 127: openshell.v1.GetSandboxLogsResponse.logs:type_name -> openshell.v1.SandboxLogLine + 157, // 128: openshell.v1.SupervisorMessage.hello:type_name -> openshell.v1.SupervisorHello + 160, // 129: openshell.v1.SupervisorMessage.heartbeat:type_name -> openshell.v1.SupervisorHeartbeat + 167, // 130: openshell.v1.SupervisorMessage.relay_open_result:type_name -> openshell.v1.RelayOpenResult + 168, // 131: openshell.v1.SupervisorMessage.relay_close:type_name -> openshell.v1.RelayClose + 158, // 132: openshell.v1.GatewayMessage.session_accepted:type_name -> openshell.v1.SessionAccepted + 159, // 133: openshell.v1.GatewayMessage.session_rejected:type_name -> openshell.v1.SessionRejected + 161, // 134: openshell.v1.GatewayMessage.heartbeat:type_name -> openshell.v1.GatewayHeartbeat + 162, // 135: openshell.v1.GatewayMessage.relay_open:type_name -> openshell.v1.RelayOpen + 168, // 136: openshell.v1.GatewayMessage.relay_close:type_name -> openshell.v1.RelayClose + 163, // 137: openshell.v1.RelayOpen.ssh:type_name -> openshell.v1.SshRelayTarget + 164, // 138: openshell.v1.RelayOpen.tcp:type_name -> openshell.v1.TcpRelayTarget + 165, // 139: openshell.v1.RelayFrame.init:type_name -> openshell.v1.RelayInit + 169, // 140: openshell.v1.DenialSummary.l7_request_samples:type_name -> openshell.v1.L7RequestSample + 171, // 141: openshell.v1.NetworkActivitySummary.denials_by_group:type_name -> openshell.v1.DenialGroupCount + 245, // 142: openshell.v1.PolicyChunk.proposed_rule:type_name -> openshell.sandbox.v1.NetworkPolicyRule + 170, // 143: openshell.v1.SubmitPolicyAnalysisRequest.summaries:type_name -> openshell.v1.DenialSummary + 173, // 144: openshell.v1.SubmitPolicyAnalysisRequest.proposed_chunks:type_name -> openshell.v1.PolicyChunk + 172, // 145: openshell.v1.SubmitPolicyAnalysisRequest.network_activity_summaries:type_name -> openshell.v1.NetworkActivitySummary + 173, // 146: openshell.v1.GetDraftPolicyResponse.chunks:type_name -> openshell.v1.PolicyChunk + 245, // 147: openshell.v1.EditDraftChunkRequest.proposed_rule:type_name -> openshell.sandbox.v1.NetworkPolicyRule + 192, // 148: openshell.v1.GetDraftHistoryResponse.entries:type_name -> openshell.v1.DraftHistoryEntry + 239, // 149: openshell.v1.PolicyRevisionPayload.policy:type_name -> openshell.sandbox.v1.SandboxPolicy + 235, // 150: openshell.v1.PolicyRevisionPayload.provenance:type_name -> openshell.v1.PolicyRevisionPayload.ProvenanceEntry + 245, // 151: openshell.v1.DraftChunkPayload.proposed_rule:type_name -> openshell.sandbox.v1.NetworkPolicyRule + 236, // 152: openshell.v1.StoredPolicyRevision.provenance:type_name -> openshell.v1.StoredPolicyRevision.ProvenanceEntry + 237, // 153: openshell.v1.CreateWorkspaceRequest.labels:type_name -> openshell.v1.CreateWorkspaceRequest.LabelsEntry + 248, // 154: openshell.v1.CreateWorkspaceResponse.workspace:type_name -> openshell.datamodel.v1.Workspace + 248, // 155: openshell.v1.GetWorkspaceResponse.workspace:type_name -> openshell.datamodel.v1.Workspace + 248, // 156: openshell.v1.ListWorkspacesResponse.workspaces:type_name -> openshell.datamodel.v1.Workspace + 238, // 157: openshell.v1.WorkspaceMember.metadata:type_name -> openshell.datamodel.v1.ObjectMeta + 6, // 158: openshell.v1.WorkspaceMember.role:type_name -> openshell.v1.WorkspaceRole + 6, // 159: openshell.v1.AddWorkspaceMemberRequest.role:type_name -> openshell.v1.WorkspaceRole + 206, // 160: openshell.v1.AddWorkspaceMemberResponse.member:type_name -> openshell.v1.WorkspaceMember + 206, // 161: openshell.v1.ListWorkspaceMembersResponse.members:type_name -> openshell.v1.WorkspaceMember + 91, // 162: openshell.v1.GetSandboxProviderEnvironmentResponse.DynamicCredentialsEntry.value:type_name -> openshell.v1.ProviderProfileCredential + 131, // 163: openshell.v1.GetSandboxProviderEnvironmentResponse.StaticCredentialBindingsEntry.value:type_name -> openshell.v1.StaticCredentialBinding + 11, // 164: openshell.v1.OpenShell.Health:input_type -> openshell.v1.HealthRequest + 13, // 165: openshell.v1.OpenShell.GetCurrentUser:input_type -> openshell.v1.GetCurrentUserRequest + 15, // 166: openshell.v1.OpenShell.GetGatewayInfo:input_type -> openshell.v1.GetGatewayInfoRequest + 29, // 167: openshell.v1.OpenShell.CreateSandbox:input_type -> openshell.v1.CreateSandboxRequest + 31, // 168: openshell.v1.OpenShell.GetSandboxDelegatedIdentityStatus:input_type -> openshell.v1.GetSandboxDelegatedIdentityStatusRequest + 33, // 169: openshell.v1.OpenShell.WithdrawSandboxDelegatedIdentity:input_type -> openshell.v1.WithdrawSandboxDelegatedIdentityRequest + 35, // 170: openshell.v1.OpenShell.ExtendSandboxDelegatedIdentity:input_type -> openshell.v1.ExtendSandboxDelegatedIdentityRequest + 37, // 171: openshell.v1.OpenShell.GetSandbox:input_type -> openshell.v1.GetSandboxRequest + 38, // 172: openshell.v1.OpenShell.ListSandboxes:input_type -> openshell.v1.ListSandboxesRequest + 39, // 173: openshell.v1.OpenShell.ListSandboxProviders:input_type -> openshell.v1.ListSandboxProvidersRequest + 40, // 174: openshell.v1.OpenShell.AttachSandboxProvider:input_type -> openshell.v1.AttachSandboxProviderRequest + 41, // 175: openshell.v1.OpenShell.DetachSandboxProvider:input_type -> openshell.v1.DetachSandboxProviderRequest + 42, // 176: openshell.v1.OpenShell.DeleteSandbox:input_type -> openshell.v1.DeleteSandboxRequest + 43, // 177: openshell.v1.OpenShell.StopSandbox:input_type -> openshell.v1.StopSandboxRequest + 44, // 178: openshell.v1.OpenShell.StartSandbox:input_type -> openshell.v1.StartSandboxRequest + 51, // 179: openshell.v1.OpenShell.CreateSshSession:input_type -> openshell.v1.CreateSshSessionRequest + 53, // 180: openshell.v1.OpenShell.ExposeService:input_type -> openshell.v1.ExposeServiceRequest + 54, // 181: openshell.v1.OpenShell.GetService:input_type -> openshell.v1.GetServiceRequest + 55, // 182: openshell.v1.OpenShell.ListServices:input_type -> openshell.v1.ListServicesRequest + 57, // 183: openshell.v1.OpenShell.DeleteService:input_type -> openshell.v1.DeleteServiceRequest + 61, // 184: openshell.v1.OpenShell.RevokeSshSession:input_type -> openshell.v1.RevokeSshSessionRequest + 63, // 185: openshell.v1.OpenShell.ExecSandbox:input_type -> openshell.v1.ExecSandboxRequest + 69, // 186: openshell.v1.OpenShell.ForwardTcp:input_type -> openshell.v1.TcpForwardFrame + 70, // 187: openshell.v1.OpenShell.ExecSandboxInteractive:input_type -> openshell.v1.ExecSandboxInput + 77, // 188: openshell.v1.OpenShell.CreateProvider:input_type -> openshell.v1.CreateProviderRequest + 78, // 189: openshell.v1.OpenShell.GetProvider:input_type -> openshell.v1.GetProviderRequest + 79, // 190: openshell.v1.OpenShell.ListProviders:input_type -> openshell.v1.ListProvidersRequest + 84, // 191: openshell.v1.OpenShell.ListProviderProfiles:input_type -> openshell.v1.ListProviderProfilesRequest + 85, // 192: openshell.v1.OpenShell.GetProviderProfile:input_type -> openshell.v1.GetProviderProfileRequest + 120, // 193: openshell.v1.OpenShell.ImportProviderProfiles:input_type -> openshell.v1.ImportProviderProfilesRequest + 122, // 194: openshell.v1.OpenShell.UpdateProviderProfiles:input_type -> openshell.v1.UpdateProviderProfilesRequest + 124, // 195: openshell.v1.OpenShell.LintProviderProfiles:input_type -> openshell.v1.LintProviderProfilesRequest + 80, // 196: openshell.v1.OpenShell.UpdateProvider:input_type -> openshell.v1.UpdateProviderRequest + 108, // 197: openshell.v1.OpenShell.GetProviderRefreshStatus:input_type -> openshell.v1.GetProviderRefreshStatusRequest + 110, // 198: openshell.v1.OpenShell.ConfigureProviderRefresh:input_type -> openshell.v1.ConfigureProviderRefreshRequest + 112, // 199: openshell.v1.OpenShell.RotateProviderCredential:input_type -> openshell.v1.RotateProviderCredentialRequest + 100, // 200: openshell.v1.OpenShell.ListDelegatedIdentityCredentials:input_type -> openshell.v1.ListDelegatedIdentityCredentialsRequest + 102, // 201: openshell.v1.OpenShell.GetDelegatedIdentityCredentialStatus:input_type -> openshell.v1.GetDelegatedIdentityCredentialStatusRequest + 104, // 202: openshell.v1.OpenShell.RevokeDelegatedIdentityCredential:input_type -> openshell.v1.RevokeDelegatedIdentityCredentialRequest + 106, // 203: openshell.v1.OpenShell.DeleteDelegatedIdentityCredential:input_type -> openshell.v1.DeleteDelegatedIdentityCredentialRequest + 114, // 204: openshell.v1.OpenShell.DeleteProviderRefresh:input_type -> openshell.v1.DeleteProviderRefreshRequest + 81, // 205: openshell.v1.OpenShell.DeleteProvider:input_type -> openshell.v1.DeleteProviderRequest + 127, // 206: openshell.v1.OpenShell.DeleteProviderProfile:input_type -> openshell.v1.DeleteProviderProfileRequest + 249, // 207: openshell.v1.OpenShell.GetSandboxConfig:input_type -> openshell.sandbox.v1.GetSandboxConfigRequest + 250, // 208: openshell.v1.OpenShell.GetGatewayConfig:input_type -> openshell.sandbox.v1.GetGatewayConfigRequest + 135, // 209: openshell.v1.OpenShell.UpdateConfig:input_type -> openshell.v1.UpdateConfigRequest + 144, // 210: openshell.v1.OpenShell.GetSandboxPolicyStatus:input_type -> openshell.v1.GetSandboxPolicyStatusRequest + 146, // 211: openshell.v1.OpenShell.ListSandboxPolicies:input_type -> openshell.v1.ListSandboxPoliciesRequest + 148, // 212: openshell.v1.OpenShell.ReportPolicyStatus:input_type -> openshell.v1.ReportPolicyStatusRequest + 129, // 213: openshell.v1.OpenShell.GetSandboxProviderEnvironment:input_type -> openshell.v1.GetSandboxProviderEnvironmentRequest + 133, // 214: openshell.v1.OpenShell.ExchangeProviderSubjectToken:input_type -> openshell.v1.ExchangeProviderSubjectTokenRequest + 151, // 215: openshell.v1.OpenShell.GetSandboxLogs:input_type -> openshell.v1.GetSandboxLogsRequest + 152, // 216: openshell.v1.OpenShell.PushSandboxLogs:input_type -> openshell.v1.PushSandboxLogsRequest + 155, // 217: openshell.v1.OpenShell.ConnectSupervisor:input_type -> openshell.v1.SupervisorMessage + 166, // 218: openshell.v1.OpenShell.RelayStream:input_type -> openshell.v1.RelayFrame + 73, // 219: openshell.v1.OpenShell.WatchSandbox:input_type -> openshell.v1.WatchSandboxRequest + 175, // 220: openshell.v1.OpenShell.SubmitPolicyAnalysis:input_type -> openshell.v1.SubmitPolicyAnalysisRequest + 177, // 221: openshell.v1.OpenShell.GetDraftPolicy:input_type -> openshell.v1.GetDraftPolicyRequest + 179, // 222: openshell.v1.OpenShell.ApproveDraftChunk:input_type -> openshell.v1.ApproveDraftChunkRequest + 181, // 223: openshell.v1.OpenShell.RejectDraftChunk:input_type -> openshell.v1.RejectDraftChunkRequest + 183, // 224: openshell.v1.OpenShell.ApproveAllDraftChunks:input_type -> openshell.v1.ApproveAllDraftChunksRequest + 185, // 225: openshell.v1.OpenShell.EditDraftChunk:input_type -> openshell.v1.EditDraftChunkRequest + 187, // 226: openshell.v1.OpenShell.UndoDraftChunk:input_type -> openshell.v1.UndoDraftChunkRequest + 189, // 227: openshell.v1.OpenShell.ClearDraftChunks:input_type -> openshell.v1.ClearDraftChunksRequest + 191, // 228: openshell.v1.OpenShell.GetDraftHistory:input_type -> openshell.v1.GetDraftHistoryRequest + 7, // 229: openshell.v1.OpenShell.IssueSandboxToken:input_type -> openshell.v1.IssueSandboxTokenRequest + 9, // 230: openshell.v1.OpenShell.RefreshSandboxToken:input_type -> openshell.v1.RefreshSandboxTokenRequest + 198, // 231: openshell.v1.OpenShell.CreateWorkspace:input_type -> openshell.v1.CreateWorkspaceRequest + 200, // 232: openshell.v1.OpenShell.GetWorkspace:input_type -> openshell.v1.GetWorkspaceRequest + 202, // 233: openshell.v1.OpenShell.ListWorkspaces:input_type -> openshell.v1.ListWorkspacesRequest + 204, // 234: openshell.v1.OpenShell.DeleteWorkspace:input_type -> openshell.v1.DeleteWorkspaceRequest + 207, // 235: openshell.v1.OpenShell.AddWorkspaceMember:input_type -> openshell.v1.AddWorkspaceMemberRequest + 209, // 236: openshell.v1.OpenShell.RemoveWorkspaceMember:input_type -> openshell.v1.RemoveWorkspaceMemberRequest + 211, // 237: openshell.v1.OpenShell.ListWorkspaceMembers:input_type -> openshell.v1.ListWorkspaceMembersRequest + 12, // 238: openshell.v1.OpenShell.Health:output_type -> openshell.v1.HealthResponse + 14, // 239: openshell.v1.OpenShell.GetCurrentUser:output_type -> openshell.v1.GetCurrentUserResponse + 16, // 240: openshell.v1.OpenShell.GetGatewayInfo:output_type -> openshell.v1.GetGatewayInfoResponse + 45, // 241: openshell.v1.OpenShell.CreateSandbox:output_type -> openshell.v1.SandboxResponse + 32, // 242: openshell.v1.OpenShell.GetSandboxDelegatedIdentityStatus:output_type -> openshell.v1.GetSandboxDelegatedIdentityStatusResponse + 34, // 243: openshell.v1.OpenShell.WithdrawSandboxDelegatedIdentity:output_type -> openshell.v1.WithdrawSandboxDelegatedIdentityResponse + 36, // 244: openshell.v1.OpenShell.ExtendSandboxDelegatedIdentity:output_type -> openshell.v1.ExtendSandboxDelegatedIdentityResponse + 45, // 245: openshell.v1.OpenShell.GetSandbox:output_type -> openshell.v1.SandboxResponse + 46, // 246: openshell.v1.OpenShell.ListSandboxes:output_type -> openshell.v1.ListSandboxesResponse + 47, // 247: openshell.v1.OpenShell.ListSandboxProviders:output_type -> openshell.v1.ListSandboxProvidersResponse + 48, // 248: openshell.v1.OpenShell.AttachSandboxProvider:output_type -> openshell.v1.AttachSandboxProviderResponse + 49, // 249: openshell.v1.OpenShell.DetachSandboxProvider:output_type -> openshell.v1.DetachSandboxProviderResponse + 50, // 250: openshell.v1.OpenShell.DeleteSandbox:output_type -> openshell.v1.DeleteSandboxResponse + 45, // 251: openshell.v1.OpenShell.StopSandbox:output_type -> openshell.v1.SandboxResponse + 45, // 252: openshell.v1.OpenShell.StartSandbox:output_type -> openshell.v1.SandboxResponse + 52, // 253: openshell.v1.OpenShell.CreateSshSession:output_type -> openshell.v1.CreateSshSessionResponse + 60, // 254: openshell.v1.OpenShell.ExposeService:output_type -> openshell.v1.ServiceEndpointResponse + 60, // 255: openshell.v1.OpenShell.GetService:output_type -> openshell.v1.ServiceEndpointResponse + 56, // 256: openshell.v1.OpenShell.ListServices:output_type -> openshell.v1.ListServicesResponse + 58, // 257: openshell.v1.OpenShell.DeleteService:output_type -> openshell.v1.DeleteServiceResponse + 62, // 258: openshell.v1.OpenShell.RevokeSshSession:output_type -> openshell.v1.RevokeSshSessionResponse + 67, // 259: openshell.v1.OpenShell.ExecSandbox:output_type -> openshell.v1.ExecSandboxEvent + 69, // 260: openshell.v1.OpenShell.ForwardTcp:output_type -> openshell.v1.TcpForwardFrame + 67, // 261: openshell.v1.OpenShell.ExecSandboxInteractive:output_type -> openshell.v1.ExecSandboxEvent + 82, // 262: openshell.v1.OpenShell.CreateProvider:output_type -> openshell.v1.ProviderResponse + 82, // 263: openshell.v1.OpenShell.GetProvider:output_type -> openshell.v1.ProviderResponse + 83, // 264: openshell.v1.OpenShell.ListProviders:output_type -> openshell.v1.ListProvidersResponse + 119, // 265: openshell.v1.OpenShell.ListProviderProfiles:output_type -> openshell.v1.ListProviderProfilesResponse + 118, // 266: openshell.v1.OpenShell.GetProviderProfile:output_type -> openshell.v1.ProviderProfileResponse + 121, // 267: openshell.v1.OpenShell.ImportProviderProfiles:output_type -> openshell.v1.ImportProviderProfilesResponse + 123, // 268: openshell.v1.OpenShell.UpdateProviderProfiles:output_type -> openshell.v1.UpdateProviderProfilesResponse + 125, // 269: openshell.v1.OpenShell.LintProviderProfiles:output_type -> openshell.v1.LintProviderProfilesResponse + 82, // 270: openshell.v1.OpenShell.UpdateProvider:output_type -> openshell.v1.ProviderResponse + 109, // 271: openshell.v1.OpenShell.GetProviderRefreshStatus:output_type -> openshell.v1.GetProviderRefreshStatusResponse + 111, // 272: openshell.v1.OpenShell.ConfigureProviderRefresh:output_type -> openshell.v1.ConfigureProviderRefreshResponse + 113, // 273: openshell.v1.OpenShell.RotateProviderCredential:output_type -> openshell.v1.RotateProviderCredentialResponse + 101, // 274: openshell.v1.OpenShell.ListDelegatedIdentityCredentials:output_type -> openshell.v1.ListDelegatedIdentityCredentialsResponse + 103, // 275: openshell.v1.OpenShell.GetDelegatedIdentityCredentialStatus:output_type -> openshell.v1.GetDelegatedIdentityCredentialStatusResponse + 105, // 276: openshell.v1.OpenShell.RevokeDelegatedIdentityCredential:output_type -> openshell.v1.RevokeDelegatedIdentityCredentialResponse + 107, // 277: openshell.v1.OpenShell.DeleteDelegatedIdentityCredential:output_type -> openshell.v1.DeleteDelegatedIdentityCredentialResponse + 115, // 278: openshell.v1.OpenShell.DeleteProviderRefresh:output_type -> openshell.v1.DeleteProviderRefreshResponse + 126, // 279: openshell.v1.OpenShell.DeleteProvider:output_type -> openshell.v1.DeleteProviderResponse + 128, // 280: openshell.v1.OpenShell.DeleteProviderProfile:output_type -> openshell.v1.DeleteProviderProfileResponse + 251, // 281: openshell.v1.OpenShell.GetSandboxConfig:output_type -> openshell.sandbox.v1.GetSandboxConfigResponse + 252, // 282: openshell.v1.OpenShell.GetGatewayConfig:output_type -> openshell.sandbox.v1.GetGatewayConfigResponse + 143, // 283: openshell.v1.OpenShell.UpdateConfig:output_type -> openshell.v1.UpdateConfigResponse + 145, // 284: openshell.v1.OpenShell.GetSandboxPolicyStatus:output_type -> openshell.v1.GetSandboxPolicyStatusResponse + 147, // 285: openshell.v1.OpenShell.ListSandboxPolicies:output_type -> openshell.v1.ListSandboxPoliciesResponse + 149, // 286: openshell.v1.OpenShell.ReportPolicyStatus:output_type -> openshell.v1.ReportPolicyStatusResponse + 132, // 287: openshell.v1.OpenShell.GetSandboxProviderEnvironment:output_type -> openshell.v1.GetSandboxProviderEnvironmentResponse + 134, // 288: openshell.v1.OpenShell.ExchangeProviderSubjectToken:output_type -> openshell.v1.ExchangeProviderSubjectTokenResponse + 154, // 289: openshell.v1.OpenShell.GetSandboxLogs:output_type -> openshell.v1.GetSandboxLogsResponse + 153, // 290: openshell.v1.OpenShell.PushSandboxLogs:output_type -> openshell.v1.PushSandboxLogsResponse + 156, // 291: openshell.v1.OpenShell.ConnectSupervisor:output_type -> openshell.v1.GatewayMessage + 166, // 292: openshell.v1.OpenShell.RelayStream:output_type -> openshell.v1.RelayFrame + 74, // 293: openshell.v1.OpenShell.WatchSandbox:output_type -> openshell.v1.SandboxStreamEvent + 176, // 294: openshell.v1.OpenShell.SubmitPolicyAnalysis:output_type -> openshell.v1.SubmitPolicyAnalysisResponse + 178, // 295: openshell.v1.OpenShell.GetDraftPolicy:output_type -> openshell.v1.GetDraftPolicyResponse + 180, // 296: openshell.v1.OpenShell.ApproveDraftChunk:output_type -> openshell.v1.ApproveDraftChunkResponse + 182, // 297: openshell.v1.OpenShell.RejectDraftChunk:output_type -> openshell.v1.RejectDraftChunkResponse + 184, // 298: openshell.v1.OpenShell.ApproveAllDraftChunks:output_type -> openshell.v1.ApproveAllDraftChunksResponse + 186, // 299: openshell.v1.OpenShell.EditDraftChunk:output_type -> openshell.v1.EditDraftChunkResponse + 188, // 300: openshell.v1.OpenShell.UndoDraftChunk:output_type -> openshell.v1.UndoDraftChunkResponse + 190, // 301: openshell.v1.OpenShell.ClearDraftChunks:output_type -> openshell.v1.ClearDraftChunksResponse + 193, // 302: openshell.v1.OpenShell.GetDraftHistory:output_type -> openshell.v1.GetDraftHistoryResponse + 8, // 303: openshell.v1.OpenShell.IssueSandboxToken:output_type -> openshell.v1.IssueSandboxTokenResponse + 10, // 304: openshell.v1.OpenShell.RefreshSandboxToken:output_type -> openshell.v1.RefreshSandboxTokenResponse + 199, // 305: openshell.v1.OpenShell.CreateWorkspace:output_type -> openshell.v1.CreateWorkspaceResponse + 201, // 306: openshell.v1.OpenShell.GetWorkspace:output_type -> openshell.v1.GetWorkspaceResponse + 203, // 307: openshell.v1.OpenShell.ListWorkspaces:output_type -> openshell.v1.ListWorkspacesResponse + 205, // 308: openshell.v1.OpenShell.DeleteWorkspace:output_type -> openshell.v1.DeleteWorkspaceResponse + 208, // 309: openshell.v1.OpenShell.AddWorkspaceMember:output_type -> openshell.v1.AddWorkspaceMemberResponse + 210, // 310: openshell.v1.OpenShell.RemoveWorkspaceMember:output_type -> openshell.v1.RemoveWorkspaceMemberResponse + 212, // 311: openshell.v1.OpenShell.ListWorkspaceMembers:output_type -> openshell.v1.ListWorkspaceMembersResponse + 238, // [238:312] is the sub-list for method output_type + 164, // [164:238] is the sub-list for method input_type + 164, // [164:164] is the sub-list for extension type_name + 164, // [164:164] is the sub-list for extension extendee + 0, // [0:164] is the sub-list for field type_name } func init() { file_openshell_proto_init() } @@ -15101,35 +16455,35 @@ func file_openshell_proto_init() { if File_openshell_proto != nil { return } - file_openshell_proto_msgTypes[15].OneofWrappers = []any{} - file_openshell_proto_msgTypes[16].OneofWrappers = []any{} - file_openshell_proto_msgTypes[51].OneofWrappers = []any{ + file_openshell_proto_msgTypes[17].OneofWrappers = []any{} + file_openshell_proto_msgTypes[18].OneofWrappers = []any{} + file_openshell_proto_msgTypes[60].OneofWrappers = []any{ (*ExecSandboxEvent_Stdout)(nil), (*ExecSandboxEvent_Stderr)(nil), (*ExecSandboxEvent_Exit)(nil), } - file_openshell_proto_msgTypes[52].OneofWrappers = []any{ + file_openshell_proto_msgTypes[61].OneofWrappers = []any{ (*TcpForwardInit_Ssh)(nil), (*TcpForwardInit_Tcp)(nil), } - file_openshell_proto_msgTypes[53].OneofWrappers = []any{ + file_openshell_proto_msgTypes[62].OneofWrappers = []any{ (*TcpForwardFrame_Init)(nil), (*TcpForwardFrame_Data)(nil), } - file_openshell_proto_msgTypes[54].OneofWrappers = []any{ + file_openshell_proto_msgTypes[63].OneofWrappers = []any{ (*ExecSandboxInput_Start)(nil), (*ExecSandboxInput_Stdin)(nil), (*ExecSandboxInput_Resize)(nil), } - file_openshell_proto_msgTypes[58].OneofWrappers = []any{ + file_openshell_proto_msgTypes[67].OneofWrappers = []any{ (*SandboxStreamEvent_Sandbox)(nil), (*SandboxStreamEvent_Log)(nil), (*SandboxStreamEvent_Event)(nil), (*SandboxStreamEvent_Warning)(nil), (*SandboxStreamEvent_DraftPolicyUpdate)(nil), } - file_openshell_proto_msgTypes[84].OneofWrappers = []any{} - file_openshell_proto_msgTypes[110].OneofWrappers = []any{ + file_openshell_proto_msgTypes[103].OneofWrappers = []any{} + file_openshell_proto_msgTypes[129].OneofWrappers = []any{ (*PolicyMergeOperation_AddRule)(nil), (*PolicyMergeOperation_RemoveEndpoint)(nil), (*PolicyMergeOperation_RemoveRule)(nil), @@ -15137,36 +16491,36 @@ func file_openshell_proto_init() { (*PolicyMergeOperation_AddAllowRules)(nil), (*PolicyMergeOperation_RemoveBinary)(nil), } - file_openshell_proto_msgTypes[129].OneofWrappers = []any{ + file_openshell_proto_msgTypes[148].OneofWrappers = []any{ (*SupervisorMessage_Hello)(nil), (*SupervisorMessage_Heartbeat)(nil), (*SupervisorMessage_RelayOpenResult)(nil), (*SupervisorMessage_RelayClose)(nil), } - file_openshell_proto_msgTypes[130].OneofWrappers = []any{ + file_openshell_proto_msgTypes[149].OneofWrappers = []any{ (*GatewayMessage_SessionAccepted)(nil), (*GatewayMessage_SessionRejected)(nil), (*GatewayMessage_Heartbeat)(nil), (*GatewayMessage_RelayOpen)(nil), (*GatewayMessage_RelayClose)(nil), } - file_openshell_proto_msgTypes[136].OneofWrappers = []any{ + file_openshell_proto_msgTypes[155].OneofWrappers = []any{ (*RelayOpen_Ssh)(nil), (*RelayOpen_Tcp)(nil), } - file_openshell_proto_msgTypes[140].OneofWrappers = []any{ + file_openshell_proto_msgTypes[159].OneofWrappers = []any{ (*RelayFrame_Init)(nil), (*RelayFrame_Data)(nil), } - file_openshell_proto_msgTypes[170].OneofWrappers = []any{} - file_openshell_proto_msgTypes[171].OneofWrappers = []any{} + file_openshell_proto_msgTypes[189].OneofWrappers = []any{} + file_openshell_proto_msgTypes[190].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: 7, - NumMessages: 212, + NumMessages: 231, 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 a3eb9044e4..748f52155f 100644 --- a/sdk/go/proto/openshellv1/openshell_grpc.pb.go +++ b/sdk/go/proto/openshellv1/openshell_grpc.pb.go @@ -23,73 +23,80 @@ 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_GetSandbox_FullMethodName = "/openshell.v1.OpenShell/GetSandbox" - OpenShell_ListSandboxes_FullMethodName = "/openshell.v1.OpenShell/ListSandboxes" - OpenShell_ListSandboxProviders_FullMethodName = "/openshell.v1.OpenShell/ListSandboxProviders" - OpenShell_AttachSandboxProvider_FullMethodName = "/openshell.v1.OpenShell/AttachSandboxProvider" - OpenShell_DetachSandboxProvider_FullMethodName = "/openshell.v1.OpenShell/DetachSandboxProvider" - 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_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_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_RelayStream_FullMethodName = "/openshell.v1.OpenShell/RelayStream" - 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_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_ListSandboxProviders_FullMethodName = "/openshell.v1.OpenShell/ListSandboxProviders" + OpenShell_AttachSandboxProvider_FullMethodName = "/openshell.v1.OpenShell/AttachSandboxProvider" + OpenShell_DetachSandboxProvider_FullMethodName = "/openshell.v1.OpenShell/DetachSandboxProvider" + 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_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_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_RelayStream_FullMethodName = "/openshell.v1.OpenShell/RelayStream" + 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. @@ -113,6 +120,12 @@ type OpenShellClient interface { GetGatewayInfo(ctx context.Context, in *GetGatewayInfoRequest, opts ...grpc.CallOption) (*GetGatewayInfoResponse, error) // Create a new sandbox. CreateSandbox(ctx context.Context, in *CreateSandboxRequest, opts ...grpc.CallOption) (*SandboxResponse, 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. @@ -173,6 +186,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. @@ -329,6 +350,36 @@ func (c *openShellClient) CreateSandbox(ctx context.Context, in *CreateSandboxRe 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) @@ -634,6 +685,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) @@ -1013,6 +1104,12 @@ type OpenShellServer interface { GetGatewayInfo(context.Context, *GetGatewayInfoRequest) (*GetGatewayInfoResponse, error) // Create a new sandbox. CreateSandbox(context.Context, *CreateSandboxRequest) (*SandboxResponse, 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. @@ -1073,6 +1170,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. @@ -1201,6 +1306,15 @@ func (UnimplementedOpenShellServer) GetGatewayInfo(context.Context, *GetGatewayI func (UnimplementedOpenShellServer) CreateSandbox(context.Context, *CreateSandboxRequest) (*SandboxResponse, error) { return nil, status.Error(codes.Unimplemented, "method CreateSandbox 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") } @@ -1288,6 +1402,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") } @@ -1483,6 +1609,60 @@ func _OpenShell_CreateSandbox_Handler(srv interface{}, ctx context.Context, dec 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 { @@ -1976,6 +2156,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 { @@ -2571,6 +2823,18 @@ var OpenShell_ServiceDesc = grpc.ServiceDesc{ MethodName: "CreateSandbox", Handler: _OpenShell_CreateSandbox_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, @@ -2675,6 +2939,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,