#!/usr/bin/env node /** * Create firewall rules via UniFi Network API * Node.js version for better JSON handling */ 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 Firewall Rules via API'); console.log('=================================='); console.log(''); console.log(`UDM URL: ${baseUrl}`); console.log(`Site ID: ${siteId}`); 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 || []; // Create VLAN to network ID mapping 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(); // Key VLANs we need const keyVlans = [11, 110, 111, 112, 120, 121, 130, 132, 133, 134, 140, 141, 150, 160, 200, 201, 202, 203]; console.log('Network ID Mapping:'); console.log('='.repeat(80)); for (const vlan of keyVlans.sort((a, b) => a - b)) { if (vlanMap[vlan]) { console.log(`VLAN ${String(vlan).padStart(3)} (${vlanMap[vlan].name.padEnd(15)}): ${vlanMap[vlan].id}`); } } console.log(''); // Create firewall rules console.log('Creating firewall rules...'); console.log(''); const rules = []; // Rule 1: Block sovereign tenant inter-VLAN traffic (VLANs 200-203) const sovereignVlans = [200, 201, 202, 203].map(v => vlanMap[v]?.id).filter(Boolean); if (sovereignVlans.length === 4) { rules.push({ name: 'Block Sovereign Tenant East-West Traffic', description: 'Deny traffic between sovereign tenant VLANs (200-203) for isolation', action: 'BLOCK', index: 100, sourceNetworks: sovereignVlans, destNetworks: sovereignVlans, protocolFilter: null, }); } // Create rules for (const ruleConfig of rules) { console.log(`Creating rule: ${ruleConfig.name}`); const result = await createACLRule(ruleConfig); if (result.success) { console.log(' ✅ Rule created successfully'); } else { console.log(` ❌ Failed to create rule (HTTP ${result.status})`); console.log(` Error: ${JSON.stringify(result.error, null, 2)}`); } console.log(''); // Small delay between requests await new Promise(resolve => setTimeout(resolve, 500)); } console.log('✅ Firewall rule creation complete!'); console.log(''); console.log('Note: Additional rules can be added for:'); console.log(' - Management VLAN access (VLAN 11 → Service VLANs)'); console.log(' - Monitoring access (Service VLANs → VLAN 11)'); console.log(' - Other inter-VLAN rules as needed'); } catch (error) { console.error('❌ Error:', error.message); if (error.stack) { console.error(error.stack); } process.exit(1); } } main();