394 lines
15 KiB
JavaScript
394 lines
15 KiB
JavaScript
|
|
#!/usr/bin/env node
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Query Omada Controller firewall rules for Blockscout access
|
|||
|
|
* Based on test-omada-direct.js - uses direct API calls
|
|||
|
|
*/
|
|||
|
|
|
|||
|
|
import https from 'https';
|
|||
|
|
import { readFileSync } from 'fs';
|
|||
|
|
import { join } from 'path';
|
|||
|
|
import { homedir } from 'os';
|
|||
|
|
|
|||
|
|
const BLOCKSCOUT_IP = '192.168.11.140';
|
|||
|
|
const BLOCKSCOUT_PORT = '80';
|
|||
|
|
|
|||
|
|
// Load environment variables
|
|||
|
|
const envPath = join(homedir(), '.env');
|
|||
|
|
let envVars = {};
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
const envFile = readFileSync(envPath, 'utf8');
|
|||
|
|
envFile.split('\n').forEach(line => {
|
|||
|
|
if (line.includes('=') && !line.trim().startsWith('#')) {
|
|||
|
|
const [key, ...values] = line.split('=');
|
|||
|
|
if (key && /^[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);
|
|||
|
|
}
|
|||
|
|
envVars[key.trim()] = value;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('Error loading .env file:', error.message);
|
|||
|
|
process.exit(1);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const baseUrl = envVars.OMADA_CONTROLLER_URL || envVars.OMADA_CONTROLLER_BASE_URL || 'https://192.168.11.8:8043';
|
|||
|
|
const username = envVars.OMADA_ADMIN_USERNAME || envVars.OMADA_API_KEY || envVars.OMADA_CLIENT_ID;
|
|||
|
|
const password = envVars.OMADA_ADMIN_PASSWORD || envVars.OMADA_API_SECRET || envVars.OMADA_CLIENT_SECRET;
|
|||
|
|
const siteId = envVars.OMADA_SITE_ID || '090862bebcb1997bb263eea9364957fe';
|
|||
|
|
const verifySSL = envVars.OMADA_VERIFY_SSL !== 'false';
|
|||
|
|
|
|||
|
|
if (!username || !password) {
|
|||
|
|
console.error('Error: Missing credentials');
|
|||
|
|
console.error('Required: OMADA_ADMIN_USERNAME/OMADA_API_KEY and OMADA_ADMIN_PASSWORD/OMADA_API_SECRET');
|
|||
|
|
process.exit(1);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Parse base URL
|
|||
|
|
const urlObj = new URL(baseUrl);
|
|||
|
|
const hostname = urlObj.hostname;
|
|||
|
|
const port = urlObj.port || (urlObj.protocol === 'https:' ? 443 : 80);
|
|||
|
|
|
|||
|
|
console.log('════════════════════════════════════════');
|
|||
|
|
console.log('Omada Firewall Rules Check for Blockscout');
|
|||
|
|
console.log('════════════════════════════════════════');
|
|||
|
|
console.log('');
|
|||
|
|
console.log(`Controller URL: ${baseUrl}`);
|
|||
|
|
console.log(`Site ID: ${siteId}`);
|
|||
|
|
console.log(`Blockscout IP: ${BLOCKSCOUT_IP}`);
|
|||
|
|
console.log(`Blockscout Port: ${BLOCKSCOUT_PORT}`);
|
|||
|
|
console.log('');
|
|||
|
|
|
|||
|
|
// Create HTTPS agent
|
|||
|
|
const agent = new https.Agent({
|
|||
|
|
rejectUnauthorized: verifySSL,
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// Function to make API request
|
|||
|
|
function apiRequest(method, path, data = null, token = null, cookies = null) {
|
|||
|
|
return new Promise((resolve, reject) => {
|
|||
|
|
const options = {
|
|||
|
|
hostname,
|
|||
|
|
port,
|
|||
|
|
path,
|
|||
|
|
method,
|
|||
|
|
agent,
|
|||
|
|
headers: {
|
|||
|
|
'Content-Type': 'application/json',
|
|||
|
|
},
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
if (token) {
|
|||
|
|
options.headers['Csrf-Token'] = token;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (cookies) {
|
|||
|
|
options.headers['Cookie'] = cookies;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const req = https.request(options, (res) => {
|
|||
|
|
let body = '';
|
|||
|
|
res.on('data', (chunk) => {
|
|||
|
|
body += chunk;
|
|||
|
|
});
|
|||
|
|
res.on('end', () => {
|
|||
|
|
try {
|
|||
|
|
const json = JSON.parse(body);
|
|||
|
|
resolve(json);
|
|||
|
|
} catch (e) {
|
|||
|
|
resolve({ raw: body, statusCode: res.statusCode, headers: res.headers });
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
req.on('error', (error) => {
|
|||
|
|
reject(error);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
if (data) {
|
|||
|
|
req.write(JSON.stringify(data));
|
|||
|
|
}
|
|||
|
|
req.end();
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
async function main() {
|
|||
|
|
try {
|
|||
|
|
console.log('1. Authenticating to Omada Controller...');
|
|||
|
|
const loginResponse = await apiRequest('POST', '/api/v2/login', {
|
|||
|
|
username,
|
|||
|
|
password,
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
if (loginResponse.errorCode !== 0) {
|
|||
|
|
console.error(` ✗ Login failed: ${loginResponse.msg || 'Unknown error'}`);
|
|||
|
|
console.error(` Error Code: ${loginResponse.errorCode}`);
|
|||
|
|
process.exit(1);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const token = loginResponse.result?.token || loginResponse.token;
|
|||
|
|
if (!token) {
|
|||
|
|
console.error(' ✗ No token received');
|
|||
|
|
process.exit(1);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
console.log(' ✓ Login successful');
|
|||
|
|
console.log('');
|
|||
|
|
|
|||
|
|
// Build cookie string for subsequent requests
|
|||
|
|
const cookies = `TOKEN=${token}`;
|
|||
|
|
const effectiveSiteId = siteId;
|
|||
|
|
|
|||
|
|
console.log(`2. Querying firewall rules for site: ${effectiveSiteId}...`);
|
|||
|
|
console.log('');
|
|||
|
|
|
|||
|
|
// Query firewall rules - try different endpoint formats
|
|||
|
|
let rulesResponse;
|
|||
|
|
let firewallPath;
|
|||
|
|
|
|||
|
|
// Try different endpoint paths
|
|||
|
|
const endpointPaths = [
|
|||
|
|
`/api/v2/sites/${effectiveSiteId}/firewall/rules`,
|
|||
|
|
`/api/v2/sites/${effectiveSiteId}/firewall`,
|
|||
|
|
`/sites/${effectiveSiteId}/firewall/rules`,
|
|||
|
|
`/sites/${effectiveSiteId}/firewall`,
|
|||
|
|
`/firewall/rules`,
|
|||
|
|
`/firewall`,
|
|||
|
|
];
|
|||
|
|
|
|||
|
|
let rulesFound = false;
|
|||
|
|
for (const path of endpointPaths) {
|
|||
|
|
try {
|
|||
|
|
firewallPath = path;
|
|||
|
|
rulesResponse = await apiRequest('GET', path, null, token, cookies);
|
|||
|
|
|
|||
|
|
// Check if we got a valid response (not a redirect)
|
|||
|
|
if (rulesResponse.errorCode === 0 || Array.isArray(rulesResponse.result) || Array.isArray(rulesResponse)) {
|
|||
|
|
rulesFound = true;
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
} catch (e) {
|
|||
|
|
// Try next path
|
|||
|
|
continue;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (!rulesFound) {
|
|||
|
|
console.error(` ✗ Could not find valid firewall rules endpoint`);
|
|||
|
|
console.error(` Tried paths: ${endpointPaths.join(', ')}`);
|
|||
|
|
console.error(` Last response: ${JSON.stringify(rulesResponse, null, 2)}`);
|
|||
|
|
console.error('');
|
|||
|
|
console.error('Note: Firewall rules may need to be checked via Omada Controller web interface:');
|
|||
|
|
console.error(` ${baseUrl}`);
|
|||
|
|
console.error(' Navigate to: Settings → Firewall → Firewall Rules');
|
|||
|
|
process.exit(1);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (rulesResponse.errorCode !== 0) {
|
|||
|
|
console.error(` ✗ Failed to query firewall rules: ${rulesResponse.msg || 'Unknown error'}`);
|
|||
|
|
console.error(` Error Code: ${rulesResponse.errorCode}`);
|
|||
|
|
console.error(` Response: ${JSON.stringify(rulesResponse, null, 2)}`);
|
|||
|
|
process.exit(1);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const rules = Array.isArray(rulesResponse.result) ? rulesResponse.result : [];
|
|||
|
|
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 ||
|
|||
|
|
(typeof rule.dstIp === 'string' && rule.dstIp.includes('192.168.11')) ||
|
|||
|
|
rule.dstIp === '192.168.11.0/24';
|
|||
|
|
|
|||
|
|
const affectsPort80 =
|
|||
|
|
!rule.dstPort ||
|
|||
|
|
rule.dstPort === BLOCKSCOUT_PORT ||
|
|||
|
|
(typeof rule.dstPort === 'string' && 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('════════════════════════════════════════');
|
|||
|
|
console.log(`🔍 Found ${relevantRules.length} rule(s) that might affect Blockscout:`);
|
|||
|
|
console.log('════════════════════════════════════════');
|
|||
|
|
console.log('');
|
|||
|
|
|
|||
|
|
relevantRules.forEach((rule, index) => {
|
|||
|
|
console.log(`Rule ${index + 1}: ${rule.name || rule.id || 'Unnamed'}`);
|
|||
|
|
console.log(` ID: ${rule.id || 'N/A'}`);
|
|||
|
|
console.log(` Enabled: ${rule.enable ? 'Yes ✓' : 'No ✗'}`);
|
|||
|
|
console.log(` Action: ${rule.action || 'N/A'}`);
|
|||
|
|
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 !== undefined ? 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('');
|
|||
|
|
console.log('✅ Recommended Action:');
|
|||
|
|
console.log(' Create an allow rule in Omada Controller with HIGH priority:');
|
|||
|
|
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 (above deny rules)');
|
|||
|
|
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('');
|
|||
|
|
|
|||
|
|
const highestAllowPriority = Math.max(...allowRules.map((r) => r.priority || 0));
|
|||
|
|
const lowestDenyPriority = Math.min(...denyRules.map((r) => r.priority || 9999));
|
|||
|
|
|
|||
|
|
if (highestAllowPriority < lowestDenyPriority) {
|
|||
|
|
console.log('✅ Priority order looks correct (allow rules above deny rules).');
|
|||
|
|
} else {
|
|||
|
|
console.log('❌ Issue: Some deny rules have higher priority than allow rules.');
|
|||
|
|
console.log(' Adjust rule priority so allow rules are 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('════════════════════════════════════════');
|
|||
|
|
console.log('ℹ️ No firewall rules found that specifically target Blockscout.');
|
|||
|
|
console.log('════════════════════════════════════════');
|
|||
|
|
console.log('');
|
|||
|
|
|
|||
|
|
// Check for deny rules
|
|||
|
|
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 in total:`);
|
|||
|
|
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 IP: ${rule.dstIp}`);
|
|||
|
|
if (rule.dstPort) console.log(` Dest Port: ${rule.dstPort}`);
|
|||
|
|
if (rule.direction) console.log(` Direction: ${rule.direction}`);
|
|||
|
|
});
|
|||
|
|
if (denyRules.length > 10) {
|
|||
|
|
console.log(` ... and ${denyRules.length - 10} more`);
|
|||
|
|
}
|
|||
|
|
console.log('');
|
|||
|
|
console.log('Note: These rules may affect Blockscout if they match the traffic pattern.');
|
|||
|
|
console.log('');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
console.log('✅ Recommendation:');
|
|||
|
|
console.log(' Create an explicit allow rule to ensure Blockscout access:');
|
|||
|
|
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');
|
|||
|
|
console.log(' Destination IP: 192.168.11.140');
|
|||
|
|
console.log(' Destination Port: 80');
|
|||
|
|
console.log(' Priority: High');
|
|||
|
|
console.log('');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Show all rules summary
|
|||
|
|
console.log('════════════════════════════════════════');
|
|||
|
|
console.log('All Firewall Rules Summary');
|
|||
|
|
console.log('════════════════════════════════════════');
|
|||
|
|
console.log('');
|
|||
|
|
|
|||
|
|
const enabledRules = rules.filter((r) => r.enable);
|
|||
|
|
const disabledRules = rules.filter((r) => !r.enable);
|
|||
|
|
const allowCount = enabledRules.filter((r) => r.action === 'allow').length;
|
|||
|
|
const denyCount = enabledRules.filter((r) => r.action === 'deny' || r.action === 'reject').length;
|
|||
|
|
|
|||
|
|
console.log(`Total Rules: ${rules.length}`);
|
|||
|
|
console.log(` Enabled: ${enabledRules.length}`);
|
|||
|
|
console.log(` Disabled: ${disabledRules.length}`);
|
|||
|
|
console.log(` Allow Actions: ${allowCount}`);
|
|||
|
|
console.log(` Deny/Reject Actions: ${denyCount}`);
|
|||
|
|
console.log('');
|
|||
|
|
|
|||
|
|
if (rules.length > 0) {
|
|||
|
|
console.log('First 10 enabled rules:');
|
|||
|
|
enabledRules.slice(0, 10).forEach((rule, index) => {
|
|||
|
|
const actionIcon = rule.action === 'allow' ? '✓' : rule.action === 'deny' ? '✗' : '?';
|
|||
|
|
console.log(` ${index + 1}. [${actionIcon}] ${rule.name || rule.id || 'Unnamed'} (Priority: ${rule.priority || 'N/A'})`);
|
|||
|
|
});
|
|||
|
|
if (enabledRules.length > 10) {
|
|||
|
|
console.log(` ... and ${enabledRules.length - 10} more`);
|
|||
|
|
}
|
|||
|
|
console.log('');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
console.log('════════════════════════════════════════');
|
|||
|
|
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('\n❌ Error:');
|
|||
|
|
console.error('');
|
|||
|
|
if (error.message) {
|
|||
|
|
console.error(` ${error.message}`);
|
|||
|
|
} else {
|
|||
|
|
console.error(' ', error);
|
|||
|
|
}
|
|||
|
|
if (error.stack) {
|
|||
|
|
console.error('');
|
|||
|
|
console.error('Stack trace:');
|
|||
|
|
console.error(error.stack);
|
|||
|
|
}
|
|||
|
|
process.exit(1);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
main();
|
|||
|
|
|