mirror of
git://f0xx.org/ac/ac-ms-remote-access
synced 2026-07-29 03:38:07 +03:00
initial
This commit is contained in:
142
src/WireGuardPeerProvisioner.php
Normal file
142
src/WireGuardPeerProvisioner.php
Normal file
@@ -0,0 +1,142 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
/** Alpine BE WireGuard peer lifecycle (wg set) for remote-access sessions. */
|
||||
final class WireGuardPeerProvisioner {
|
||||
private function __construct() {}
|
||||
|
||||
public static function isEnabled(): bool {
|
||||
return (bool) cfg('remote_access.provision_peers', true);
|
||||
}
|
||||
|
||||
public static function interfaceName(): string {
|
||||
return (string) cfg('remote_access.wg_interface', 'wg0');
|
||||
}
|
||||
|
||||
public static function publicKeyFromPrivate(string $privateKey): string {
|
||||
$priv = trim($privateKey);
|
||||
if ($priv === '') {
|
||||
return '';
|
||||
}
|
||||
$pub = trim((string) @shell_exec('echo ' . escapeshellarg($priv) . ' | wg pubkey 2>/dev/null') ?: '');
|
||||
return $pub;
|
||||
}
|
||||
|
||||
public static function addClientPeer(string $clientPublicKey, string $clientAddressCidr): bool {
|
||||
if (!self::isEnabled()) {
|
||||
return true;
|
||||
}
|
||||
$pub = trim($clientPublicKey);
|
||||
if ($pub === '' || str_starts_with($pub, 'dev-placeholder-pub-')) {
|
||||
return false;
|
||||
}
|
||||
$iface = self::interfaceName();
|
||||
$allowed = trim($clientAddressCidr);
|
||||
if ($allowed === '') {
|
||||
$allowed = WireGuardAddressPool::allocateClientAddress('default');
|
||||
}
|
||||
$code = self::runWg(
|
||||
'set ' . escapeshellarg($iface)
|
||||
. ' peer ' . escapeshellarg($pub)
|
||||
. ' allowed-ips ' . escapeshellarg($allowed)
|
||||
);
|
||||
return $code === 0;
|
||||
}
|
||||
|
||||
public static function removeClientPeer(?string $clientPublicKey): void {
|
||||
if (!self::isEnabled()) {
|
||||
return;
|
||||
}
|
||||
$pub = trim((string) $clientPublicKey);
|
||||
if ($pub === '' || str_starts_with($pub, 'dev-placeholder-pub-')) {
|
||||
return;
|
||||
}
|
||||
$iface = self::interfaceName();
|
||||
self::runWg(
|
||||
'set ' . escapeshellarg($iface)
|
||||
. ' peer ' . escapeshellarg($pub)
|
||||
. ' remove'
|
||||
);
|
||||
}
|
||||
|
||||
/** @return list<array{public_key: string, allowed_ips: string}> */
|
||||
public static function listPeerDumpRows(): array {
|
||||
if (!self::isEnabled()) {
|
||||
return [];
|
||||
}
|
||||
$iface = self::interfaceName();
|
||||
$prefix = rtrim((string) cfg('remote_access.wg_set_prefix', ''), " \t");
|
||||
$cmd = ($prefix !== '' ? $prefix . ' ' : '') . 'wg show ' . escapeshellarg($iface) . ' dump 2>/dev/null';
|
||||
$raw = @shell_exec($cmd);
|
||||
if (!is_string($raw) || trim($raw) === '') {
|
||||
return [];
|
||||
}
|
||||
$rows = [];
|
||||
foreach (preg_split('/\r\n|\n|\r/', trim($raw)) as $line) {
|
||||
if ($line === '' || str_starts_with($line, 'interface:')) {
|
||||
continue;
|
||||
}
|
||||
$cols = explode("\t", $line);
|
||||
if (count($cols) < 4) {
|
||||
continue;
|
||||
}
|
||||
$pub = trim($cols[0]);
|
||||
if ($pub === '') {
|
||||
continue;
|
||||
}
|
||||
$rows[] = [
|
||||
'public_key' => $pub,
|
||||
'allowed_ips' => trim((string) ($cols[3] ?? '')),
|
||||
];
|
||||
}
|
||||
return $rows;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove live wg peers not tied to pending/active sessions (or with empty AllowedIPs).
|
||||
*
|
||||
* @param list<string> $keepPublicKeys
|
||||
* @return list<array{public_key: string, allowed_ips: string, reason: string}>
|
||||
*/
|
||||
public static function pruneOrphanPeers(array $keepPublicKeys, bool $dryRun = false): array {
|
||||
if (!self::isEnabled()) {
|
||||
return [];
|
||||
}
|
||||
$keep = [];
|
||||
foreach ($keepPublicKeys as $key) {
|
||||
$pub = trim((string) $key);
|
||||
if ($pub !== '') {
|
||||
$keep[$pub] = true;
|
||||
}
|
||||
}
|
||||
$pruned = [];
|
||||
foreach (self::listPeerDumpRows() as $row) {
|
||||
$pub = $row['public_key'];
|
||||
$allowed = trim($row['allowed_ips']);
|
||||
$broken = ($allowed === '' || $allowed === '(none)');
|
||||
$orphan = !isset($keep[$pub]);
|
||||
if (!$orphan && !$broken) {
|
||||
continue;
|
||||
}
|
||||
if (!$dryRun) {
|
||||
self::removeClientPeer($pub);
|
||||
}
|
||||
$pruned[] = [
|
||||
'public_key' => $pub,
|
||||
'allowed_ips' => $allowed,
|
||||
'reason' => $broken ? 'broken_allowed_ips' : 'orphan',
|
||||
];
|
||||
}
|
||||
return $pruned;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user