Browse Source

be able to change hooks

pull/1572/head
Bernd Storath 3 months ago
parent
commit
d9b82f8207
  1. 1
      src/app/pages/admin.vue
  2. 58
      src/app/pages/admin/hooks.vue
  3. 5
      src/i18n/locales/en.json
  4. 4
      src/server/api/admin/hooks.get.ts
  5. 8
      src/server/api/admin/hooks.post.ts
  6. 27
      src/server/utils/template.ts
  7. 18
      src/server/utils/types.ts
  8. 8
      src/server/utils/wgHelper.ts
  9. 8
      src/services/database/lowdb.ts
  10. 52
      src/services/database/migrations/1.ts
  11. 6
      src/services/database/repositories/system.ts

1
src/app/pages/admin.vue

@ -44,6 +44,7 @@ const menuItems = [
{ id: '', name: 'General' },
{ id: 'config', name: 'Config' },
{ id: 'interface', name: 'Interface' },
{ id: 'hooks', name: 'Hooks' },
{ id: 'metrics', name: 'Metrics' },
];

58
src/app/pages/admin/hooks.vue

@ -0,0 +1,58 @@
<template>
<main v-if="data">
<FormElement @submit.prevent="submit">
<FormGroup>
<FormTextField id="PreUp" v-model="data.PreUp" label="PreUp" />
<FormTextField id="PostUp" v-model="data.PostUp" label="PostUp" />
<FormTextField id="PreDown" v-model="data.PreDown" label="PreDown" />
<FormTextField id="PostDown" v-model="data.PostDown" label="PostDown" />
</FormGroup>
<FormGroup>
<FormHeading>Actions</FormHeading>
<FormActionField type="submit" label="Save" />
<FormActionField label="Revert" @click="revert" />
</FormGroup>
</FormElement>
</main>
</template>
<script setup lang="ts">
const toast = useToast();
const { data: _data, refresh } = await useFetch(`/api/admin/hooks`, {
method: 'get',
});
const data = toRef(_data.value);
async function submit() {
try {
const res = await $fetch(`/api/admin/hooks`, {
method: 'post',
body: data.value,
});
toast.showToast({
type: 'success',
title: 'Success',
message: 'Saved',
});
if (!res.success) {
throw new Error('Failed to save');
}
await refreshNuxtData();
} catch (e) {
if (e instanceof Error) {
toast.showToast({
type: 'error',
title: 'Error',
message: e.message,
});
}
}
}
async function revert() {
await refresh();
data.value = toRef(_data.value).value;
}
</script>

5
src/i18n/locales/en.json

@ -87,7 +87,10 @@
"portMin": "Port must be at least 1",
"portMax": "Port must be at most 65535",
"sessionTimeout": "Session Timeout must be a valid number",
"device": "Device has to be a valid string"
"device": "Device must be a valid string",
"deviceMin": "Device must be at least 1 Character",
"hook": "Hook must be a valid string",
"dns": "DNS must be a valid array of strings"
},
"name": "Name",
"username": "Username",

4
src/server/api/admin/hooks.get.ts

@ -0,0 +1,4 @@
export default defineEventHandler(async () => {
const system = await Database.system.get();
return system.hooks;
});

8
src/server/api/admin/hooks.post.ts

@ -0,0 +1,8 @@
export default defineEventHandler(async (event) => {
const data = await readValidatedBody(
event,
validateZod(hooksUpdateType, event)
);
await Database.system.updateHooks(data);
return { success: true };
});

27
src/server/utils/template.ts

@ -0,0 +1,27 @@
import type { DeepReadonly } from 'vue';
import type { System } from '~~/services/database/repositories/system';
/**
* Replace all {{key}} in the template with the values[key]
*/
export function template(templ: string, values: Record<string, string>) {
return templ.replace(/\{\{(\w+)\}\}/g, (match, key) => {
return values[key] !== undefined ? values[key] : match;
});
}
/**
* Available keys:
* - address4: IPv4 address range
* - address6: IPv6 address range
* - device: Network device
* - port: Port number
*/
export function iptablesTemplate(templ: string, system: DeepReadonly<System>) {
return template(templ, {
address4: system.userConfig.address4Range,
address6: system.userConfig.address6Range,
device: system.interface.device,
port: system.interface.port.toString(),
});
}

18
src/server/utils/types.ts

@ -173,10 +173,15 @@ export const generalUpdateType = z.object({
sessionTimeout: z.number({ message: 'zod.sessionTimeout' }),
});
const device = z
.string({ message: 'zod.device' })
.min(1, 'zod.deviceMin')
.pipe(safeStringRefine);
export const interfaceUpdateType = z.object({
mtu: z.number({ message: 'zod.mtu' }),
mtu: mtu,
port: port,
device: z.string({ message: 'zod.device' }),
device: device,
});
export const userConfigUpdateType = z.object({
@ -188,6 +193,15 @@ export const userConfigUpdateType = z.object({
persistentKeepalive: persistentKeepalive,
});
const hook = z.string({ message: 'zod.hook' }).pipe(safeStringRefine);
export const hooksUpdateType = z.object({
PreUp: hook,
PostUp: hook,
PreDown: hook,
PostDown: hook,
});
// from https://github.com/airjp73/rvf/blob/7e7c35d98015ea5ecff5affaf89f78296e84e8b9/packages/zod-form-data/src/helpers.ts#L117
type FormDataLikeInput = {
[Symbol.iterator](): IterableIterator<[string, FormDataEntryValue]>;

8
src/server/utils/wgHelper.ts

@ -33,10 +33,10 @@ PrivateKey = ${system.interface.privateKey}
Address = ${system.interface.address4}/${cidr4Block}, ${system.interface.address6}/${cidr6Block}
ListenPort = ${system.interface.port}
MTU = ${system.interface.mtu}
PreUp = ${system.iptables.PreUp}
PostUp = ${system.iptables.PostUp}
PreDown = ${system.iptables.PreDown}
PostDown = ${system.iptables.PostDown}`;
PreUp = ${iptablesTemplate(system.hooks.PreUp, system)}
PostUp = ${iptablesTemplate(system.hooks.PostUp, system)}
PreDown = ${iptablesTemplate(system.hooks.PreDown, system)}
PostDown = ${iptablesTemplate(system.hooks.PostDown, system)}`;
},
generateClientConfig: (

8
src/services/database/lowdb.ts

@ -24,6 +24,7 @@ import {
type General,
type UpdateWGConfig,
type UpdateWGInterface,
type WGHooks,
} from './repositories/system';
import { SetupRepository, type Steps } from './repositories/setup';
import type { DeepReadonly } from 'vue';
@ -115,6 +116,13 @@ class LowDBSystem extends SystemRepository {
};
});
}
async updateHooks(hooks: WGHooks) {
DEBUG('Update Hooks');
this.#db.update((v) => {
v.system.hooks = hooks;
});
}
}
class LowDBUser extends UserRepository {

52
src/services/database/migrations/1.ts

@ -40,12 +40,30 @@ export async function run1(db: Low<Database>) {
host: '',
port: 51820,
},
// Config to configure Firewall
iptables: {
// Config to configure Firewall or general hooks
hooks: {
PreUp: '',
PostUp: '',
PostUp: [
'iptables -t nat -A POSTROUTING -s {{address4}} -o {{device}} -j MASQUERADE;',
'iptables -A INPUT -p udp -m udp --dport {{port}} -j ACCEPT;',
'iptables -A FORWARD -i wg0 -j ACCEPT;',
'iptables -A FORWARD -o wg0 -j ACCEPT;',
'ip6tables -t nat -A POSTROUTING -s {{address6}} -o {{device}} -j MASQUERADE;',
'ip6tables -A INPUT -p udp -m udp --dport {{port}} -j ACCEPT;',
'ip6tables -A FORWARD -i wg0 -j ACCEPT;',
'ip6tables -A FORWARD -o wg0 -j ACCEPT;',
].join(' '),
PreDown: '',
PostDown: '',
PostDown: [
'iptables -t nat -D POSTROUTING -s {{address4}} -o {{device}} -j MASQUERADE;',
'iptables -D INPUT -p udp -m udp --dport {{port}} -j ACCEPT;',
'iptables -D FORWARD -i wg0 -j ACCEPT;',
'iptables -D FORWARD -o wg0 -j ACCEPT;',
'ip6tables -t nat -D POSTROUTING -s {{address6}} -o {{device}} -j MASQUERADE;',
'ip6tables -D INPUT -p udp -m udp --dport {{port}} -j ACCEPT;',
'ip6tables -D FORWARD -i wg0 -j ACCEPT;',
'ip6tables -D FORWARD -o wg0 -j ACCEPT;',
].join(' '),
},
metrics: {
prometheus: {
@ -64,32 +82,6 @@ export async function run1(db: Low<Database>) {
clients: {},
};
// TODO: be able to regenerate this on changes
database.system.iptables.PostUp =
`iptables -t nat -A POSTROUTING -s ${database.system.userConfig.address4Range} -o ${database.system.interface.device} -j MASQUERADE;
iptables -A INPUT -p udp -m udp --dport ${database.system.interface.port} -j ACCEPT;
iptables -A FORWARD -i wg0 -j ACCEPT;
iptables -A FORWARD -o wg0 -j ACCEPT;
ip6tables -t nat -A POSTROUTING -s ${database.system.userConfig.address6Range} -o ${database.system.interface.device} -j MASQUERADE;
ip6tables -A INPUT -p udp -m udp --dport ${database.system.interface.port} -j ACCEPT;
ip6tables -A FORWARD -i wg0 -j ACCEPT;
ip6tables -A FORWARD -o wg0 -j ACCEPT;`
.split('\n')
.join(' ');
database.system.iptables.PostDown =
`iptables -t nat -D POSTROUTING -s ${database.system.userConfig.address4Range} -o ${database.system.interface.device} -j MASQUERADE;
iptables -D INPUT -p udp -m udp --dport ${database.system.interface.port} -j ACCEPT;
iptables -D FORWARD -i wg0 -j ACCEPT;
iptables -D FORWARD -o wg0 -j ACCEPT;
ip6tables -t nat -D POSTROUTING -s ${database.system.userConfig.address6Range} -o ${database.system.interface.device} -j MASQUERADE;
ip6tables -D INPUT -p udp -m udp --dport ${database.system.interface.port} -j ACCEPT;
ip6tables -D FORWARD -i wg0 -j ACCEPT;
ip6tables -D FORWARD -o wg0 -j ACCEPT;`
.split('\n')
.join(' ');
db.data = database;
db.write();
}

6
src/services/database/repositories/system.ts

@ -1,7 +1,7 @@
import type { SessionConfig } from 'h3';
import type { DeepReadonly } from 'vue';
export type IpTables = {
export type WGHooks = {
PreUp: string;
PostUp: string;
PreDown: string;
@ -56,7 +56,7 @@ export type System = {
userConfig: WGConfig;
iptables: IpTables;
hooks: WGHooks;
metrics: Metrics;
@ -85,4 +85,6 @@ export abstract class SystemRepository {
abstract updateInterface(wgInterface: UpdateWGInterface): Promise<void>;
abstract updateUserConfig(userConfig: UpdateWGConfig): Promise<void>;
abstract updateHooks(hooks: WGHooks): Promise<void>;
}

Loading…
Cancel
Save