mirror of https://github.com/wg-easy/wg-easy
15 changed files with 320 additions and 90 deletions
@ -0,0 +1,153 @@ |
|||
'use strict'; |
|||
|
|||
const { existsSync } = require('fs'); |
|||
const path = require('path'); |
|||
const debug = require('debug')('Firewall'); |
|||
|
|||
const Util = require('./Util'); |
|||
|
|||
const WG_IPT_CHAIN_NAME = 'WGEASY'; |
|||
const WG_IPT_FILE_RULE_NAME = 'fw.rules'; |
|||
|
|||
const { WG_PATH } = require('../config'); |
|||
|
|||
module.exports = class Firewall { |
|||
|
|||
async init() { |
|||
try { |
|||
this.rulesFile = path.join(WG_PATH, WG_IPT_FILE_RULE_NAME); |
|||
const configFile = this.rulesFile; |
|||
if (existsSync(configFile)) { |
|||
await this.__restoreIptablesRules(); |
|||
} |
|||
// check otherwise create
|
|||
await Util.exec(`iptables -L ${WG_IPT_CHAIN_NAME} -n`); |
|||
} catch (error) { |
|||
debug(`Create rules on chain "${WG_IPT_CHAIN_NAME}"...`); |
|||
await Util.exec(`iptables -N ${WG_IPT_CHAIN_NAME}`); |
|||
await this.__saveIptablesRules(); |
|||
debug('Rules created.'); |
|||
} |
|||
} |
|||
|
|||
async __saveIptablesRules() { |
|||
debug('Rules saving...'); |
|||
const configFile = this.rulesFile; |
|||
await Util.exec(`iptables-save > ${configFile}`); |
|||
debug('Rules saved.'); |
|||
} |
|||
|
|||
async __restoreIptablesRules() { |
|||
debug('Rules restoring...'); |
|||
const configFile = this.rulesFile; |
|||
await Util.exec(`iptables-restore < ${configFile}`); |
|||
debug('Rules restored.'); |
|||
} |
|||
|
|||
async getIptablesRules() { |
|||
// iptables list the rules WG_IPT_CHAIN_NAME chain
|
|||
//
|
|||
// $ iptables -L WGEASY -nv --line-numbers
|
|||
// Chain WGEASY (0 references)
|
|||
// num pkts bytes target prot opt in out source destination
|
|||
// 1 0 0 ACCEPT 6 -- * * 172.16.7.2 10.8.2.5
|
|||
//
|
|||
// $ iptables -L ${WG_IPT_CHAIN_NAME} -nv --line-numbers | awk '{print $1,$4,$5,$9,$10}'
|
|||
// Chain (0 references)
|
|||
// num target prot source destination
|
|||
// 1 ACCEPT 6 172.16.7.2 10.8.2.5
|
|||
const stdout = await Util.exec(`iptables -L ${WG_IPT_CHAIN_NAME} -nv --line-numbers | awk '{print $1,$4,$5,$9,$10}'`); |
|||
|
|||
const lines = stdout.split(/\r?\n/); |
|||
const rules = lines.slice(2).map((line) => { |
|||
const [num, target, protocol, source, destination] = line.trim().split(' '); |
|||
const targetToName = Util.getTargetName(target); |
|||
const protocolToName = Util.getProtocolName(protocol); |
|||
|
|||
return { |
|||
num, target: targetToName, source, destination, protocol: protocolToName, |
|||
}; |
|||
}).filter((rule) => rule !== null); |
|||
|
|||
return JSON.stringify(rules); |
|||
} |
|||
|
|||
async addIptablesRule(source, destination, protocol, target) { |
|||
debug('Rule adding...'); |
|||
// Validate target & protocol
|
|||
if (!Util.isTarget(target) || !Util.isProtocol(protocol)) { |
|||
throw new Error('Invalid target or protocol.'); |
|||
} |
|||
|
|||
/* |
|||
Support command : |
|||
iptables -A CHAIN -s [IPv4 | IPv4/CIDR] [--sport PORT] -d [IPv4 | IPv4/CIDR] [--dport PORT] -p PROTOCOL -j TARGET |
|||
*/ |
|||
|
|||
let iptablesCommand = `iptables -A ${WG_IPT_CHAIN_NAME}`; |
|||
|
|||
if (Util.isSupportedAddress(source)) { |
|||
const [address, port] = source.split(':'); |
|||
if (Util.isIPAddress(address) || Util.isCIDR(address)) { |
|||
iptablesCommand += ` -s ${address}`; |
|||
if (port) { |
|||
iptablesCommand += ` --sport ${port}`; |
|||
} |
|||
} else { |
|||
throw new Error('Invalid source address.'); |
|||
} |
|||
} else { |
|||
throw new Error('Invalid source format.'); |
|||
} |
|||
|
|||
if (Util.isSupportedAddress(destination)) { |
|||
const [address, port] = destination.split(':'); |
|||
if (Util.isIPAddress(address) || Util.isCIDR(address)) { |
|||
iptablesCommand += ` -d ${address}`; |
|||
if (port) { |
|||
iptablesCommand += ` --dport ${port}`; |
|||
} |
|||
} else { |
|||
throw new Error('Invalid destination address.'); |
|||
} |
|||
} else { |
|||
throw new Error('Invalid destination format.'); |
|||
} |
|||
|
|||
iptablesCommand += ` -p ${protocol} -j ${target}`; |
|||
|
|||
await Util.exec(iptablesCommand); |
|||
await this.__saveIptablesRules(); |
|||
debug('Rule added'); |
|||
} |
|||
|
|||
async deleteIptablesRule(num) { |
|||
debug('Rule deleting...'); |
|||
await Util.exec(`iptables -D ${WG_IPT_CHAIN_NAME} ${num}`); |
|||
await this.__saveIptablesRules(); |
|||
debug('Rule deleted.'); |
|||
} |
|||
|
|||
async getInterfaces() { |
|||
// $ ip -brief address show
|
|||
// lo UNKNOWN 127.0.0.1/8 ::1/128
|
|||
// eth0@if150 UP 172.17.0.3/16
|
|||
// $ ip -brief address show | awk '{print $1,$3,$4}'
|
|||
// lo 127.0.0.1/8 ::1/128
|
|||
// eth0@if150 172.17.0.3/16
|
|||
const interfaces = await Util.exec("ip -brief a s | awk '{print $1,$3,$4}'"); |
|||
|
|||
const result = []; |
|||
if (typeof interfaces === 'string') { |
|||
for (const line of interfaces.split(/\r?\n/)) { |
|||
const [name, ipv4, ipv6] = line.split(' '); |
|||
result.push({ |
|||
name, ipv4: ipv4 || '', ipv6: ipv6 || '', |
|||
}); |
|||
} |
|||
} |
|||
|
|||
return JSON.stringify(result); |
|||
} |
|||
|
|||
}; |
|||
@ -0,0 +1,5 @@ |
|||
'use strict'; |
|||
|
|||
const Firewall = require('../lib/Firewall'); |
|||
|
|||
module.exports = new Firewall(); |
|||
@ -0,0 +1,2 @@ |
|||
(function(t,e){"object"===typeof exports&&"object"===typeof module?module.exports=e():"function"===typeof define&&define.amd?define([],e):"object"===typeof exports?exports["VueFirewall"]=e():t["VueFirewall"]=e()})("undefined"!==typeof self?self:this,(()=>(()=>{"use strict";var t={};(()=>{t.d=(e,r)=>{for(var a in r)t.o(r,a)&&!t.o(e,a)&&Object.defineProperty(e,a,{enumerable:!0,get:r[a]})}})(),(()=>{t.o=(t,e)=>Object.prototype.hasOwnProperty.call(t,e)})(),(()=>{t.p=""})();var e={};if(t.d(e,{default:()=>u}),"undefined"!==typeof window){var r=window.document.currentScript,a=r&&r.src.match(/(.+\/)[^/]+\.js(\?.*)?$/);a&&(t.p=a[1])}var s=function(){var t=this,e=t.$createElement,r=t._self._c||e;return r("div",{staticClass:"shadow-md rounded-lg bg-white dark:bg-neutral-700 overflow-hidden"},[r("div",{staticClass:"flex flex-row flex-auto items-center mb-4 p-3 px-5 border-b-2 border-gray-100 dark:border-neutral-600"},[r("div",{staticClass:"flex-grow"},[r("p",{staticClass:"text-2xl font-medium dark:text-neutral-200"},[t._v(t._s(t.$t("firewall")))])])]),r("table",{staticClass:"table-auto w-[70%] dark:text-neutral-200 mb-4"},[t._m(0),r("tbody",{staticClass:"text-center"},t._l(t.interfaces,(function(e,a){return r("tr",{key:a},[r("td",[t._v(t._s(e.name))]),r("td",[t._v(t._s(e.ipv4))]),r("td",[t._v(t._s(e.ipv6))])])})),0)]),t._m(1)])},o=[function(){var t=this,e=t.$createElement,r=t._self._c||e;return r("thead",[r("tr",[r("th",[t._v("Interface")]),r("th",[t._v("IPv4 address")]),r("th",[t._v("IPv6 address")])])])},function(){var t=this,e=t.$createElement,r=t._self._c||e;return r("table",{staticClass:"table-auto border-t-2 dark:border-neutral-200 w-full dark:text-neutral-200"},[r("thead",{staticClass:"bg-gray-200 dark:bg-gray-600"},[r("tr",[r("th",[t._v("Action")]),r("th",{staticClass:"border-x-2 dark:border-neutral-200"},[t._v("Source")]),r("th",{staticClass:"border-x-2 dark:border-neutral-200"},[t._v("Destination")]),r("th",{staticClass:"border-x-2 dark:border-neutral-200"},[t._v("Protocol")]),r("th")])]),r("tbody",{staticClass:"text-center"},[r("tr",[r("td",{staticClass:"border border-gray-600 p-1"},[t._v("delete")]),r("td",{staticClass:"border border-gray-600 p-1"},[t._v("192.168.11.3")]),r("td",{staticClass:"border border-gray-600 p-1"},[t._v("10.9.8.1")]),r("td",{staticClass:"border border-gray-600 p-1"},[t._v("udp")]),r("td",{staticClass:"border border-gray-600 p-1"})]),r("tr",[r("td",{staticClass:"border border-gray-600 p-1"},[r("form",{attrs:{id:"fw"}},[r("input",{staticClass:"w-full outline-none bg-transparent text-center",attrs:{form:"fw",type:"submit",value:"send"}})])]),r("td",{staticClass:"border border-gray-600"},[r("input",{staticClass:"w-full outline-none bg-transparent text-center",attrs:{form:"fw",type:"text",name:"source"}})]),r("td",{staticClass:"border border-gray-600"},[r("input",{staticClass:"w-full outline-none bg-transparent text-center",attrs:{form:"fw",type:"text",name:"destination"}})]),r("td",{staticClass:"border border-gray-600"},[r("select",{staticClass:"bg-transparent",attrs:{form:"fw",name:"protocol"}},[r("option",{attrs:{value:"tcp"}},[t._v("tcp")]),r("option",{attrs:{value:"udp"}},[t._v("udp")])])]),r("td",{staticClass:"border border-gray-600 p-1"},[r("select",{staticClass:"bg-transparent",attrs:{form:"fw",name:"action"}},[r("option",{attrs:{value:"allow"}},[t._v("allow")]),r("option",{attrs:{value:"block"}},[t._v("block")])])])])])])}];const n={name:"Firewall",data(){let t=[];return{interfaces:t}},methods:{getInterfaces(){this.api.getInterfaces().then((t=>{this.interfaces=t})).catch((t=>{alert(t.message||t.toString())}))}},mounted(){this.api=new API,this.getInterfaces()}},i=n;function d(t,e,r,a,s,o,n,i){var d,l="function"===typeof t?t.options:t;if(e&&(l.render=e,l.staticRenderFns=r,l._compiled=!0),a&&(l.functional=!0),o&&(l._scopeId="data-v-"+o),n?(d=function(t){t=t||this.$vnode&&this.$vnode.ssrContext||this.parent&&this.parent.$vnode&&this.parent.$vnode.ssrContext,t||"undefined"===typeof __VUE_SSR_CONTEXT__||(t=__VUE_SSR_CONTEXT__),s&&s.call(this,t),t&&t._registeredComponents&&t._registeredComponents.add(n)},l._ssrRegister=d):s&&(d=i?function(){s.call(this,(l.functional?this.parent:this).$root.$options.shadowRoot)}:s),d)if(l.functional){l._injectStyles=d;var c=l.render;l.render=function(t,e){return d.call(e),c(t,e)}}else{var u=l.beforeCreate;l.beforeCreate=u?[].concat(u,d):[d]}return{exports:t,options:l}}var l=d(i,s,o,!1,null,null,null);const c=l.exports,u=c;return e=e["default"],e})())); |
|||
//# sourceMappingURL=vue-firewall.umd.min.js.map
|
|||
File diff suppressed because one or more lines are too long
@ -0,0 +1,96 @@ |
|||
<script> |
|||
export default { |
|||
name: 'Firewall', |
|||
data() { |
|||
let interfaces = []; |
|||
return { interfaces }; |
|||
}, |
|||
methods: { |
|||
getInterfaces() { |
|||
this.api.getInterfaces().then((interfaces) => { |
|||
this.interfaces = interfaces; |
|||
}) |
|||
.catch((err) => { |
|||
alert(err.message || err.toString()); |
|||
}) |
|||
} |
|||
}, |
|||
mounted() { |
|||
this.api = new API(); |
|||
this.getInterfaces(); |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<template> |
|||
<div class="shadow-md rounded-lg bg-white dark:bg-neutral-700 overflow-hidden"> |
|||
<div class="flex flex-row flex-auto items-center mb-4 p-3 px-5 border-b-2 border-gray-100 dark:border-neutral-600"> |
|||
<div class="flex-grow"> |
|||
<p class="text-2xl font-medium dark:text-neutral-200">{{ $t("firewall") }}</p> |
|||
</div> |
|||
</div> |
|||
|
|||
<table class="table-auto w-[70%] dark:text-neutral-200 mb-4"> |
|||
<thead> |
|||
<tr> |
|||
<th>Interface</th> |
|||
<th>IPv4 address</th> |
|||
<th>IPv6 address</th> |
|||
</tr> |
|||
</thead> |
|||
<tbody class="text-center"> |
|||
<tr v-for="(iface, index) in interfaces" :key="index"> |
|||
<td>{{ iface.name }}</td> |
|||
<td>{{ iface.ipv4 }}</td> |
|||
<td>{{ iface.ipv6 }}</td> |
|||
</tr> |
|||
</tbody> |
|||
</table> |
|||
|
|||
<table class="table-auto border-t-2 dark:border-neutral-200 w-full dark:text-neutral-200"> |
|||
<thead class="bg-gray-200 dark:bg-gray-600"> |
|||
<tr> |
|||
<th>Action</th> |
|||
<th class="border-x-2 dark:border-neutral-200">Source</th> |
|||
<th class="border-x-2 dark:border-neutral-200">Destination</th> |
|||
<th class="border-x-2 dark:border-neutral-200">Protocol</th> |
|||
<th></th> |
|||
</tr> |
|||
</thead> |
|||
<tbody class="text-center"> |
|||
<tr> |
|||
<td class="border border-gray-600 p-1">delete</td> |
|||
<td class="border border-gray-600 p-1">192.168.11.3</td> |
|||
<td class="border border-gray-600 p-1">10.9.8.1</td> |
|||
<td class="border border-gray-600 p-1">udp</td> |
|||
<td class="border border-gray-600 p-1"></td> |
|||
</tr> |
|||
<tr> |
|||
<td class="border border-gray-600 p-1"> |
|||
<form id="fw"> |
|||
<input form="fw" type="submit" value="send" class="w-full outline-none bg-transparent text-center" /> |
|||
</form> |
|||
</td> |
|||
<td class="border border-gray-600"> |
|||
<input form="fw" type="text" name="source" class="w-full outline-none bg-transparent text-center" /> |
|||
</td> |
|||
<td class="border border-gray-600"> |
|||
<input form="fw" type="text" name="destination" class="w-full outline-none bg-transparent text-center" /> |
|||
</td> |
|||
<td class="border border-gray-600"> |
|||
<select form="fw" name="protocol" class="bg-transparent"> |
|||
<option value="tcp">tcp</option> |
|||
<option value="udp">udp</option> |
|||
</select> |
|||
</td> |
|||
<td class="border border-gray-600 p-1"> |
|||
<select form="fw" name="action" class="bg-transparent"> |
|||
<option value="allow">allow</option> |
|||
<option value="block">block</option> |
|||
</select> |
|||
</td> |
|||
</tr> |
|||
</tbody> |
|||
</table> |
|||
</div> |
|||
</template> |
|||
Loading…
Reference in new issue