- Require admin auth on interface-local-ip, network-interfaces, next-listen-port and public-ip (interface-local-ip and public-ip allowed unauthenticated command injection as root) - Validate interface names and call ip/curl via execFileSync - Write host/client WireGuard configs with fs instead of shell heredocs, validating IPs, AllowedIPs, endpoint and listen port - Replace rm -rf/cat/mkdir/chmod/test shell calls with fs APIs - Refuse private media requested through the public media route Co-Authored-By: Claude Opus 5.5 <[email protected]>
213 lines
6.1 KiB
TypeScript
213 lines
6.1 KiB
TypeScript
import type {
|
|
BUN_SQLITE_WGUI_CLIENTS,
|
|
BUN_SQLITE_WGUI_HOSTS,
|
|
} from "@/db/types/db";
|
|
import { AppData } from "@/src/data/app-data";
|
|
import grabDirNames from "@/src/utils/grab-dir-names";
|
|
import { execSync } from "node:child_process";
|
|
import { readFileSync, writeFileSync } from "node:fs";
|
|
import path from "node:path";
|
|
import validateIpv4 from "@/src/utils/validate-ipv4";
|
|
import validateAllowedIps from "@/src/utils/validate-allowed-ips";
|
|
import validateEndpointHost from "@/src/utils/validate-endpoint-host";
|
|
import grabHostPublicIPAddress from "./grab-host-public-ip-address";
|
|
import setupWireguardHost from "./setup-wireguard-host";
|
|
import type { APIResponseObject } from "@moduletrace/bunext/types";
|
|
import BunSQLite from "@moduletrace/bun-sqlite";
|
|
import type { TableType, User } from "@/src/types";
|
|
import grabClientDirnames from "./grab-client-dir-names";
|
|
|
|
const { WIREGUARD_PUBLIC_KEY_FILE_NAME, WIREGUARD_CLIENT_CONFIG_FILE_NAME } =
|
|
grabDirNames();
|
|
|
|
type Params = {
|
|
client: BUN_SQLITE_WGUI_CLIENTS;
|
|
host?: BUN_SQLITE_WGUI_HOSTS;
|
|
user: User;
|
|
/**
|
|
* Skip the host setup/restart at the end. Use when
|
|
* regenerating multiple clients and restarting the
|
|
* host once afterwards instead.
|
|
*/
|
|
skip_host_setup?: boolean;
|
|
};
|
|
|
|
/**
|
|
* Function to setup a wireguard client. This
|
|
* function will eventually call the setupWireguardHost
|
|
* function afterwards to update it
|
|
* @param param0
|
|
* @returns
|
|
*/
|
|
export default async function setupWireguardClient({
|
|
client,
|
|
host,
|
|
user,
|
|
skip_host_setup,
|
|
}: Params): Promise<APIResponseObject> {
|
|
const host_id = host?.id || AppData["WireguardMainHostID"];
|
|
|
|
let resolved_host = host;
|
|
|
|
if (!resolved_host) {
|
|
const host_res = await BunSQLite.select<
|
|
BUN_SQLITE_WGUI_HOSTS,
|
|
TableType
|
|
>({
|
|
table: "hosts",
|
|
targetId: host_id,
|
|
});
|
|
resolved_host = host_res.singleRes || undefined;
|
|
}
|
|
|
|
const {
|
|
CLIENT_WG_IP,
|
|
CLIENT_CONFIG_FILE,
|
|
CLIENT_DIR,
|
|
CLIENT_PRIVATE_KEY_FILE,
|
|
CLIENT_PUBLIC_KEY_FILE,
|
|
HOST_CONFIG_DIR,
|
|
} = grabClientDirnames({ client, host_id });
|
|
|
|
if (!CLIENT_WG_IP) {
|
|
return {
|
|
success: false,
|
|
msg: `No Client Private IP address provided`,
|
|
};
|
|
}
|
|
|
|
if (!validateIpv4({ ip: CLIENT_WG_IP })) {
|
|
return {
|
|
success: false,
|
|
msg: `Invalid Client Private IP address: ${CLIENT_WG_IP}`,
|
|
};
|
|
}
|
|
|
|
if (
|
|
client?.allow_all_ips != 1 &&
|
|
client?.allowed_ips &&
|
|
!validateAllowedIps({ value: client.allowed_ips })
|
|
) {
|
|
return {
|
|
success: false,
|
|
msg: `Invalid Allowed IPs: ${client.allowed_ips}`,
|
|
};
|
|
}
|
|
|
|
const HOST_WG_IP = resolved_host?.wg_ip_address;
|
|
|
|
if (!HOST_WG_IP) {
|
|
return {
|
|
success: false,
|
|
msg: `No Host Private IP address provided`,
|
|
};
|
|
}
|
|
|
|
let pre_sh = ``;
|
|
|
|
pre_sh += `mkdir -p ${CLIENT_DIR}\n`;
|
|
pre_sh += `if [ ! -f ${CLIENT_PRIVATE_KEY_FILE} ]; then\n`;
|
|
pre_sh += ` wg genkey | tee ${CLIENT_PRIVATE_KEY_FILE} | wg pubkey > ${CLIENT_PUBLIC_KEY_FILE}\n`;
|
|
pre_sh += `fi\n`;
|
|
|
|
try {
|
|
const exec_pre_setup = execSync(pre_sh, { encoding: "utf-8" });
|
|
} catch (error: any) {
|
|
return {
|
|
success: false,
|
|
msg: error.message,
|
|
};
|
|
}
|
|
|
|
try {
|
|
const PUBLIC_IP_ADDRESS =
|
|
resolved_host?.public_ip_address ||
|
|
(await grabHostPublicIPAddress()) ||
|
|
client?.public_ip_address ||
|
|
HOST_WG_IP;
|
|
|
|
if (!validateEndpointHost({ value: PUBLIC_IP_ADDRESS })) {
|
|
return {
|
|
success: false,
|
|
msg: `Invalid Host public IP address: ${PUBLIC_IP_ADDRESS}`,
|
|
};
|
|
}
|
|
|
|
const LISTEN_PORT = Number(
|
|
resolved_host?.listen_port || AppData["DefaultWGListenPort"],
|
|
);
|
|
|
|
if (!Number.isInteger(LISTEN_PORT)) {
|
|
return {
|
|
success: false,
|
|
msg: `Invalid Host listen port: ${resolved_host?.listen_port}`,
|
|
};
|
|
}
|
|
|
|
const CLIENT_PRIVATE_KEY = readFileSync(
|
|
CLIENT_PRIVATE_KEY_FILE,
|
|
"utf-8",
|
|
).trim();
|
|
|
|
const HOST_PUBLIC_KEY = readFileSync(
|
|
path.join(HOST_CONFIG_DIR, WIREGUARD_PUBLIC_KEY_FILE_NAME),
|
|
"utf-8",
|
|
).trim();
|
|
|
|
let CLIENT_CONFIG = ``;
|
|
|
|
CLIENT_CONFIG += `\n`;
|
|
CLIENT_CONFIG += `[Interface]\n`;
|
|
CLIENT_CONFIG += `Address = ${CLIENT_WG_IP}/32\n`;
|
|
CLIENT_CONFIG += `PrivateKey = ${CLIENT_PRIVATE_KEY}\n`;
|
|
if (client?.allow_all_ips == 1) {
|
|
CLIENT_CONFIG += `DNS = 1.1.1.1\n`;
|
|
}
|
|
CLIENT_CONFIG += `\n`;
|
|
CLIENT_CONFIG += `[Peer]\n`;
|
|
CLIENT_CONFIG += `PublicKey = ${HOST_PUBLIC_KEY}\n`;
|
|
CLIENT_CONFIG += `Endpoint = ${PUBLIC_IP_ADDRESS}:${LISTEN_PORT}\n`;
|
|
|
|
if (client?.allow_all_ips == 1) {
|
|
CLIENT_CONFIG += `AllowedIPs = 0.0.0.0/0, ::/0\n`;
|
|
} else if (client?.allowed_ips) {
|
|
CLIENT_CONFIG += `AllowedIPs = ${client.allowed_ips}\n`;
|
|
}
|
|
|
|
CLIENT_CONFIG += `PersistentKeepalive = 25\n`;
|
|
|
|
writeFileSync(CLIENT_CONFIG_FILE, CLIENT_CONFIG, { mode: 0o600 });
|
|
|
|
const host_setup_res = skip_host_setup
|
|
? { success: true }
|
|
: await setupWireguardHost({
|
|
host: resolved_host,
|
|
user,
|
|
is_update_after_client_setup: true,
|
|
});
|
|
|
|
if (!host_setup_res.success) {
|
|
return {
|
|
success: false,
|
|
msg: `Client config created but Host update failed: ${host_setup_res.msg}`,
|
|
};
|
|
}
|
|
|
|
return {
|
|
success: true,
|
|
msg: `Client ${client.name} setup complete`,
|
|
stringRes: CLIENT_CONFIG,
|
|
};
|
|
} catch (error: any) {
|
|
console.log(
|
|
`Error setting up wireguard client ${client.id} for host ${host_id} =>`,
|
|
error.message,
|
|
);
|
|
|
|
return {
|
|
success: false,
|
|
msg: error.message,
|
|
};
|
|
}
|
|
}
|