harden gsproxy

This commit is contained in:
Luxferre
2026-09-15 10:14:52 +03:00
parent 6b1bc595a3
commit a8f3767b90
2 changed files with 602 additions and 117 deletions
+34 -8
View File
@@ -24,11 +24,13 @@ The whole tool is written with core Perl modules only, so it runs without extra
- A gateway SOCKS port that follows the first alive proxy.
- Daemon mode with pidfile, status, and stop commands.
- Remote and local teardown that kills leftover gs-netcat processes.
- Offline self-tests that cover config parsing, command building, and the SOCKS handshake.
- Optional SOCKS5 username/password auth on the gateway port.
- Optional SHA-256 pinning of the remote gs-netcat binary.
- Offline self-tests that cover config parsing, command building, secret redaction, the single-instance lock, gateway auth, and the SOCKS handshake.
## Requirements
- Perl 5 with the core modules `Getopt::Long`, `IO::Socket::INET`, `IO::Select`, `POSIX`, `HTTP::Tiny`, `JSON::PP`, `Time::HiRes`, `File::Spec`, `Errno`, and `File::Basename`.
- Perl 5 with the core modules `Getopt::Long`, `IO::Socket::INET`, `IO::Select`, `POSIX`, `HTTP::Tiny`, `JSON::PP`, `Time::HiRes`, `File::Spec`, `Errno`, `File::Basename`, `Fcntl`, and `IO::Handle`.
- The `gs-netcat` binary in `PATH`, or a path supplied with `--gs-netcat`.
- Network access from the MCP endpoint so the remote bring-up command can download and run gs-netcat.
- A reachable MCP endpoint that exposes the `Tools_Shell_Command` tool.
@@ -38,6 +40,7 @@ The whole tool is written with core Perl modules only, so it runs without extra
- Copy `gsproxy.pl` somewhere on the host and make it executable with `chmod +x gsproxy.pl`.
- Make sure `gs-netcat` is installed and on the `PATH`.
- Create a `gsproxy.csv` next to the script, or point `--config` at another file.
- The script runs with `umask 0077`; runtime files are created mode 0600.
## Configuration
@@ -64,6 +67,8 @@ https://mcp.other.org,topsecret9,20002,beta
The `endpoint` may be a base URL or a full MCP URL. If it does not already end in `/gradio_api/mcp` or `/mcp`, the value of `--mcp-path` is appended.
Keep the config file mode 0600; the script warns when it is group/world readable.
## Usage
Run the tool as a daemon:
@@ -91,6 +96,7 @@ The script accepts these command line options:
- `--config FILE` sets the CSV config path. The default is `gsproxy.csv` next to the script.
- `--gateway-port PORT` sets the gateway SOCKS port. The default is `1080`.
- `--bind ADDR` sets the address the gateway listens on. The default is `0.0.0.0`.
- `--gateway-auth FILE` requires SOCKS5 username/password auth on the gateway. The first non-empty, non-comment line of FILE is `user:pass`; keep the file mode 0600.
- `--interval SEC` sets the monitor interval in seconds. The default is `30`.
- `--timeout SEC` sets the per-check timeout in seconds. The default is `10`.
- `--mcp-timeout SEC` sets the MCP shell command timeout in seconds. The default is `60`.
@@ -98,6 +104,7 @@ The script accepts these command line options:
- `--test-port PORT` sets the port probed through each SOCKS proxy. The default is `443`.
- `--workdir DIR` sets the remote working directory for the MCP call. The default is `.`.
- `--gs-netcat PATH` sets the gs-netcat binary. The default is `gs-netcat` from `PATH`.
- `--target-sha256 HASH` pins the SHA-256 of the remote gs-netcat binary (64 hex chars). The remote bring-up verifies the extracted binary before running it; a mismatch aborts the bring-up.
- `--log FILE` sets the log file. The default is `gsproxy.log` next to the script.
- `--pidfile FILE` sets the pidfile. The default is `gsproxy.pid` next to the script.
- `--state FILE` sets the runtime state file. The default is `gsproxy.state` next to the script.
@@ -109,7 +116,7 @@ The script accepts these command line options:
- `--once` runs one monitor pass and then exits.
- `--list` parses the config, prints the rows, and exits.
- `--status` reports whether the daemon is running.
- `--stop` stops a running daemon and tears down proxies.
- `--stop` stops running daemons and tears down proxies.
- `--selftest` runs the offline self-tests.
- `-h, --help` shows the help text.
@@ -136,6 +143,8 @@ On every interval the monitor checks each proxy, logs only status changes, and w
The gateway listens, forks per connection, and reads the target port from the state file. It opens a TCP connection to that port and copies bytes in both directions with `IO::Select`, shutting down each direction as its side closes. If the state file has no valid port, the connection is dropped.
Without `--gateway-auth` the gateway is a transparent relay. With `--gateway-auth` the gateway performs the SOCKS5 username/password exchange itself (RFC 1929), then greets the upstream proxy and forwards the client's CONNECT request, so only authenticated clients can route through it.
### Teardown
Stopping the tool cleans up both sides. Locally it scans `/proc` for gs-netcat processes that match the `-D -p PORT` shape and sends them `TERM`. Remotely it runs a shell command over MCP that scans `/proc` for processes whose environment contains `GSOCKET_ARGS=-lSD -s SECRET` and kills them.
@@ -148,6 +157,7 @@ Stopping the tool cleans up both sides. Locally it scans `/proc` for gs-netcat p
- List config rows: `./gsproxy.pl --list`
- One pass, foreground: `./gsproxy.pl --once --no-daemon`
- Run self-tests: `./gsproxy.pl --selftest`
- Run with gateway auth: `./gsproxy.pl --gateway-auth /etc/gsproxy.auth`
## Files
@@ -155,6 +165,9 @@ Stopping the tool cleans up both sides. Locally it scans `/proc` for gs-netcat p
- `gsproxy.log` is the append-only log, one UTC-stamped line per event.
- `gsproxy.pid` holds the daemon and gateway process ids.
- `gsproxy.state` holds the first alive port on the first line and all alive ports on the second.
- `gsproxy.auth` (with `--gateway-auth`) holds one `user:pass` line.
All runtime files are created mode 0600.
## Logging
@@ -166,27 +179,40 @@ Every event is written with a UTC timestamp in the form `[YYYY-MM-DDTHH:MM:SSZ]
- It re-reads the state file on every new connection, so failover is immediate.
- A dead or missing target port causes the connection to be dropped without reply.
- Each connection is handled by a forked child, keeping the listener responsive.
- With `--gateway-auth`, clients must complete a SOCKS5 username/password login before any relay happens.
## Self-tests
The `--selftest` mode runs offline checks and prints `PASS` or `FAIL` per case. It writes a temporary CSV, a temporary state file, and a temporary log under `/tmp`. It covers config parsing, command string quoting, gs-netcat process discovery, live and dead SOCKS detection, and a full gateway relay against a fake SOCKS server. It exits non-zero if any check fails.
The `--selftest` mode runs offline checks and prints `PASS` or `FAIL` per case. It writes a temporary CSV, a temporary state file, and a temporary log under `/tmp`. It covers config parsing, command string quoting, gs-netcat process discovery, secret redaction, the single-instance lock, zombie detection, gateway auth accept/reject, live and dead SOCKS detection, and a full gateway relay (with and without auth) against a fake SOCKS server. It exits non-zero if any check fails.
## Hardening
- **File permissions.** The script runs with `umask 0077`, creates the log, pidfile, state, and auth files with mode 0600, and tightens pre-existing files at startup. It warns when the CSV config or the gateway auth file is group/world readable.
- **Secret redaction.** Every log line passes through a redactor that replaces configured secrets with `**`. This includes MCP tool results that echo the full remote command, so secrets no longer leak into the log.
- **Single instance.** An advisory `flock` on the pidfile plus a `/proc` scan for already-daemonized gsproxy processes prevents the overlapping-instance failure mode. A second instance refuses to start, and `--once`/foreground runs refuse while a daemon is active.
- **Gateway supervision.** The gateway child reports bind success through a pipe before serving. A bind failure aborts startup instead of leaving a half-alive instance, and the monitor restarts a crashed gateway (reaping zombies on every pass).
- **Verified stop.** `--stop` only signals processes that are really gsproxy instances, waits up to five seconds for them to exit, escalates to `KILL` if needed, and also finds daemons that are missing from the pidfile.
- **Gateway auth.** `--gateway-auth FILE` turns the gateway into a SOCKS5 username/password front-end. Without it the gateway is an open relay, and a warning is logged whenever the gateway binds to a non-loopback address.
- **Binary pinning.** `--target-sha256 HASH` verifies the downloaded gs-netcat binary on the remote host before it runs, so a tampered download fails the bring-up.
## Security notes
- Secrets are masked in logs and list output, so only the first two characters appear.
- Secrets are masked in list output and redacted from every log line, including MCP tool results that echo remote commands.
- All runtime files (log, pidfile, state, gateway auth file) are created with mode 0600.
- The remote process is deliberately renamed to `python` to hide on the target host.
- The `--insecure` flag turns off TLS verification and should be used only when the MCP endpoint is trusted.
- The gateway binds to `0.0.0.0` by default. Restrict `--bind` if the host should not expose the proxy to the network.
- The gateway binds to `0.0.0.0` by default and is an open relay unless `--gateway-auth` is set. Restrict `--bind` to `127.0.0.1` or enable auth if the host should not expose the proxy to the network.
- Anyone who can reach the gateway SOCKS port can route through the current proxy, so the port should be firewalled.
## Troubleshooting
- If the script reports no usable rows, check that the CSV has valid `endpoint,secret,port` fields and a numeric port.
- If the daemon will not start, run `--status` to see whether it is already running, then use `--stop` first.
- If the daemon will not start, run `--status` to see whether it is already running, then use `--stop` first. A live daemon detected in `/proc` or a locked pidfile also refuses startup.
- If MCP bring-up fails, confirm the endpoint exposes `Tools_Shell_Command` and try `--insecure` only if the certificate is the problem.
- If no proxy becomes alive, run `--list` to confirm the rows and check that `gs-netcat` is on the `PATH`.
- If the gateway has no target, watch the log for `SOCKS ... is dead` lines to see which proxies are failing.
- If the gateway port is already in use, the script aborts at startup with `gateway failed to start` instead of lingering.
## Credits
Created by Luxferre in 2026, released into the public domain with no warranties.
Created by Luxferre in 2026, released into the public domain with no warranties.