updates WireGuard lib for creating peers

This commit is contained in:
Shahrad Elahi 2023-09-25 15:27:43 +03:30
parent 2b6083a692
commit 4e4449435d
4 changed files with 133 additions and 48 deletions

View File

@ -1,7 +1,6 @@
import { z } from "zod";
import { IPV4_REGEX } from "@lib/constants";
import { isBetween, isPrivateIP } from "@lib/utils";
import { ZodErrorMap } from "zod/lib/ZodError";
export const NameSchema = z
.string()
@ -50,6 +49,10 @@ export const ServerId = z
.string()
.uuid({ message: 'Server ID must be a valid UUID' })
export const ClientId = z
.string()
.uuid({ message: 'Client ID must be a valid UUID' })
export const ServerStatusSchema = z
.enum([ 'up', 'down' ], {
errorMap: issue => {

View File

@ -2,10 +2,12 @@ import { z } from "zod";
import type React from "react";
import { IPV4_REGEX } from "@lib/constants";
import { NextApiRequest as TNextApiRequest } from "next/dist/shared/lib/utils";
import { NameSchema } from "@lib/schemas/WireGuard";
export const WgKeySchema = z.object({
privateKey: z.string(),
publicKey: z.string(),
preSharedKey: z.string(),
})
export type WgKey = z.infer<typeof WgKeySchema>
@ -32,7 +34,7 @@ export const WgServerSchema = z.object({
id: z.string().uuid(),
confId: z.number(),
type: z.enum([ 'direct', 'bridge', 'tor' ]),
name: z.string().regex(/^[A-Za-z\d\s]{3,32}$/),
name: NameSchema,
address: z.string().regex(IPV4_REGEX),
listen: z.number(),
preUp: z.string().nullable(),
@ -40,17 +42,21 @@ export const WgServerSchema = z.object({
preDown: z.string().nullable(),
postDown: z.string().nullable(),
dns: z.string().regex(IPV4_REGEX).nullable(),
peers: z.array(z.object({
publicKey: z.string(),
preSharedKey: z.string().nullable(),
allowedIps: z.string().regex(IPV4_REGEX),
persistentKeepalive: z.number().nullable(),
})),
peers: z.array(
z.object({
id: z.string().uuid(),
name: NameSchema,
preSharedKey: z.string().nullable(),
allowedIps: z.string().regex(IPV4_REGEX),
persistentKeepalive: z.number().nullable(),
})
.merge(WgKeySchema)
),
createdAt: z.string().datetime(),
updatedAt: z.string().datetime(),
status: z.enum([ 'up', 'down' ]),
})
.merge(WgKeySchema)
.merge(WgKeySchema.omit({ preSharedKey: true }))
export type WgServer = z.infer<typeof WgServerSchema>

View File

@ -0,0 +1,55 @@
import { WgServer } from "@lib/typings";
export function getServerConf(server: WgServer): string {
const lines = [
'# Autogenerated by WireGuard UI (WireAdmin)',
'[Interface]',
`PrivateKey = ${server.privateKey}`,
`Address = ${server.address}/24`,
`ListenPort = ${server.listen}`,
`${server.dns ? `DNS = ${server.dns}` : 'OMIT'}`,
'',
`${server.preUp ? `PreUp = ${server.preUp}` : 'OMIT'}`,
`${server.postUp ? `PostUp = ${server.postUp}` : 'OMIT'}`,
`${server.preDown ? `PreDown = ${server.preDown}` : 'OMIT'}`,
`${server.postDown ? `PostDown = ${server.postDown}` : 'OMIT'}`,
...server.peers.map((peer, index) => ([
'',
`## ${peer.name || `Peer #${index + 1}`}`,
'[Peer]',
`PublicKey = ${peer.publicKey}`,
`${peer.preSharedKey ? `PresharedKey = ${peer.preSharedKey}` : 'OMIT'}`,
`AllowedIPs = ${peer.allowedIps}/32`,
`${peer.persistentKeepalive ? `PersistentKeepalive = ${peer.persistentKeepalive}` : 'OMIT'}`
]))
]
return lines
.filter((l) => l !== 'OMIT')
.join('\n')
}
type Peer = WgServer['peers'][0]
interface GenPeerConParams extends Peer {
serverAddress?: string
port: number
}
export function getPeerConf(params: GenPeerConParams): string {
const lines = [
'# Autogenerated by WireGuard UI (WireAdmin)',
'[Interface]',
`PrivateKey = ${params.privateKey}`,
`Address = ${params.allowedIps}/32`,
'',
'[Peer]',
`PublicKey = ${params.publicKey}`,
`${params.preSharedKey ? `PresharedKey = ${params.preSharedKey}` : 'OMIT'}`,
`AllowedIPs = ${params.allowedIps}/32`,
`PersistentKeepalive = ${params.persistentKeepalive}`,
`Endpoint = ${params.serverAddress || process.env.NEXT_PUBLIC_WG_HOST}:${params.port}`,
]
return lines
.filter((l) => l !== 'OMIT')
.join('\n')
}

View File

@ -6,6 +6,7 @@ import { WgKey, WgServer } from "@lib/typings";
import { client, WG_SEVER_PATH } from "@lib/redis";
import { isJson } from "@lib/utils";
import deepmerge from "deepmerge";
import { getPeerConf, getServerConf } from "@lib/wireguard-utils";
export class WGServer {
@ -63,7 +64,10 @@ export class WGServer {
console.warn('findServerIndex: index not found')
return true
}
const res = await client.lset(WG_SEVER_PATH, index, JSON.stringify(deepmerge(server, update)))
const res = await client.lset(WG_SEVER_PATH, index, JSON.stringify({
...deepmerge(server, update),
updatedAt: new Date().toISOString()
}))
return res === 'OK'
}
@ -109,14 +113,23 @@ export class WGServer {
return false
}
const peers = await wgPeersStr(server.confId)
const peerIndex = peers.findIndex((p) => p
.replace(/\s/g, '')
.includes(`PublicKey=${publicKey}`)
)
const index = await findServerIndex(id)
if (typeof index !== 'number') {
console.warn('findServerIndex: index not found')
return true
}
await client.lset(WG_SEVER_PATH, index, JSON.stringify({
...server,
peers: server.peers.filter((p) => p.publicKey !== publicKey)
}))
const peerIndex = peers.findIndex((p) => p.includes(`PublicKey = ${publicKey}`))
if (peerIndex === -1) {
console.warn('no peer found')
console.warn('removePeer: no peer found')
return false
}
const confPath = path.join(WG_PATH, `wg${server.confId}.conf`)
const conf = await fs.readFile(confPath, 'utf-8')
const serverConfStr = conf.includes('[Peer]') ?
@ -124,14 +137,46 @@ export class WGServer {
conf
const peersStr = peers.filter((_, i) => i !== peerIndex).join('\n')
await fs.writeFile(confPath, `${serverConfStr}\n${peersStr}`)
await WGServer.update(server.id, {
peers: server.peers.filter((_, i) => i !== peerIndex)
})
await WGServer.stop(server.id)
await WGServer.start(server.id)
return true
}
static async getFreePeerIp(id: string): Promise<string | undefined> {
const server = await findServer(id)
if (!server) {
console.error('getFreePeerIp: server not found')
return undefined
}
const reservedIps = server.peers.map((p) => p.allowedIps)
const ips = reservedIps.map((ip) => ip.split('/')[0])
const net = server.address.split('/')[0].split('.')
for (let i = 1; i < 255; i++) {
const ip = `${net[0]}.${net[1]}.${net[2]}.${i}`
if (!ips.includes(ip) && ip !== server.address.split('/')[0]) {
return ip
}
}
console.error('getFreePeerIp: no free ip found')
return undefined
}
static async generatePeerConfig(id: string, peerId: string): Promise<string | undefined> {
const server = await findServer(id)
if (!server) {
console.error('generatePeerConfig: server not found')
return undefined
}
const peer = server.peers.find((p) => p.id === peerId)
if (!peer) {
console.error('generatePeerConfig: peer not found')
return undefined
}
return getPeerConf({ ...peer, port: server.listen })
}
}
/**
@ -171,8 +216,11 @@ export async function readWgConf(configId: number): Promise<WgServer> {
if (reachedPeers) {
if (key === '[Peer]') {
server.peers.push({
id: crypto.randomUUID(),
name: `Unknown #${server.peers.length + 1}`,
publicKey: '',
preSharedKey: null,
privateKey: '', // it's okay to be empty because, we not using it on server
preSharedKey: '',
allowedIps: '',
persistentKeepalive: null
})
@ -268,7 +316,8 @@ async function wgPeersStr(configId: number): Promise<string[]> {
export async function generateWgKey(): Promise<WgKey> {
const privateKey = await Shell.exec('wg genkey');
const publicKey = await Shell.exec(`echo ${privateKey} | wg pubkey`);
return { privateKey, publicKey }
const preSharedKey = await Shell.exec('wg genkey');
return { privateKey, publicKey, preSharedKey }
}
export async function generateWgServer(config: {
@ -386,34 +435,6 @@ export async function dropInterface(configId: number) {
await Shell.exec(`ip link delete dev wg${configId}`, true)
}
export function getServerConf(server: WgServer): string {
return `
# Autogenerated by WireGuard UI (WireAdmin)
[Interface]
PrivateKey = ${server.privateKey}
Address = ${server.address}/24
ListenPort = ${server.listen}
${server.dns ? `DNS = ${server.dns}` : ''}
PreUp = ${server.preUp}
PostUp = ${server.postUp}
PreDown = ${server.preDown}
PostDown = ${server.postDown}
${server.peers.map(getPeerConf).join('\n')}
`
}
export function getPeerConf(peer: WgServer['peers'][0]): string {
return `
[Peer]
PublicKey = ${peer.publicKey}
${peer.preSharedKey ? `PresharedKey = ${peer.preSharedKey}` : ''}
AllowedIPs = ${peer.allowedIps}/32
${peer.persistentKeepalive ? `PersistentKeepalive = ${peer.persistentKeepalive}` : ''}
`
}
export async function maxConfId(): Promise<number> {
// get files in /etc/wireguard
const files = await fs.readdir(WG_PATH)