mirror of
git://f0xx.org/ac/ac-ms-remote-access
synced 2026-07-29 02:58:09 +03:00
initial
This commit is contained in:
42
examples/rssh/linux-sim/README.md
Normal file
42
examples/rssh/linux-sim/README.md
Normal file
@@ -0,0 +1,42 @@
|
||||
# Linux RSSH device/operator simulation
|
||||
|
||||
Simulates the **Android RSSH path** on a laptop with real OpenSSH client + optional local `sshd`.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- `curl`, `jq`, `sshpass` (for non-interactive sim)
|
||||
- Backend reachable (`CRASHES_BASE` or default from `ra_lib.sh`)
|
||||
- Admin whitelist + open session for the simulated `device_id`
|
||||
|
||||
## Device sim (heartbeat + reverse forward)
|
||||
|
||||
```bash
|
||||
export CRASHES_BASE="http://cast01.intra.raptor.org/app/androidcast_project/crashes"
|
||||
export RA_DEVICE_ID="linux-rssh-lab-01"
|
||||
|
||||
# 1) First run registers heartbeat (wait)
|
||||
./ra_device_sim.sh
|
||||
|
||||
# 2) Whitelist device + open session in admin UI, then:
|
||||
export RA_SKIP_ADMIN=1
|
||||
./ra_device_sim.sh
|
||||
```
|
||||
|
||||
## Operator sim (on bastion / BE)
|
||||
|
||||
After device sim prints `REMOTE_BIND_PORT` and username:
|
||||
|
||||
```bash
|
||||
./ra_operator_connect.sh 18022 ra-SESSIONID shell
|
||||
./ra_operator_connect.sh 18022 ra-SESSIONID sftp
|
||||
```
|
||||
|
||||
## Android parity
|
||||
|
||||
| Android | Linux sim |
|
||||
|---------|-----------|
|
||||
| `RemoteAccessService` poll | `ra_ra_post` via `ra_lib.sh` |
|
||||
| `RsshLocalSshServer` :8022 | local `sshd -p 8022` |
|
||||
| `ReverseSshTunnelBridge` JSch `-R` | `ssh -N -R …` |
|
||||
|
||||
See [REMOTE_ACCESS_IMPL.md](../../../docs/REMOTE_ACCESS_IMPL.md) and [20260602_REVERSE_SSH_proposals_summary.md](../../../docs/20260602_REVERSE_SSH_proposals_summary.md).
|
||||
75
examples/rssh/linux-sim/ra_device_sim.sh
Executable file
75
examples/rssh/linux-sim/ra_device_sim.sh
Executable file
@@ -0,0 +1,75 @@
|
||||
#!/usr/bin/env bash
|
||||
# Linux laptop simulates Android RSSH device: heartbeat poll + outbound -R + local sshd on 8022.
|
||||
set -euo pipefail
|
||||
|
||||
ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
|
||||
BE_SCRIPTS="$(cd "$(dirname "$0")/../../crash_reporter/backend/scripts" && pwd)"
|
||||
# shellcheck source=../../crash_reporter/backend/scripts/ra_lib.sh
|
||||
source "$BE_SCRIPTS/ra_lib.sh"
|
||||
|
||||
DEVICE_ID="${RA_DEVICE_ID:-linux-rssh-$(hostname -s)-$$}"
|
||||
RANDOM_ID="${RA_RANDOM:-rssh-linux-$(date +%s)}"
|
||||
LOCAL_PORT="${RA_LOCAL_PORT:-8022}"
|
||||
WORKDIR="${RA_SIM_WORKDIR:-/tmp/rssh-sim-$$}"
|
||||
SSH_PID=""
|
||||
DB_PID=""
|
||||
|
||||
cleanup() {
|
||||
[ -n "$SSH_PID" ] && kill "$SSH_PID" 2>/dev/null || true
|
||||
[ -n "$DB_PID" ] && kill "$DB_PID" 2>/dev/null || true
|
||||
rm -rf "$WORKDIR"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
mkdir -p "$WORKDIR"
|
||||
HOST_KEY="$WORKDIR/host_key"
|
||||
|
||||
if ! command -v ssh-keygen >/dev/null 2>&1; then
|
||||
echo "openssh client required" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "== local device SSH on 127.0.0.1:${LOCAL_PORT} =="
|
||||
ssh-keygen -t ed25519 -f "$HOST_KEY" -N "" -q
|
||||
/usr/sbin/sshd -D -f /dev/null -h "$HOST_KEY" -p "$LOCAL_PORT" -o AuthorizedKeysFile=/dev/null \
|
||||
-o PasswordAuthentication=yes -o PermitRootLogin=no -o UsePAM=no \
|
||||
-o AllowUsers="${RA_SSH_USER:-rssh-sim}" 2>/dev/null &
|
||||
DB_PID=$!
|
||||
sleep 0.5
|
||||
|
||||
echo "== heartbeat enable (tunnel_mode=rssh) =="
|
||||
export RA_TUNNEL_MODE=rssh
|
||||
ra_ra_post enable "$DEVICE_ID" "$RANDOM_ID" "linux-sim/1.0" >/tmp/rssh-sim-wait.json
|
||||
grep -q '"action":"wait"' /tmp/rssh-sim-wait.json && echo OK wait
|
||||
|
||||
echo "Operator must whitelist + open session in admin UI, then re-run with RA_SKIP_ADMIN=1"
|
||||
if [[ "${RA_SKIP_ADMIN:-}" != "1" ]]; then
|
||||
echo "Set RA_SKIP_ADMIN=1 after whitelisting device_id=$DEVICE_ID"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "== heartbeat connect =="
|
||||
ra_ra_post enable "$DEVICE_ID" "$RANDOM_ID-b" "linux-sim/1.0" >/tmp/rssh-sim-connect.json
|
||||
grep -q '"action":"connect"' /tmp/rssh-sim-connect.json || { cat /tmp/rssh-sim-connect.json; exit 1; }
|
||||
echo OK connect payload
|
||||
|
||||
BASTION="$(ra_json -r '.credentials.bastion_host // empty' /tmp/rssh-sim-connect.json)"
|
||||
BPORT="$(ra_json -r '.credentials.bastion_port // 443' /tmp/rssh-sim-connect.json)"
|
||||
USER="$(ra_json -r '.credentials.username // empty' /tmp/rssh-sim-connect.json)"
|
||||
PASS="$(ra_json -r '.credentials.password // empty' /tmp/rssh-sim-connect.json)"
|
||||
RPORT="$(ra_json -r '.credentials.remote_bind_port // 0' /tmp/rssh-sim-connect.json)"
|
||||
|
||||
[ -n "$BASTION" ] && [ -n "$USER" ] && [ "$RPORT" -gt 0 ] || { echo "missing credentials"; exit 1; }
|
||||
|
||||
echo "== reverse SSH -R 127.0.0.1:${RPORT}:127.0.0.1:${LOCAL_PORT} =="
|
||||
sshpass -p "$PASS" ssh -N \
|
||||
-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \
|
||||
-p "$BPORT" -R "127.0.0.1:${RPORT}:127.0.0.1:${LOCAL_PORT}" \
|
||||
"${USER}@${BASTION}" &
|
||||
SSH_PID=$!
|
||||
sleep 2
|
||||
|
||||
echo "Tunnel up. Operator on bastion:"
|
||||
echo " ssh -p ${RPORT} ${USER}@127.0.0.1"
|
||||
echo "Press Ctrl+C to stop."
|
||||
wait "$SSH_PID"
|
||||
25
examples/rssh/linux-sim/ra_operator_connect.sh
Executable file
25
examples/rssh/linux-sim/ra_operator_connect.sh
Executable file
@@ -0,0 +1,25 @@
|
||||
#!/usr/bin/env bash
|
||||
# Operator-side: SSH/SFTP to forwarded port on bastion (run on BE or jump host).
|
||||
set -euo pipefail
|
||||
|
||||
RPORT="${1:-}"
|
||||
USER="${2:-}"
|
||||
MODE="${3:-shell}"
|
||||
|
||||
if [ -z "$RPORT" ] || [ -z "$USER" ]; then
|
||||
echo "usage: $0 REMOTE_BIND_PORT RA_USERNAME [shell|sftp]" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
case "$MODE" in
|
||||
shell)
|
||||
exec ssh -p "$RPORT" -o StrictHostKeyChecking=no "${USER}@127.0.0.1"
|
||||
;;
|
||||
sftp)
|
||||
exec sftp -P "$RPORT" -o StrictHostKeyChecking=no "${USER}@127.0.0.1"
|
||||
;;
|
||||
*)
|
||||
echo "unknown mode: $MODE" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
9
examples/wireguard/BE_alpine/README.txt
Normal file
9
examples/wireguard/BE_alpine/README.txt
Normal file
@@ -0,0 +1,9 @@
|
||||
alpine:
|
||||
# apk add wireguard-tools iptables
|
||||
# wg genkey | tee server.privatekey | wg pubkey > server.publickey
|
||||
|
||||
create /etc/wireguard/wg0.conf -> symlinked to /var/www/localhost/htdocs/apps/app/androidcast_project/android_cast/examples/wireguard/BE_alpine/wg0.conf
|
||||
|
||||
follow https://wiki.alpinelinux.org/wiki/Configure_a_Wireguard_interface_(wg)
|
||||
|
||||
# chmod go= server.privatekey
|
||||
1
examples/wireguard/BE_alpine/server.privatekey
Normal file
1
examples/wireguard/BE_alpine/server.privatekey
Normal file
@@ -0,0 +1 @@
|
||||
uIhZdcs0U6W09PVytrMD7qChzzvAgBAnZUI47V/axX4=
|
||||
1
examples/wireguard/BE_alpine/server.publickey
Normal file
1
examples/wireguard/BE_alpine/server.publickey
Normal file
@@ -0,0 +1 @@
|
||||
EV2nnwHH7xe3jA5J46Rjtl8ao+ybZinibKg2Hy83GBw=
|
||||
12
examples/wireguard/BE_alpine/wg0.conf
Normal file
12
examples/wireguard/BE_alpine/wg0.conf
Normal file
@@ -0,0 +1,12 @@
|
||||
[Interface]
|
||||
Address = 172.200.2.1/16, fddd::ffff/64
|
||||
ListenPort = 45340
|
||||
# the key from the previously generated privatekey file
|
||||
PrivateKey = uIhZdcs0U6W09PVytrMD7qChzzvAgBAnZUI47V/axX4=
|
||||
PostUp = iptables -A FORWARD -i %i -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE;iptables -A FORWARD -o %i -j ACCEPT; ip6tables -A FORWARD -i %i -j ACCEPT; ip6tables -t nat -A POSTROUTING -o eth0 -j MASQUERADE;ip6tables -A FORWARD -o %i -j ACCEPT
|
||||
PostDown = iptables -D FORWARD -i %i -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE;iptables -D FORWARD -o %i -j ACCEPT; ip6tables -D FORWARD -i %i -j ACCEPT; ip6tables -t nat -D POSTROUTING -o eth0 -j MASQUERADE;ip6tables -D FORWARD -o %i -j ACCEPT
|
||||
|
||||
[Peer]
|
||||
# Static lab peer only — remote-access clients are added dynamically via wg set (PHP).
|
||||
# PublicKey = <client-device-pubkey>
|
||||
# AllowedIPs = 172.200.2.2/32
|
||||
43
examples/wireguard/FE_wireguard_dnat.sh
Executable file
43
examples/wireguard/FE_wireguard_dnat.sh
Executable file
@@ -0,0 +1,43 @@
|
||||
#!/bin/bash
|
||||
# FE (f0xx-monstro): DNAT public UDP → BE wg0. Matches existing rules to 10.7.16.128 (8088, etc.).
|
||||
# Invoked from xen_firewall.sh start/stop; also: wireguard_fe_dnat.sh [start|stop]
|
||||
set -euo pipefail
|
||||
|
||||
BE_IP="${BE_IP:-10.7.16.128}"
|
||||
WG_PORT="${WG_PORT:-45340}"
|
||||
|
||||
function start() {
|
||||
if iptables -t nat -C PREROUTING -p udp --dport "$WG_PORT" -j DNAT --to-destination "${BE_IP}:${WG_PORT}" 2>/dev/null; then
|
||||
echo "DNAT rule already present for udp/${WG_PORT}"
|
||||
else
|
||||
iptables -t nat -A PREROUTING -p udp --dport "$WG_PORT" -j DNAT --to-destination "${BE_IP}:${WG_PORT}"
|
||||
echo "Added PREROUTING DNAT udp/${WG_PORT} -> ${BE_IP}:${WG_PORT}"
|
||||
fi
|
||||
|
||||
if iptables -C FORWARD -p udp -d "${BE_IP}" --dport "$WG_PORT" -j ACCEPT 2>/dev/null; then
|
||||
echo "FORWARD rule already present"
|
||||
else
|
||||
iptables -A FORWARD -p udp -d "${BE_IP}" --dport "$WG_PORT" -j ACCEPT
|
||||
echo "Added FORWARD udp -> ${BE_IP}:${WG_PORT}"
|
||||
fi
|
||||
}
|
||||
|
||||
function stop() {
|
||||
if iptables -t nat -C PREROUTING -p udp --dport "$WG_PORT" -j DNAT --to-destination "${BE_IP}:${WG_PORT}" 2>/dev/null; then
|
||||
iptables -t nat -D PREROUTING -p udp --dport "$WG_PORT" -j DNAT --to-destination "${BE_IP}:${WG_PORT}"
|
||||
echo "Removed PREROUTING DNAT udp/${WG_PORT}"
|
||||
else
|
||||
echo "DNAT rule not present for udp/${WG_PORT}"
|
||||
fi
|
||||
|
||||
if iptables -C FORWARD -p udp -d "${BE_IP}" --dport "$WG_PORT" -j ACCEPT 2>/dev/null; then
|
||||
iptables -D FORWARD -p udp -d "${BE_IP}" --dport "$WG_PORT" -j ACCEPT
|
||||
echo "Removed FORWARD udp -> ${BE_IP}:${WG_PORT}"
|
||||
else
|
||||
echo "FORWARD rule not present"
|
||||
fi
|
||||
}
|
||||
|
||||
cmd=$1
|
||||
cmd=${cmd:-start}
|
||||
[ "$cmd" = "start" ] && start || stop
|
||||
8
examples/wireguard/alpine-be-sudoers-wg
Normal file
8
examples/wireguard/alpine-be-sudoers-wg
Normal file
@@ -0,0 +1,8 @@
|
||||
# Install on Alpine BE: /etc/sudoers.d/androidcast-wg (chmod 440)
|
||||
# PHP-FPM pool user is nginx (see /etc/php81/php-fpm.d/www.conf).
|
||||
Defaults:nobody !requiretty
|
||||
Defaults:nginx !requiretty
|
||||
nobody ALL=(root) NOPASSWD: /usr/bin/wg set wg0 peer *
|
||||
nginx ALL=(root) NOPASSWD: /usr/bin/wg set wg0 peer *
|
||||
nginx ALL=(root) NOPASSWD: /usr/bin/wg show wg0 dump
|
||||
nobody ALL=(root) NOPASSWD: /usr/bin/wg show wg0 dump
|
||||
47
examples/wireguard/install_be_remote_access.sh
Executable file
47
examples/wireguard/install_be_remote_access.sh
Executable file
@@ -0,0 +1,47 @@
|
||||
#!/bin/bash
|
||||
# Run on alpine-be as root after syncing backend tree.
|
||||
set -euo pipefail
|
||||
|
||||
ROOT="${1:-/var/www/localhost/htdocs/apps/app/androidcast_project/android_cast/examples/crash_reporter/backend}"
|
||||
CFG="$ROOT/config/config.php"
|
||||
SUDOERS_DST="/etc/sudoers.d/androidcast-wg"
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
|
||||
if [[ ! -f "$CFG" ]]; then
|
||||
echo "Missing $CFG" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
php81 -r '
|
||||
$path = $argv[1];
|
||||
$c = require $path;
|
||||
if (!is_array($c)) { fwrite(STDERR, "config.php must return array\n"); exit(1); }
|
||||
if (isset($c["remote_access"]) && is_array($c["remote_access"]) && ($c["remote_access"]["wg_server_public_key"] ?? "") !== "") {
|
||||
echo "remote_access already configured\n";
|
||||
exit(0);
|
||||
}
|
||||
$c["remote_access"] = array_merge($c["remote_access"] ?? [], [
|
||||
"wg_endpoint" => "ra.apps.f0xx.org:45340",
|
||||
"wg_server_public_key" => "EV2nnwHH7xe3jA5J46Rjtl8ao+ybZinibKg2Hy83GBw=",
|
||||
"wg_interface" => "wg0",
|
||||
"wg_server_allowed_ips" => "172.200.2.1/32",
|
||||
"wg_client_ip_prefix" => "172.200.2.",
|
||||
"wg_client_ip_min" => 10,
|
||||
"wg_client_ip_max" => 250,
|
||||
"wg_set_prefix" => "sudo -n ",
|
||||
"provision_peers" => true,
|
||||
"require_wg_tools" => true,
|
||||
"session_ttl_s" => 3600,
|
||||
"min_poll_interval_s" => 45,
|
||||
]);
|
||||
$export = var_export($c, true);
|
||||
file_put_contents($path, "<?php\nreturn " . $export . ";\n");
|
||||
echo "Wrote remote_access into config.php\n";
|
||||
' "$CFG"
|
||||
|
||||
install -m 440 "$SCRIPT_DIR/alpine-be-sudoers-wg" "$SUDOERS_DST"
|
||||
visudo -cf "$SUDOERS_DST"
|
||||
echo "Installed $SUDOERS_DST"
|
||||
|
||||
rc-service php-fpm81 restart 2>/dev/null || rc-service php-fpm restart 2>/dev/null || true
|
||||
echo "Restarted PHP-FPM"
|
||||
44
examples/wireguard/router_wireguard_dnat.sh
Normal file
44
examples/wireguard/router_wireguard_dnat.sh
Normal file
@@ -0,0 +1,44 @@
|
||||
#!/bin/bash
|
||||
# Raspberry Pi router (f0xx.org): WAN UDP 45340 → FE (10.7.0.10).
|
||||
# Invoked at boot or from iodine_warmup_helper; also: router_wireguard_dnat.sh [start|stop]
|
||||
set -euo pipefail
|
||||
|
||||
FE_IP="${FE_IP:-10.7.0.10}"
|
||||
WG_PORT="${WG_PORT:-45340}"
|
||||
WAN_IF="${WAN_IF:-eth0}"
|
||||
|
||||
function start() {
|
||||
if iptables -t nat -C PREROUTING -i "$WAN_IF" -p udp --dport "$WG_PORT" -j DNAT --to-destination "${FE_IP}:${WG_PORT}" 2>/dev/null; then
|
||||
echo "Router DNAT already present for udp/${WG_PORT}"
|
||||
else
|
||||
iptables -t nat -A PREROUTING -i "$WAN_IF" -p udp --dport "$WG_PORT" -j DNAT --to-destination "${FE_IP}:${WG_PORT}"
|
||||
echo "Added router PREROUTING udp/${WG_PORT} -> ${FE_IP}:${WG_PORT}"
|
||||
fi
|
||||
|
||||
if iptables -C FORWARD -p udp -d "${FE_IP}" --dport "$WG_PORT" -j ACCEPT 2>/dev/null; then
|
||||
echo "Router FORWARD rule already present"
|
||||
else
|
||||
iptables -A FORWARD -p udp -d "${FE_IP}" --dport "$WG_PORT" -j ACCEPT
|
||||
echo "Added router FORWARD udp -> ${FE_IP}:${WG_PORT}"
|
||||
fi
|
||||
}
|
||||
|
||||
function stop() {
|
||||
if iptables -t nat -C PREROUTING -i "$WAN_IF" -p udp --dport "$WG_PORT" -j DNAT --to-destination "${FE_IP}:${WG_PORT}" 2>/dev/null; then
|
||||
iptables -t nat -D PREROUTING -i "$WAN_IF" -p udp --dport "$WG_PORT" -j DNAT --to-destination "${FE_IP}:${WG_PORT}"
|
||||
echo "Removed router PREROUTING DNAT udp/${WG_PORT}"
|
||||
else
|
||||
echo "Router DNAT rule not present for udp/${WG_PORT}"
|
||||
fi
|
||||
|
||||
if iptables -C FORWARD -p udp -d "${FE_IP}" --dport "$WG_PORT" -j ACCEPT 2>/dev/null; then
|
||||
iptables -D FORWARD -p udp -d "${FE_IP}" --dport "$WG_PORT" -j ACCEPT
|
||||
echo "Removed router FORWARD udp -> ${FE_IP}:${WG_PORT}"
|
||||
else
|
||||
echo "Router FORWARD rule not present"
|
||||
fi
|
||||
}
|
||||
|
||||
cmd=$1
|
||||
cmd=${cmd:-start}
|
||||
[ "$cmd" = "start" ] && start || stop
|
||||
Reference in New Issue
Block a user