Skip to content

feat(server): implement SSH host key pinning for sandbox connections (SEC-003 follow-up) #557

@johntmyers

Description

@johntmyers

Problem Statement

SandboxSshClientHandler::check_server_key in crates/openshell-server/src/grpc.rs unconditionally returns Ok(true), accepting any SSH host key from the sandbox. While the HMAC handshake preface provides some authentication and the connection is internal-only, a MITM positioned on the gateway-to-sandbox network path could intercept the SSH session without detection.

PR #548 (SEC-003) added defense-in-depth command validation and logging at the transport boundary, but did not address the host key verification gap. This follow-up implements full SSH host key pinning.

Proposed Design

  1. At sandbox creation time: When the sandbox pod starts its SSH server, the server generates an SSH host key. Record the host key fingerprint (SHA-256) in the sandbox's status record in the K8s store (e.g., status.ssh_host_key_fingerprint).

  2. At exec time: When run_exec_with_russh connects to the sandbox, pass the expected fingerprint to SandboxSshClientHandler. In check_server_key, compute the fingerprint of server_public_key and compare it to the expected value. Return Ok(true) only on match.

  3. Fallback: If the fingerprint is not yet available (e.g., during a rolling upgrade where old sandboxes don't have the field), log a warning and fall back to accepting any key (current behavior). This allows a graceful transition.

Key considerations

  • The sandbox SSH server's host key must be generated deterministically or stored persistently across pod restarts to avoid false rejections.
  • The fingerprint must be populated before the sandbox reaches Ready phase.
  • The ExecSandboxRequest proto may need a field for the expected fingerprint, or it can be loaded from the sandbox status record in exec_sandbox.

Alternatives Considered

  • Certificate-based SSH auth: Use the existing mTLS PKI to issue SSH certificates. More robust but significantly more complex.
  • IP range validation only: Already implemented in PR fix: security hardening batch 1 (SEC-002 through SEC-010) #548 (SEC-006). Complements but doesn't replace host key verification.
  • Mutual authentication via the HMAC preface: The preface authenticates the gateway to the sandbox, but not the sandbox to the gateway. Host key pinning closes this direction.

Agent Investigation

Codebase analysis from the SEC-003 security review (PR #548):

  • SandboxSshClientHandler::check_server_key at crates/openshell-server/src/grpc.rs:3725 — unconditionally returns Ok(true)
  • authenticate_none("sandbox") at crates/openshell-server/src/grpc.rs:3748 — no credential exchange
  • HMAC preface in start_single_use_ssh_proxy at crates/openshell-server/src/grpc.rs:3863
  • Sandbox SSH server key generation in crates/openshell-sandbox/src/ssh.rs

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions