mirror of
git://f0xx.org/android_cast
synced 2026-07-29 05:37:52 +03:00
sync
This commit is contained in:
@@ -47,15 +47,20 @@ return [
|
||||
],
|
||||
// On-demand remote access (WireGuard v1) — see docs/REMOTE_ACCESS_IMPL.md
|
||||
'remote_access' => [
|
||||
// Public UDP endpoint shown to devices (FE DNAT → BE wg0)
|
||||
'wg_endpoint' => 'ra.apps.f0xx.org:51820',
|
||||
// Server WG public key (wg show wg0 public-key on BE)
|
||||
'wg_server_public_key' => '',
|
||||
// Public UDP endpoint (FE DNAT → BE wg0 ListenPort)
|
||||
'wg_endpoint' => 'ra.apps.f0xx.org:45340',
|
||||
// wg show wg0 public-key on BE — REQUIRED for production connect
|
||||
'wg_server_public_key' => 'EV2nnwHH7xe3jA5J46Rjtl8ao+ybZinibKg2Hy83GBw=',
|
||||
'wg_interface' => 'wg0',
|
||||
// Run `wg set wg0 peer …` when device receives connect (requires wireguard-tools on BE)
|
||||
// Client / server addresses (must match wg0 Address subnet on BE)
|
||||
'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,
|
||||
// Prefix for `wg set` when PHP-FPM runs as nobody (see examples/wireguard/alpine-be-sudoers-wg)
|
||||
'wg_set_prefix' => 'sudo -n ',
|
||||
'provision_peers' => true,
|
||||
// Reject connect if wg genkey/pubkey unavailable (set true on production BE)
|
||||
'require_wg_tools' => false,
|
||||
'require_wg_tools' => true,
|
||||
'session_ttl_s' => 3600,
|
||||
'min_poll_interval_s' => 45,
|
||||
// Reverse SSH (alpha): outbound -R from device; no VpnService on phone
|
||||
|
||||
@@ -9,8 +9,8 @@ ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
source "$ROOT/scripts/ra_lib.sh"
|
||||
|
||||
WG_HOST="${WG_HOST:-ra.apps.f0xx.org}"
|
||||
WG_PORT="${WG_PORT:-51820}"
|
||||
BE_WG_IP="${BE_WG_IP:-10.66.66.1}"
|
||||
WG_PORT="${WG_PORT:-45340}"
|
||||
BE_WG_IP="${BE_WG_IP:-172.200.2.1}"
|
||||
DEVICE_ID="${RA_DEVICE_ID:-ra-udp-$(hostname -s | tr ' ' '-')}"
|
||||
CONF="/tmp/ra-udp-${DEVICE_ID}.conf"
|
||||
APPLY="${RA_UDP_APPLY:-0}"
|
||||
|
||||
@@ -336,7 +336,7 @@ final class RemoteAccessRepository {
|
||||
&& (bool) cfg('remote_access.require_wg_tools', false)) {
|
||||
return self::raEnvelope('deny', ['reason' => 'wg_tools_required']);
|
||||
}
|
||||
$endpoint = (string) ($session['endpoint'] ?? cfg('remote_access.wg_endpoint', 'ra.apps.f0xx.org:51820'));
|
||||
$endpoint = (string) ($session['endpoint'] ?? WireGuardAddressPool::defaultEndpoint());
|
||||
$expiresAt = (int) ($session['expires_at'] ?? (time() + 3600));
|
||||
|
||||
self::markSessionActive($sessionId);
|
||||
@@ -435,7 +435,7 @@ final class RemoteAccessRepository {
|
||||
'client_public' => $pub,
|
||||
'client_address' => $addr,
|
||||
'server_public' => $serverPub,
|
||||
'allowed_ips' => $allowed !== '' ? $allowed : '10.66.66.1/32',
|
||||
'allowed_ips' => $allowed !== '' ? $allowed : WireGuardAddressPool::serverAllowedIps(),
|
||||
];
|
||||
}
|
||||
$pair = self::generateWireGuardKeyPair();
|
||||
@@ -444,13 +444,13 @@ final class RemoteAccessRepository {
|
||||
if ($pub === '' || str_starts_with($pub, 'dev-placeholder-pub-')) {
|
||||
$pub = WireGuardPeerProvisioner::publicKeyFromPrivate($priv);
|
||||
}
|
||||
$clientIp = self::allocateClientAddress($sessionId);
|
||||
$clientIp = WireGuardAddressPool::allocateClientAddress($sessionId);
|
||||
$serverPub = (string) cfg('remote_access.wg_server_public_key', '');
|
||||
if ($serverPub === '') {
|
||||
$serverPair = self::generateWireGuardKeyPair();
|
||||
$serverPub = $serverPair['public'];
|
||||
}
|
||||
$allowed = '10.66.66.1/32';
|
||||
$allowed = WireGuardAddressPool::serverAllowedIps();
|
||||
Database::pdo()->prepare(
|
||||
'UPDATE remote_access_sessions SET wg_client_private_key = ?, wg_client_public_key = ?, wg_client_address = ?, wg_server_public_key = ?, wg_peer_allowed_ips = ? WHERE session_id = ?'
|
||||
)->execute([$priv, $pub, $clientIp, $serverPub, $allowed, $sessionId]);
|
||||
@@ -468,11 +468,6 @@ final class RemoteAccessRepository {
|
||||
];
|
||||
}
|
||||
|
||||
private static function allocateClientAddress(string $sessionId): string {
|
||||
$n = abs(crc32($sessionId)) % 200 + 2;
|
||||
return '10.66.66.' . $n . '/32';
|
||||
}
|
||||
|
||||
/** @return array{private:string,public:string} */
|
||||
public static function generateWireGuardKeyPair(): array {
|
||||
$priv = trim((string) @shell_exec('wg genkey 2>/dev/null') ?: '');
|
||||
@@ -552,7 +547,7 @@ final class RemoteAccessRepository {
|
||||
$port = (int) cfg('remote_access.rssh.bastion_port', 443);
|
||||
$endpoint = $port === 443 ? $host : $host . ':' . $port;
|
||||
} else {
|
||||
$endpoint = (string) cfg('remote_access.wg_endpoint', 'ra.apps.f0xx.org:51820');
|
||||
$endpoint = WireGuardAddressPool::defaultEndpoint();
|
||||
}
|
||||
$expiresAt = time() + (int) cfg('remote_access.session_ttl_s', 3600);
|
||||
$now = self::nowSql();
|
||||
|
||||
25
examples/crash_reporter/backend/src/WireGuardAddressPool.php
Normal file
25
examples/crash_reporter/backend/src/WireGuardAddressPool.php
Normal file
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
/** WG client IPs + server AllowedIPs from config (matches live wg0 on BE). */
|
||||
final class WireGuardAddressPool {
|
||||
private function __construct() {
|
||||
}
|
||||
|
||||
public static function defaultEndpoint(): string {
|
||||
return (string) cfg('remote_access.wg_endpoint', 'ra.apps.f0xx.org:45340');
|
||||
}
|
||||
|
||||
public static function serverAllowedIps(): string {
|
||||
return (string) cfg('remote_access.wg_server_allowed_ips', '172.200.2.1/32');
|
||||
}
|
||||
|
||||
public static function allocateClientAddress(string $sessionId): string {
|
||||
$prefix = (string) cfg('remote_access.wg_client_ip_prefix', '172.200.2.');
|
||||
$min = max(2, (int) cfg('remote_access.wg_client_ip_min', 10));
|
||||
$max = max($min, (int) cfg('remote_access.wg_client_ip_max', 250));
|
||||
$span = $max - $min + 1;
|
||||
$host = $min + (abs(crc32($sessionId)) % $span);
|
||||
return $prefix . $host . '/32';
|
||||
}
|
||||
}
|
||||
@@ -33,15 +33,13 @@ final class WireGuardPeerProvisioner {
|
||||
$iface = self::interfaceName();
|
||||
$allowed = trim($clientAddressCidr);
|
||||
if ($allowed === '') {
|
||||
$allowed = '10.66.66.2/32';
|
||||
$allowed = WireGuardAddressPool::allocateClientAddress('default');
|
||||
}
|
||||
$cmd = 'wg set ' . escapeshellarg($iface)
|
||||
$code = self::runWg(
|
||||
'set ' . escapeshellarg($iface)
|
||||
. ' peer ' . escapeshellarg($pub)
|
||||
. ' allowed-ips ' . escapeshellarg($allowed);
|
||||
exec($cmd, $out, $code);
|
||||
if ($code !== 0) {
|
||||
error_log('WireGuardPeerProvisioner add failed: ' . implode(' ', $out));
|
||||
}
|
||||
. ' allowed-ips ' . escapeshellarg($allowed)
|
||||
);
|
||||
return $code === 0;
|
||||
}
|
||||
|
||||
@@ -54,9 +52,20 @@ final class WireGuardPeerProvisioner {
|
||||
return;
|
||||
}
|
||||
$iface = self::interfaceName();
|
||||
$cmd = 'wg set ' . escapeshellarg($iface)
|
||||
self::runWg(
|
||||
'set ' . escapeshellarg($iface)
|
||||
. ' peer ' . escapeshellarg($pub)
|
||||
. ' remove';
|
||||
exec($cmd);
|
||||
. ' remove'
|
||||
);
|
||||
}
|
||||
|
||||
private static function runWg(string $args): int {
|
||||
$prefix = rtrim((string) cfg('remote_access.wg_set_prefix', ''), " \t");
|
||||
$cmd = ($prefix !== '' ? $prefix . ' ' : '') . 'wg ' . $args;
|
||||
exec($cmd . ' 2>&1', $out, $code);
|
||||
if ($code !== 0) {
|
||||
error_log('WireGuardPeerProvisioner: ' . $cmd . ' failed: ' . implode(' ', $out));
|
||||
}
|
||||
return $code;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,6 +65,7 @@ require_once __DIR__ . '/TicketRepository.php';
|
||||
require_once __DIR__ . '/GraphRepository.php';
|
||||
require_once __DIR__ . '/RemoteAccessRepository.php';
|
||||
require_once __DIR__ . '/WireGuardPeerProvisioner.php';
|
||||
require_once __DIR__ . '/WireGuardAddressPool.php';
|
||||
require_once __DIR__ . '/RsshSessionProvisioner.php';
|
||||
require_once __DIR__ . '/AnalyticsHead.php';
|
||||
|
||||
|
||||
@@ -7,6 +7,6 @@ PostUp = iptables -A FORWARD -i %i -j ACCEPT; iptables -t nat -A POSTROUTING -o
|
||||
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]
|
||||
# obtained from client device via wireguard connection setup process
|
||||
PublicKey = EV2nnwHH7xe3jA5J46Rjtl8ao+ybZinibKg2Hy83GBw=
|
||||
AllowedIPs = 172.200.2.2/16, fddd::1/128
|
||||
# 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
|
||||
6
examples/wireguard/alpine-be-sudoers-wg
Normal file
6
examples/wireguard/alpine-be-sudoers-wg
Normal file
@@ -0,0 +1,6 @@
|
||||
# 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 *
|
||||
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 ",
|
||||
"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"
|
||||
13
examples/wireguard/router_wireguard_dnat.sh
Normal file
13
examples/wireguard/router_wireguard_dnat.sh
Normal file
@@ -0,0 +1,13 @@
|
||||
#!/bin/bash
|
||||
# Raspberry Pi router (f0xx.org): WAN UDP 45340 → FE (10.7.0.10). Persist via your firewall save.
|
||||
set -euo pipefail
|
||||
FE_IP="${FE_IP:-10.7.0.10}"
|
||||
WG_PORT="${WG_PORT:-45340}"
|
||||
WAN_IF="${WAN_IF:-eth0}"
|
||||
|
||||
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"
|
||||
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
|
||||
Reference in New Issue
Block a user