Skip to content

feat(sandbox): proxy-side OCI request signing for CONNECT tunnels (credential_signing: oci) #3960

Description

@fede-kamel

Problem Statement

OpenShell replaces real secrets with opaque placeholders in sandbox environment variables and resolves them in HTTP headers (Bearer, Basic) before forwarding upstream. That is enough for the OCI Generative AI OpenAI-compatible endpoint, which is why the oci-genai example profile (#3904) works today.

Every other OCI API is signed, not bearer-authenticated. OCI request signing is an RSA-SHA256 HTTP Signature over date (request-target) host, plus content-length content-type x-content-sha256 for requests with a body, with keyId either <tenancy>/<user>/<fingerprint> (API key) or ST$<security-token> (session, instance, resource, or workload principal). Like SigV4 (#1631), a placeholder value produces an invalid signature that header replacement cannot fix. Sandboxed agents therefore cannot reach the native Generative AI API (/20231130), Object Storage, or any other OCI service through the credential proxy.

Proposed Design

Add proxy-side OCI signing in the sandbox supervisor's CONNECT tunnel path, as a sibling of SigV4. When credential_signing: oci is set on a REST endpoint (policy YAML or provider profile endpoint), the proxy:

  1. Terminates TLS as usual for protocol: rest.
  2. Strips the client's authorization, date, x-date, and x-content-sha256 headers before the fail-closed placeholder scan.
  3. Resolves OCI_KEY_ID and OCI_PRIVATE_KEY (PEM, optional OCI_PRIVATE_KEY_PASSPHRASE) from the SecretResolver. OCI_KEY_ID is either the API-key triple or an ST$ token, so the same code path serves all OCI principals once the token is minted.
  4. Sets date (RFC 7231) if absent, computes x-content-sha256 as base64 SHA-256 of the body for POST, PUT, and PATCH, and signs the header string with RSA PKCS#1 v1.5 SHA-256. ring is banned by deny.toml; aws-lc-rs already in the tree provides the primitive.
  5. Injects Authorization: Signature version="1",keyId="...",algorithm="rsa-sha256",headers="...",signature="..." and forwards.

No signing_service or region parsing is needed: OCI signatures are host-independent, which makes this simpler than SigV4.

endpoints:
  - host: "inference.generativeai.*.oci.oraclecloud.com"
    port: 443
    protocol: rest
    credential_signing: oci
    rules:
      - allow: { method: POST, path: "/20231130/actions/chat" }
  - host: "objectstorage.*.oraclecloud.com"
    port: 443
    protocol: rest
    credential_signing: oci
    access: read-write

Edge cases to cover: header casing and duplicate host; requests without content-type (OCI accepts application/json default only for JSON bodies; sign what is sent); streaming or chunked bodies must be buffered or rejected because x-content-sha256 needs the full body; OCI rejects date skew beyond five minutes; passphrase-protected keys; non-commercial realms use other domains and need no code change.

Impact / Why This Matters

Without this, OCI users must inject a long-lived API key and private key as plain values, run a signing bridge per service (the workaround the aws-bedrock profile header warns against), or stay on the bearer-only Generative AI endpoint. Oracle Cloud Infrastructure is named as an infrastructure partner of the Open Agent Safety Platform and hosts a growing share of NVIDIA GPU capacity; agents running there need the same governed path AWS got with SigV4.

Acceptance Criteria

  • credential_signing: oci is accepted by policy and profile validation alongside the sigv4* values.
  • A sandbox holding only placeholders can POST /20231130/actions/chat on the Generative AI native API and GET/PUT objects on Object Storage, with the real key never present in the sandbox.
  • Signatures verify against the OCI Python SDK signer for fixed test vectors (generic headers, body headers, ST$ key ids).
  • Client-supplied authorization, date, x-date, and x-content-sha256 are stripped before signing and never leak upstream.
  • Example profiles for the native Generative AI API and Object Storage are added under providers/ with header notes, following Remove the inference provider table and provider profile telemetry buckets #3906.
  • Docs for the credential_signing field list oci next to sigv4.

Alternatives Considered

  • Bridge-fronted profile like aws-bedrock: works today but moves the OCI credential into another workload and grants the sandbox egress to it. Documented as the interim path, not the goal.
  • Sandbox-side signing with injected key material: OpenShell delivers env credentials as proxy-resolved placeholders by design, so an in-sandbox SDK cannot sign with them.
  • Reuse sigv4: different algorithm (RSA vs HMAC), different canonical string, different header set. A sibling module is cleaner than a shared abstraction over two schemes.

Agent Investigation

Checked against main at 5acaaba:

Component Key files Role
Signing scheme enum crates/openshell-supervisor-network/src/l7/mod.rs:83 (CredentialSigning) Add Oci variant
Policy validation crates/openshell-policy/src/lib.rs:1222, :1529 Accept oci alongside sigv4, sigv4:body, sigv4:no_body
Profile schema crates/openshell-providers/src/profiles.rs:360-361, :420-422 (credential_signing, signing_service) signing_service stays optional and unused for oci
Proxy wiring crates/openshell-supervisor-network/src/l7/rest.rs:907 (strip), :985 (resolve keys), :1083, :1102 (apply) Mirror the SigV4 branches
Signer new crates/openshell-supervisor-network/src/oci_signature.rs, sibling of sigv4.rs (585 lines) Canonical string, body hash, RSA-SHA256, header assembly
Example profiles providers/oci-genai-native.yaml, providers/oci-object-storage.yaml Reviewable YAML only, per #3906

Related: #3879 (umbrella), #3904 (bearer profile, merged), #1631 / #1638 (SigV4 precedent). Principal-based minting of the ST$ token and session key is a separate follow-up, filed alongside this issue, and consumes this signing path unchanged.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    state:triage-neededOpened without agent diagnostics and needs triage

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions