Browse Source

be able to change hooks

pull/1572/head
Bernd Storath 5 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: '', name: 'General' },
{ id: 'config', name: 'Config' }, { id: 'config', name: 'Config' },
{ id: 'interface', name: 'Interface' }, { id: 'interface', name: 'Interface' },
{ id: 'hooks', name: 'Hooks' },
{ id: 'metrics', name: 'Metrics' }, { 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", "portMin": "Port must be at least 1",
"portMax": "Port must be at most 65535", "portMax": "Port must be at most 65535",
"sessionTimeout": "Session Timeout must be a valid number", "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", "name": "Name",
"username": "Username", "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' }), sessionTimeout: z.number({ message: 'zod.sessionTimeout' }),
}); });
const device = z
.string({ message: 'zod.device' })
.min(1, 'zod.deviceMin')
.pipe(safeStringRefine);
export const interfaceUpdateType = z.object({ export const interfaceUpdateType = z.object({
mtu: z.number({ message: 'zod.mtu' }), mtu: mtu,
port: port, port: port,
device: z.string({ message: 'zod.device' }), device: device,
}); });
export const userConfigUpdateType = z.object({ export const userConfigUpdateType = z.object({
@ -188,6 +193,15 @@ export const userConfigUpdateType = z.object({
persistentKeepalive: persistentKeepalive, 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 // from https://github.com/airjp73/rvf/blob/7e7c35d98015ea5ecff5affaf89f78296e84e8b9/packages/zod-form-data/src/helpers.ts#L117
type FormDataLikeInput = { type FormDataLikeInput = {
[Symbol.iterator](): IterableIterator<[string, FormDataEntryValue]>; [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} Address = ${system.interface.address4}/${cidr4Block}, ${system.interface.address6}/${cidr6Block}
ListenPort = ${system.interface.port} ListenPort = ${system.interface.port}
MTU = ${system.interface.mtu} MTU = ${system.interface.mtu}
PreUp = ${system.iptables.PreUp} PreUp = ${iptablesTemplate(system.hooks.PreUp, system)}
PostUp = ${system.iptables.PostUp} PostUp = ${iptablesTemplate(system.hooks.PostUp, system)}
PreDown = ${system.iptables.PreDown} PreDown = ${iptablesTemplate(system.hooks.PreDown, system)}
PostDown = ${system.iptables.PostDown}`; PostDown = ${iptablesTemplate(system.hooks.PostDown, system)}`;
}, },
generateClientConfig: ( generateClientConfig: (

8
src/services/database/lowdb.ts

@ -24,6 +24,7 @@ import {
type General, type General,
type UpdateWGConfig, type UpdateWGConfig,
type UpdateWGInterface, type UpdateWGInterface,
type WGHooks,
} from './repositories/system'; } from './repositories/system';
import { SetupRepository, type Steps } from './repositories/setup'; import { SetupRepository, type Steps } from './repositories/setup';
import type { DeepReadonly } from 'vue'; 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 { class LowDBUser extends UserRepository {

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

@ -40,12 +40,30 @@ export async function run1(db: Low<Database>) {
host: '', host: '',
port: 51820, port: 51820,
}, },
// Config to configure Firewall // Config to configure Firewall or general hooks
iptables: { hooks: {
PreUp: '', 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: '', 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: { metrics: {
prometheus: { prometheus: {
@ -64,32 +82,6 @@ export async function run1(db: Low<Database>) {
clients: {}, 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.data = database;
db.write(); db.write();
} }

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

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

Loading…
Cancel
Save