Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Overview and Core Idea

Ruroco is built around one deliberately narrow idea: a client proves, with a shared secret, that it is allowed to trigger a named command on a server, while revealing nothing to anyone watching the wire and giving an attacker nothing to attack.

Everything in the codebase follows from that sentence. This chapter explains the idea and the shape of the system before the later chapters drill into the flow, the protocol, and the individual files.

The problem being solved

Exposing a service port to the internet (SSH, a web admin panel, a database) invites constant brute-force traffic and leaves you one zero-day away from compromise. The safe move is to keep the port firewalled shut. But then you cannot reach it either.

Ruroco resolves this tension. The port stays shut. When you want in, you send one encrypted UDP packet that authorizes a server-defined command (for example “add a firewall allow-rule for my IP”). The port opens just for you, just long enough to connect. A second command closes it again.

Opening a port like this is the use case that overlaps with a VPN — but it is only one shape of what ruroco does. The general job is to trigger a server-defined action: deploy, restart a service, rotate a secret, run a backup. In those cases there is no service to connect to, only something to make happen — and a VPN does not apply.

Ruroco is not a VPN

A fair question is why not just hide everything behind a VPN like WireGuard and open the tunnel when you need a service. For reaching your own services yourself, a VPN is the better tool: WireGuard is also silent to unauthenticated packets, far more heavily audited than any custom daemon, and gives you all your services at once once connected. Ruroco does not try to replace it.

The distinction is what each one grants. A VPN grants access; ruroco grants a capability. A VPN peer gets a position on your network and bidirectional reach to everything behind the tunnel, and a compromised client inherits that foothold. A ruroco client can only fire whitelisted, pre-defined commands and never gets a network position at all. That makes ruroco the right tool where a VPN does not fit:

  • triggering server-side actions (deploy, restart, backup) where there is no service to connect to, only an action to perform;
  • triggers from clients that cannot or should not hold a tunnel — a CI runner, a cron job, an IoT device, a phone tap: one stateless UDP packet, no handshake, no session;
  • granting a third party an action without granting them your network — capability without connectivity, a least-privilege property a VPN cannot express.

Putting ruroco in front of a VPN (knock to open the WireGuard port) is not a security win: it fronts a more-audited daemon with a less-audited one without removing the internet-facing packet parser. Use ruroco for what only it does — triggering actions — not to wrap a VPN that is already silent on its own.

The design constraints

These constraints are invariants. They are enforced throughout the code and called out in the per-module chapters.

mindmap
  root((Ruroco core idea))
    One-way
      Server never replies
      Nothing to port-scan
      No oracle for attackers
    Least authority
      Server unprivileged
      Commander privileged but local-only
      Unix socket boundary
    Client picks, never defines
      Sends Blake2b-64 hash of a name
      Commands live in server config
      Captured packet reveals nothing
    Confidential and authentic
      AES-256-GCM-SIV with shared key
      GCM tag authenticates
      Fresh random IV per packet
    Replay-proof
      u128 nanosecond counter
      Per-key monotonic floor
      Equal counter is a replay

The four modules at a glance

flowchart TB
    subgraph local["Local host (you)"]
        UI["client_ui (GUI)<br/>src/ui"]
        CLI["client (CLI)<br/>src/client"]
        UI -->|calls send directly| CLI
    end
    subgraph remote["Remote host"]
        SRV["server (unprivileged)<br/>src/server"]
        CMD["commander (privileged)<br/>src/commander"]
        SRV -->|24-byte CommanderData<br/>over Unix socket| CMD
    end
    CLI -->|"one 94-byte<br/>AES-256-GCM-SIV UDP datagram"| SRV
    CMD -->|"sh -c with $RUROCO_IP"| OS["configured shell command"]

    COM["common<br/>src/common<br/>crypto - protocol - ipc - fs - logging"]
    CLI -.shared code.- COM
    SRV -.shared code.- COM
    CMD -.shared code.- COM
  • client (src/client) is the CLI. It hashes a command name, builds the plaintext, encrypts it, and sends exactly one UDP datagram per destination IP. It also owns the local replay counter, key generation, self-update, and the server-setup wizard.
  • ui (src/ui) is an egui GUI. It is a thin view layer: it calls the client’s send path directly rather than reimplementing networking. The same binary runs on desktop and Android.
  • server (src/server) is the unprivileged, internet-facing daemon. It receives the datagram, decrypts it, runs rate-limit / replay / IP checks, and forwards an authorized command to the commander. It never sends a network response.
  • commander (src/commander, separate binary) is the privileged executor. It owns a local Unix socket, looks the command hash up in its config, and runs the configured shell command. It links no OpenSSL and no network code.
  • common (src/common) is the shared library: cryptography, the wire protocol, the server <-> commander IPC contract (CommanderData + socket path), atomic file IO, and the project’s own logger. Client, server, and commander each compile pieces of it, gated by Cargo features.

Where to go next