File manager - Edit - /usr/local/cpanel/whostmgr/docroot/templates/host_access/nftables_access.tmpl
Back
[% PROCESS 'master_templates/_defheader.tmpl' theme="bootstrap" app_key='host_access_control' -%] <!--XXX Hack to fix page presentation, as neither LTR or RTL are set with bootstrap theme. Oops--> <script type="text/javascript"> document.getElementsByTagName('html')[0].dir = "ltr"; </script> <style> .spinner { animation-name: spinner; animation-duration: 1000ms; animation-iteration-count: infinite; animation-timing-function: linear; } @keyframes spinner { from {transform:rotate(0deg);} to {transform:rotate(360deg);} } </style> <p> Host Access Control allows you to set up specific rules to accept, reject or drop access to your server on various ports based on the IP address that is attempting to connect. Denying all connections and only allowing connections that you wish to allow is the most secure way to use Host Access Control. </p> <p> To set up a rule, add the ports you wish to create the rule for, the IP addresses for which the rule will apply to, and the action the system should take (ACCEPT, DROP, or REJECT). </p> <button id="showHideExample" class="btn btn-default">Show/Hide Example</button><br><br> <div id="exampleDiv" style="display: none;" class="well"> <table class="table table-striped"> <tr> <th>Port</th> <th>IP Address/CIDR</th> <th>Protocol</th> <th>Action</th> </tr> <tr> <td>22</td> <td>192.168.0.0</td> <td>TCP</td> <td>ACCEPT</td> </tr> <tr> <td>22</td> <td>198.66.254.1/24</td> <td>TCP</td> <td>ACCEPT</td> </tr> <tr> <td>22</td> <td>ALL</td> <td>TCP</td> <td>REJECT</td> </tr> </table> </div> <p class="alert alert-info"> <strong>Note:</strong> Rules have an order of precedence. You need to place your ACCEPT rules before your deny (REJECT or DROP) rules. <br><br> This list allows all IP addresses to access the port except for the IP address range that you specify. </p> <h3>Add Rule</h3> <form name="addform" action="hostaccess.cgi" method="POST"> <input type="hidden" name="add_nftables_rule" value="1"></input> <table id="addRuleTbl" class="table table striped"> <thead> <tr> <th>Port</th> <th>IP Address/CIDR</th> <th>Protocol</th> <th>Action</th> </tr> </thead> <tbody> <tr> <td><input required id="addPort" name="port" type="number" placeholder="1234" min="1" max="65535"></input></td> <td><input id="addIp" name="ip" type="text" placeholder="123.12.1.123"></input></td> <td> <select name="protocol" id="addProtocol"> <option value="tcp" default>TCP</option> <option value="udp">UDP</option> </select> </td> <td> <select name="action" id="addAction"> <option value="reject" default>REJECT</option> <option value="drop">DROP</option> <option value="accept">ACCEPT</option> </select> </td> </tr> </tbody> </table> <button id="addSubmit" class="btn btn-success">Add Rule</button> </form> <h3>Current Rules</h3> <table id="ruleTbl" class="table table-striped"> <thead> <tr> <th>Handle</th> <th>Port</th> <th>IP Address/CIDR</th> <th>Protocol</th> <th>Action</th> <th>Delete?</th> </tr> </thead> <tbody> <tr><td colspan=5>Loading... <span class="glyphicon glyphicon-cog spinner"></span></td></tr> </tbody> </table> <div class="controls"> <a id="reloadRules" href="hostaccess.cgi" class="btn btn-default" id="btn-reload">Reload</a> </div> <script type="text/javascript"> document.getElementById("showHideExample").addEventListener("click", function () { let elem = document.getElementById("exampleDiv"); let mode = ( elem.style.display === "none" ) ? "block" : "none"; elem.style.display = mode; }); // I've done this pattern many times. // May not be the prettiest, but it's easy. - TAB function selfRequest (method, handler, errorHandler, args) { 'use strict'; let oReq = new XMLHttpRequest(); oReq.onreadystatechange = function() { if (this.readyState === XMLHttpRequest.DONE) { if( this.status === 200 ) { handler(this.responseText); } else { errorHandler(method, this.status, this.responseText); } } } let argarr = []; if( typeof args === 'object' ) { Object.keys(args).forEach( function(argument) { argarr.push(`${argument}=${args[argument]}`); }); } let argstr = argarr.join("&"); if( method === 'GET' ) { oReq.open( method, `hostaccess.cgi?${argstr}`, true ); oReq.send(); } else if ( method === 'POST' ) { oReq.open( method, "hostaccess.cgi", true ); oReq.setRequestHeader( "Content-type", "application/x-www-form-urlencoded" ); oReq.send(argstr); } return false; } // Every value below comes from `nft -j`, which reports rules this interface // did not necessarily create, so build nodes and set text rather than // assembling markup: a value is then data, never markup, whatever it holds. function textCell(value) { let cell = document.createElement('td'); cell.textContent = ( value === undefined || value === null ) ? '' : String(value); return cell; } function hiddenInput(name, value) { let input = document.createElement('input'); input.type = 'hidden'; input.name = name; input.value = ( value === undefined || value === null ) ? '' : String(value); return input; } function populateTbl(blob) { let tbody = document.querySelector("table#ruleTbl > tbody"); // Parse before emptying the table. The other way round, malformed JSON // throws with the tbody already cleared, leaving no rules, no // placeholder row and nothing on screen to say anything went wrong. let rules = JSON.parse(blob); tbody.replaceChildren(); rules.forEach(function(elem) { // Not sure yet how to represent IN or OUT bound traffic distinction. if(elem.chain !== 'cPanel-HostAccessControl') { return; } // OK, we're looking for match left along with reject or accept. let row = document.createElement('tr'); row.id = `handle_${elem.handle}`; row.appendChild( textCell(elem.handle) ); let rule = {}; rule.ip = 'ALL'; elem.expr.forEach(function(expr) { if(expr.hasOwnProperty('reject') ) { rule.action = 'REJECT'; return; } if(expr.hasOwnProperty('accept') ) { rule.action = 'ACCEPT'; return; } if(expr.hasOwnProperty('drop') ) { rule.action = 'DROP'; return; } if( expr.hasOwnProperty('match') && expr.match.op === '==' ) { if( expr.match.left.payload.field === "saddr" ) { if( typeof expr.match.right === 'object' ) { if(expr.match.right.hasOwnProperty('prefix')) { rule.ip = expr.match.right.prefix.addr + '/' + expr.match.right.prefix.len; } else if(expr.match.right.hasOwnProperty('range')) { rule.ip = expr.match.right.range.join("-"); } } else { rule.ip = expr.match.right; } } else if ( expr.match.left.payload.field === "dport" ) { rule.port = expr.match.right; rule.protocol = expr.match.left.payload.protocol; } return; } return; }); [ rule.port, rule.ip, rule.protocol, rule.action ].forEach( function (value) { row.appendChild( textCell(value) ); }); let form = document.createElement('form'); form.id = `delete_${elem.handle}`; form.name = `delete_${elem.handle}`; form.action = 'hostaccess.cgi'; form.method = 'POST'; form.appendChild( hiddenInput( 'chain', elem.chain ) ); form.appendChild( hiddenInput( 'delete_nftables_rule', elem.handle ) ); let button = document.createElement('button'); button.className = 'btn btn-link'; let icon = document.createElement('span'); icon.className = 'glyphicon glyphicon-trash'; button.appendChild(icon); form.appendChild(button); let actions = document.createElement('td'); actions.appendChild(form); row.appendChild(actions); tbody.appendChild(row); }); if( tbody.childElementCount === 0 ) { let row = document.createElement('tr'); let cell = textCell('No host access rules currently exist.'); cell.colSpan = 5; row.appendChild(cell); tbody.appendChild(row); } return false; } // Numeric validator already good to go function register_validators() { let ip = document.querySelector('#addRuleTbl input[name=ip]'); // Kept in step with what Cpanel::NFTables::add_rule accepts, so a value // cannot pass here and then be refused after submission. Note that '\.' // in a single-quoted string is just '.', which matches any character, // so every backslash below has to be doubled. let v4Octet = '([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])'; // A zero first octet and a zero mask are rejected by // Cpanel::Validate::IP, so they are rejected here too. let v4First = '([1-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])'; let v4Addr = v4First + '\\.(' + v4Octet + '\\.){2}' + v4Octet; let v4Mask = '(\\/([1-9]|[1-2][0-9]|3[0-2]))?'; // The IPv6 grammar, one leading-zero-compression case per alternative. let v6Seg = '[0-9A-Fa-f]{1,4}'; // The dotted tail of an IPv4-mapped address is held to the same rules // as a bare IPv4 address, zero first octet included: the server checks // it with the same code either way, so restating the grammar here is // how '::ffff:0.1.2.3' came to pass the form and then fail on submit. let v6Trailv4 = '(' + v4Addr + ')'; let v6Addr = '(' + '(' + v6Seg + ':){7}(' + v6Seg + '|:)' + '|' + '(' + v6Seg + ':){6}(:' + v6Seg + '|' + v6Trailv4 + '|:)' + '|' + '(' + v6Seg + ':){5}(((:' + v6Seg + '){1,2})|:' + v6Trailv4 + '|:)' + '|' + '(' + v6Seg + ':){4}(((:' + v6Seg + '){1,3})|((:' + v6Seg + ')?:' + v6Trailv4 + ')|:)' + '|' + '(' + v6Seg + ':){3}(((:' + v6Seg + '){1,4})|((:' + v6Seg + '){0,2}:' + v6Trailv4 + ')|:)' + '|' + '(' + v6Seg + ':){2}(((:' + v6Seg + '){1,5})|((:' + v6Seg + '){0,3}:' + v6Trailv4 + ')|:)' + '|' + '(' + v6Seg + ':){1}(((:' + v6Seg + '){1,6})|((:' + v6Seg + '){0,4}:' + v6Trailv4 + ')|:)' + '|' + '(:(((:' + v6Seg + '){1,7})|((:' + v6Seg + '){0,5}:' + v6Trailv4 + ')|:))' + ')'; // No zone index: Cpanel::Validate::IP refuses 'fe80::1%eth0', and a // zero prefix is only accepted as the '::/0' any-source spelling below. let v6Mask = '(\\/([1-9]|[1-9][0-9]|1[0-1][0-9]|12[0-8]))?'; // add_rule takes a range only as two plain addresses of one family -- // no prefix on either end -- so the form has to offer that shape too, // or the range support the server documents is unreachable from here. let validators = [ new RegExp( '^' + v4Addr + v4Mask + '$' ), new RegExp( '^' + v6Addr + v6Mask + '$' ), new RegExp( '^' + v4Addr + '-' + v4Addr + '$' ), new RegExp( '^' + v6Addr + '-' + v6Addr + '$' ), ]; let msg = "Enter an IPv4 or IPv6 address, a CIDR range, an address range, or ALL"; // 'ALL' and the any-source spellings mean the same thing to the server // and none of them match the address patterns above. add_rule compares // 'ALL' case-insensitively, so this has to as well. let anySource = [ 'ALL', '0.0.0.0', '0.0.0.0/0', '::', '::/0' ]; let validatorFn = function() { if( anySource.indexOf( ip.value.toUpperCase() ) !== -1 ) { ip.setCustomValidity(""); return; } validators.some(function (validator) { if (validator.test(ip.value)) { ip.setCustomValidity(""); return true; } else { ip.setCustomValidity(msg); } }); } ip.addEventListener( 'keyup', validatorFn ); validatorFn(); } function genericErr(method, stat, message) { alert(`${method} hostaccess.cgi: ${stat} ${message}`); } document.addEventListener('DOMContentLoaded', function(event) { selfRequest( "GET", populateTbl, genericErr, { 'fetch_nftables_rules': 1, 'no_cache': [% data.no_cache %] } ); register_validators(); return; }); </script> [% PROCESS 'master_templates/_deffooter.tmpl' theme="bootstrap" %]
| ver. 1.4 |
Github
|
.
| PHP 8.1.34 | Generation time: 0.05 |
proxy
|
phpinfo
|
Settings