Some checks failed
Deploy to Phoenix / deploy (push) Has been cancelled
- 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>
278 lines
9.0 KiB
JavaScript
Executable File
278 lines
9.0 KiB
JavaScript
Executable File
#!/usr/bin/env node
|
|
/**
|
|
* List current ACL rules from UniFi Network API
|
|
* Helps diagnose what might be blocking access to VLAN 11
|
|
*/
|
|
|
|
import { readFileSync } from 'fs';
|
|
import { join } from 'path';
|
|
import { homedir } from 'os';
|
|
|
|
// Load environment variables
|
|
const envPath = join(homedir(), '.env');
|
|
function loadEnvFile(filePath) {
|
|
try {
|
|
const envFile = readFileSync(filePath, 'utf8');
|
|
const envVars = envFile.split('\n').filter(
|
|
(line) => line.includes('=') && !line.trim().startsWith('#')
|
|
);
|
|
for (const line of envVars) {
|
|
const [key, ...values] = line.split('=');
|
|
if (key && values.length > 0 && /^[A-Z_][A-Z0-9_]*$/.test(key.trim())) {
|
|
let value = values.join('=').trim();
|
|
if (
|
|
(value.startsWith('"') && value.endsWith('"')) ||
|
|
(value.startsWith("'") && value.endsWith("'"))
|
|
) {
|
|
value = value.slice(1, -1);
|
|
}
|
|
process.env[key.trim()] = value;
|
|
}
|
|
}
|
|
return true;
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
loadEnvFile(envPath);
|
|
|
|
const baseUrl = process.env.UNIFI_UDM_URL || 'https://192.168.0.1';
|
|
const apiKey = process.env.UNIFI_API_KEY;
|
|
const siteId = '88f7af54-98f8-306a-a1c7-c9349722b1f6';
|
|
|
|
if (!apiKey) {
|
|
console.error('❌ UNIFI_API_KEY not set in environment');
|
|
process.exit(1);
|
|
}
|
|
|
|
// Fetch networks to map IDs to VLANs
|
|
async function fetchNetworks() {
|
|
const response = await fetch(`${baseUrl}/proxy/network/integration/v1/sites/${siteId}/networks`, {
|
|
headers: {
|
|
'X-API-KEY': apiKey,
|
|
'Accept': 'application/json',
|
|
},
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`Failed to fetch networks: ${response.status} ${response.statusText}`);
|
|
}
|
|
|
|
const data = await response.json();
|
|
const networks = data.data || [];
|
|
|
|
const networkMap = {};
|
|
for (const net of networks) {
|
|
networkMap[net.id] = {
|
|
vlanId: net.vlanId,
|
|
name: net.name,
|
|
subnet: net.ipSubnet,
|
|
};
|
|
}
|
|
|
|
return networkMap;
|
|
}
|
|
|
|
// List ACL rules
|
|
async function listACLRules() {
|
|
const response = await fetch(`${baseUrl}/proxy/network/integration/v1/sites/${siteId}/acl-rules`, {
|
|
headers: {
|
|
'X-API-KEY': apiKey,
|
|
'Accept': 'application/json',
|
|
},
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`Failed to fetch ACL rules: ${response.status} ${response.statusText}`);
|
|
}
|
|
|
|
const data = await response.json();
|
|
return data.data || [];
|
|
}
|
|
|
|
// Main function
|
|
async function main() {
|
|
try {
|
|
console.log('Fetching ACL Rules from UniFi Network API');
|
|
console.log('==========================================');
|
|
console.log('');
|
|
|
|
// Fetch networks and ACL rules in parallel
|
|
const [networkMap, rules] = await Promise.all([
|
|
fetchNetworks(),
|
|
listACLRules(),
|
|
]);
|
|
|
|
console.log(`Found ${rules.length} ACL rules`);
|
|
console.log('');
|
|
|
|
if (rules.length === 0) {
|
|
console.log('No ACL rules configured. Default policy applies.');
|
|
return;
|
|
}
|
|
|
|
// Find VLAN 11 network ID
|
|
const vlan11Network = Object.values(networkMap).find(n => n.vlanId === 11);
|
|
const vlan11Id = vlan11Network ? Object.keys(networkMap).find(id => networkMap[id].vlanId === 11) : null;
|
|
|
|
console.log('='.repeat(80));
|
|
console.log('ALL ACL RULES (sorted by priority/index)');
|
|
console.log('='.repeat(80));
|
|
console.log('');
|
|
|
|
// Sort rules by index (lower index = higher priority)
|
|
const sortedRules = [...rules].sort((a, b) => (a.index || 9999) - (b.index || 9999));
|
|
|
|
for (const rule of sortedRules) {
|
|
const enabled = rule.enabled ? '✅' : '❌';
|
|
const action = rule.action === 'BLOCK' ? '🚫 BLOCK' : '✅ ALLOW';
|
|
|
|
console.log(`${enabled} ${action} - ${rule.name || 'Unnamed Rule'}`);
|
|
console.log(` Priority/Index: ${rule.index ?? 'N/A'}`);
|
|
if (rule.description) {
|
|
console.log(` Description: ${rule.description}`);
|
|
}
|
|
|
|
// Parse source filter
|
|
if (rule.sourceFilter) {
|
|
if (rule.sourceFilter.type === 'NETWORKS' && rule.sourceFilter.networkIds) {
|
|
const sourceNetworks = rule.sourceFilter.networkIds
|
|
.map(id => {
|
|
const net = networkMap[id];
|
|
return net ? `VLAN ${net.vlanId} (${net.name})` : id;
|
|
})
|
|
.join(', ');
|
|
console.log(` Source: ${sourceNetworks}`);
|
|
} else if (rule.sourceFilter.type === 'NETWORK' && rule.sourceFilter.networkIds) {
|
|
const sourceNetworks = rule.sourceFilter.networkIds
|
|
.map(id => {
|
|
const net = networkMap[id];
|
|
return net ? `VLAN ${net.vlanId} (${net.name})` : id;
|
|
})
|
|
.join(', ');
|
|
console.log(` Source: ${sourceNetworks}`);
|
|
} else if (rule.sourceFilter.type === 'IP_ADDRESS') {
|
|
console.log(` Source: IP Address Filter`);
|
|
} else {
|
|
console.log(` Source: ${JSON.stringify(rule.sourceFilter)}`);
|
|
}
|
|
} else {
|
|
console.log(` Source: Any`);
|
|
}
|
|
|
|
// Parse destination filter
|
|
if (rule.destinationFilter) {
|
|
if (rule.destinationFilter.type === 'NETWORKS' && rule.destinationFilter.networkIds) {
|
|
const destNetworks = rule.destinationFilter.networkIds
|
|
.map(id => {
|
|
const net = networkMap[id];
|
|
return net ? `VLAN ${net.vlanId} (${net.name})` : id;
|
|
})
|
|
.join(', ');
|
|
console.log(` Destination: ${destNetworks}`);
|
|
} else if (rule.destinationFilter.type === 'NETWORK' && rule.destinationFilter.networkIds) {
|
|
const destNetworks = rule.destinationFilter.networkIds
|
|
.map(id => {
|
|
const net = networkMap[id];
|
|
return net ? `VLAN ${net.vlanId} (${net.name})` : id;
|
|
})
|
|
.join(', ');
|
|
console.log(` Destination: ${destNetworks}`);
|
|
} else if (rule.destinationFilter.type === 'IP_ADDRESS') {
|
|
console.log(` Destination: IP Address Filter`);
|
|
} else {
|
|
console.log(` Destination: ${JSON.stringify(rule.destinationFilter)}`);
|
|
}
|
|
} else {
|
|
console.log(` Destination: Any`);
|
|
}
|
|
|
|
// Protocol filter
|
|
if (rule.protocolFilter && rule.protocolFilter.length > 0) {
|
|
console.log(` Protocol: ${rule.protocolFilter.join(', ')}`);
|
|
} else {
|
|
console.log(` Protocol: Any`);
|
|
}
|
|
|
|
// Port filter
|
|
if (rule.portFilter) {
|
|
if (rule.portFilter.type === 'PORTS' && rule.portFilter.ports) {
|
|
console.log(` Ports: ${rule.portFilter.ports.join(', ')}`);
|
|
} else {
|
|
console.log(` Ports: ${JSON.stringify(rule.portFilter)}`);
|
|
}
|
|
}
|
|
|
|
console.log('');
|
|
}
|
|
|
|
// Analyze rules affecting VLAN 11
|
|
if (vlan11Id) {
|
|
console.log('='.repeat(80));
|
|
console.log('RULES AFFECTING VLAN 11 (MGMT-LAN)');
|
|
console.log('='.repeat(80));
|
|
console.log('');
|
|
|
|
const vlan11Rules = sortedRules.filter(rule => {
|
|
if (!rule.enabled) return false;
|
|
|
|
// Check if rule affects VLAN 11 as source
|
|
const affectsSource = rule.sourceFilter &&
|
|
((rule.sourceFilter.type === 'NETWORKS' || rule.sourceFilter.type === 'NETWORK') &&
|
|
rule.sourceFilter.networkIds &&
|
|
rule.sourceFilter.networkIds.includes(vlan11Id));
|
|
|
|
// Check if rule affects VLAN 11 as destination
|
|
const affectsDest = rule.destinationFilter &&
|
|
((rule.destinationFilter.type === 'NETWORKS' || rule.destinationFilter.type === 'NETWORK') &&
|
|
rule.destinationFilter.networkIds &&
|
|
rule.destinationFilter.networkIds.includes(vlan11Id));
|
|
|
|
return affectsSource || affectsDest;
|
|
});
|
|
|
|
if (vlan11Rules.length === 0) {
|
|
console.log('No specific rules found affecting VLAN 11.');
|
|
console.log('Default policy applies (typically ALLOW for inter-VLAN traffic).');
|
|
} else {
|
|
for (const rule of vlan11Rules) {
|
|
const action = rule.action === 'BLOCK' ? '🚫 BLOCKS' : '✅ ALLOWS';
|
|
const direction = rule.sourceFilter?.networkIds?.includes(vlan11Id)
|
|
? 'FROM VLAN 11'
|
|
: rule.destinationFilter?.networkIds?.includes(vlan11Id)
|
|
? 'TO VLAN 11'
|
|
: 'AFFECTING VLAN 11';
|
|
|
|
console.log(`${action} ${direction}: ${rule.name || 'Unnamed Rule'}`);
|
|
console.log(` Priority: ${rule.index ?? 'N/A'}`);
|
|
if (rule.description) {
|
|
console.log(` ${rule.description}`);
|
|
}
|
|
console.log('');
|
|
}
|
|
}
|
|
}
|
|
|
|
// Summary
|
|
console.log('='.repeat(80));
|
|
console.log('SUMMARY');
|
|
console.log('='.repeat(80));
|
|
console.log('');
|
|
console.log(`Total Rules: ${rules.length}`);
|
|
console.log(`Enabled Rules: ${rules.filter(r => r.enabled).length}`);
|
|
console.log(`Block Rules: ${rules.filter(r => r.action === 'BLOCK' && r.enabled).length}`);
|
|
console.log(`Allow Rules: ${rules.filter(r => r.action === 'ALLOW' && r.enabled).length}`);
|
|
console.log('');
|
|
|
|
} catch (error) {
|
|
console.error('❌ Error:', error.message);
|
|
if (error.stack) {
|
|
console.error(error.stack);
|
|
}
|
|
process.exit(1);
|
|
}
|
|
}
|
|
|
|
main();
|