feat(grouped wifi): route updates through user-defined SSID groups + verify
User-defined SSID groups (configured on the WiFi Networks page and
stored in unifi.ssid_groups) now drive PPSK sibling propagation. The
previous same-SSID-name detection missed cases where two grouped
WLANs have *different* names — e.g. "VCS Guest" on 2.4 and "VCS
Guest 5G" on 5GHz manually grouped by the operator. Falls back to
same-name siblings when no group is configured.
Match-by-name fix: embedded PPSKs on this controller don't carry a
name field — the human "GUEST" label is the *network's* name, with
the entry referenced via networkconf_id. updateEmbeddedPpsk and
verifyEmbeddedPpsk now resolve name → networkconf_id first and match
on that, with entry-name and current-passphrase as fallbacks for
other controller variants.
After every rotation we re-fetch each affected WLAN and verify the
new passphrase is actually present on the named network. Failures
("mismatch" or "fetch_failed" on the primary, anything other than
"not_found" on a sibling) surface in the cron run details as failed
PPSKs so the operator sees what didn't propagate.
v1.10.4.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -80,17 +80,17 @@ class RotatePasswords extends Command
|
||||
$newPass = $passwords[array_rand($passwords)];
|
||||
try {
|
||||
if (str_starts_with((string) $ppsk->unifi_id, 'emb_')) {
|
||||
// Embedded PPSK: update inside the parent WLAN object.
|
||||
// Match by name (most reliable) — falls back to
|
||||
// passphrase if name is missing.
|
||||
// Embedded PPSK: update inside the parent WLAN object,
|
||||
// matched by name (synthetic id changes with the
|
||||
// passphrase, so it's not a stable matcher).
|
||||
$unifi->updateEmbeddedPpsk($ppsk->wlan_id, $ppsk->x_passphrase, $newPass, $ppsk->name);
|
||||
$newUid = 'emb_' . substr(hash('sha256', $ppsk->wlan_id . ':' . $newPass), 0, 32);
|
||||
$ppsk->update(['x_passphrase' => $newPass, 'unifi_id' => $newUid]);
|
||||
|
||||
// Sibling WLANs (same SSID name on a different band):
|
||||
// rotate the matching-name PPSK in each so the
|
||||
// SSID's 2.4/5GHz halves stay in sync.
|
||||
foreach ($unifi->getWlanSiblings($ppsk->wlan_id) as $siblingWlanId) {
|
||||
// Update every grouped sibling (user-defined SSID
|
||||
// groups take precedence; same-name fallback for
|
||||
// installs that haven't grouped manually).
|
||||
foreach ($unifi->getGroupedWlans($ppsk->wlan_id) as $siblingWlanId) {
|
||||
$sibling = UnifiPpsk::where('wlan_id', $siblingWlanId)
|
||||
->where('name', $ppsk->name)
|
||||
->where('state', 'active')
|
||||
@@ -104,11 +104,6 @@ class RotatePasswords extends Command
|
||||
]);
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
// "Not found" in a sibling just means the
|
||||
// PPSK isn't mirrored on that band — totally
|
||||
// normal if GUEST was only configured on one
|
||||
// band. Skip quietly; don't poison the
|
||||
// run status.
|
||||
if (str_contains($e->getMessage(), 'not found')) {
|
||||
\Illuminate\Support\Facades\Log::info('unifi.ppsk_sibling_skipped', [
|
||||
'sibling_wlan' => $siblingWlanId,
|
||||
@@ -120,6 +115,26 @@ class RotatePasswords extends Command
|
||||
$failedPpsks[] = ['name' => $ppsk->name . ' (sibling wlan ' . $siblingWlanId . ')', 'error' => $e->getMessage()];
|
||||
}
|
||||
}
|
||||
|
||||
// Verify that the new passphrase actually applied
|
||||
// on every grouped WLAN. UniFi can 200 an update
|
||||
// that doesn't stick (cluster sync race, etc).
|
||||
// Anything we expected to rotate that didn't is a
|
||||
// failure — surface it in the cron log.
|
||||
$allWlanIds = array_merge([$ppsk->wlan_id], $unifi->getGroupedWlans($ppsk->wlan_id));
|
||||
foreach ($allWlanIds as $checkWlanId) {
|
||||
$result = $unifi->verifyEmbeddedPpsk($checkWlanId, $ppsk->name, $newPass);
|
||||
if ($result['ok']) continue;
|
||||
|
||||
// 'not_found' on a sibling = PPSK isn't on that band — ignore
|
||||
// (consistent with the skip in the update loop).
|
||||
if ($result['reason'] === 'not_found' && $checkWlanId !== $ppsk->wlan_id) continue;
|
||||
|
||||
$failedPpsks[] = [
|
||||
'name' => $ppsk->name . ' (verify wlan ' . $checkWlanId . ')',
|
||||
'error' => 'verification ' . $result['reason'] . ($result['error'] ?? null ? ': ' . $result['error'] : ''),
|
||||
];
|
||||
}
|
||||
} else {
|
||||
$unifi->updatePpsk($ppsk->unifi_id, ['x_passphrase' => $newPass]);
|
||||
$ppsk->update(['x_passphrase' => $newPass]);
|
||||
|
||||
Reference in New Issue
Block a user