mirror of
https://github.com/NVIDIA/OpenShell.git
synced 2026-10-22 06:19:47 +08:00
* fix(security): authenticate extension services Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com> * feat(extension-core): verify gateway JWTs Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com> * refactor(extension-core): keep inbound verification external This should become an extension SDK package. Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com> * fix(security): harden the extension authentication contract Follow-up hardening on the alpha extension authentication mechanism. Claim contract: - Extension tokens carry an explicit `typ` of `openshell-ext+jwt`. They share a signing key with sandbox-to-gateway admission tokens and were otherwise separated by audience alone, so a verifier that neglects to check `aud` could accept a gateway credential. The header is a second, independent discriminator. - Publish OIDC-shaped discovery at `/.well-known/openid-configuration` so a service configured with only the gateway URL can learn the exact expected issuer and the JWKS location. It is shaped, not compliant: `issuer` is the gateway identity, not the serving URL. Audience agreement: - `MiddlewareManifest` and `InterceptorManifest` gain `expected_audience`. The audience is otherwise configured independently on each side of the boundary, where a mismatch surfaces only as an opaque authentication failure on every call. OpenShell now compares the two and fails at startup. An empty field keeps the check off for existing services. Compatibility: - Add `allow_insecure_transport` per registration. Enabling gateway JWT signing previously made any plaintext endpoint a hard startup failure, including the endpoint form used in our own documentation. The opt-out attaches no credential, is refused by the gateway if a supervisor asks for one, and warns at every startup. - Make the transport requirement kind-aware. A middleware endpoint must be reachable from every sandbox supervisor, so only interceptors may use a gateway-local Unix socket. Credential lifecycle: - Replace the process-global slot map with a supervisor-owned `ExtensionCredentialStore` shared explicitly across the gateway connections the supervisor opens, removing test-order coupling. - Rotate only when a credential is missing or has passed four fifths of its lifetime. Configuration polling ran every ten seconds against fifteen-minute credentials, so each poll re-ran gateway effective-policy resolution and re-minted the gateway token. - Bound credential minting per sandbox, since each request resolves the caller's effective policy. Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com> * docs: record alpha extension authentication in RFC appendices Restore the RFC 0009 and 0010 bodies to their accepted text and move every extension-authentication update into appendices instead. An RFC records a decision at a point in time; superseding detail belongs alongside it rather than rewritten into it. RFC 0009's appendix carries the shared contract: claims, authorization, key distribution, the `allow_insecure_transport` replacement for the body's `allow_insecure`, and residual risks. RFC 0010's records only what differs for interceptors and links to it. The existing protocol-extensions appendix, which parked the phase 2 transport question, now points forward to what was built. Also document the audience handshake, the discovery endpoint, the `typ` requirement, and `jti` replay guidance in the extensibility and gateway configuration pages, and correct the middleware transport guidance: middleware endpoints must be reachable from sandbox supervisors, so Unix sockets are not an option there. Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com> * refactor(extension-core): abstract extension server trust Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com> * docs(core): update middleware manifest example Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com> * fix(extension-auth): preserve unsigned gateway compatibility Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com> * fix(extension-auth): reject cross-domain token replay Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com> --------- Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>
146 lines
5.3 KiB
Protocol Buffer
146 lines
5.3 KiB
Protocol Buffer
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
syntax = "proto3";
|
|
|
|
package openshell.gateway_interceptor.v1;
|
|
|
|
import "google/protobuf/struct.proto";
|
|
import "openshell.proto";
|
|
|
|
// GatewayInterceptor lets an external governance service evaluate gateway
|
|
// control-plane operations after OpenShell admission and before or after the
|
|
// gateway applies the operation.
|
|
service GatewayInterceptor {
|
|
// Describe returns the interceptor manifest and declared bindings.
|
|
rpc Describe(DescribeRequest) returns (InterceptorManifest);
|
|
|
|
// SnapshotProviderProfiles returns the interceptor's current provider
|
|
// profile snapshot when the manifest advertises provider_profiles = true.
|
|
rpc SnapshotProviderProfiles(ProviderProfileSnapshotRequest)
|
|
returns (ProviderProfileSnapshot);
|
|
|
|
// Evaluate returns an allow, deny, or mutation decision for one operation
|
|
// phase.
|
|
rpc Evaluate(InterceptorEvaluation) returns (InterceptorResult);
|
|
}
|
|
|
|
message DescribeRequest {}
|
|
|
|
message ProviderProfileSnapshotRequest {}
|
|
|
|
enum GatewayInterceptorPhase {
|
|
GATEWAY_INTERCEPTOR_PHASE_UNSPECIFIED = 0;
|
|
GATEWAY_INTERCEPTOR_PHASE_MODIFY_OPERATION = 2;
|
|
GATEWAY_INTERCEPTOR_PHASE_VALIDATE = 3;
|
|
GATEWAY_INTERCEPTOR_PHASE_POST_COMMIT = 4;
|
|
}
|
|
|
|
message InterceptorEvaluation {
|
|
// Configured interceptor instance name.
|
|
string interceptor_name = 1;
|
|
// Manifest binding id selected for this evaluation.
|
|
string binding_id = 2;
|
|
// Public gRPC service name, e.g. "openshell.v1.OpenShell".
|
|
string service = 3;
|
|
// Public gRPC method name, e.g. "CreateSandbox".
|
|
string method = 4;
|
|
// Caller identity summary. Values are intentionally non-secret.
|
|
map<string, string> principal = 5;
|
|
|
|
// Exactly one phase payload is set for each evaluation.
|
|
oneof phase {
|
|
// Evaluation that may modify the prepared gateway operation.
|
|
ModifyOperationEvaluation modify_operation = 6;
|
|
// Evaluation that may reject, but not mutate, the prepared operation.
|
|
ValidateEvaluation validate = 7;
|
|
// Evaluation after the gateway operation has completed successfully.
|
|
PostCommitEvaluation post_commit = 8;
|
|
}
|
|
}
|
|
|
|
message ModifyOperationEvaluation {
|
|
// Gateway-prepared operation the gateway proposes to execute.
|
|
google.protobuf.Struct proposed_operation = 1;
|
|
}
|
|
|
|
message ValidateEvaluation {
|
|
// Gateway-prepared operation after earlier interceptor modifications.
|
|
google.protobuf.Struct proposed_operation = 1;
|
|
// Optional read-only gateway state loaded before applying the operation.
|
|
google.protobuf.Struct current_state = 2;
|
|
}
|
|
|
|
message PostCommitEvaluation {
|
|
// Protobuf JSON-shaped response returned by the committed gateway operation.
|
|
google.protobuf.Struct committed_response = 1;
|
|
}
|
|
|
|
message InterceptorResult {
|
|
// False denies the operation before side effects for modify_operation and
|
|
// validate. Post-commit denial is invalid.
|
|
bool allowed = 1;
|
|
// Human-readable reason for logs and denied gRPC status messages.
|
|
string reason = 2;
|
|
// Optional gRPC status code name for denials, e.g. "PERMISSION_DENIED".
|
|
string status_code = 3;
|
|
// RFC 6902 JSON patches. Only valid during modify_operation.
|
|
repeated JsonPatch patches = 4;
|
|
// Non-secret annotations included in gateway logs.
|
|
map<string, string> log_annotations = 5;
|
|
}
|
|
|
|
message InterceptorManifest {
|
|
// Human-readable interceptor name declared by the service.
|
|
string name = 1;
|
|
// Bindings declared by the interceptor service.
|
|
repeated InterceptorBinding bindings = 2;
|
|
// Optional default failure policy for bindings without their own policy.
|
|
// Supported values are "fail_closed" and "fail_open".
|
|
string failure_policy = 3;
|
|
// True when this interceptor implements SnapshotProviderProfiles.
|
|
bool provider_profiles = 4;
|
|
// Exact JWT audience this service verifies on inbound OpenShell calls.
|
|
// After authenticated Describe succeeds, the gateway refuses to start unless
|
|
// this matches the operator-configured audience. A strict verifier may reject
|
|
// an incorrect audience before returning this manifest. Empty skips this
|
|
// post-authentication consistency check.
|
|
string expected_audience = 5;
|
|
}
|
|
|
|
message ProviderProfileSnapshot {
|
|
// Opaque source revision used for cache freshness and sandbox reload checks.
|
|
string revision = 1;
|
|
// Complete profile snapshot vended by this source.
|
|
repeated openshell.v1.ProviderProfile profiles = 2;
|
|
}
|
|
|
|
message InterceptorBinding {
|
|
// Stable binding id used for config overrides and audit logs.
|
|
string id = 1;
|
|
// RPC selector. Selectors are intentionally tied to the public API shape.
|
|
InterceptorSelector selector = 2;
|
|
// Phases this binding wants to evaluate.
|
|
repeated GatewayInterceptorPhase phases = 3;
|
|
// Optional binding-specific failure policy.
|
|
// Supported values are "fail_closed" and "fail_open".
|
|
// Bindings that include post_commit must resolve to "fail_open" because the
|
|
// gateway operation has already committed before that phase runs.
|
|
string failure_policy = 4;
|
|
}
|
|
|
|
message InterceptorSelector {
|
|
// Full selector form: "openshell.v1.OpenShell/CreateSandbox".
|
|
string rpc = 1;
|
|
// Structured service/method form. If rpc is set, it takes precedence.
|
|
string service = 2;
|
|
string method = 3;
|
|
}
|
|
|
|
message JsonPatch {
|
|
string op = 1;
|
|
string path = 2;
|
|
google.protobuf.Value value = 3;
|
|
string from = 4;
|
|
}
|