From 58813ffbe4797012b1bce469940858c500b52b03 Mon Sep 17 00:00:00 2001 From: Bernd Storath <32197462+kaaax0815@users.noreply.github.com> Date: Thu, 5 Sep 2024 11:24:39 +0200 Subject: [PATCH] Feat: IPv6 (#1354) * start supporting ipv6 * add ipv6 support * build server with es2020 es2019 doesn't support bigint * fix issues, better naming --- Dockerfile | 2 + Dockerfile.dev | 2 + docker-compose.yml | 3 + src/app/components/Client/Address.vue | 60 ------------ src/app/components/Client/Address4.vue | 60 ++++++++++++ src/app/components/Client/Client.vue | 2 +- src/app/utils/api.ts | 10 +- src/nuxt.config.ts | 7 ++ src/package.json | 5 +- src/pnpm-lock.yaml | 96 +++++++++++++++---- src/server/api/release.get.ts | 3 +- src/server/api/session.post.ts | 2 +- .../{address.put.ts => address4.put.ts} | 7 +- src/server/utils/WireGuard.ts | 93 +++++++++++------- src/server/utils/config.ts | 3 + src/server/utils/types.ts | 8 +- src/services/database/lowdb.ts | 13 +-- src/services/database/migrations/1.ts | 35 ++++--- src/services/database/repositories/client.ts | 5 +- .../database/repositories/database.ts | 2 +- src/services/database/repositories/system.ts | 8 +- src/services/database/repositories/user.ts | 6 +- 22 files changed, 274 insertions(+), 158 deletions(-) delete mode 100644 src/app/components/Client/Address.vue create mode 100644 src/app/components/Client/Address4.vue rename src/server/api/wireguard/client/[clientId]/{address.put.ts => address4.put.ts} (54%) diff --git a/Dockerfile b/Dockerfile index 966632b0..8b09d381 100644 --- a/Dockerfile +++ b/Dockerfile @@ -27,11 +27,13 @@ RUN apk add --no-cache \ dpkg \ dumb-init \ iptables \ + ip6tables \ iptables-legacy \ wireguard-tools # Use iptables-legacy RUN update-alternatives --install /sbin/iptables iptables /sbin/iptables-legacy 10 --slave /sbin/iptables-restore iptables-restore /sbin/iptables-legacy-restore --slave /sbin/iptables-save iptables-save /sbin/iptables-legacy-save +RUN update-alternatives --install /sbin/ip6tables ip6tables /sbin/ip6tables-legacy 10 --slave /sbin/ip6tables-restore ip6tables-restore /sbin/ip6tables-legacy-restore --slave /sbin/ip6tables-save ip6tables-save /sbin/ip6tables-legacy-save # Set Environment ENV DEBUG=Server,WireGuard,LowDB diff --git a/Dockerfile.dev b/Dockerfile.dev index fed4ec47..9be2832b 100644 --- a/Dockerfile.dev +++ b/Dockerfile.dev @@ -17,11 +17,13 @@ RUN apk add --no-cache \ dpkg \ dumb-init \ iptables \ + ip6tables \ iptables-legacy \ wireguard-tools # Use iptables-legacy RUN update-alternatives --install /sbin/iptables iptables /sbin/iptables-legacy 10 --slave /sbin/iptables-restore iptables-restore /sbin/iptables-legacy-restore --slave /sbin/iptables-save iptables-save /sbin/iptables-legacy-save +RUN update-alternatives --install /sbin/ip6tables ip6tables /sbin/ip6tables-legacy 10 --slave /sbin/ip6tables-restore ip6tables-restore /sbin/ip6tables-legacy-restore --slave /sbin/ip6tables-save ip6tables-save /sbin/ip6tables-legacy-save # Set Environment ENV DEBUG=Server,WireGuard,LowDB diff --git a/docker-compose.yml b/docker-compose.yml index f607837b..c5a6b346 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -24,3 +24,6 @@ services: sysctls: - net.ipv4.ip_forward=1 - net.ipv4.conf.all.src_valid_mark=1 + - net.ipv6.conf.all.disable_ipv6=0 + - net.ipv6.conf.all.forwarding=1 + - net.ipv6.conf.default.forwarding=1 diff --git a/src/app/components/Client/Address.vue b/src/app/components/Client/Address.vue deleted file mode 100644 index ca3e8dc4..00000000 --- a/src/app/components/Client/Address.vue +++ /dev/null @@ -1,60 +0,0 @@ - - - diff --git a/src/app/components/Client/Address4.vue b/src/app/components/Client/Address4.vue new file mode 100644 index 00000000..e4cdfde5 --- /dev/null +++ b/src/app/components/Client/Address4.vue @@ -0,0 +1,60 @@ + + + diff --git a/src/app/components/Client/Client.vue b/src/app/components/Client/Client.vue index 71bd6b4c..c1a9295e 100644 --- a/src/app/components/Client/Client.vue +++ b/src/app/components/Client/Client.vue @@ -12,7 +12,7 @@
- + =8'} + cidr-tools@11.0.2: + resolution: {integrity: sha512-OLeM9EOXybbhMsGGBNRLCMjn8e+wFOXARIShF/sZwmJLsxWywqfE0By4BMftT6BFWpbcETWpW7TfM2KGCtrZDg==} + engines: {node: '>=18'} + citty@0.1.6: resolution: {integrity: sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==} @@ -1699,6 +1703,10 @@ packages: resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} engines: {node: '>=12'} + clone-regexp@3.0.0: + resolution: {integrity: sha512-ujdnoq2Kxb8s3ItNBtnYeXdm07FcU0u8ARAT1lQ2YdMwQC+cdiXX8KoqMVuglztILivceTtp4ivqGSmEmhBUJw==} + engines: {node: '>=12'} + cluster-key-slot@1.1.2: resolution: {integrity: sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==} engines: {node: '>=0.10.0'} @@ -1784,6 +1792,10 @@ packages: resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} engines: {node: '>= 0.6'} + convert-hrtime@5.0.0: + resolution: {integrity: sha512-lOETlkIeYSJWcbbcvjRKGxVMXJR+8+OQb/mTPbA4ObPMytYIsUbuOE0Jzy60hjARYszq1id0j8KgVhC+WGZVTg==} + engines: {node: '>=12'} + convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} @@ -2338,6 +2350,10 @@ packages: function-bind@1.1.2: resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + function-timeout@0.1.1: + resolution: {integrity: sha512-0NVVC0TaP7dSTvn1yMiy6d6Q8gifzbvQafO46RtLG/kHJUBNd+pVRGOBoK44wNBvtSPUJRfdVvkFdD3p0xvyZg==} + engines: {node: '>=14.16'} + gauge@3.0.2: resolution: {integrity: sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==} engines: {node: '>=10'} @@ -2555,8 +2571,13 @@ packages: resolution: {integrity: sha512-2YZsvl7jopIa1gaePkeMtd9rAcSjOOjPtpcLlOeusyO+XH2SK5ZcT+UCrElPP+WVIInh2TzeI4XW9ENaSLVVHA==} engines: {node: '>=12.22.0'} - ip@2.0.1: - resolution: {integrity: sha512-lJUL9imLTNi1ZfXT+DU6rBBdbiKGBuay9B6xGSPVjUeQwaH1RIGqef8RZkUtHioLmSNpPR5M4HVKJGm1j8FWVQ==} + ip-bigint@8.2.0: + resolution: {integrity: sha512-46EAEKzGNxojH5JaGEeCix49tL4h1W8ia5mhogZ68HroVAfyLj1E+SFFid4GuyK0mdIKjwcAITLqwg1wlkx2iQ==} + engines: {node: '>=18'} + + ip-regex@5.0.0: + resolution: {integrity: sha512-fOCG6lhoKKakwv+C6KdsOnGvgXnmgfmp0myi3bcNwj3qfwPAxRKWEuFhvEFF7ceYIz6+1jRZ+yguLFAmUNPEfw==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} iron-webcrypto@1.2.1: resolution: {integrity: sha512-feOM6FaSr6rEABp/eDfVseKyTMDt+KGpeB35SkVn9Tyn0CqvVsY3EwI0v5i8nMHyJnzCIQf7nsy3p41TPkJZhg==} @@ -2614,6 +2635,10 @@ packages: resolution: {integrity: sha512-K55T22lfpQ63N4KEN57jZUAaAYqYHEe8veb/TycJRk9DdSCLLcovXz/mL6mOnhQaZsQGwPhuFopdQIlqGSEjiQ==} engines: {node: '>=18'} + is-ip@5.0.1: + resolution: {integrity: sha512-FCsGHdlrOnZQcp0+XT5a+pYowf33itBalCl+7ovNXC/7o5BhIpG14M3OrpPPdBSIQJCm+0M5+9mO7S9VVTTCFw==} + engines: {node: '>=14.16'} + is-module@1.0.0: resolution: {integrity: sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==} @@ -2632,6 +2657,10 @@ packages: is-reference@1.2.1: resolution: {integrity: sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==} + is-regexp@3.1.0: + resolution: {integrity: sha512-rbku49cWloU5bSMI+zaRaXdQHXnthP6DZ/vLnfdSKyL4zUzuWnomtOEiZZOd+ioQ+avFo/qau3KPTc7Fjy1uPA==} + engines: {node: '>=12'} + is-ssh@1.4.0: resolution: {integrity: sha512-x7+VxdxOdlV3CYpjvRLBv5Lo9OJerlYanjwFrPR9fuGPjCiNiCzFgAWpiLAohSbsnH4ZAys3SBh+hq5rJosxUQ==} @@ -3840,6 +3869,10 @@ packages: engines: {node: '>=16 || 14 >=14.17'} hasBin: true + super-regex@0.2.0: + resolution: {integrity: sha512-WZzIx3rC1CvbMDloLsVw0lkZVKJWbrkJ0k1ghKFmcnPrW1+jWbgTkTEWVtD9lMdmI4jZEz40+naBxl1dCUhXXw==} + engines: {node: '>=14.16'} + superjson@2.2.1: resolution: {integrity: sha512-8iGv75BYOa0xRJHK5vRLEjE2H/i4lulTjzpUXic3Eg8akftYjkmQDa8JARQ42rlczXyFR3IeRoeFCc7RxHsYZA==} engines: {node: '>=16'} @@ -3948,6 +3981,10 @@ packages: thenify@3.3.1: resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} + time-span@5.1.0: + resolution: {integrity: sha512-75voc/9G4rDIJleOo4jPvN4/YC4GRZrY8yy1uU4lwrB3XEQbWve8zXoO5No4eFrGcTAMYyoY67p8jRQdtA1HbA==} + engines: {node: '>=12'} + timeago.js@4.0.2: resolution: {integrity: sha512-a7wPxPdVlQL7lqvitHGGRsofhdwtkoSXPGATFuSOA2i1ZNQEPLrGnj68vOp2sOJTCFAQVXPeNMX/GctBaO9L2w==} @@ -5631,10 +5668,6 @@ snapshots: dependencies: '@types/node': 22.5.2 - '@types/ip@1.1.3': - dependencies: - '@types/node': 22.5.2 - '@types/json-schema@7.0.15': {} '@types/ms@0.7.34': {} @@ -6211,6 +6244,10 @@ snapshots: ci-info@4.0.0: {} + cidr-tools@11.0.2: + dependencies: + ip-bigint: 8.2.0 + citty@0.1.6: dependencies: consola: 3.2.3 @@ -6239,6 +6276,10 @@ snapshots: strip-ansi: 6.0.1 wrap-ansi: 7.0.0 + clone-regexp@3.0.0: + dependencies: + is-regexp: 3.1.0 + cluster-key-slot@1.1.2: {} co@4.6.0: {} @@ -6299,6 +6340,8 @@ snapshots: content-type@1.0.5: {} + convert-hrtime@5.0.0: {} + convert-source-map@2.0.0: {} cookie-es@1.2.2: {} @@ -6948,6 +6991,8 @@ snapshots: function-bind@1.1.2: {} + function-timeout@0.1.1: {} + gauge@3.0.2: dependencies: aproba: 2.0.0 @@ -7187,7 +7232,9 @@ snapshots: transitivePeerDependencies: - supports-color - ip@2.0.1: {} + ip-bigint@8.2.0: {} + + ip-regex@5.0.0: {} iron-webcrypto@1.2.1: {} @@ -7232,6 +7279,11 @@ snapshots: global-directory: 4.0.1 is-path-inside: 4.0.0 + is-ip@5.0.1: + dependencies: + ip-regex: 5.0.0 + super-regex: 0.2.0 + is-module@1.0.0: {} is-number@7.0.0: {} @@ -7244,6 +7296,8 @@ snapshots: dependencies: '@types/estree': 1.0.5 + is-regexp@3.1.0: {} + is-ssh@1.4.0: dependencies: protocols: 2.0.1 @@ -8593,6 +8647,12 @@ snapshots: pirates: 4.0.6 ts-interface-checker: 0.1.13 + super-regex@0.2.0: + dependencies: + clone-regexp: 3.0.0 + function-timeout: 0.1.1 + time-span: 5.1.0 + superjson@2.2.1: dependencies: copy-anything: 3.0.5 @@ -8738,6 +8798,10 @@ snapshots: dependencies: any-promise: 1.3.0 + time-span@5.1.0: + dependencies: + convert-hrtime: 5.0.0 + timeago.js@4.0.2: {} tiny-invariant@1.3.3: {} diff --git a/src/server/api/release.get.ts b/src/server/api/release.get.ts index ebca8838..a7c3f02f 100644 --- a/src/server/api/release.get.ts +++ b/src/server/api/release.get.ts @@ -1,8 +1,7 @@ export default defineEventHandler(async () => { - const system = await Database.getSystem(); const latestRelease = await fetchLatestRelease(); return { - currentRelease: system.release, + currentRelease: RELEASE, latestRelease: latestRelease, }; }); diff --git a/src/server/api/session.post.ts b/src/server/api/session.post.ts index c4a9b7e3..0eb77690 100644 --- a/src/server/api/session.post.ts +++ b/src/server/api/session.post.ts @@ -29,7 +29,7 @@ export default defineEventHandler(async (event) => { if (remember) { conf.cookie = { ...(system.sessionConfig.cookie ?? {}), - maxAge: system.cookieMaxAge * 60, + maxAge: system.sessionTimeout, }; } diff --git a/src/server/api/wireguard/client/[clientId]/address.put.ts b/src/server/api/wireguard/client/[clientId]/address4.put.ts similarity index 54% rename from src/server/api/wireguard/client/[clientId]/address.put.ts rename to src/server/api/wireguard/client/[clientId]/address4.put.ts index eb834521..b646d4fe 100644 --- a/src/server/api/wireguard/client/[clientId]/address.put.ts +++ b/src/server/api/wireguard/client/[clientId]/address4.put.ts @@ -3,7 +3,10 @@ export default defineEventHandler(async (event) => { event, validateZod(clientIdType) ); - const { address } = await readValidatedBody(event, validateZod(addressType)); - await WireGuard.updateClientAddress({ clientId, address }); + const { address4 } = await readValidatedBody( + event, + validateZod(address4Type) + ); + await WireGuard.updateClientAddress({ clientId, address4 }); return { success: true }; }); diff --git a/src/server/utils/WireGuard.ts b/src/server/utils/WireGuard.ts index 85f6462f..99093c0d 100644 --- a/src/server/utils/WireGuard.ts +++ b/src/server/utils/WireGuard.ts @@ -6,7 +6,9 @@ import QRCode from 'qrcode'; import CRC32 from 'crc-32'; import type { NewClient } from '~~/services/database/repositories/client'; -import ip from 'ip'; +import { parseCidr } from 'cidr-tools'; +import { stringifyIp } from 'ip-bigint'; +import { isIPv4 } from 'is-ip'; const DEBUG = debug('WireGuard'); @@ -19,9 +21,8 @@ class WireGuard { async #saveWireguardConfig() { const system = await Database.getSystem(); const clients = await Database.getClients(); - const cidrBlock = ip.cidrSubnet( - system.userConfig.addressRange - ).subnetMaskLength; + const cidr4Block = parseCidr(system.userConfig.address4Range).prefix; + const cidr6Block = parseCidr(system.userConfig.address6Range).prefix; let result = ` # Note: Do not edit this file directly. # Your changes will be overwritten! @@ -29,7 +30,7 @@ class WireGuard { # Server [Interface] PrivateKey = ${system.interface.privateKey} -Address = ${system.interface.address}/${cidrBlock} +Address = ${system.interface.address4}/${cidr4Block}, ${system.interface.address6}/${cidr6Block} ListenPort = ${system.wgPort} PreUp = ${system.iptables.PreUp} PostUp = ${system.iptables.PostUp} @@ -46,7 +47,7 @@ PostDown = ${system.iptables.PostDown} [Peer] PublicKey = ${client.publicKey} PresharedKey = ${client.preSharedKey} -AllowedIPs = ${client.address}/32`; +AllowedIPs = ${client.address4}/32, ${client.address6}/128`; } DEBUG('Config saving...'); @@ -68,7 +69,8 @@ AllowedIPs = ${client.address}/32`; id: clientId, name: client.name, enabled: client.enabled, - address: client.address, + address4: client.address4, + address6: client.address6, publicKey: client.publicKey, createdAt: new Date(client.createdAt), updatedAt: new Date(client.updatedAt), @@ -134,18 +136,20 @@ AllowedIPs = ${client.address}/32`; async getClientConfiguration({ clientId }: { clientId: string }) { const system = await Database.getSystem(); const client = await this.getClient({ clientId }); + const cidr4Block = parseCidr(system.userConfig.address4Range).prefix; + const cidr6Block = parseCidr(system.userConfig.address6Range).prefix; return ` [Interface] PrivateKey = ${client.privateKey} -Address = ${client.address} -DNS = ${system.userConfig.defaultDns.join(',')} +Address = ${client.address4}/${cidr4Block}, ${client.address6}/${cidr6Block} +DNS = ${system.userConfig.defaultDns.join(', ')} MTU = ${system.userConfig.mtu} [Peer] PublicKey = ${system.interface.publicKey} PresharedKey = ${client.preSharedKey} -AllowedIPs = ${client.allowedIPs} +AllowedIPs = ${client.allowedIPs.join(', ')} PersistentKeepalive = ${client.persistentKeepalive} Endpoint = ${system.wgHost}:${system.wgConfigPort}`; } @@ -165,10 +169,6 @@ Endpoint = ${system.wgHost}:${system.wgConfigPort}`; name: string; expireDate: string | null; }) { - if (!name) { - throw new Error('Missing: Name'); - } - const system = await Database.getSystem(); const clients = await Database.getClients(); @@ -179,26 +179,48 @@ Endpoint = ${system.wgHost}:${system.wgConfigPort}`; const preSharedKey = await exec('wg genpsk'); // Calculate next IP - const cidr = ip.cidrSubnet(system.userConfig.addressRange); - let address; - for ( - let i = ip.toLong(cidr.firstAddress) + 1; - i <= ip.toLong(cidr.lastAddress) - 1; - i++ - ) { - const currentIp = ip.fromLong(i); + const cidr4 = parseCidr(system.userConfig.address4Range); + let address4; + for (let i = cidr4.start + 2n; i <= cidr4.end - 1n; i++) { + const currentIp4 = stringifyIp({ number: i, version: 4 }); + const client = Object.values(clients).find((client) => { + return client.address4 === currentIp4; + }); + + if (!client) { + address4 = currentIp4; + break; + } + } + + if (!address4) { + throw createError({ + statusCode: 409, + statusMessage: 'Maximum number of clients reached.', + data: { cause: 'IPv4 Address Pool exhausted' }, + }); + } + + const cidr6 = parseCidr(system.userConfig.address6Range); + let address6; + for (let i = cidr6.start + 2n; i <= cidr6.end - 1n; i++) { + const currentIp6 = stringifyIp({ number: i, version: 6 }); const client = Object.values(clients).find((client) => { - return client.address === currentIp; + return client.address6 === currentIp6; }); if (!client) { - address = currentIp; + address6 = currentIp6; break; } } - if (!address) { - throw new Error('Maximum number of clients reached.'); + if (!address6) { + throw createError({ + statusCode: 409, + statusMessage: 'Maximum number of clients reached.', + data: { cause: 'IPv6 Address Pool exhausted' }, + }); } // Create Client @@ -207,7 +229,8 @@ Endpoint = ${system.wgHost}:${system.wgConfigPort}`; const client: NewClient = { id, name, - address, + address4, + address6, privateKey, publicKey, preSharedKey, @@ -281,19 +304,19 @@ Endpoint = ${system.wgHost}:${system.wgConfigPort}`; async updateClientAddress({ clientId, - address, + address4, }: { clientId: string; - address: string; + address4: string; }) { - if (!ip.isV4Format(address)) { + if (!isIPv4(address4)) { throw createError({ statusCode: 400, - statusMessage: `Invalid Address: ${address}`, + statusMessage: `Invalid Address: ${address4}`, }); } - await Database.updateClientAddress(clientId, address); + await Database.updateClientAddress4(clientId, address4); await this.saveConfig(); } @@ -433,9 +456,9 @@ Endpoint = ${system.wgHost}:${system.wgConfigPort}`; if (client.endpoint !== null) { wireguardConnectedPeersCount++; } - wireguardSentBytes += `wireguard_sent_bytes{interface="wg0",enabled="${client.enabled}",address="${client.address}",name="${client.name}"} ${Number(client.transferTx)}\n`; - wireguardReceivedBytes += `wireguard_received_bytes{interface="wg0",enabled="${client.enabled}",address="${client.address}",name="${client.name}"} ${Number(client.transferRx)}\n`; - wireguardLatestHandshakeSeconds += `wireguard_latest_handshake_seconds{interface="wg0",enabled="${client.enabled}",address="${client.address}",name="${client.name}"} ${client.latestHandshakeAt ? (new Date().getTime() - new Date(client.latestHandshakeAt).getTime()) / 1000 : 0}\n`; + wireguardSentBytes += `wireguard_sent_bytes{interface="wg0",enabled="${client.enabled}",address4="${client.address4}",address6="${client.address6}",name="${client.name}"} ${Number(client.transferTx)}\n`; + wireguardReceivedBytes += `wireguard_received_bytes{interface="wg0",enabled="${client.enabled}",address4="${client.address4}",address6="${client.address6}",name="${client.name}"} ${Number(client.transferRx)}\n`; + wireguardLatestHandshakeSeconds += `wireguard_latest_handshake_seconds{interface="wg0",enabled="${client.enabled}",address4="${client.address4}",address6="${client.address6}",name="${client.name}"} ${client.latestHandshakeAt ? (new Date().getTime() - new Date(client.latestHandshakeAt).getTime()) / 1000 : 0}\n`; } let returnText = '# HELP wg-easy and wireguard metrics\n'; diff --git a/src/server/utils/config.ts b/src/server/utils/config.ts index f9fba02f..31498076 100644 --- a/src/server/utils/config.ts +++ b/src/server/utils/config.ts @@ -1,5 +1,8 @@ import debug from 'debug'; +import packageJson from '@@/package.json'; export const WG_PATH = process.env.WG_PATH || '/etc/wireguard/'; +export const RELEASE = packageJson.release.version; + export const SERVER_DEBUG = debug('Server'); diff --git a/src/server/utils/types.ts b/src/server/utils/types.ts index c0f54988..a5acbbcc 100644 --- a/src/server/utils/types.ts +++ b/src/server/utils/types.ts @@ -13,8 +13,8 @@ const id = z .uuid('Client ID must be a valid UUID') .pipe(safeStringRefine); -const address = z - .string({ message: 'Address must be a valid string' }) +const address4 = z + .string({ message: 'IPv4 Address must be a valid string' }) .pipe(safeStringRefine); const name = z @@ -54,9 +54,9 @@ export const clientIdType = z.object( { message: "This shouldn't happen" } ); -export const addressType = z.object( +export const address4Type = z.object( { - address: address, + address4: address4, }, { message: 'Body must be a valid object' } ); diff --git a/src/services/database/lowdb.ts b/src/services/database/lowdb.ts index c4ff8ad1..1e33787b 100644 --- a/src/services/database/lowdb.ts +++ b/src/services/database/lowdb.ts @@ -102,6 +102,7 @@ export default class LowDB extends DatabaseProvider { id: crypto.randomUUID(), password: hashPassword(password), username, + name: 'Administrator', role: isUserEmpty ? 'ADMIN' : 'CLIENT', enabled: true, createdAt: now, @@ -175,17 +176,17 @@ export default class LowDB extends DatabaseProvider { }); } - async updateClientAddress(id: string, address: string) { - DEBUG('Update Client Address'); + async updateClientAddress4(id: string, address4: string) { + DEBUG('Update Client Address4'); await this.#db.update((data) => { if (data.clients[id]) { - data.clients[id].address = address; + data.clients[id].address4 = address4; } }); } async updateClientExpirationDate(id: string, expirationDate: string | null) { - DEBUG('Update Client Address'); + DEBUG('Update Client Expiration Date'); await this.#db.update((data) => { if (data.clients[id]) { data.clients[id].expiresAt = expirationDate; @@ -194,7 +195,7 @@ export default class LowDB extends DatabaseProvider { } async deleteOneTimeLink(id: string) { - DEBUG('Update Client Address'); + DEBUG('Delete Client One Time Link'); await this.#db.update((data) => { if (data.clients[id]) { if (data.clients[id].oneTimeLink) { @@ -208,7 +209,7 @@ export default class LowDB extends DatabaseProvider { } async createOneTimeLink(id: string, oneTimeLink: OneTimeLink) { - DEBUG('Update Client Address'); + DEBUG('Create Client One Time Link'); await this.#db.update((data) => { if (data.clients[id]) { data.clients[id].oneTimeLink = oneTimeLink; diff --git a/src/services/database/migrations/1.ts b/src/services/database/migrations/1.ts index 7f4f5aef..79da292b 100644 --- a/src/services/database/migrations/1.ts +++ b/src/services/database/migrations/1.ts @@ -1,33 +1,35 @@ import type { Low } from 'lowdb'; import type { Database } from '../repositories/database'; -import packageJson from '@@/package.json'; import { ChartType } from '../repositories/system'; -import ip from 'ip'; +import { parseCidr } from 'cidr-tools'; +import { stringifyIp } from 'ip-bigint'; export async function run1(db: Low) { const privateKey = await exec('wg genkey'); const publicKey = await exec(`echo ${privateKey} | wg pubkey`, { log: 'echo ***hidden*** | wg pubkey', }); - const addressRange = '10.8.0.0/24'; - const cidr = ip.cidrSubnet(addressRange); + const address4Range = '10.8.0.0/24'; + const address6Range = 'fdcc:ad94:bacf:61a4::cafe:0/112'; + const cidr4 = parseCidr(address4Range); + const cidr6 = parseCidr(address6Range); const database: Database = { migrations: [], system: { - // TODO: move to var, no need for database - release: packageJson.release.version, interface: { privateKey: privateKey, publicKey: publicKey, - address: cidr.firstAddress, + address4: stringifyIp({ number: cidr4.start + 1n, version: 4 }), + address6: stringifyIp({ number: cidr6.start + 1n, version: 6 }), }, sessionTimeout: 3600, // 1 hour lang: 'en', userConfig: { mtu: 1420, persistentKeepalive: 0, - addressRange: addressRange, - defaultDns: ['1.1.1.1'], + address4Range: address4Range, + address6Range: address6Range, + defaultDns: ['1.1.1.1', '2606:4700:4700::1111'], allowedIps: ['0.0.0.0/0', '::/0'], }, wgDevice: 'eth0', @@ -63,26 +65,35 @@ export async function run1(db: Low) { name: 'wg-easy', cookie: {}, }, - cookieMaxAge: 24 * 60, }, users: [], clients: {}, }; // TODO: use variables inside up/down script + // TODO: properly check if ipv6 support database.system.iptables.PostUp = ` -iptables -t nat -A POSTROUTING -s ${database.system.userConfig.addressRange} -o ${database.system.wgDevice} -j MASQUERADE; +iptables -t nat -A POSTROUTING -s ${database.system.userConfig.address4Range} -o ${database.system.wgDevice} -j MASQUERADE; iptables -A INPUT -p udp -m udp --dport ${database.system.wgPort} -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.wgDevice} -j MASQUERADE; +ip6tables -A INPUT -p udp -m udp --dport ${database.system.wgPort} -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.addressRange} -o ${database.system.wgDevice} -j MASQUERADE; +iptables -t nat -D POSTROUTING -s ${database.system.userConfig.address4Range} -o ${database.system.wgDevice} -j MASQUERADE; iptables -D INPUT -p udp -m udp --dport ${database.system.wgPort} -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.wgDevice} -j MASQUERADE; +ip6tables -D INPUT -p udp -m udp --dport ${database.system.wgPort} -j ACCEPT; +ip6tables -D FORWARD -i wg0 -j ACCEPT; +ip6tables -D FORWARD -o wg0 -j ACCEPT; ` .split('\n') .join(' '); diff --git a/src/services/database/repositories/client.ts b/src/services/database/repositories/client.ts index 8666f300..3d641c23 100644 --- a/src/services/database/repositories/client.ts +++ b/src/services/database/repositories/client.ts @@ -7,7 +7,8 @@ export type OneTimeLink = { export type Client = { id: string; name: string; - address: string; + address4: string; + address6: string; privateKey: string; publicKey: string; preSharedKey: string; @@ -37,7 +38,7 @@ export interface ClientRepository { deleteClient(id: string): Promise; toggleClient(id: string, enable: boolean): Promise; updateClientName(id: string, name: string): Promise; - updateClientAddress(id: string, address: string): Promise; + updateClientAddress4(id: string, address4: string): Promise; updateClientExpirationDate( id: string, expirationDate: string | null diff --git a/src/services/database/repositories/database.ts b/src/services/database/repositories/database.ts index 3c8c1e5f..2f90f15f 100644 --- a/src/services/database/repositories/database.ts +++ b/src/services/database/repositories/database.ts @@ -59,7 +59,7 @@ export abstract class DatabaseProvider abstract deleteClient(id: string): Promise; abstract toggleClient(id: string, enable: boolean): Promise; abstract updateClientName(id: string, name: string): Promise; - abstract updateClientAddress(id: string, address: string): Promise; + abstract updateClientAddress4(id: string, address4: string): Promise; abstract updateClientExpirationDate( id: string, expirationDate: string | null diff --git a/src/services/database/repositories/system.ts b/src/services/database/repositories/system.ts index 6ba9cfac..aba8129a 100644 --- a/src/services/database/repositories/system.ts +++ b/src/services/database/repositories/system.ts @@ -12,13 +12,15 @@ export type IpTables = { export type WGInterface = { privateKey: string; publicKey: string; - address: string; + address4: string; + address6: string; }; export type WGConfig = { mtu: number; persistentKeepalive: number; - addressRange: string; + address4Range: string; + address6Range: string; defaultDns: string[]; allowedIps: string[]; }; @@ -50,7 +52,6 @@ export type Feature = { export type System = { interface: WGInterface; - release: string; // maxAge sessionTimeout: number; lang: Lang; @@ -71,7 +72,6 @@ export type System = { prometheus: Prometheus; sessionConfig: SessionConfig; - cookieMaxAge: number; }; /** diff --git a/src/services/database/repositories/user.ts b/src/services/database/repositories/user.ts index 260b113a..6a9c1bb5 100644 --- a/src/services/database/repositories/user.ts +++ b/src/services/database/repositories/user.ts @@ -16,11 +16,7 @@ export type User = { role: ROLE; username: string; password: string; - name?: string; - address?: string; - privateKey?: string; - publicKey?: string; - preSharedKey?: string; + name: string; /** ISO String */ createdAt: string; /** ISO String */