Files
proxmox/scripts/unifi/verify-vlan-settings-playwright.js
defiQUG fbda1b4beb
Some checks failed
Deploy to Phoenix / deploy (push) Has been cancelled
docs: Ledger Live integration, contract deploy learnings, NEXT_STEPS updates
- ADD_CHAIN138_TO_LEDGER_LIVE: Ledger form done; public code review repo bis-innovations/LedgerLive; init/push commands
- CONTRACT_DEPLOYMENT_RUNBOOK: Chain 138 gas price 1 gwei, 36-addr check, TransactionMirror workaround
- CONTRACT_*: AddressMapper, MirrorManager deployed 2026-02-12; 36-address on-chain check
- NEXT_STEPS_FOR_YOU: Ledger done; steps completable now (no LAN); run-completable-tasks-from-anywhere
- MASTER_INDEX, OPERATOR_OPTIONAL, SMART_CONTRACTS_INVENTORY_SIMPLE: updates
- LEDGER_BLOCKCHAIN_INTEGRATION_COMPLETE: bis-innovations/LedgerLive reference

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-12 15:46:57 -08:00

406 lines
17 KiB
JavaScript
Executable File
Raw Permalink Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env node
/**
* Verify VLAN Settings - Network Isolation and Zone Matrix
* Automates verification of critical VLAN settings on UDM Pro
*/
import { chromium } from 'playwright';
import { readFileSync, existsSync } from 'fs';
import { join } from 'path';
import { homedir } from 'os';
// Load environment variables
const envFile = join(homedir(), '.env');
let env = {};
if (existsSync(envFile)) {
readFileSync(envFile, 'utf8').split('\n').forEach(line => {
const match = line.match(/^([^=]+)=(.*)$/);
if (match) {
const key = match[1].trim();
const value = match[2].trim().replace(/^['"]|['"]$/g, '');
env[key] = value;
}
});
}
// Configuration
const UDM_PRO_URL = env.UNIFI_UDM_URL || 'https://192.168.0.1';
const USERNAME = env.UNIFI_USERNAME || 'unifi_api';
const PASSWORD = env.UNIFI_PASSWORD || '';
const HEADLESS = env.HEADLESS !== 'false';
const TIMEOUT = 60000;
// VLAN list to verify
const VLAN_LIST = [
{ name: 'Default', vlanId: 1, subnet: '192.168.0.0/24' },
{ name: 'MGMT-LAN', vlanId: 11, subnet: '192.168.11.0/24' },
{ name: 'BESU-VAL', vlanId: 110, subnet: '10.110.0.0/24' },
{ name: 'BESU-SEN', vlanId: 111, subnet: '10.111.0.0/24' },
{ name: 'BESU-RPC', vlanId: 112, subnet: '10.112.0.0/24' },
{ name: 'BLOCKSCOUT', vlanId: 120, subnet: '10.120.0.0/24' },
{ name: 'CACTI', vlanId: 121, subnet: '10.121.0.0/24' },
{ name: 'CCIP-OPS', vlanId: 130, subnet: '10.130.0.0/24' },
{ name: 'CCIP-COMMIT', vlanId: 132, subnet: '10.132.0.0/24' },
{ name: 'CCIP-EXEC', vlanId: 133, subnet: '10.133.0.0/24' },
{ name: 'CCIP-RMN', vlanId: 134, subnet: '10.134.0.0/24' },
{ name: 'FABRIC', vlanId: 140, subnet: '10.140.0.0/24' },
{ name: 'FIREFLY', vlanId: 141, subnet: '10.141.0.0/24' },
{ name: 'INDY', vlanId: 150, subnet: '10.150.0.0/24' },
{ name: 'SANKOFA-SVC', vlanId: 160, subnet: '10.160.0.0/22' },
{ name: 'PHX-SOV-SMOM', vlanId: 200, subnet: '10.200.0.0/20' },
{ name: 'PHX-SOV-ICCC', vlanId: 201, subnet: '10.201.0.0/20' },
{ name: 'PHX-SOV-DBIS', vlanId: 202, subnet: '10.202.0.0/24' },
{ name: 'PHX-SOV-AR', vlanId: 203, subnet: '10.203.0.0/20' },
];
const log = (message) => {
const timestamp = new Date().toISOString();
console.log(`[${timestamp}] ${message}`);
};
const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));
async function login(page) {
log('🔐 Logging in to UDM Pro...');
try {
await page.goto(UDM_PRO_URL, { waitUntil: 'networkidle', timeout: TIMEOUT });
await sleep(2000);
// Check if already logged in
const currentUrl = page.url();
if (!currentUrl.includes('/login')) {
log('✅ Already logged in');
return true;
}
// Fill login form
log('Filling login form...');
await page.fill('input[type="text"], input[name="username"], input[placeholder*="username" i], input[placeholder*="email" i]', USERNAME);
await page.fill('input[type="password"], input[name="password"]', PASSWORD);
// Click login button
await page.click('button[type="submit"], button:has-text("Sign In"), button:has-text("Login")');
await sleep(3000);
// Wait for navigation
await page.waitForURL(/^(?!.*login)/, { timeout: 10000 });
log('✅ Login successful');
return true;
} catch (error) {
log(`❌ Login failed: ${error.message}`);
return false;
}
}
async function verifyNetworkIsolation(page, vlan) {
log(`\n🔍 Verifying Network Isolation for ${vlan.name} (VLAN ${vlan.vlanId})...`);
try {
// Navigate to Networks
log('Navigating to Networks...');
await page.goto(`${UDM_PRO_URL}/network/default/settings/networks`, { waitUntil: 'networkidle', timeout: TIMEOUT });
await sleep(2000);
// Find and click on the VLAN
log(`Looking for ${vlan.name}...`);
const vlanRow = page.locator(`text=${vlan.name}`).first();
await vlanRow.waitFor({ timeout: 10000 });
await vlanRow.click();
await sleep(2000);
// Check for Network Isolation checkbox
log('Checking Network Isolation setting...');
// Try multiple selectors for the checkbox
const isolationSelectors = [
'input[type="checkbox"][name*="isolate" i]',
'input[type="checkbox"][id*="isolate" i]',
'label:has-text("Isolate Network") input[type="checkbox"]',
'input[type="checkbox"]:near(:text("Isolate Network"))',
];
let isolationChecked = null;
for (const selector of isolationSelectors) {
try {
const checkbox = page.locator(selector).first();
if (await checkbox.count() > 0) {
isolationChecked = await checkbox.isChecked();
log(`Found Network Isolation checkbox: ${isolationChecked ? 'CHECKED ❌' : 'UNCHECKED ✅'}`);
break;
}
} catch (e) {
// Continue to next selector
}
}
if (isolationChecked === null) {
// Try to find text and check nearby checkbox
const isolateText = page.locator('text=/isolate.*network/i').first();
if (await isolateText.count() > 0) {
const nearbyCheckbox = isolateText.locator('..').locator('input[type="checkbox"]').first();
if (await nearbyCheckbox.count() > 0) {
isolationChecked = await nearbyCheckbox.isChecked();
log(`Found Network Isolation checkbox (near text): ${isolationChecked ? 'CHECKED ❌' : 'UNCHECKED ✅'}`);
}
}
}
if (isolationChecked === null) {
log('⚠️ Could not find Network Isolation checkbox');
return { vlan: vlan.name, isolation: 'unknown', error: 'Checkbox not found' };
}
return {
vlan: vlan.name,
vlanId: vlan.vlanId,
isolation: isolationChecked ? 'enabled' : 'disabled',
status: isolationChecked ? '❌ NEEDS FIX' : '✅ OK'
};
} catch (error) {
log(`❌ Error verifying ${vlan.name}: ${error.message}`);
return { vlan: vlan.name, isolation: 'error', error: error.message };
}
}
async function verifyZoneMatrix(page) {
log('\n🔍 Verifying Zone Matrix configuration...');
try {
// Navigate to Policy Engine / Zone Matrix
log('Navigating to Policy Engine...');
await page.goto(`${UDM_PRO_URL}/network/default/settings/policy-engine`, { waitUntil: 'networkidle', timeout: TIMEOUT });
await sleep(3000);
// Look for Zone Matrix
log('Looking for Zone Matrix...');
// Try to find "Internal" → "Internal" cell
const internalText = page.locator('text=/internal/i').first();
if (await internalText.count() > 0) {
log('Found Internal zone reference');
}
// Look for grid/table with zone matrix
const matrixSelectors = [
'text=/allow.*all/i',
'text=/internal.*internal/i',
'[data-testid*="zone"]',
'.zone-matrix',
];
let zoneMatrixStatus = null;
for (const selector of matrixSelectors) {
try {
const element = page.locator(selector).first();
if (await element.count() > 0) {
const text = await element.textContent();
log(`Found zone matrix element: ${text}`);
if (text && text.toLowerCase().includes('allow all')) {
zoneMatrixStatus = 'allow_all';
}
}
} catch (e) {
// Continue
}
}
// Try JavaScript evaluation to find zone matrix
try {
const zoneMatrixInfo = await page.evaluate(() => {
// Look for elements containing "Internal" and "Allow"
const elements = Array.from(document.querySelectorAll('*'));
for (const el of elements) {
const text = el.textContent || '';
if (text.includes('Internal') && text.includes('Allow')) {
return { found: true, text: text.substring(0, 100) };
}
}
return { found: false };
});
if (zoneMatrixInfo.found) {
log(`Found zone matrix reference: ${zoneMatrixInfo.text}`);
if (zoneMatrixInfo.text.toLowerCase().includes('allow all')) {
zoneMatrixStatus = 'allow_all';
}
}
} catch (e) {
log(`JavaScript evaluation failed: ${e.message}`);
}
if (zoneMatrixStatus === 'allow_all') {
return { status: '✅ OK', configured: true };
} else {
return { status: '⚠️ NEEDS VERIFICATION', configured: false };
}
} catch (error) {
log(`❌ Error verifying Zone Matrix: ${error.message}`);
return { status: 'error', error: error.message };
}
}
async function testInterVlanRouting() {
log('\n🧪 Testing Inter-VLAN Routing...');
const gateways = [
{ ip: '10.110.0.1', name: 'BESU-VAL (VLAN 110)' },
{ ip: '10.111.0.1', name: 'BESU-SEN (VLAN 111)' },
{ ip: '10.112.0.1', name: 'BESU-RPC (VLAN 112)' },
{ ip: '10.120.0.1', name: 'BLOCKSCOUT (VLAN 120)' },
{ ip: '10.121.0.1', name: 'CACTI (VLAN 121)' },
{ ip: '10.130.0.1', name: 'CCIP-OPS (VLAN 130)' },
{ ip: '10.132.0.1', name: 'CCIP-COMMIT (VLAN 132)' },
{ ip: '10.133.0.1', name: 'CCIP-EXEC (VLAN 133)' },
{ ip: '10.134.0.1', name: 'CCIP-RMN (VLAN 134)' },
{ ip: '10.140.0.1', name: 'FABRIC (VLAN 140)' },
{ ip: '10.141.0.1', name: 'FIREFLY (VLAN 141)' },
{ ip: '10.150.0.1', name: 'INDY (VLAN 150)' },
{ ip: '10.160.0.1', name: 'SANKOFA-SVC (VLAN 160)' },
{ ip: '10.200.0.1', name: 'PHX-SOV-SMOM (VLAN 200)' },
{ ip: '10.201.0.1', name: 'PHX-SOV-ICCC (VLAN 201)' },
{ ip: '10.202.0.1', name: 'PHX-SOV-DBIS (VLAN 202)' },
{ ip: '10.203.0.1', name: 'PHX-SOV-AR (VLAN 203)' },
];
const results = [];
let reachable = 0;
let unreachable = 0;
const { execSync } = await import('child_process');
for (const gateway of gateways) {
try {
execSync(`ping -c 1 -W 2 ${gateway.ip}`, { stdio: 'ignore', timeout: 3000 });
log(`${gateway.name} (${gateway.ip}) - REACHABLE`);
results.push({ ...gateway, reachable: true });
reachable++;
} catch (e) {
log(`${gateway.name} (${gateway.ip}) - UNREACHABLE`);
results.push({ ...gateway, reachable: false });
unreachable++;
}
}
log(`\n📊 Routing Test Results: ${reachable} reachable, ${unreachable} unreachable`);
return { results, reachable, unreachable };
}
async function main() {
log('🚀 Starting VLAN Settings Verification');
log(`UDM Pro URL: ${UDM_PRO_URL}`);
log(`Headless: ${HEADLESS}`);
log('');
if (!PASSWORD) {
log('❌ UNIFI_PASSWORD not set. Please set it in ~/.env');
process.exit(1);
}
const browser = await chromium.launch({
headless: HEADLESS,
ignoreHTTPSErrors: true,
});
const context = await browser.newContext({
ignoreHTTPSErrors: true,
});
const page = await context.newPage();
try {
// Login
const loginSuccess = await login(page);
if (!loginSuccess) {
log('❌ Failed to login. Exiting.');
await browser.close();
process.exit(1);
}
// Verify Network Isolation for all VLANs
log('\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
log('1⃣ VERIFYING NETWORK ISOLATION');
log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
const isolationResults = [];
for (const vlan of VLAN_LIST) {
const result = await verifyNetworkIsolation(page, vlan);
isolationResults.push(result);
await sleep(1000); // Brief pause between VLANs
}
// Verify Zone Matrix
log('\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
log('2⃣ VERIFYING ZONE MATRIX');
log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
const zoneMatrixResult = await verifyZoneMatrix(page);
// Test Inter-VLAN Routing
log('\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
log('3⃣ TESTING INTER-VLAN ROUTING');
log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
const routingResults = await testInterVlanRouting();
// Summary
log('\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
log('📊 VERIFICATION SUMMARY');
log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
log('\n1. Network Isolation:');
const isolationOk = isolationResults.filter(r => r.isolation === 'disabled').length;
const isolationNeedsFix = isolationResults.filter(r => r.isolation === 'enabled').length;
const isolationUnknown = isolationResults.filter(r => r.isolation === 'unknown' || r.isolation === 'error').length;
log(` ✅ Correctly disabled: ${isolationOk}/${VLAN_LIST.length}`);
log(` ❌ Needs fix (enabled): ${isolationNeedsFix}`);
log(` ⚠️ Unknown/Error: ${isolationUnknown}`);
if (isolationNeedsFix > 0) {
log('\n VLANs that need Network Isolation disabled:');
isolationResults.filter(r => r.isolation === 'enabled').forEach(r => {
log(`${r.vlan} (VLAN ${r.vlanId})`);
});
}
log('\n2. Zone Matrix:');
log(` ${zoneMatrixResult.status}`);
if (!zoneMatrixResult.configured) {
log(' ⚠️ Please verify manually: Policy Engine → Zone Matrix → Internal → Internal = Allow All');
}
log('\n3. Inter-VLAN Routing:');
log(` ✅ Reachable: ${routingResults.reachable}`);
log(` ❌ Unreachable: ${routingResults.unreachable}`);
if (routingResults.unreachable > 0) {
log('\n Unreachable gateways:');
routingResults.results.filter(r => !r.reachable).forEach(r => {
log(`${r.name} (${r.ip})`);
});
log('\n 💡 If routing is not working, ensure:');
log(' • Network Isolation is disabled for all VLANs');
log(' • Zone Matrix: Internal → Internal = Allow All');
}
log('\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
log('✅ Verification Complete');
log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
} catch (error) {
log(`❌ Error: ${error.message}`);
console.error(error);
} finally {
if (!HEADLESS) {
log('\n⏸ Browser will remain open for 30 seconds for inspection...');
await sleep(30000);
}
await browser.close();
}
}
main().catch(console.error);