#!/usr/bin/env node /** * Query Omada Controller firewall rules for Blockscout access * Uses direct API calls with admin credentials */ import dotenv from 'dotenv'; import { fileURLToPath } from 'url'; import { dirname, join } from 'path'; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); // Load environment variables dotenv.config({ path: join(__dirname, '..', '.env') }); const BLOCKSCOUT_IP = '192.168.11.140'; const BLOCKSCOUT_PORT = '80'; // Get configuration from environment const controllerUrl = process.env.OMADA_CONTROLLER_URL || process.env.OMADA_CONTROLLER_BASE_URL || 'https://192.168.11.8:8043'; const username = process.env.OMADA_ADMIN_USERNAME || process.env.OMADA_API_KEY || process.env.OMADA_CLIENT_ID; const password = process.env.OMADA_ADMIN_PASSWORD || process.env.OMADA_API_SECRET || process.env.OMADA_CLIENT_SECRET; const siteId = process.env.OMADA_SITE_ID || '090862bebcb1997bb263eea9364957fe'; const verifySSL = process.env.OMADA_VERIFY_SSL !== 'false'; // Disable SSL verification if needed if (!verifySSL) { process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'; } async function login() { const url = `${controllerUrl}/api/v2/login`; const response = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ username: username, password: password, }), }); if (!response.ok) { const errorText = await response.text(); throw new Error(`Login failed: ${response.status} ${response.statusText} - ${errorText}`); } const data = await response.json(); if (data.errorCode !== 0) { throw new Error(`Login failed: ${data.msg || 'Unknown error'}`); } const token = data.result?.token || data.token; if (!token) { throw new Error('No token received from server'); } return token; } async function requestWithToken(token, endpoint, method = 'GET', body = null) { const url = `${controllerUrl}${endpoint}`; const options = { method, headers: { 'Content-Type': 'application/json', 'Csrf-Token': token, 'Cookie': `TOKEN=${token}`, }, }; if (body) { options.body = JSON.stringify(body); } const response = await fetch(url, options); if (!response.ok) { const errorText = await response.text(); throw new Error(`API request failed: ${response.status} ${response.statusText} - ${errorText}`); } const data = await response.json(); if (data.errorCode !== 0) { throw new Error(`API error: ${data.msg || 'Unknown error'}`); } return data.result || data; } async function main() { console.log('════════════════════════════════════════'); console.log('Omada Firewall Rules Check for Blockscout'); console.log('════════════════════════════════════════'); console.log(''); console.log(`Controller: ${controllerUrl}`); console.log(`Site ID: ${siteId}`); console.log(`Blockscout IP: ${BLOCKSCOUT_IP}`); console.log(`Blockscout Port: ${BLOCKSCOUT_PORT}`); console.log(''); if (!username || !password) { console.error('❌ Missing Omada credentials in .env file'); console.error(''); console.error('Required environment variables:'); console.error(' OMADA_ADMIN_USERNAME (or OMADA_API_KEY/OMADA_CLIENT_ID)'); console.error(' OMADA_ADMIN_PASSWORD (or OMADA_API_SECRET/OMADA_CLIENT_SECRET)'); console.error(''); console.error('Optional:'); console.error(' OMADA_CONTROLLER_URL (default: https://192.168.11.8:8043)'); console.error(' OMADA_SITE_ID (default: auto-detect)'); process.exit(1); } try { console.log('Authenticating to Omada Controller...'); const token = await login(); console.log('✓ Authentication successful'); console.log(''); console.log('Fetching firewall rules...'); const rules = await requestWithToken(token, `/api/v2/sites/${siteId}/firewall/rules`); console.log(`✓ Found ${rules.length} firewall rules`); console.log(''); // Filter rules that might affect Blockscout const relevantRules = rules.filter((rule) => { const affectsBlockscoutIP = !rule.dstIp || rule.dstIp === BLOCKSCOUT_IP || rule.dstIp.includes(BLOCKSCOUT_IP.split('.').slice(0, 3).join('.')) || rule.dstIp === '192.168.11.0/24' || rule.dstIp === '192.168.11.0/255.255.255.0'; const affectsPort80 = !rule.dstPort || rule.dstPort === BLOCKSCOUT_PORT || rule.dstPort.includes(BLOCKSCOUT_PORT) || rule.dstPort === 'all' || rule.dstPort === '0-65535'; const isTCP = !rule.protocol || rule.protocol === 'tcp' || rule.protocol === 'tcp/udp' || rule.protocol === 'all'; return rule.enable && (affectsBlockscoutIP || affectsPort80) && isTCP; }); if (relevantRules.length > 0) { console.log(`🔍 Found ${relevantRules.length} rule(s) that might affect Blockscout:`); console.log(''); relevantRules.forEach((rule) => { console.log(`Rule: ${rule.name || rule.id}`); console.log(` ID: ${rule.id}`); console.log(` Enabled: ${rule.enable ? 'Yes ✓' : 'No ✗'}`); console.log(` Action: ${rule.action}`); console.log(` Direction: ${rule.direction || 'N/A'}`); console.log(` Protocol: ${rule.protocol || 'all'}`); console.log(` Source IP: ${rule.srcIp || 'Any'}`); console.log(` Source Port: ${rule.srcPort || 'Any'}`); console.log(` Destination IP: ${rule.dstIp || 'Any'}`); console.log(` Destination Port: ${rule.dstPort || 'Any'}`); console.log(` Priority: ${rule.priority || 'N/A'}`); console.log(''); if (rule.action === 'deny' || rule.action === 'reject') { console.log(' ⚠️ WARNING: This rule BLOCKS traffic!'); console.log(''); } }); // Separate allow and deny rules const allowRules = relevantRules.filter((rule) => rule.action === 'allow'); const denyRules = relevantRules.filter((rule) => rule.action === 'deny' || rule.action === 'reject'); console.log('════════════════════════════════════════'); console.log('Analysis'); console.log('════════════════════════════════════════'); console.log(''); if (denyRules.length > 0 && allowRules.length === 0) { console.log('❌ Issue Found:'); console.log(' Deny rules exist that block Blockscout, but no allow rules found.'); console.log(' This explains the "No route to host" error.'); console.log(''); } else if (allowRules.length > 0 && denyRules.length > 0) { console.log('⚠️ Both allow and deny rules exist.'); console.log(' Check rule priority - allow rules must be above deny rules.'); console.log(''); } else if (allowRules.length > 0) { console.log('✅ Allow rules exist for Blockscout.'); console.log(' If issues persist, check rule priority or default policies.'); console.log(''); } } else { console.log('ℹ️ No firewall rules found that specifically target Blockscout.'); console.log(''); console.log('Checking all deny/reject rules...'); console.log(''); const denyRules = rules.filter( (rule) => rule.enable && (rule.action === 'deny' || rule.action === 'reject') ); if (denyRules.length > 0) { console.log(`⚠️ Found ${denyRules.length} deny/reject rules:`); console.log(''); denyRules.slice(0, 10).forEach((rule) => { console.log(` - ${rule.name || rule.id} (Priority: ${rule.priority || 'N/A'})`); if (rule.dstIp) console.log(` Dest: ${rule.dstIp}`); if (rule.dstPort) console.log(` Port: ${rule.dstPort}`); }); if (denyRules.length > 10) { console.log(` ... and ${denyRules.length - 10} more`); } console.log(''); } } console.log('════════════════════════════════════════'); console.log('Recommendations'); console.log('════════════════════════════════════════'); console.log(''); if (relevantRules.length === 0 || relevantRules.filter(r => r.action === 'allow').length === 0) { console.log('✅ Recommended Action:'); console.log(' Create an allow rule in Omada Controller:'); console.log(''); console.log(' Name: Allow Internal to Blockscout HTTP'); console.log(' Enable: Yes'); console.log(' Action: Allow'); console.log(' Direction: Forward'); console.log(' Protocol: TCP'); console.log(' Source IP: 192.168.11.0/24 (or leave blank for Any)'); console.log(' Destination IP: 192.168.11.140'); console.log(' Destination Port: 80'); console.log(' Priority: High (ensure it\'s above any deny rules)'); console.log(''); console.log(' Then apply the configuration.'); console.log(''); } console.log('════════════════════════════════════════'); } catch (error) { console.error('❌ Error:'); console.error(''); if (error.message) { console.error(` ${error.message}`); } else { console.error(' ', error); } console.error(''); process.exit(1); } } main().catch((error) => { console.error('Fatal error:', error); process.exit(1); });