MCPcopy Index your code
hub / github.com/beac0n/ruroco

github.com/beac0n/ruroco @v0.14.2

Chat with this repo
repository ↗ · DeepWiki ↗ · release v0.14.2 ↗ · + Follow
524 symbols 1,481 edges 70 files 18 documented · 3%
What it actually does AI analysis from the code graph — generated when you open this
loading…
README

build release codecov

ruroco - Run Remote Command

ruroco is a tool that lets you execute commands on a server by sending UDP packets.

the tool consist of 4 binaries:

  • ruroco-client: runs on your notebook/computer and sends the UDP packets
  • ruroco-client-ui: presents most of the functionality of ruroco-client in an easier to use user interface.
  • ruroco-server: receives the UDP packets and makes sure that they are valid
  • ruroco-commander: runs the command encoded by the data of the UDP packet if it's valid

The commands are configured on the server side, so the client does not define what is going to be executed, it only picks from existing commands.

Table of Contents

Installation

Download binaries from the releases page or build them yourself by running

make release

you can find the binaries in target/release/client, target/release/client_ui, target/release/server and target/release/commander

Client

Self-build

See make goal install_client. This builds the project and copies the client binary to /usr/local/bin/ruroco-client

Pre-build

Run the following script

curl -Ls "$(curl -s https://api.github.com/repos/beac0n/ruroco/releases/latest | grep -oE 'https://[^"]*/client-v[0-9]+\.[0-9]+\.[0-9]+-x86_64-linux')" -o ~/.local/bin/ruroco-client 
chmod +x ~/.local/bin/ruroco-client
~/.local/bin/ruroco-client update --force

Server

Self-build

See make goal install_server, which

  • Builds the project
  • Copies the client binary to ~/.local/bin/
  • Copies the server binaries to /usr/local/bin/
  • Runs ruroco-client wizard
  • After running the make goal, you have to
    • generate a shared .key file and copy it to the right place
    • setup the config.toml

Pre-build

Run the following script

curl -Ls "$(curl -s https://api.github.com/repos/beac0n/ruroco/releases/latest | grep -oE 'https://[^"]*/client-v[0-9]+\.[0-9]+\.[0-9]+-x86_64-linux')" -o ~/.local/bin/ruroco-client 
chmod +x ~/.local/bin/ruroco-client
~/.local/bin/ruroco-client update --force
sudo ~/.local/bin/ruroco-client wizard

Android

See nix/android.nix, scripts/dev_ui_android.sh and scripts/release_android.sh

security

  • client sends UDP packet to server, server never responds to it -> port-scanning does not help an adversary
  • data sent from client to server is encrypted symmetrically with AES-256-GCM using a shared key
  • client only defines command to execute, commands are saved on server -> client can pick command but not define it
  • run server software in such a way so that it uses as little operating system rights as possible
  • replay protection by adding every packet that the server received to a blocklist

client ui usage

ruroco-client-ui

Use the Generate Key action (or ruroco-client gen) to produce a base64-encoded shared key. Copy that key into the server's .key files (see server config) and reuse the same string with ruroco-client send. You can save your key safely in a password manager or use secret-tool to store it in the local keyring, e.g. with secret-tool store --label="ruroco" token ruroco

client usage

commands

ruroco-client
Usage: ruroco-client <COMMAND>

Commands:
  gen     Generate a shared AES key (base64 with embedded key id)
  send    Send a command to a specific address
  update  Update the client binary
  wizard  Run the wizard to set up the server side
  reseed  Reseed the replay-protection counter to the current timestamp
  help    Print this message or the help of the given subcommand(s)

Options:
  -h, --help     Print help
  -V, --version  Print version

gen

ruroco-client gen
Generate a shared AES key (base64 with embedded key id)

Usage: ruroco-client gen

Options:
  -h, --help  Print help

send

ruroco-client send --help
Send a command to a specific address

Usage: ruroco-client send [OPTIONS] --address <ADDRESS> --key <KEY>

Options:
  -a, --address <ADDRESS>          Address to send the command to
  -k, --key <KEY>                  Base64 key with id (output of `ruroco-client gen` or the UI)
  -c, --command <COMMAND>          Command to send [default: default]
  -e, --permissive                 Allow permissive IP validation - source IP does not have to match provided IP
  -i, --ip <IP>                    Optional IP address from which the command was sent. Use -6ei "dead:beef:dead:beef::/64" to allow you whole current IPv6 network. To do this automatically, use -6ei $(curl -s6 https://api64.ipify.org | awk -F: '{print $1":"$2":"$3":"$4"::/64"}')
  -4, --ipv4                       Connect via IPv4
  -6, --ipv6                       Connect via IPv6
  -d, --send-delay-ms <DELAY_MS>   Delay in milliseconds between sending to multiple destinations (IPv4 + IPv6) [default: 50]
  -h, --help                       Print help

Pass the same base64 key string that you placed on the server. Example:

ruroco-client send -a 127.0.0.1:34020 -k "$(secret-tool lookup token ruroco)" -c default

update

ruroco-client update --help
Update the client binary

Usage: ruroco-client update [OPTIONS]

Options:
  -f, --force                  Force update even if already on the latest version
  -v, --version <VERSION>      Target version to install (default: latest)
  -b, --bin-path <BIN_PATH>    Directory where the binary is saved (default: same as current binary)
  -s, --server                 Update server-side binaries instead of the client
  -h, --help                   Print help

Every downloaded binary is verified against an Ed25519 signature before it is written to disk. The public key is embedded in the client at build time; releases are signed in CI with the matching private key. If the .sig asset is missing or the signature does not match, the update aborts and the existing binary is left untouched. As a result, the client can only update to releases that ship signatures (v0.14.0 and later).

reseed

ruroco-client reseed

Resets the local replay-protection counter to the current nanosecond timestamp. Use this if the counter file is lost or corrupted, or after a system clock jump that would cause the server to reject packets.

The UI equivalent is the Reseed Counter button on the dashboard.

wizard

ruroco-client wizard --help
Run the wizard to set up the server side

Usage: ruroco-client wizard [OPTIONS]

Options:
  -f, --force   Overwrite existing configuration files
  -h, --help    Print help

server usage

ruroco-server --help
Usage: ruroco-server [OPTIONS]

Options:
  -c, --config <CONFIG>  [default: /etc/ruroco/config.toml]
  -h, --help             Print help
  -V, --version          Print version

commander usage

ruroco-commander --help
Usage: ruroco-commander [OPTIONS]

Options:
  -c, --config <CONFIG>  [default: /etc/ruroco/config.toml]
  -h, --help             Print help
  -V, --version          Print version

server config

  1. run ruroco-client gen > ~/.config/ruroco/user.key to create a shared base64 key (includes key id)
  2. copy the same .key file to the server config dir (default /etc/ruroco/user.key); the server loads every *.key file there
  3. add server config to /etc/ruroco/config.toml -> see config.toml
  4. call ruroco-client send with -k "$(secret-tool lookup token ruroco)" so client and server share the identical key

use cases

single packet authorization (SPA)

If you host a server on the web, you know that you'll get lots of brute-force attacks on (at least) the SSH port of your server. While using good practices in securing your server will keep you safe from such attacks, these attacks are quite annoying (filling up logs) and even if you secured your server correctly, you will still not be 100% safe, see https://www.schneier.com/blog/archives/2024/04/xz-utils-backdoor.html or https://www.qualys.com/2024/07/01/cve-2024-6387/regresshion.txt

Completely blocking all traffic to all ports that do not have to be open at all times can reduce the attack surface. But blocking the SSH port completely will make SSH unusable for that server.

This is where ruroco comes in. Ruroco can execute a command that opens up the SSH port for just a short amount of time, so that you can ssh into your server. Afterward ruruco closes the SSH port again. To implement this use case with ruroco, you have to use a configuration similar to the one shown below:

# see chapter "server config"
[commands]
open_ssh = "ufw allow from $RUROCO_IP proto tcp to any port 22"         #  open ssh for IP where request came from
close_ssh = "ufw delete allow from $RUROCO_IP proto tcp to any port 22" # close ssh for IP where request came from

If you have configured ruroco on server like that and execute the following client side command

ruroco-client send --address host.domain:8080 --command open_ssh --key "$(secret-tool lookup token ruroco)"

If you want to use a different IP address than the one you are sending the packet from, you can use the --ip argument together with --permissive:

ruroco-client send --address host.domain:8080 --command open_ssh --ip 94.111.111.111 --permissive --key "$(secret-tool lookup token ruroco)"

If you want to make sure that an adversary does not spoof your source IP address, you can get your external IP address from a service - the ruroco server will make sure that the IP addresses match:

ruroco-client send --address host.domain:8080 --command open_ssh --ip $(curl -s https://api64.ipify.org) --key "$(secret-tool lookup token ruroco)"

the server will validate that the client is authorized to execute that command by using the shared AES key (id is sent with the packet) and will then execute the command defined in the config above under "open_ssh". The --deadline argument means that the command has to be started on the server within 5 seconds after executing the command.

This gives you the ability to effectively only allow access to the SSH port, for only the IP that the UDP packet was sent from, if you want to connect to your server. Of course, you should also do all the other security hardening tasks you would do if the SSH port would be exposed to the internet.

You can define any number of commands you wish, by adding more commands to configuration file.

Enabling webservice

You may run a webservice like https://github.com/filebrowser/filebrowser on your server, which you do not want to publicly expose. If you use nginx as a reverse proxy, you can use ruroco to enable or disable services:

# see chapter "server config"
[commands]
disable_file_browser = "mv /etc/nginx/conf.d/https_file_browser.conf /etc/nginx/conf.d/https_file_browser.conf_disabled && nginx -s reload"
enable_file_browser = "mv /etc/nginx/conf.d/https_file_browser.conf_disabled /etc/nginx/conf.d/https_file_browser.conf && nginx -s reload"

If you have configured ruroco on server like that and execute the following client side command

ruroco-client send --address host.domain:8080 --command enable_file_browser --key "$(secret-tool lookup token ruroco)"

the file browser nginx config will be enabled and nginx reloaded, effectively making the file browser accessible.

troubleshooting

server rejects packets from one client

Each client must have its own unique key. If two clients share the same key, they each maintain an independent local counter, but the server tracks only one counter per key. Whichever client sends a packet last advances the server's counter - the other client's counter now lags behind, and the server will reject all its future packets as replays.

To fix this:

  1. Generate a new key for each client with ruroco-client gen (or the Generate button in the UI)
  2. Copy the new key to the server config dir alongside the existing key
  3. Use the new key when calling ruroco-client send

If you intentionally share a key (not recommended) and the counter falls out of sync, you can recover without generating a new key by running:

ruroco-client reseed

This resets the local counter to the current nanosecond timestamp, which will be higher than any value the server has seen and allows packets to be accepted again. The UI equivalent is the Reseed Counter button on the dashboard.

architecture

overview

The service consists of three parts:

  • ruroco-client
    • binary that is executed on your local host
  • ruroco-server
    • service that runs on a remote host where you wish to execute the commands on
    • exposed to the internet
    • has minimal rights to receive and decrypt data and to communicate with the commander
  • `ruroco-comman

Core symbols most depended-on inside this repo

info
called by 40
src/common/logging.rs
run
called by 25
src/server/mod.rs
error
called by 23
src/common/logging.rs
gen
called by 22
src/client/gen.rs
set_test_conf_dir
called by 20
src/client/send/core.rs
add
called by 20
src/ui/saved_command_list.rs
set_permissions
called by 14
src/client/util.rs
make_cmd
called by 12
src/ui/saved_command_list.rs

Shape

Function 335
Method 146
Class 39
Enum 4

Languages

Rust100%

Modules by API surface

src/client/update/mod.rs37 symbols
src/server/mod.rs33 symbols
tests/integration_test.rs28 symbols
src/client/send/core.rs27 symbols
src/server/config.rs25 symbols
src/ui/saved_command_list.rs23 symbols
src/server/commander.rs22 symbols
src/server/blocklist.rs21 symbols
src/common/fs.rs21 symbols
src/ui/command_data.rs18 symbols
src/client/counter.rs14 symbols
src/ui/tabs/widgets.rs12 symbols

For agents

$ claude mcp add ruroco \
  -- python -m otcore.mcp_server <graph>

⬇ download graph artifact