Browse Source

fix: restrict access to vue templates directory

- throw 403 forbidden if client try to access to templates dir
- update: responsive component & better icons
- add: sh script to build all in one command
pull/1210/head
tetuaoro 2 years ago
parent
commit
b19f940adb
  1. 44
      buildall.sh
  2. 19
      src/lib/Firewall.js
  3. 13
      src/lib/Server.js
  4. 57
      src/www/css/app.css
  5. 2
      src/www/js/vendor/vue-firewall.umd.min.js
  6. 2
      src/www/js/vendor/vue-firewall.umd.min.js.map
  7. 172
      src/www/templates/Firewall.vue

44
buildall.sh

@ -0,0 +1,44 @@
#!/bin/bash
# The buildall.sh script is designed to streamline the build process for the project.
# It allows you to run different build commands based on whether you are using "sudo" and supports multiple package managers.
# If you need to run the build commands with "sudo[script]" (look at package.json scripts), provide the "with-sudo" argument :
#
# $ ./buildall with-sudo
#
set -e # exit if throw
# r u "sudo"
check_sudo() {
if [ "$(id -u)" -eq 0 ]; then
echo "true"
else
echo "false"
fi
}
# determine which build command to use
# first, run a command with sudo like `sudo echo` before
if [ "$1" == "with-sudo" ]; then
echo "You are running with sudo"
BUILD_CMD="sudobuild"
else
BUILD_CMD="build"
fi
if command -v pnpm &>/dev/null; then
PACKAGE_MANAGER=pnpm
elif command -v yarn &>/dev/null; then
PACKAGE_MANAGER=yarn
else
PACKAGE_MANAGER=npm
fi
echo "Build the project using \"$PACKAGE_MANAGER\" as package manager"
$PACKAGE_MANAGER run -C src buildfirewall
$PACKAGE_MANAGER run -C src buildcss
$PACKAGE_MANAGER run $BUILD_CMD
echo "Done"

19
src/lib/Firewall.js

@ -46,6 +46,7 @@ module.exports = class Firewall {
async getIptablesRules() {
// iptables list the rules WG_IPT_CHAIN_NAME chain
<<<<<<< HEAD
<<<<<<< HEAD
//
// $ iptables -L WGEASY -nv --line-numbers
@ -59,16 +60,24 @@ module.exports = class Firewall {
// 1 ACCEPT 6 172.16.7.2 10.8.2.5
=======
// $ iptables -L ${WG_IPT_CHAIN_NAME} -n -v
=======
//
// $ iptables -L WGEASY -nv --line-numbers
>>>>>>> b4f7165 (fix: restrict access to vue templates directory)
// Chain WGEASY (0 references)
// num pkts bytes target prot opt in out source destination
// 1 0 0 DROP 6 -- * * 172.16.8.2 172.16.8.3
// 2 0 0 ACCEPT 6 -- * * 172.16.9.2 172.16.8.3
// $ iptables -L ${WG_IPT_CHAIN_NAME} -n -v | awk '{print $3,$4,$8,$9}'
// (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
<<<<<<< HEAD
// 1 DROP 6 172.16.8.2 172.16.8.3
// 2 ACCEPT 6 172.16.9.2 172.16.8.3
>>>>>>> 9ec7359 (feat: firewall)
=======
// 1 ACCEPT 6 172.16.7.2 10.8.2.5
>>>>>>> b4f7165 (fix: restrict access to vue templates directory)
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/);

13
src/lib/Server.js

@ -276,6 +276,19 @@ module.exports = class Server {
return { success: true };
}));
// templates directory & vue files
app.use(
fromNodeMiddleware((req, res, next) => {
if (req.url.startsWith('/templates/') || req.url.endsWith('vue')) {
return res.status(403).json({
error: 'Forbidden',
});
}
next()
}),
);
// firewall
const routerFirewall = createRouter();
app.use(routerFirewall);

57
src/www/css/app.css

@ -887,10 +887,6 @@ video {
width: 2rem;
}
.w-\[70\%\] {
width: 70%;
}
.w-full {
width: 100%;
}
@ -923,8 +919,8 @@ video {
flex-grow: 0;
}
.table-auto {
table-layout: auto;
.border-collapse {
border-collapse: collapse;
}
.transform {
@ -1024,6 +1020,10 @@ video {
overflow: hidden;
}
.overflow-x-auto {
overflow-x: auto;
}
.overflow-y-auto {
overflow-y: auto;
}
@ -1072,11 +1072,6 @@ video {
border-width: 2px;
}
.border-x-2 {
border-left-width: 2px;
border-right-width: 2px;
}
.border-b {
border-bottom-width: 1px;
}
@ -1107,9 +1102,8 @@ video {
border-color: rgb(209 213 219 / var(--tw-border-opacity));
}
.border-gray-600 {
--tw-border-opacity: 1;
border-color: rgb(75 85 99 / var(--tw-border-opacity));
.border-neutral-500\/50 {
border-color: rgb(115 115 115 / 0.5);
}
.border-red-800 {
@ -1136,6 +1130,10 @@ video {
background-color: rgb(229 231 235 / var(--tw-bg-opacity));
}
.bg-gray-200\/10 {
background-color: rgb(229 231 235 / 0.1);
}
.bg-gray-50 {
--tw-bg-opacity: 1;
background-color: rgb(249 250 251 / var(--tw-bg-opacity));
@ -1146,6 +1144,11 @@ video {
background-color: rgb(107 114 128 / var(--tw-bg-opacity));
}
.bg-neutral-700 {
--tw-bg-opacity: 1;
background-color: rgb(64 64 64 / var(--tw-bg-opacity));
}
.bg-red-100 {
--tw-bg-opacity: 1;
background-color: rgb(254 226 226 / var(--tw-bg-opacity));
@ -1272,6 +1275,10 @@ video {
text-align: center;
}
.text-start {
text-align: start;
}
.align-middle {
vertical-align: middle;
}
@ -1515,9 +1522,8 @@ video {
background-color: rgb(249 250 251 / var(--tw-bg-opacity));
}
.hover\:bg-green-600:hover {
--tw-bg-opacity: 1;
background-color: rgb(22 163 74 / var(--tw-bg-opacity));
.hover\:bg-green-800\/50:hover {
background-color: rgb(22 101 52 / 0.5);
}
.hover\:bg-red-700:hover {
@ -1530,6 +1536,10 @@ video {
background-color: rgb(153 27 27 / var(--tw-bg-opacity));
}
.hover\:bg-red-800\/50:hover {
background-color: rgb(153 27 27 / 0.5);
}
.hover\:text-gray-800:hover {
--tw-text-opacity: 1;
color: rgb(31 41 55 / var(--tw-text-opacity));
@ -1729,6 +1739,10 @@ video {
display: inline-block;
}
.md\:w-\[75\%\] {
width: 75%;
}
.md\:min-w-24 {
min-width: 6rem;
}
@ -1737,6 +1751,10 @@ video {
flex-shrink: 0;
}
.md\:items-center {
align-items: center;
}
.md\:gap-4 {
gap: 1rem;
}
@ -1795,11 +1813,6 @@ video {
background-color: rgb(0 0 0 / var(--tw-bg-opacity));
}
.dark\:bg-gray-600:where(.dark, .dark *) {
--tw-bg-opacity: 1;
background-color: rgb(75 85 99 / var(--tw-bg-opacity));
}
.dark\:bg-neutral-400:where(.dark, .dark *) {
--tw-bg-opacity: 1;
background-color: rgb(163 163 163 / var(--tw-bg-opacity));

2
src/www/js/vendor/vue-firewall.umd.min.js

File diff suppressed because one or more lines are too long

2
src/www/js/vendor/vue-firewall.umd.min.js.map

File diff suppressed because one or more lines are too long

172
src/www/templates/Firewall.vue

@ -36,8 +36,6 @@ export default {
const { source, destination, protocol, target } = this.newRule;
this.api.addRule({ source, destination, protocol, target }).then(() => {
this.getRules();
}).then(() => {
this.newRule = {
source: '',
destination: '',
@ -46,13 +44,15 @@ export default {
};
}).catch((err) => {
alert(err.message || err.toString());
}).finally(() => {
this.getRules();
});
},
deleteRule(num) {
this.api.deleteRule({ num }).then(() => {
this.getRules();
}).catch((err) => {
this.api.deleteRule({ num }).catch((err) => {
alert(err.message || err.toString());
}).finally(() => {
this.getRules();
});
},
},
@ -65,89 +65,93 @@ export default {
</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="shadow-md rounded-lg bg-white dark:bg-neutral-700 overflow-hidden mb-4">
<div class="flex flex-row flex-auto items-center p-3 px-5 border-b-2 border-neutral-500/50 dark:border-neutral-600">
<div class="flex-grow">
<p class="text-2xl font-medium dark:text-neutral-200">{{ $t("fwFirewall") }}</p>
<p class="text-2xl font-medium dark:text-neutral-200">{{ $t('fwFirewall') }}</p>
</div>
</div>
<table class="table-auto w-[70%] dark:text-neutral-200 mb-4">
<thead>
<tr>
<th>{{ $t('fwInterface') }}</th>
<th>{{ $t('fwIpv4') }}</th>
<th>{{ $t('fwIpv6') }}</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>
<div class="container p-2 flex flex-col md:items-center">
<table class="border border-neutral-500/50 bg-gray-200/10 dark:text-neutral-200 md:w-[75%] mb-4">
<thead>
<tr>
<th class="border border-neutral-500/50 text-start p-1">{{ $t('fwInterface') }}</th>
<th class="border border-neutral-500/50 text-start p-1">{{ $t('fwIpv4') }}</th>
<th class="border border-neutral-500/50 text-start p-1">{{ $t('fwIpv6') }}</th>
</tr>
</thead>
<tbody>
<tr v-for="(iface, index) in interfaces" :key="index">
<td class="border border-neutral-500/50 p-1">{{ iface.name }}</td>
<td class="border border-neutral-500/50 p-1">{{ iface.ipv4 }}</td>
<td class="border border-neutral-500/50 p-1">{{ iface.ipv6 }}</td>
</tr>
</tbody>
</table>
<form id="fw" @submit="addRule"></form>
<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>{{ $t('fwAction') }}</th>
<th class="border-x-2 dark:border-neutral-200">{{ $t('fwSource') }}</th>
<th class="border-x-2 dark:border-neutral-200">{{ $t('fwDestination') }}</th>
<th class="border-x-2 dark:border-neutral-200">{{ $t('fwProtocol') }}</th>
<th>{{ $t('fwTarget') }}</th>
</tr>
</thead>
<tbody class="text-center">
<tr v-for="(rule, index) in rules" :key="index">
<td class="border border-gray-600 p-1 hover:bg-red-800 transition">
<button @click="deleteRule(rule.num)">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
stroke="currentColor" class="size-6">
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18 18 6M6 6l12 12"/>
</svg>
</button>
</td>
<td class="border border-gray-600 p-1">{{ rule.source }}</td>
<td class="border border-gray-600 p-1">{{ rule.destination }}</td>
<td class="border border-gray-600 p-1">{{ rule.protocol }}</td>
<td class="border border-gray-600 p-1">{{ rule.target }}</td>
</tr>
<tr>
<td class="border border-gray-600 p-1 hover:bg-green-600 transition">
<form id="fw" @submit="addRule">
<button type="submit">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
stroke="currentColor" class="size-6">
<path stroke-linecap="round" stroke-linejoin="round"
d="M12 9v6m3-3H9m12 0a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z"/>
</svg>
</button>
</form>
</td>
<td class="border border-gray-600">
<input form="fw" type="text" name="source" v-model="newRule.source"
class="w-full outline-none bg-transparent text-center"/>
</td>
<td class="border border-gray-600">
<input form="fw" type="text" name="destination" v-model="newRule.destination"
class="w-full outline-none bg-transparent text-center"/>
</td>
<td class="border border-gray-600">
<select form="fw" name="protocol" v-model="newRule.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="target" v-model="newRule.target" class="bg-transparent">
<option value="ACCEPT">ALLOW</option>
<option value="DROP">BLOCK</option>
</select>
</td>
</tr>
</tbody>
</table>
<div class="overflow-x-auto">
<table class=" dark:text-neutral-200 border-t-2 border-collapse dark:border-neutral-200 p-2">
<thead class="bg-gray-200 dark:bg-neutral-600">
<tr>
<th class="border dark:border-neutral-200">{{ $t('fwAction') }}</th>
<th class="border dark:border-neutral-200">{{ $t('fwSource') }}</th>
<th class="border dark:border-neutral-200">{{ $t('fwDestination') }}</th>
<th class="border dark:border-neutral-200">{{ $t('fwProtocol') }}</th>
<th class="border dark:border-neutral-200">{{ $t('fwTarget') }}</th>
</tr>
</thead>
<tbody class="text-center">
<tr v-for="(rule, index) in rules" :key="index">
<td class="border border-neutral-500/50 p-1 hover:bg-red-800/50 transition">
<button type="button" @click="deleteRule(rule.num)">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
stroke="currentColor" class="size-6">
<path stroke-linecap="round" stroke-linejoin="round"
d="m14.74 9-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 0 1-2.244 2.077H8.084a2.25 2.25 0 0 1-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 0 0-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 0 1 3.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 0 0-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 0 0-7.5 0" />
</svg>
</button>
</td>
<td class="border border-neutral-500/50 p-1">{{ rule.source }}</td>
<td class="border border-neutral-500/50 p-1">{{ rule.destination }}</td>
<td class="border border-neutral-500/50 p-1">{{ rule.protocol }}</td>
<td class="border border-neutral-500/50 p-1">{{ rule.target }}</td>
</tr>
<tr>
<td class="border border-neutral-500/50 p-1 hover:bg-green-800/50 transition">
<button form="fw" type="submit">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
stroke="currentColor" class="size-6">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 4.5v15m7.5-7.5h-15" />
</svg>
</button>
</td>
<td class="border border-neutral-500/50">
<input form="fw" type="text" name="source" v-model="newRule.source"
class=" outline-none bg-transparent text-center" />
</td>
<td class="border border-neutral-500/50">
<input form="fw" type="text" name="destination" v-model="newRule.destination"
class=" outline-none bg-transparent text-center" />
</td>
<td class="border border-neutral-500/50">
<select form="fw" name="protocol" v-model="newRule.protocol" class="bg-transparent">
<option class="dark:text-white dark:bg-neutral-700" value="tcp">TCP</option>
<option class="dark:text-white dark:bg-neutral-700" value="udp">UDP</option>
</select>
</td>
<td class="border border-neutral-500/50 p-1">
<select form="fw" name="target" v-model="newRule.target" class="bg-transparent">
<option class="dark:text-white dark:bg-neutral-700" value="ACCEPT">ALLOW</option>
<option class="dark:text-white dark:bg-neutral-700" value="DROP">BLOCK</option>
</select>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</template>

Loading…
Cancel
Save