mirror of
https://github.com/wireadmin/wireadmin
synced 2025-06-26 18:28:06 +00:00
updates WireGuard
lib for creating peers
This commit is contained in:
parent
2b6083a692
commit
4e4449435d
@ -1,7 +1,6 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { IPV4_REGEX } from "@lib/constants";
|
import { IPV4_REGEX } from "@lib/constants";
|
||||||
import { isBetween, isPrivateIP } from "@lib/utils";
|
import { isBetween, isPrivateIP } from "@lib/utils";
|
||||||
import { ZodErrorMap } from "zod/lib/ZodError";
|
|
||||||
|
|
||||||
export const NameSchema = z
|
export const NameSchema = z
|
||||||
.string()
|
.string()
|
||||||
@ -50,6 +49,10 @@ export const ServerId = z
|
|||||||
.string()
|
.string()
|
||||||
.uuid({ message: 'Server ID must be a valid UUID' })
|
.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
|
export const ServerStatusSchema = z
|
||||||
.enum([ 'up', 'down' ], {
|
.enum([ 'up', 'down' ], {
|
||||||
errorMap: issue => {
|
errorMap: issue => {
|
||||||
|
@ -2,10 +2,12 @@ import { z } from "zod";
|
|||||||
import type React from "react";
|
import type React from "react";
|
||||||
import { IPV4_REGEX } from "@lib/constants";
|
import { IPV4_REGEX } from "@lib/constants";
|
||||||
import { NextApiRequest as TNextApiRequest } from "next/dist/shared/lib/utils";
|
import { NextApiRequest as TNextApiRequest } from "next/dist/shared/lib/utils";
|
||||||
|
import { NameSchema } from "@lib/schemas/WireGuard";
|
||||||
|
|
||||||
export const WgKeySchema = z.object({
|
export const WgKeySchema = z.object({
|
||||||
privateKey: z.string(),
|
privateKey: z.string(),
|
||||||
publicKey: z.string(),
|
publicKey: z.string(),
|
||||||
|
preSharedKey: z.string(),
|
||||||
})
|
})
|
||||||
|
|
||||||
export type WgKey = z.infer<typeof WgKeySchema>
|
export type WgKey = z.infer<typeof WgKeySchema>
|
||||||
@ -32,7 +34,7 @@ export const WgServerSchema = z.object({
|
|||||||
id: z.string().uuid(),
|
id: z.string().uuid(),
|
||||||
confId: z.number(),
|
confId: z.number(),
|
||||||
type: z.enum([ 'direct', 'bridge', 'tor' ]),
|
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),
|
address: z.string().regex(IPV4_REGEX),
|
||||||
listen: z.number(),
|
listen: z.number(),
|
||||||
preUp: z.string().nullable(),
|
preUp: z.string().nullable(),
|
||||||
@ -40,17 +42,21 @@ export const WgServerSchema = z.object({
|
|||||||
preDown: z.string().nullable(),
|
preDown: z.string().nullable(),
|
||||||
postDown: z.string().nullable(),
|
postDown: z.string().nullable(),
|
||||||
dns: z.string().regex(IPV4_REGEX).nullable(),
|
dns: z.string().regex(IPV4_REGEX).nullable(),
|
||||||
peers: z.array(z.object({
|
peers: z.array(
|
||||||
publicKey: z.string(),
|
z.object({
|
||||||
preSharedKey: z.string().nullable(),
|
id: z.string().uuid(),
|
||||||
allowedIps: z.string().regex(IPV4_REGEX),
|
name: NameSchema,
|
||||||
persistentKeepalive: z.number().nullable(),
|
preSharedKey: z.string().nullable(),
|
||||||
})),
|
allowedIps: z.string().regex(IPV4_REGEX),
|
||||||
|
persistentKeepalive: z.number().nullable(),
|
||||||
|
})
|
||||||
|
.merge(WgKeySchema)
|
||||||
|
),
|
||||||
createdAt: z.string().datetime(),
|
createdAt: z.string().datetime(),
|
||||||
updatedAt: z.string().datetime(),
|
updatedAt: z.string().datetime(),
|
||||||
status: z.enum([ 'up', 'down' ]),
|
status: z.enum([ 'up', 'down' ]),
|
||||||
})
|
})
|
||||||
.merge(WgKeySchema)
|
.merge(WgKeySchema.omit({ preSharedKey: true }))
|
||||||
|
|
||||||
export type WgServer = z.infer<typeof WgServerSchema>
|
export type WgServer = z.infer<typeof WgServerSchema>
|
||||||
|
|
||||||
|
55
src/lib/wireguard-utils.ts
Normal file
55
src/lib/wireguard-utils.ts
Normal 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')
|
||||||
|
}
|
@ -6,6 +6,7 @@ import { WgKey, WgServer } from "@lib/typings";
|
|||||||
import { client, WG_SEVER_PATH } from "@lib/redis";
|
import { client, WG_SEVER_PATH } from "@lib/redis";
|
||||||
import { isJson } from "@lib/utils";
|
import { isJson } from "@lib/utils";
|
||||||
import deepmerge from "deepmerge";
|
import deepmerge from "deepmerge";
|
||||||
|
import { getPeerConf, getServerConf } from "@lib/wireguard-utils";
|
||||||
|
|
||||||
export class WGServer {
|
export class WGServer {
|
||||||
|
|
||||||
@ -63,7 +64,10 @@ export class WGServer {
|
|||||||
console.warn('findServerIndex: index not found')
|
console.warn('findServerIndex: index not found')
|
||||||
return true
|
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'
|
return res === 'OK'
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -109,14 +113,23 @@ export class WGServer {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
const peers = await wgPeersStr(server.confId)
|
const peers = await wgPeersStr(server.confId)
|
||||||
const peerIndex = peers.findIndex((p) => p
|
|
||||||
.replace(/\s/g, '')
|
const index = await findServerIndex(id)
|
||||||
.includes(`PublicKey=${publicKey}`)
|
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) {
|
if (peerIndex === -1) {
|
||||||
console.warn('no peer found')
|
console.warn('removePeer: no peer found')
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
const confPath = path.join(WG_PATH, `wg${server.confId}.conf`)
|
const confPath = path.join(WG_PATH, `wg${server.confId}.conf`)
|
||||||
const conf = await fs.readFile(confPath, 'utf-8')
|
const conf = await fs.readFile(confPath, 'utf-8')
|
||||||
const serverConfStr = conf.includes('[Peer]') ?
|
const serverConfStr = conf.includes('[Peer]') ?
|
||||||
@ -124,14 +137,46 @@ export class WGServer {
|
|||||||
conf
|
conf
|
||||||
const peersStr = peers.filter((_, i) => i !== peerIndex).join('\n')
|
const peersStr = peers.filter((_, i) => i !== peerIndex).join('\n')
|
||||||
await fs.writeFile(confPath, `${serverConfStr}\n${peersStr}`)
|
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.stop(server.id)
|
||||||
await WGServer.start(server.id)
|
await WGServer.start(server.id)
|
||||||
|
|
||||||
return true
|
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 (reachedPeers) {
|
||||||
if (key === '[Peer]') {
|
if (key === '[Peer]') {
|
||||||
server.peers.push({
|
server.peers.push({
|
||||||
|
id: crypto.randomUUID(),
|
||||||
|
name: `Unknown #${server.peers.length + 1}`,
|
||||||
publicKey: '',
|
publicKey: '',
|
||||||
preSharedKey: null,
|
privateKey: '', // it's okay to be empty because, we not using it on server
|
||||||
|
preSharedKey: '',
|
||||||
allowedIps: '',
|
allowedIps: '',
|
||||||
persistentKeepalive: null
|
persistentKeepalive: null
|
||||||
})
|
})
|
||||||
@ -268,7 +316,8 @@ async function wgPeersStr(configId: number): Promise<string[]> {
|
|||||||
export async function generateWgKey(): Promise<WgKey> {
|
export async function generateWgKey(): Promise<WgKey> {
|
||||||
const privateKey = await Shell.exec('wg genkey');
|
const privateKey = await Shell.exec('wg genkey');
|
||||||
const publicKey = await Shell.exec(`echo ${privateKey} | wg pubkey`);
|
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: {
|
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)
|
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> {
|
export async function maxConfId(): Promise<number> {
|
||||||
// get files in /etc/wireguard
|
// get files in /etc/wireguard
|
||||||
const files = await fs.readdir(WG_PATH)
|
const files = await fs.readdir(WG_PATH)
|
||||||
|
Loading…
Reference in New Issue
Block a user