firewall-smoke.cjs 5.9 KB

12345678910111213141516171819202122232425262728293031323334353637
  1. const {chromium}=require(process.env.PLAYWRIGHT_MODULE || 'playwright');
  2. const assert=require('node:assert/strict');const fs=require('fs');
  3. (async()=>{
  4. const browser=await chromium.launch({headless:true,args:['--no-sandbox'],env:process.env});const page=await browser.newPage();const writes=[],errors=[],passed=[];
  5. page.on('pageerror',e=>errors.push(e.message));
  6. await page.route('**/api/firewall/**',async route=>{if(route.request().method()==='GET')return route.continue();writes.push({path:new URL(route.request().url()).pathname,body:route.request().postDataJSON()});await route.fulfill({json:{success:true,id:route.request().postDataJSON()?.id||'filter-preview'}})});
  7. try{
  8. await page.goto(process.env.DEBTIK_TEST_URL||'http://127.0.0.1:8080/',{waitUntil:'domcontentloaded'});await page.locator('#login-username').fill(process.env.DEBTIK_TEST_USER || 'admin');await page.locator('#login-password').fill(process.env.DEBTIK_TEST_PASSWORD || 'admin');await page.locator('#login-submit-btn').click();await page.waitForFunction(()=>state.currentUser!==null);
  9. await page.evaluate(async()=>{navigate('firewall');await loadFirewall(false)});
  10. for(const prefix of ['fw','nat','mangle'])for(const key of ['layer7','connbytes','connrate','pcc','tlshost','psd','ipv4options','dstlimit','ingresspriority','priority','hotspot'])assert.equal(await page.locator('#'+prefix+'-field-'+key).count(),0);
  11. for(const [type,bad]of [['filter',['tarpit','fasttrack-connection']],['nat',['same','socksify','endpoint-independent-nat']],['mangle',['route','sniff-pc','sniff-tzsp','clear-df','strip-ipv4-options','fasttrack-connection']]])for(const action of bad)assert.equal(await page.locator('#'+type+'-modal-action option[value="'+action+'"]').count(),0);
  12. passed.push('Unsupported fields and actions physically removed');
  13. for(const [type,open,apply]of [['filter','openEditFilterModal','applyFilterRule'],['nat','openEditNATModal','applyNATRule'],['mangle','openEditMangleModal','applyMangleRule']]){
  14. const chain=await page.evaluate(async({type,open,apply})=>{const key=type==='filter'?'filterRules':type==='nat'?'natRules':'mangleRules';state[key]=[{id:type+'-preview',chain:'CUSTOM_TEST',action:'accept',disabled:false,passthrough:true}];window[open](type+'-preview');const value=document.getElementById(type+'-modal-chain').value;await window[apply](true);return value},{type,open,apply});
  15. assert.equal(chain,'CUSTOM_TEST');assert.equal(writes.at(-1).body.chain,'CUSTOM_TEST');
  16. }passed.push('Custom chain preserved in all three forms');
  17. for(const [type,prefix,open,apply]of [['filter','fw','openAddFilterModal','applyFilterRule'],['nat','nat','openAddNATModal','applyNATRule']]){
  18. await page.evaluate(async({type,prefix,open,apply})=>{window[open]();window[prefix==='fw'?'toggleFwField':'toggleNATField']('days',true);for(const d of ['sun','mon','tue','wed','thu','fri','sat'])document.getElementById(prefix+'-day-'+d).checked=d==='fri';await window[apply](true)},{type,prefix,open,apply});assert.equal(writes.at(-1).body.days,'fri');
  19. }passed.push('Weekdays serialized for Filter and NAT');
  20. await page.evaluate(()=>{state.filterRules=[{id:'filter-xss',chain:'input',action:'accept',comment:'<img src="/audit-local-image" onerror="window.__fwAuditXss=true">'}];renderFilterRules()});await page.waitForTimeout(250);assert.equal(await page.evaluate(()=>!!window.__fwAuditXss),false);assert.equal(await page.locator('#filter-table-body img').count(),0);passed.push('Stored firewall comment escaped');
  21. await page.evaluate(()=>showToast('Error','<img src=x onerror="window.__toastXss=true">','error')); await page.waitForTimeout(50); assert.equal(await page.evaluate(()=>!!window.__toastXss),false); assert.equal(await page.locator('#toast-container img').count(),0); passed.push('Firewall error messages escaped in notifications');
  22. await page.evaluate(()=>{state.addressLists=[{id:'dynamic-test',name:'<img src=x onerror="window.__listXss=true">',address:'127.0.0.1',dynamic:true,timeout:30}];renderAddressLists()});assert.equal(await page.locator('#addrlist-table-body img').count(),0);assert.equal(await page.locator('#addrlist-table-body').getByText('Edit',{exact:true}).count(),0);passed.push('Dynamic entries escaped and read-only except Delete');
  23. await page.evaluate(()=>{state.filterRules=[{id:'filter-reset',chain:'input',action:'accept'}];openEditFilterModal('filter-reset')});await page.evaluate(()=>resetCurrentRuleCounters());assert.equal(writes.at(-1).path,'/api/firewall/filter/filter-reset/reset-counters');passed.push('Single-rule reset calls backend');
  24. await page.evaluate(()=>{state.filterRules=[{id:'filter-old',chain:'forward',action:'accept',tos:'min-delay'}];openEditFilterModal('filter-old');openAddFilterModal()});assert.equal(await page.locator('#fw-val-tos').inputValue(),'');passed.push('New rule does not inherit old TOS');
  25. await page.evaluate(()=>{openAddNATModal();document.getElementById('nat-modal-chain').value='dstnat';handleNATChainChange()});
  26. assert.equal(await page.locator('#nat-modal-action').inputValue(),'dst-nat');
  27. assert.equal(await page.locator('#nat-modal-toaddr').isVisible(),true);
  28. await page.locator('#nat-modal-toaddr').fill('192.0.2.53');
  29. await page.evaluate(()=>toggleNATField('dport',true)); await page.locator('#nat-val-dport').fill('53');
  30. let beforeNAT=writes.length;await page.evaluate(()=>applyNATRule(true));assert.equal(writes.length,beforeNAT);
  31. assert.equal(await page.locator('#nat-val-proto').inputValue(),'all');
  32. await page.locator('#nat-val-proto').selectOption('udp');await page.evaluate(()=>applyNATRule(true));
  33. assert.equal(writes.at(-1).body.protocol,'udp');assert.equal(writes.at(-1).body.dst_port,'53');assert.equal(writes.at(-1).body.to_addresses,'192.0.2.53');
  34. passed.push('DNAT destination IP visible and DNS port requires explicit protocol');
  35. await page.evaluate(()=>handleLogoutClick());assert.deepEqual(errors,[]);console.log(JSON.stringify({passed,errors,mockedWrites:writes.length},null,2));
  36. }finally{await browser.close()}
  37. })().catch(e=>{console.error(e);process.exitCode=1});