#!/usr/bin/env node /** * Create management VLAN and monitoring firewall rules via UniFi Network API * These rules CAN be automated (non-overlapping source/destination) */ 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); } console.log('Creating Management VLAN Firewall Rules via API'); console.log('================================================='); console.log(''); // Fetch networks 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 vlanMap = {}; for (const net of networks) { const vlanId = net.vlanId; if (vlanId && vlanId > 1) { vlanMap[vlanId] = { id: net.id, name: net.name, }; } } return vlanMap; } // Create ACL rule async function createACLRule(ruleConfig) { const { name, description, action, index, sourceNetworks, destNetworks, protocolFilter } = ruleConfig; const rule = { type: 'IPV4', enabled: true, name, description, action, index, sourceFilter: sourceNetworks && sourceNetworks.length > 0 ? { type: 'NETWORKS', networkIds: sourceNetworks } : null, destinationFilter: destNetworks && destNetworks.length > 0 ? { type: 'NETWORKS', networkIds: destNetworks } : null, protocolFilter: protocolFilter || null, enforcingDeviceFilter: null, }; const response = await fetch(`${baseUrl}/proxy/network/integration/v1/sites/${siteId}/acl-rules`, { method: 'POST', headers: { 'X-API-KEY': apiKey, 'Content-Type': 'application/json', 'Accept': 'application/json', }, body: JSON.stringify(rule), }); const responseText = await response.text(); let responseData; try { responseData = JSON.parse(responseText); } catch { responseData = responseText; } if (response.ok) { return { success: true, data: responseData }; } else { return { success: false, error: responseData, status: response.status }; } } // Main function async function main() { try { console.log('Fetching network IDs...'); const vlanMap = await fetchNetworks(); const mgmtVlanId = vlanMap[11]?.id; if (!mgmtVlanId) { console.error('❌ VLAN 11 (MGMT-LAN) not found'); process.exit(1); } // Service VLANs (excluding sovereign tenants for now) const serviceVlanIds = [110, 111, 112, 120, 121, 130, 132, 133, 134, 140, 141, 150, 160] .map(v => vlanMap[v]?.id) .filter(Boolean); console.log(`✅ Found ${serviceVlanIds.length} service VLANs`); console.log(''); const rules = []; // Rule 1: Allow Management VLAN → Service VLANs (TCP for SSH, HTTPS, etc.) if (serviceVlanIds.length > 0) { rules.push({ name: 'Allow Management to Service VLANs (TCP)', description: 'Allow Management VLAN (11) to access Service VLANs via TCP (SSH, HTTPS, database admin ports)', action: 'ALLOW', index: 10, sourceNetworks: [mgmtVlanId], destNetworks: serviceVlanIds, protocolFilter: ['TCP'], }); } // Rule 2: Allow Service VLANs → Management VLAN (UDP/TCP for monitoring) if (serviceVlanIds.length > 0) { rules.push({ name: 'Allow Monitoring to Management VLAN', description: 'Allow Service VLANs to send monitoring/logging data to Management VLAN (SNMP, monitoring agents)', action: 'ALLOW', index: 20, sourceNetworks: serviceVlanIds, destNetworks: [mgmtVlanId], protocolFilter: ['TCP', 'UDP'], }); } console.log(`Creating ${rules.length} firewall rules...`); console.log(''); let successCount = 0; let failCount = 0; for (const ruleConfig of rules) { console.log(`Creating rule: ${ruleConfig.name}`); const result = await createACLRule(ruleConfig); if (result.success) { console.log(' ✅ Rule created successfully'); successCount++; } else { console.log(` ❌ Failed to create rule (HTTP ${result.status})`); if (result.error && typeof result.error === 'object') { console.log(` Error: ${result.error.message || JSON.stringify(result.error)}`); } else { console.log(` Error: ${result.error}`); } failCount++; } console.log(''); // Small delay between requests await new Promise(resolve => setTimeout(resolve, 500)); } console.log('='.repeat(50)); console.log(`✅ Successfully created: ${successCount}/${rules.length} rules`); if (failCount > 0) { console.log(`❌ Failed: ${failCount} rules`); } console.log(''); // Verify rules console.log('Verifying created rules...'); const verifyResponse = await fetch(`${baseUrl}/proxy/network/integration/v1/sites/${siteId}/acl-rules`, { headers: { 'X-API-KEY': apiKey, 'Accept': 'application/json', }, }); if (verifyResponse.ok) { const verifyData = await verifyResponse.json(); const ruleCount = verifyData.count || 0; console.log(`✅ Found ${ruleCount} ACL rules total`); } } catch (error) { console.error('❌ Error:', error.message); if (error.stack) { console.error(error.stack); } process.exit(1); } } main();