mirror of
git://f0xx.org/android_cast
synced 2026-07-29 07:20:00 +03:00
sync
This commit is contained in:
@@ -85,10 +85,13 @@ public final class RemoteAccessVpnService extends VpnService {
|
|||||||
String[] parts = address.split("/");
|
String[] parts = address.split("/");
|
||||||
b.addAddress(InetAddress.getByName(parts[0]),
|
b.addAddress(InetAddress.getByName(parts[0]),
|
||||||
parts.length > 1 ? Integer.parseInt(parts[1]) : 32);
|
parts.length > 1 ? Integer.parseInt(parts[1]) : 32);
|
||||||
} else {
|
|
||||||
b.addAddress("10.66.66.2", 32);
|
|
||||||
}
|
}
|
||||||
b.addRoute("10.66.66.0", 24);
|
for (String route : parseAllowedRoutes(wgConfig)) {
|
||||||
|
String[] rp = route.split("/");
|
||||||
|
if (rp.length >= 2) {
|
||||||
|
b.addRoute(rp[0], Integer.parseInt(rp[1]));
|
||||||
|
}
|
||||||
|
}
|
||||||
b.setMtu(1280);
|
b.setMtu(1280);
|
||||||
tun = b.establish();
|
tun = b.establish();
|
||||||
Log.i(TAG, "VPN interface established");
|
Log.i(TAG, "VPN interface established");
|
||||||
@@ -105,6 +108,25 @@ public final class RemoteAccessVpnService extends VpnService {
|
|||||||
return m.find() ? m.group(1).trim() : null;
|
return m.find() ? m.group(1).trim() : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Routes from AllowedIPs= lines (split tunnel — typically server wg /32). */
|
||||||
|
private static String[] parseAllowedRoutes(String wgConfig) {
|
||||||
|
if (wgConfig == null) {
|
||||||
|
return new String[0];
|
||||||
|
}
|
||||||
|
Matcher m = Pattern.compile("(?m)^AllowedIPs\\s*=\\s*(.+)$").matcher(wgConfig);
|
||||||
|
if (!m.find()) {
|
||||||
|
return new String[0];
|
||||||
|
}
|
||||||
|
String[] parts = m.group(1).trim().split("\\s*,\\s*");
|
||||||
|
java.util.ArrayList<String> out = new java.util.ArrayList<>();
|
||||||
|
for (String p : parts) {
|
||||||
|
if (!p.isEmpty()) {
|
||||||
|
out.add(p.trim());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out.toArray(new String[0]);
|
||||||
|
}
|
||||||
|
|
||||||
private void tearDown() {
|
private void tearDown() {
|
||||||
if (tun != null) {
|
if (tun != null) {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Open tasks — dependency graph
|
# Open tasks — dependency graph
|
||||||
|
|
||||||
_Last synced: 2026-06-08. Renumbered per [20260608_ALPHA_PRIORITIES.md](20260608_ALPHA_PRIORITIES.md)._
|
_Last synced: 2026-06-04. **2.1–2.3 postponed** (developer DNS path deferred). Renumbered per [20260608_ALPHA_PRIORITIES.md](20260608_ALPHA_PRIORITIES.md)._
|
||||||
|
|
||||||
**Documentation index:** [README.md](README.md)
|
**Documentation index:** [README.md](README.md)
|
||||||
|
|
||||||
@@ -9,25 +9,53 @@ _Last synced: 2026-06-08. Renumbered per [20260608_ALPHA_PRIORITIES.md](20260608
|
|||||||
## Table of contents
|
## Table of contents
|
||||||
|
|
||||||
<!-- toc -->
|
<!-- toc -->
|
||||||
- [Priority tiers](#priority-tiers)
|
- [Removed from graph (done)](#removed-from-graph-done)
|
||||||
|
- [Postponed (off graph)](#postponed-off-graph)
|
||||||
|
- [Priority tiers (open only)](#priority-tiers-open-only)
|
||||||
- [Dependency graph](#dependency-graph)
|
- [Dependency graph](#dependency-graph)
|
||||||
- [Task index](#task-index)
|
- [Task index (open)](#task-index-open)
|
||||||
- [Critical path](#critical-path)
|
- [Critical path](#critical-path)
|
||||||
<!-- /toc -->
|
<!-- /toc -->
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Priority tiers
|
## Removed from graph (done)
|
||||||
|
|
||||||
|
| ID | Note |
|
||||||
|
|----|------|
|
||||||
|
| **0.1** | Stream dump — `StreamDumpWriter` + dev setting |
|
||||||
|
| **3.x** | Register + verify + TOTP + migration `008` (BE + views) |
|
||||||
|
| **4.x** | RSSH alpha — BE bastion, Android mode, JSch tunnel |
|
||||||
|
| **4.wg** | WG lab — BE `wg0`, FE DNAT via `xen_firewall.sh`, prod API smoke OK |
|
||||||
|
| **RBAC** (core) | Admin panel + lockout clear; **5.5 validate** still open |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Postponed (off graph)
|
||||||
|
|
||||||
|
| ID | Owner | Note |
|
||||||
|
|----|-------|------|
|
||||||
|
| **2.1** | Developer | DNS MX/SPF — deferred |
|
||||||
|
| **2.2** | Developer | Forward addresses — deferred |
|
||||||
|
| **2.3** | Developer | Inbound verify — deferred |
|
||||||
|
| **2.4** | Developer | Gmail filters — optional; with 2.x |
|
||||||
|
| **2.5** | Agent | SMTP outbound — frozen until 2.1 resumes |
|
||||||
|
| **5.4** (mail) | Agent | G2–G6 **email** legs — frozen until 2.5; UI/TOTP can be tested without mail |
|
||||||
|
|
||||||
|
Checklist when resumed: [tmp/20260608_help_request.md](../tmp/20260608_help_request.md)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Priority tiers (open only)
|
||||||
|
|
||||||
| Tier | IDs | Alpha blocker |
|
| Tier | IDs | Alpha blocker |
|
||||||
|------|-----|---------------|
|
|------|-----|---------------|
|
||||||
| P0 debug | **0.1** stream dump | No (feeds 6.5/6.6) |
|
| **WIP / QA** | Browser JS, reachability | **Yes** (blocks BE sign-off) |
|
||||||
| P1 | **1** graphs | No |
|
| **P1** | **1.1 / 1.2** graphs | No |
|
||||||
| P2 | **2.1–2.4** dev, **2.5** + **3.x** agent, RBAC | **Yes** (auth) |
|
| **P4** | **5.1–5.3, 5.5** validation + OTA/URLs | **Yes** (auth mail path **deferred**) |
|
||||||
| P3 | **4.x** RSSH | **Yes** |
|
| **P5** | **6.1–6.6** app LAN | LAN alpha |
|
||||||
| P4 | **5.x** backend validation | **Yes** |
|
| **P9** | **9.x** landing | No |
|
||||||
| P5 | **6.x** app LAN | LAN alpha |
|
| **Infra** | Router WG DNAT persist | No (lab only) |
|
||||||
| P9 | **9.x** landing | No |
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -35,87 +63,90 @@ _Last synced: 2026-06-08. Renumbered per [20260608_ALPHA_PRIORITIES.md](20260608
|
|||||||
|
|
||||||
```mermaid
|
```mermaid
|
||||||
flowchart TB
|
flowchart TB
|
||||||
S01["0.1 stream dump<br/>agent · P0"]
|
subgraph WIP["WIP — QA / deploy"]
|
||||||
G1["1.x graphs<br/>agent · P1"]
|
BR["Browser checks<br/>JS SyntaxError · views"]
|
||||||
|
REACH["Services reachability<br/>OTA/crash URLs"]
|
||||||
|
end
|
||||||
|
|
||||||
D21["2.1 DNS<br/>DEV"]
|
subgraph P1["P1 graphs"]
|
||||||
D22["2.2 forward<br/>DEV"]
|
G11["1.1 pending fixes"]
|
||||||
D23["2.3 inbound test<br/>DEV"]
|
G12["1.2 pending FRs"]
|
||||||
A25["2.5 SMTP<br/>agent"]
|
end
|
||||||
A3["3.x 2FA<br/>agent"]
|
|
||||||
RBAC["RBAC<br/>agent"]
|
|
||||||
|
|
||||||
R4["4.x RSSH<br/>agent · P3"]
|
subgraph P4["P4 backend alpha"]
|
||||||
|
B51["5.1 OTA channel<br/>50/50"]
|
||||||
B51["5.1 OTA<br/>50/50"]
|
B52["5.2 device URLs<br/>50/50"]
|
||||||
B52["5.2 URLs<br/>50/50"]
|
|
||||||
B53["5.3 RSSH validate"]
|
B53["5.3 RSSH validate"]
|
||||||
B54["5.4 auth validate"]
|
|
||||||
B55["5.5 RBAC validate"]
|
B55["5.5 RBAC validate"]
|
||||||
|
end
|
||||||
|
|
||||||
A61["6.1 Opus"]
|
subgraph P5["P5 app LAN — dev"]
|
||||||
|
A61["6.1 Opus/Speex"]
|
||||||
A62["6.2 LAN soak"]
|
A62["6.2 LAN soak"]
|
||||||
A63["6.3 lifecycle"]
|
A63["6.3 lifecycle"]
|
||||||
A64["6.4 rotation"]
|
A64["6.4 rotation"]
|
||||||
A65["6.5 audio lag"]
|
A65["6.5 audio lag"]
|
||||||
|
A66["6.6 stream analysis<br/>uses 0.1 dumps"]
|
||||||
|
end
|
||||||
|
|
||||||
L9["9.x landing<br/>LOW"]
|
subgraph P9["P9 landing"]
|
||||||
|
L9["9.x hub / globe"]
|
||||||
|
end
|
||||||
|
|
||||||
FULL["FULL_ALPHA"]
|
subgraph INFRA["Infra — non-alpha"]
|
||||||
|
RDNAT["Router WG DNAT<br/>f0xx.org persist"]
|
||||||
|
end
|
||||||
|
|
||||||
S01 -.-> A65 & A64
|
FULL["FULL_ALPHA<br/>mail path deferred"]
|
||||||
D21 --> D22 --> D23
|
|
||||||
D21 --> A25 --> A3 --> RBAC
|
|
||||||
A3 --> B54 --> FULL
|
|
||||||
RBAC --> B55
|
|
||||||
|
|
||||||
R4 --> B53 --> FULL
|
BR --> G11 & G12
|
||||||
B51 & B52 --> FULL
|
BR --> B55
|
||||||
|
REACH --> B51 & B52
|
||||||
|
|
||||||
|
B53 --> FULL
|
||||||
|
B55 --> FULL
|
||||||
|
B51 --> FULL
|
||||||
|
B52 --> FULL
|
||||||
A62 --> FULL
|
A62 --> FULL
|
||||||
A63 <--> A64
|
|
||||||
|
|
||||||
G1 -.-> L9
|
A63 <--> A64
|
||||||
L9 -.-> RBAC
|
A65 --> A66
|
||||||
|
G11 -.-> L9
|
||||||
|
|
||||||
style FULL fill:#1e3a5f,color:#fff
|
style FULL fill:#1e3a5f,color:#fff
|
||||||
style D21 fill:#b45309,color:#fff
|
style BR fill:#991b1b,color:#fff
|
||||||
style D22 fill:#b45309,color:#fff
|
|
||||||
style D23 fill:#b45309,color:#fff
|
|
||||||
style R4 fill:#7c2d12,color:#fff
|
|
||||||
style A3 fill:#7c2d12,color:#fff
|
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Task index
|
## Task index (open)
|
||||||
|
|
||||||
| ID | Owner | Depends on |
|
| ID | Owner | Depends on | Status |
|
||||||
|----|-------|------------|
|
|----|-------|------------|--------|
|
||||||
| **0.1** | Agent | — |
|
| **Browser** | Agent | BE deploy | WIP — `SyntaxError` on crashes/graphs/tickets |
|
||||||
| **1 / 1.1 / 1.2** | Agent | BE deploy soft |
|
| **Reachability** | 50/50 | deploy | WIP — OTA path, crash URL host |
|
||||||
| **2.1–2.4** | **Developer** | DNS UI access |
|
| **1.1** | Agent | browser OK | Open |
|
||||||
| **2.5** | Agent | 2.1 |
|
| **1.2** | Agent | 1.1 soft | Open |
|
||||||
| **3.x** | Agent | 2.5 |
|
| **5.1** | 50/50 | reachability | Open |
|
||||||
| **RBAC** | Agent | 3.x, stable pages |
|
| **5.2** | 50/50 | reachability | Open |
|
||||||
| **4.x** | Agent | BE deploy |
|
| **5.3** | Agent | 4.x ✓ | Validate E2E RSSH |
|
||||||
| **5.1–5.2** | 50/50 | deploy |
|
| **5.5** | Agent | RBAC ✓, browser | Validate admin flows |
|
||||||
| **5.3** | Agent | 4.x |
|
| **6.1–6.6** | Developer (~99%) | 0.1 ✓ for 6.6 | Open |
|
||||||
| **5.4–5.5** | Agent | 3.x, RBAC |
|
| **9.x** | Agent (low) | bandwidth | Open |
|
||||||
| **6.1–6.6** | Developer (~99%) | 0.1 ↔ 6.6 |
|
| **Router DNAT** | Agent | — | Open — `router_wireguard_dnat.sh` |
|
||||||
| **9.x** | Agent (low) | — |
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Critical path
|
## Critical path
|
||||||
|
|
||||||
```text
|
```text
|
||||||
DEV: 2.1 → 2.2 → 2.3
|
BLOCKER: Browser JS fix + deploy ──► 1.x, 5.5 UI validation
|
||||||
AGENT: 2.5 → 3.x → 5.4 ──┐
|
|
||||||
AGENT: 4.x → 5.3 ─────────┼──► FULL_ALPHA
|
|
||||||
SHARED: 5.1 OTA, 5.2 URLs ─┤
|
|
||||||
DEV: 6.2 LAN soak ────────┘
|
|
||||||
|
|
||||||
Parallel: 0.1, 1.x, 6.1/6.3–6.5, 9.x
|
AGENT: 5.3 RSSH validate (4.x done) ──┐
|
||||||
|
SHARED: 5.1 OTA, 5.2 URLs ─────────────┼──► FULL_ALPHA (LAN + RSSH + RBAC)
|
||||||
|
DEV: 6.2 LAN soak ─────────────────┘
|
||||||
|
|
||||||
|
Deferred (not on graph): 2.1 → 2.2 → 2.3 → 2.5 → 5.4 mail E2E
|
||||||
|
|
||||||
|
Parallel: 6.1/6.3–6.6, 9.x, router WG DNAT, Android APK rebuild (WG route fix)
|
||||||
```
|
```
|
||||||
|
|
||||||
**Developer blocked checklist:** [tmp/20260608_help_request.md](../tmp/20260608_help_request.md)
|
|
||||||
|
|||||||
@@ -47,15 +47,20 @@ return [
|
|||||||
],
|
],
|
||||||
// On-demand remote access (WireGuard v1) — see docs/REMOTE_ACCESS_IMPL.md
|
// On-demand remote access (WireGuard v1) — see docs/REMOTE_ACCESS_IMPL.md
|
||||||
'remote_access' => [
|
'remote_access' => [
|
||||||
// Public UDP endpoint shown to devices (FE DNAT → BE wg0)
|
// Public UDP endpoint (FE DNAT → BE wg0 ListenPort)
|
||||||
'wg_endpoint' => 'ra.apps.f0xx.org:51820',
|
'wg_endpoint' => 'ra.apps.f0xx.org:45340',
|
||||||
// Server WG public key (wg show wg0 public-key on BE)
|
// wg show wg0 public-key on BE — REQUIRED for production connect
|
||||||
'wg_server_public_key' => '',
|
'wg_server_public_key' => 'EV2nnwHH7xe3jA5J46Rjtl8ao+ybZinibKg2Hy83GBw=',
|
||||||
'wg_interface' => 'wg0',
|
'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,
|
'provision_peers' => true,
|
||||||
// Reject connect if wg genkey/pubkey unavailable (set true on production BE)
|
'require_wg_tools' => true,
|
||||||
'require_wg_tools' => false,
|
|
||||||
'session_ttl_s' => 3600,
|
'session_ttl_s' => 3600,
|
||||||
'min_poll_interval_s' => 45,
|
'min_poll_interval_s' => 45,
|
||||||
// Reverse SSH (alpha): outbound -R from device; no VpnService on phone
|
// 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"
|
source "$ROOT/scripts/ra_lib.sh"
|
||||||
|
|
||||||
WG_HOST="${WG_HOST:-ra.apps.f0xx.org}"
|
WG_HOST="${WG_HOST:-ra.apps.f0xx.org}"
|
||||||
WG_PORT="${WG_PORT:-51820}"
|
WG_PORT="${WG_PORT:-45340}"
|
||||||
BE_WG_IP="${BE_WG_IP:-10.66.66.1}"
|
BE_WG_IP="${BE_WG_IP:-172.200.2.1}"
|
||||||
DEVICE_ID="${RA_DEVICE_ID:-ra-udp-$(hostname -s | tr ' ' '-')}"
|
DEVICE_ID="${RA_DEVICE_ID:-ra-udp-$(hostname -s | tr ' ' '-')}"
|
||||||
CONF="/tmp/ra-udp-${DEVICE_ID}.conf"
|
CONF="/tmp/ra-udp-${DEVICE_ID}.conf"
|
||||||
APPLY="${RA_UDP_APPLY:-0}"
|
APPLY="${RA_UDP_APPLY:-0}"
|
||||||
|
|||||||
@@ -336,7 +336,7 @@ final class RemoteAccessRepository {
|
|||||||
&& (bool) cfg('remote_access.require_wg_tools', false)) {
|
&& (bool) cfg('remote_access.require_wg_tools', false)) {
|
||||||
return self::raEnvelope('deny', ['reason' => 'wg_tools_required']);
|
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));
|
$expiresAt = (int) ($session['expires_at'] ?? (time() + 3600));
|
||||||
|
|
||||||
self::markSessionActive($sessionId);
|
self::markSessionActive($sessionId);
|
||||||
@@ -435,7 +435,7 @@ final class RemoteAccessRepository {
|
|||||||
'client_public' => $pub,
|
'client_public' => $pub,
|
||||||
'client_address' => $addr,
|
'client_address' => $addr,
|
||||||
'server_public' => $serverPub,
|
'server_public' => $serverPub,
|
||||||
'allowed_ips' => $allowed !== '' ? $allowed : '10.66.66.1/32',
|
'allowed_ips' => $allowed !== '' ? $allowed : WireGuardAddressPool::serverAllowedIps(),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
$pair = self::generateWireGuardKeyPair();
|
$pair = self::generateWireGuardKeyPair();
|
||||||
@@ -444,13 +444,13 @@ final class RemoteAccessRepository {
|
|||||||
if ($pub === '' || str_starts_with($pub, 'dev-placeholder-pub-')) {
|
if ($pub === '' || str_starts_with($pub, 'dev-placeholder-pub-')) {
|
||||||
$pub = WireGuardPeerProvisioner::publicKeyFromPrivate($priv);
|
$pub = WireGuardPeerProvisioner::publicKeyFromPrivate($priv);
|
||||||
}
|
}
|
||||||
$clientIp = self::allocateClientAddress($sessionId);
|
$clientIp = WireGuardAddressPool::allocateClientAddress($sessionId);
|
||||||
$serverPub = (string) cfg('remote_access.wg_server_public_key', '');
|
$serverPub = (string) cfg('remote_access.wg_server_public_key', '');
|
||||||
if ($serverPub === '') {
|
if ($serverPub === '') {
|
||||||
$serverPair = self::generateWireGuardKeyPair();
|
$serverPair = self::generateWireGuardKeyPair();
|
||||||
$serverPub = $serverPair['public'];
|
$serverPub = $serverPair['public'];
|
||||||
}
|
}
|
||||||
$allowed = '10.66.66.1/32';
|
$allowed = WireGuardAddressPool::serverAllowedIps();
|
||||||
Database::pdo()->prepare(
|
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 = ?'
|
'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]);
|
)->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} */
|
/** @return array{private:string,public:string} */
|
||||||
public static function generateWireGuardKeyPair(): array {
|
public static function generateWireGuardKeyPair(): array {
|
||||||
$priv = trim((string) @shell_exec('wg genkey 2>/dev/null') ?: '');
|
$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);
|
$port = (int) cfg('remote_access.rssh.bastion_port', 443);
|
||||||
$endpoint = $port === 443 ? $host : $host . ':' . $port;
|
$endpoint = $port === 443 ? $host : $host . ':' . $port;
|
||||||
} else {
|
} 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);
|
$expiresAt = time() + (int) cfg('remote_access.session_ttl_s', 3600);
|
||||||
$now = self::nowSql();
|
$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();
|
$iface = self::interfaceName();
|
||||||
$allowed = trim($clientAddressCidr);
|
$allowed = trim($clientAddressCidr);
|
||||||
if ($allowed === '') {
|
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)
|
. ' peer ' . escapeshellarg($pub)
|
||||||
. ' allowed-ips ' . escapeshellarg($allowed);
|
. ' allowed-ips ' . escapeshellarg($allowed)
|
||||||
exec($cmd, $out, $code);
|
);
|
||||||
if ($code !== 0) {
|
|
||||||
error_log('WireGuardPeerProvisioner add failed: ' . implode(' ', $out));
|
|
||||||
}
|
|
||||||
return $code === 0;
|
return $code === 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,9 +52,20 @@ final class WireGuardPeerProvisioner {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
$iface = self::interfaceName();
|
$iface = self::interfaceName();
|
||||||
$cmd = 'wg set ' . escapeshellarg($iface)
|
self::runWg(
|
||||||
|
'set ' . escapeshellarg($iface)
|
||||||
. ' peer ' . escapeshellarg($pub)
|
. ' peer ' . escapeshellarg($pub)
|
||||||
. ' remove';
|
. ' remove'
|
||||||
exec($cmd);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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__ . '/GraphRepository.php';
|
||||||
require_once __DIR__ . '/RemoteAccessRepository.php';
|
require_once __DIR__ . '/RemoteAccessRepository.php';
|
||||||
require_once __DIR__ . '/WireGuardPeerProvisioner.php';
|
require_once __DIR__ . '/WireGuardPeerProvisioner.php';
|
||||||
|
require_once __DIR__ . '/WireGuardAddressPool.php';
|
||||||
require_once __DIR__ . '/RsshSessionProvisioner.php';
|
require_once __DIR__ . '/RsshSessionProvisioner.php';
|
||||||
require_once __DIR__ . '/AnalyticsHead.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
|
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]
|
[Peer]
|
||||||
# obtained from client device via wireguard connection setup process
|
# Static lab peer only — remote-access clients are added dynamically via wg set (PHP).
|
||||||
PublicKey = EV2nnwHH7xe3jA5J46Rjtl8ao+ybZinibKg2Hy83GBw=
|
# PublicKey = <client-device-pubkey>
|
||||||
AllowedIPs = 172.200.2.2/16, fddd::1/128
|
# 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