mirror of
https://github.com/fosrl/pangolin.git
synced 2026-03-11 09:23:19 +00:00
Compare commits
1 Commits
msg-opt
...
dependabot
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
aca9d1e070 |
2
.github/workflows/cicd.yml
vendored
2
.github/workflows/cicd.yml
vendored
@@ -264,7 +264,7 @@ jobs:
|
|||||||
shell: bash
|
shell: bash
|
||||||
|
|
||||||
- name: Install Go
|
- name: Install Go
|
||||||
uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0
|
uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0
|
||||||
with:
|
with:
|
||||||
go-version: 1.24
|
go-version: 1.24
|
||||||
|
|
||||||
|
|||||||
@@ -1102,12 +1102,6 @@
|
|||||||
"actionGetUser": "Get User",
|
"actionGetUser": "Get User",
|
||||||
"actionGetOrgUser": "Get Organization User",
|
"actionGetOrgUser": "Get Organization User",
|
||||||
"actionListOrgDomains": "List Organization Domains",
|
"actionListOrgDomains": "List Organization Domains",
|
||||||
"actionGetDomain": "Get Domain",
|
|
||||||
"actionCreateOrgDomain": "Create Domain",
|
|
||||||
"actionUpdateOrgDomain": "Update Domain",
|
|
||||||
"actionDeleteOrgDomain": "Delete Domain",
|
|
||||||
"actionGetDNSRecords": "Get DNS Records",
|
|
||||||
"actionRestartOrgDomain": "Restart Domain",
|
|
||||||
"actionCreateSite": "Create Site",
|
"actionCreateSite": "Create Site",
|
||||||
"actionDeleteSite": "Delete Site",
|
"actionDeleteSite": "Delete Site",
|
||||||
"actionGetSite": "Get Site",
|
"actionGetSite": "Get Site",
|
||||||
|
|||||||
123
server/lib/ip.ts
123
server/lib/ip.ts
@@ -571,129 +571,6 @@ export function generateSubnetProxyTargets(
|
|||||||
return targets;
|
return targets;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type SubnetProxyTargetV2 = {
|
|
||||||
sourcePrefixes: string[]; // must be cidrs
|
|
||||||
destPrefix: string; // must be a cidr
|
|
||||||
disableIcmp?: boolean;
|
|
||||||
rewriteTo?: string; // must be a cidr
|
|
||||||
portRange?: {
|
|
||||||
min: number;
|
|
||||||
max: number;
|
|
||||||
protocol: "tcp" | "udp";
|
|
||||||
}[];
|
|
||||||
};
|
|
||||||
|
|
||||||
export function generateSubnetProxyTargetV2(
|
|
||||||
siteResource: SiteResource,
|
|
||||||
clients: {
|
|
||||||
clientId: number;
|
|
||||||
pubKey: string | null;
|
|
||||||
subnet: string | null;
|
|
||||||
}[]
|
|
||||||
): SubnetProxyTargetV2 | undefined {
|
|
||||||
if (clients.length === 0) {
|
|
||||||
logger.debug(
|
|
||||||
`No clients have access to site resource ${siteResource.siteResourceId}, skipping target generation.`
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let target: SubnetProxyTargetV2 | null = null;
|
|
||||||
|
|
||||||
const portRange = [
|
|
||||||
...parsePortRangeString(siteResource.tcpPortRangeString, "tcp"),
|
|
||||||
...parsePortRangeString(siteResource.udpPortRangeString, "udp")
|
|
||||||
];
|
|
||||||
const disableIcmp = siteResource.disableIcmp ?? false;
|
|
||||||
|
|
||||||
if (siteResource.mode == "host") {
|
|
||||||
let destination = siteResource.destination;
|
|
||||||
// check if this is a valid ip
|
|
||||||
const ipSchema = z.union([z.ipv4(), z.ipv6()]);
|
|
||||||
if (ipSchema.safeParse(destination).success) {
|
|
||||||
destination = `${destination}/32`;
|
|
||||||
|
|
||||||
target = {
|
|
||||||
sourcePrefixes: [],
|
|
||||||
destPrefix: destination,
|
|
||||||
portRange,
|
|
||||||
disableIcmp
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (siteResource.alias && siteResource.aliasAddress) {
|
|
||||||
// also push a match for the alias address
|
|
||||||
target = {
|
|
||||||
sourcePrefixes: [],
|
|
||||||
destPrefix: `${siteResource.aliasAddress}/32`,
|
|
||||||
rewriteTo: destination,
|
|
||||||
portRange,
|
|
||||||
disableIcmp
|
|
||||||
};
|
|
||||||
}
|
|
||||||
} else if (siteResource.mode == "cidr") {
|
|
||||||
target = {
|
|
||||||
sourcePrefixes: [],
|
|
||||||
destPrefix: siteResource.destination,
|
|
||||||
portRange,
|
|
||||||
disableIcmp
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!target) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const clientSite of clients) {
|
|
||||||
if (!clientSite.subnet) {
|
|
||||||
logger.debug(
|
|
||||||
`Client ${clientSite.clientId} has no subnet, skipping for site resource ${siteResource.siteResourceId}.`
|
|
||||||
);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const clientPrefix = `${clientSite.subnet.split("/")[0]}/32`;
|
|
||||||
|
|
||||||
// add client prefix to source prefixes
|
|
||||||
target.sourcePrefixes.push(clientPrefix);
|
|
||||||
}
|
|
||||||
|
|
||||||
// print a nice representation of the targets
|
|
||||||
// logger.debug(
|
|
||||||
// `Generated subnet proxy targets for: ${JSON.stringify(targets, null, 2)}`
|
|
||||||
// );
|
|
||||||
|
|
||||||
return target;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts a SubnetProxyTargetV2 to an array of SubnetProxyTarget (v1)
|
|
||||||
* by expanding each source prefix into its own target entry.
|
|
||||||
* @param targetV2 - The v2 target to convert
|
|
||||||
* @returns Array of v1 SubnetProxyTarget objects
|
|
||||||
*/
|
|
||||||
export function convertSubnetProxyTargetsV2ToV1(
|
|
||||||
targetsV2: SubnetProxyTargetV2[]
|
|
||||||
): SubnetProxyTarget[] {
|
|
||||||
return targetsV2.flatMap((targetV2) =>
|
|
||||||
targetV2.sourcePrefixes.map((sourcePrefix) => ({
|
|
||||||
sourcePrefix,
|
|
||||||
destPrefix: targetV2.destPrefix,
|
|
||||||
...(targetV2.disableIcmp !== undefined && {
|
|
||||||
disableIcmp: targetV2.disableIcmp
|
|
||||||
}),
|
|
||||||
...(targetV2.rewriteTo !== undefined && {
|
|
||||||
rewriteTo: targetV2.rewriteTo
|
|
||||||
}),
|
|
||||||
...(targetV2.portRange !== undefined && {
|
|
||||||
portRange: targetV2.portRange
|
|
||||||
})
|
|
||||||
}))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Custom schema for validating port range strings
|
// Custom schema for validating port range strings
|
||||||
// Format: "80,443,8000-9000" or "*" for all ports, or empty string
|
// Format: "80,443,8000-9000" or "*" for all ports, or empty string
|
||||||
export const portRangeStringSchema = z
|
export const portRangeStringSchema = z
|
||||||
|
|||||||
@@ -302,8 +302,8 @@ export const configSchema = z
|
|||||||
.optional()
|
.optional()
|
||||||
.default({
|
.default({
|
||||||
block_size: 24,
|
block_size: 24,
|
||||||
subnet_group: "100.90.128.0/20",
|
subnet_group: "100.90.128.0/24",
|
||||||
utility_subnet_group: "100.96.128.0/20"
|
utility_subnet_group: "100.96.128.0/24"
|
||||||
}),
|
}),
|
||||||
rate_limits: z
|
rate_limits: z
|
||||||
.object({
|
.object({
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ import logger from "@server/logger";
|
|||||||
import {
|
import {
|
||||||
generateAliasConfig,
|
generateAliasConfig,
|
||||||
generateRemoteSubnets,
|
generateRemoteSubnets,
|
||||||
generateSubnetProxyTargetV2,
|
generateSubnetProxyTargets,
|
||||||
parseEndpoint,
|
parseEndpoint,
|
||||||
formatEndpoint
|
formatEndpoint
|
||||||
} from "@server/lib/ip";
|
} from "@server/lib/ip";
|
||||||
@@ -659,14 +659,17 @@ async function handleSubnetProxyTargetUpdates(
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (addedClients.length > 0) {
|
if (addedClients.length > 0) {
|
||||||
const targetToAdd = generateSubnetProxyTargetV2(
|
const targetsToAdd = generateSubnetProxyTargets(
|
||||||
siteResource,
|
siteResource,
|
||||||
addedClients
|
addedClients
|
||||||
);
|
);
|
||||||
|
|
||||||
if (targetToAdd) {
|
if (targetsToAdd.length > 0) {
|
||||||
|
logger.info(
|
||||||
|
`Adding ${targetsToAdd.length} subnet proxy targets for siteResource ${siteResource.siteResourceId}`
|
||||||
|
);
|
||||||
proxyJobs.push(
|
proxyJobs.push(
|
||||||
addSubnetProxyTargets(newt.newtId, [targetToAdd])
|
addSubnetProxyTargets(newt.newtId, targetsToAdd)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -692,14 +695,17 @@ async function handleSubnetProxyTargetUpdates(
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (removedClients.length > 0) {
|
if (removedClients.length > 0) {
|
||||||
const targetToRemove = generateSubnetProxyTargetV2(
|
const targetsToRemove = generateSubnetProxyTargets(
|
||||||
siteResource,
|
siteResource,
|
||||||
removedClients
|
removedClients
|
||||||
);
|
);
|
||||||
|
|
||||||
if (targetToRemove) {
|
if (targetsToRemove.length > 0) {
|
||||||
|
logger.info(
|
||||||
|
`Removing ${targetsToRemove.length} subnet proxy targets for siteResource ${siteResource.siteResourceId}`
|
||||||
|
);
|
||||||
proxyJobs.push(
|
proxyJobs.push(
|
||||||
removeSubnetProxyTargets(newt.newtId, [targetToRemove])
|
removeSubnetProxyTargets(newt.newtId, targetsToRemove)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1153,7 +1159,7 @@ async function handleMessagesForClientResources(
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (const resource of resources) {
|
for (const resource of resources) {
|
||||||
const target = generateSubnetProxyTargetV2(resource, [
|
const targets = generateSubnetProxyTargets(resource, [
|
||||||
{
|
{
|
||||||
clientId: client.clientId,
|
clientId: client.clientId,
|
||||||
pubKey: client.pubKey,
|
pubKey: client.pubKey,
|
||||||
@@ -1161,8 +1167,8 @@ async function handleMessagesForClientResources(
|
|||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if (target) {
|
if (targets.length > 0) {
|
||||||
proxyJobs.push(addSubnetProxyTargets(newt.newtId, [target]));
|
proxyJobs.push(addSubnetProxyTargets(newt.newtId, targets));
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -1224,7 +1230,7 @@ async function handleMessagesForClientResources(
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (const resource of resources) {
|
for (const resource of resources) {
|
||||||
const target = generateSubnetProxyTargetV2(resource, [
|
const targets = generateSubnetProxyTargets(resource, [
|
||||||
{
|
{
|
||||||
clientId: client.clientId,
|
clientId: client.clientId,
|
||||||
pubKey: client.pubKey,
|
pubKey: client.pubKey,
|
||||||
@@ -1232,9 +1238,9 @@ async function handleMessagesForClientResources(
|
|||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if (target) {
|
if (targets.length > 0) {
|
||||||
proxyJobs.push(
|
proxyJobs.push(
|
||||||
removeSubnetProxyTargets(newt.newtId, [target])
|
removeSubnetProxyTargets(newt.newtId, targets)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,4 +14,3 @@ export * from "./verifyApiKeyApiKeyAccess";
|
|||||||
export * from "./verifyApiKeyClientAccess";
|
export * from "./verifyApiKeyClientAccess";
|
||||||
export * from "./verifyApiKeySiteResourceAccess";
|
export * from "./verifyApiKeySiteResourceAccess";
|
||||||
export * from "./verifyApiKeyIdpAccess";
|
export * from "./verifyApiKeyIdpAccess";
|
||||||
export * from "./verifyApiKeyDomainAccess";
|
|
||||||
|
|||||||
@@ -1,90 +0,0 @@
|
|||||||
import { Request, Response, NextFunction } from "express";
|
|
||||||
import { db, domains, orgDomains, apiKeyOrg } from "@server/db";
|
|
||||||
import { and, eq } from "drizzle-orm";
|
|
||||||
import createHttpError from "http-errors";
|
|
||||||
import HttpCode from "@server/types/HttpCode";
|
|
||||||
|
|
||||||
export async function verifyApiKeyDomainAccess(
|
|
||||||
req: Request,
|
|
||||||
res: Response,
|
|
||||||
next: NextFunction
|
|
||||||
) {
|
|
||||||
try {
|
|
||||||
const apiKey = req.apiKey;
|
|
||||||
const domainId =
|
|
||||||
req.params.domainId || req.body.domainId || req.query.domainId;
|
|
||||||
const orgId = req.params.orgId;
|
|
||||||
|
|
||||||
if (!apiKey) {
|
|
||||||
return next(
|
|
||||||
createHttpError(HttpCode.UNAUTHORIZED, "Key not authenticated")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!domainId) {
|
|
||||||
return next(
|
|
||||||
createHttpError(HttpCode.BAD_REQUEST, "Invalid domain ID")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (apiKey.isRoot) {
|
|
||||||
// Root keys can access any domain in any org
|
|
||||||
return next();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify domain exists and belongs to the organization
|
|
||||||
const [domain] = await db
|
|
||||||
.select()
|
|
||||||
.from(domains)
|
|
||||||
.innerJoin(orgDomains, eq(orgDomains.domainId, domains.domainId))
|
|
||||||
.where(
|
|
||||||
and(
|
|
||||||
eq(orgDomains.domainId, domainId),
|
|
||||||
eq(orgDomains.orgId, orgId)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.limit(1);
|
|
||||||
|
|
||||||
if (!domain) {
|
|
||||||
return next(
|
|
||||||
createHttpError(
|
|
||||||
HttpCode.NOT_FOUND,
|
|
||||||
`Domain with ID ${domainId} not found in organization ${orgId}`
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify the API key has access to this organization
|
|
||||||
if (!req.apiKeyOrg) {
|
|
||||||
const apiKeyOrgRes = await db
|
|
||||||
.select()
|
|
||||||
.from(apiKeyOrg)
|
|
||||||
.where(
|
|
||||||
and(
|
|
||||||
eq(apiKeyOrg.apiKeyId, apiKey.apiKeyId),
|
|
||||||
eq(apiKeyOrg.orgId, orgId)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.limit(1);
|
|
||||||
req.apiKeyOrg = apiKeyOrgRes[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!req.apiKeyOrg) {
|
|
||||||
return next(
|
|
||||||
createHttpError(
|
|
||||||
HttpCode.FORBIDDEN,
|
|
||||||
"Key does not have access to this organization"
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return next();
|
|
||||||
} catch (error) {
|
|
||||||
return next(
|
|
||||||
createHttpError(
|
|
||||||
HttpCode.INTERNAL_SERVER_ERROR,
|
|
||||||
"Error verifying domain access"
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,15 +1,8 @@
|
|||||||
import { sendToClient } from "#dynamic/routers/ws";
|
import { sendToClient } from "#dynamic/routers/ws";
|
||||||
import { S } from "@faker-js/faker/dist/airline-Dz1uGqgJ";
|
import { db, olms, Transaction } from "@server/db";
|
||||||
import { db, newts, olms, Transaction } from "@server/db";
|
import { Alias, SubnetProxyTarget } from "@server/lib/ip";
|
||||||
import {
|
|
||||||
Alias,
|
|
||||||
convertSubnetProxyTargetsV2ToV1,
|
|
||||||
SubnetProxyTarget,
|
|
||||||
SubnetProxyTargetV2
|
|
||||||
} from "@server/lib/ip";
|
|
||||||
import logger from "@server/logger";
|
import logger from "@server/logger";
|
||||||
import { eq } from "drizzle-orm";
|
import { eq } from "drizzle-orm";
|
||||||
import semver from "semver";
|
|
||||||
|
|
||||||
const BATCH_SIZE = 50;
|
const BATCH_SIZE = 50;
|
||||||
const BATCH_DELAY_MS = 50;
|
const BATCH_DELAY_MS = 50;
|
||||||
@@ -26,149 +19,57 @@ function chunkArray<T>(array: T[], size: number): T[][] {
|
|||||||
return chunks;
|
return chunks;
|
||||||
}
|
}
|
||||||
|
|
||||||
const NEWT_V2_TARGETS_VERSION = ">=1.11.0";
|
export async function addTargets(newtId: string, targets: SubnetProxyTarget[]) {
|
||||||
|
const batches = chunkArray(targets, BATCH_SIZE);
|
||||||
export async function convertTargetsIfNessicary(
|
|
||||||
newtId: string,
|
|
||||||
targets: SubnetProxyTarget[] | SubnetProxyTargetV2[]
|
|
||||||
) {
|
|
||||||
// get the newt
|
|
||||||
const [newt] = await db
|
|
||||||
.select()
|
|
||||||
.from(newts)
|
|
||||||
.where(eq(newts.newtId, newtId));
|
|
||||||
if (!newt) {
|
|
||||||
throw new Error(`No newt found for id: ${newtId}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// check the semver
|
|
||||||
if (
|
|
||||||
newt.version &&
|
|
||||||
!semver.satisfies(newt.version, NEWT_V2_TARGETS_VERSION)
|
|
||||||
) {
|
|
||||||
logger.debug(
|
|
||||||
`addTargets Newt version ${newt.version} does not support targets v2 falling back`
|
|
||||||
);
|
|
||||||
targets = convertSubnetProxyTargetsV2ToV1(
|
|
||||||
targets as SubnetProxyTargetV2[]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return targets;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function addTargets(
|
|
||||||
newtId: string,
|
|
||||||
targets: SubnetProxyTarget[] | SubnetProxyTargetV2[]
|
|
||||||
) {
|
|
||||||
targets = await convertTargetsIfNessicary(newtId, targets);
|
|
||||||
|
|
||||||
const batches = chunkArray<SubnetProxyTarget | SubnetProxyTargetV2>(
|
|
||||||
targets,
|
|
||||||
BATCH_SIZE
|
|
||||||
);
|
|
||||||
|
|
||||||
for (let i = 0; i < batches.length; i++) {
|
for (let i = 0; i < batches.length; i++) {
|
||||||
if (i > 0) {
|
if (i > 0) {
|
||||||
await sleep(BATCH_DELAY_MS);
|
await sleep(BATCH_DELAY_MS);
|
||||||
}
|
}
|
||||||
await sendToClient(
|
await sendToClient(newtId, {
|
||||||
newtId,
|
type: `newt/wg/targets/add`,
|
||||||
{
|
data: batches[i]
|
||||||
type: `newt/wg/targets/add`,
|
}, { incrementConfigVersion: true });
|
||||||
data: batches[i]
|
|
||||||
},
|
|
||||||
{ incrementConfigVersion: true }
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function removeTargets(
|
export async function removeTargets(
|
||||||
newtId: string,
|
newtId: string,
|
||||||
targets: SubnetProxyTarget[] | SubnetProxyTargetV2[]
|
targets: SubnetProxyTarget[]
|
||||||
) {
|
) {
|
||||||
targets = await convertTargetsIfNessicary(newtId, targets);
|
const batches = chunkArray(targets, BATCH_SIZE);
|
||||||
|
|
||||||
const batches = chunkArray<SubnetProxyTarget | SubnetProxyTargetV2>(
|
|
||||||
targets,
|
|
||||||
BATCH_SIZE
|
|
||||||
);
|
|
||||||
for (let i = 0; i < batches.length; i++) {
|
for (let i = 0; i < batches.length; i++) {
|
||||||
if (i > 0) {
|
if (i > 0) {
|
||||||
await sleep(BATCH_DELAY_MS);
|
await sleep(BATCH_DELAY_MS);
|
||||||
}
|
}
|
||||||
await sendToClient(
|
await sendToClient(newtId, {
|
||||||
newtId,
|
type: `newt/wg/targets/remove`,
|
||||||
{
|
data: batches[i]
|
||||||
type: `newt/wg/targets/remove`,
|
},{ incrementConfigVersion: true });
|
||||||
data: batches[i]
|
|
||||||
},
|
|
||||||
{ incrementConfigVersion: true }
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function updateTargets(
|
export async function updateTargets(
|
||||||
newtId: string,
|
newtId: string,
|
||||||
targets: {
|
targets: {
|
||||||
oldTargets: SubnetProxyTarget[] | SubnetProxyTargetV2[];
|
oldTargets: SubnetProxyTarget[];
|
||||||
newTargets: SubnetProxyTarget[] | SubnetProxyTargetV2[];
|
newTargets: SubnetProxyTarget[];
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
// get the newt
|
const oldBatches = chunkArray(targets.oldTargets, BATCH_SIZE);
|
||||||
const [newt] = await db
|
const newBatches = chunkArray(targets.newTargets, BATCH_SIZE);
|
||||||
.select()
|
|
||||||
.from(newts)
|
|
||||||
.where(eq(newts.newtId, newtId));
|
|
||||||
if (!newt) {
|
|
||||||
logger.error(`addTargetsL No newt found for id: ${newtId}`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// check the semver
|
|
||||||
if (
|
|
||||||
newt.version &&
|
|
||||||
!semver.satisfies(newt.version, NEWT_V2_TARGETS_VERSION)
|
|
||||||
) {
|
|
||||||
logger.debug(
|
|
||||||
`addTargets Newt version ${newt.version} does not support targets v2 falling back`
|
|
||||||
);
|
|
||||||
targets = {
|
|
||||||
oldTargets: convertSubnetProxyTargetsV2ToV1(
|
|
||||||
targets.oldTargets as SubnetProxyTargetV2[]
|
|
||||||
),
|
|
||||||
newTargets: convertSubnetProxyTargetsV2ToV1(
|
|
||||||
targets.newTargets as SubnetProxyTargetV2[]
|
|
||||||
)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const oldBatches = chunkArray<SubnetProxyTarget | SubnetProxyTargetV2>(
|
|
||||||
targets.oldTargets,
|
|
||||||
BATCH_SIZE
|
|
||||||
);
|
|
||||||
const newBatches = chunkArray<SubnetProxyTarget | SubnetProxyTargetV2>(
|
|
||||||
targets.newTargets,
|
|
||||||
BATCH_SIZE
|
|
||||||
);
|
|
||||||
|
|
||||||
const maxBatches = Math.max(oldBatches.length, newBatches.length);
|
const maxBatches = Math.max(oldBatches.length, newBatches.length);
|
||||||
|
|
||||||
for (let i = 0; i < maxBatches; i++) {
|
for (let i = 0; i < maxBatches; i++) {
|
||||||
if (i > 0) {
|
if (i > 0) {
|
||||||
await sleep(BATCH_DELAY_MS);
|
await sleep(BATCH_DELAY_MS);
|
||||||
}
|
}
|
||||||
await sendToClient(
|
await sendToClient(newtId, {
|
||||||
newtId,
|
type: `newt/wg/targets/update`,
|
||||||
{
|
data: {
|
||||||
type: `newt/wg/targets/update`,
|
oldTargets: oldBatches[i] || [],
|
||||||
data: {
|
newTargets: newBatches[i] || []
|
||||||
oldTargets: oldBatches[i] || [],
|
}
|
||||||
newTargets: newBatches[i] || []
|
}, { incrementConfigVersion: true }).catch((error) => {
|
||||||
}
|
|
||||||
},
|
|
||||||
{ incrementConfigVersion: true }
|
|
||||||
).catch((error) => {
|
|
||||||
logger.warn(`Error sending message:`, error);
|
logger.warn(`Error sending message:`, error);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -193,18 +94,14 @@ export async function addPeerData(
|
|||||||
olmId = olm.olmId;
|
olmId = olm.olmId;
|
||||||
}
|
}
|
||||||
|
|
||||||
await sendToClient(
|
await sendToClient(olmId, {
|
||||||
olmId,
|
type: `olm/wg/peer/data/add`,
|
||||||
{
|
data: {
|
||||||
type: `olm/wg/peer/data/add`,
|
siteId: siteId,
|
||||||
data: {
|
remoteSubnets: remoteSubnets,
|
||||||
siteId: siteId,
|
aliases: aliases
|
||||||
remoteSubnets: remoteSubnets,
|
}
|
||||||
aliases: aliases
|
}, { incrementConfigVersion: true }).catch((error) => {
|
||||||
}
|
|
||||||
},
|
|
||||||
{ incrementConfigVersion: true }
|
|
||||||
).catch((error) => {
|
|
||||||
logger.warn(`Error sending message:`, error);
|
logger.warn(`Error sending message:`, error);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -228,18 +125,14 @@ export async function removePeerData(
|
|||||||
olmId = olm.olmId;
|
olmId = olm.olmId;
|
||||||
}
|
}
|
||||||
|
|
||||||
await sendToClient(
|
await sendToClient(olmId, {
|
||||||
olmId,
|
type: `olm/wg/peer/data/remove`,
|
||||||
{
|
data: {
|
||||||
type: `olm/wg/peer/data/remove`,
|
siteId: siteId,
|
||||||
data: {
|
remoteSubnets: remoteSubnets,
|
||||||
siteId: siteId,
|
aliases: aliases
|
||||||
remoteSubnets: remoteSubnets,
|
}
|
||||||
aliases: aliases
|
}, { incrementConfigVersion: true }).catch((error) => {
|
||||||
}
|
|
||||||
},
|
|
||||||
{ incrementConfigVersion: true }
|
|
||||||
).catch((error) => {
|
|
||||||
logger.warn(`Error sending message:`, error);
|
logger.warn(`Error sending message:`, error);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -273,18 +166,14 @@ export async function updatePeerData(
|
|||||||
olmId = olm.olmId;
|
olmId = olm.olmId;
|
||||||
}
|
}
|
||||||
|
|
||||||
await sendToClient(
|
await sendToClient(olmId, {
|
||||||
olmId,
|
type: `olm/wg/peer/data/update`,
|
||||||
{
|
data: {
|
||||||
type: `olm/wg/peer/data/update`,
|
siteId: siteId,
|
||||||
data: {
|
...remoteSubnets,
|
||||||
siteId: siteId,
|
...aliases
|
||||||
...remoteSubnets,
|
}
|
||||||
...aliases
|
}, { incrementConfigVersion: true }).catch((error) => {
|
||||||
}
|
|
||||||
},
|
|
||||||
{ incrementConfigVersion: true }
|
|
||||||
).catch((error) => {
|
|
||||||
logger.warn(`Error sending message:`, error);
|
logger.warn(`Error sending message:`, error);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,8 +27,7 @@ import {
|
|||||||
verifyApiKeyClientAccess,
|
verifyApiKeyClientAccess,
|
||||||
verifyApiKeySiteResourceAccess,
|
verifyApiKeySiteResourceAccess,
|
||||||
verifyApiKeySetResourceClients,
|
verifyApiKeySetResourceClients,
|
||||||
verifyLimits,
|
verifyLimits
|
||||||
verifyApiKeyDomainAccess
|
|
||||||
} from "@server/middlewares";
|
} from "@server/middlewares";
|
||||||
import HttpCode from "@server/types/HttpCode";
|
import HttpCode from "@server/types/HttpCode";
|
||||||
import { Router } from "express";
|
import { Router } from "express";
|
||||||
@@ -348,56 +347,6 @@ authenticated.get(
|
|||||||
domain.listDomains
|
domain.listDomains
|
||||||
);
|
);
|
||||||
|
|
||||||
authenticated.get(
|
|
||||||
"/org/:orgId/domain/:domainId",
|
|
||||||
verifyApiKeyOrgAccess,
|
|
||||||
verifyApiKeyDomainAccess,
|
|
||||||
verifyApiKeyHasAction(ActionsEnum.getDomain),
|
|
||||||
domain.getDomain
|
|
||||||
);
|
|
||||||
|
|
||||||
authenticated.put(
|
|
||||||
"/org/:orgId/domain",
|
|
||||||
verifyApiKeyOrgAccess,
|
|
||||||
verifyApiKeyHasAction(ActionsEnum.createOrgDomain),
|
|
||||||
logActionAudit(ActionsEnum.createOrgDomain),
|
|
||||||
domain.createOrgDomain
|
|
||||||
);
|
|
||||||
|
|
||||||
authenticated.patch(
|
|
||||||
"/org/:orgId/domain/:domainId",
|
|
||||||
verifyApiKeyOrgAccess,
|
|
||||||
verifyApiKeyDomainAccess,
|
|
||||||
verifyApiKeyHasAction(ActionsEnum.updateOrgDomain),
|
|
||||||
domain.updateOrgDomain
|
|
||||||
);
|
|
||||||
|
|
||||||
authenticated.delete(
|
|
||||||
"/org/:orgId/domain/:domainId",
|
|
||||||
verifyApiKeyOrgAccess,
|
|
||||||
verifyApiKeyDomainAccess,
|
|
||||||
verifyApiKeyHasAction(ActionsEnum.deleteOrgDomain),
|
|
||||||
logActionAudit(ActionsEnum.deleteOrgDomain),
|
|
||||||
domain.deleteAccountDomain
|
|
||||||
);
|
|
||||||
|
|
||||||
authenticated.get(
|
|
||||||
"/org/:orgId/domain/:domainId/dns-records",
|
|
||||||
verifyApiKeyOrgAccess,
|
|
||||||
verifyApiKeyDomainAccess,
|
|
||||||
verifyApiKeyHasAction(ActionsEnum.getDNSRecords),
|
|
||||||
domain.getDNSRecords
|
|
||||||
);
|
|
||||||
|
|
||||||
authenticated.post(
|
|
||||||
"/org/:orgId/domain/:domainId/restart",
|
|
||||||
verifyApiKeyOrgAccess,
|
|
||||||
verifyApiKeyDomainAccess,
|
|
||||||
verifyApiKeyHasAction(ActionsEnum.restartOrgDomain),
|
|
||||||
logActionAudit(ActionsEnum.restartOrgDomain),
|
|
||||||
domain.restartOrgDomain
|
|
||||||
);
|
|
||||||
|
|
||||||
authenticated.get(
|
authenticated.get(
|
||||||
"/org/:orgId/invitations",
|
"/org/:orgId/invitations",
|
||||||
verifyApiKeyOrgAccess,
|
verifyApiKeyOrgAccess,
|
||||||
|
|||||||
@@ -1,23 +1,9 @@
|
|||||||
import {
|
import { clients, clientSiteResourcesAssociationsCache, clientSitesAssociationsCache, db, ExitNode, resources, Site, siteResources, targetHealthCheck, targets } from "@server/db";
|
||||||
clients,
|
|
||||||
clientSiteResourcesAssociationsCache,
|
|
||||||
clientSitesAssociationsCache,
|
|
||||||
db,
|
|
||||||
ExitNode,
|
|
||||||
resources,
|
|
||||||
Site,
|
|
||||||
siteResources,
|
|
||||||
targetHealthCheck,
|
|
||||||
targets
|
|
||||||
} from "@server/db";
|
|
||||||
import logger from "@server/logger";
|
import logger from "@server/logger";
|
||||||
import { initPeerAddHandshake, updatePeer } from "../olm/peers";
|
import { initPeerAddHandshake, updatePeer } from "../olm/peers";
|
||||||
import { eq, and } from "drizzle-orm";
|
import { eq, and } from "drizzle-orm";
|
||||||
import config from "@server/lib/config";
|
import config from "@server/lib/config";
|
||||||
import {
|
import { generateSubnetProxyTargets, SubnetProxyTarget } from "@server/lib/ip";
|
||||||
generateSubnetProxyTargetV2,
|
|
||||||
SubnetProxyTargetV2
|
|
||||||
} from "@server/lib/ip";
|
|
||||||
|
|
||||||
export async function buildClientConfigurationForNewtClient(
|
export async function buildClientConfigurationForNewtClient(
|
||||||
site: Site,
|
site: Site,
|
||||||
@@ -140,7 +126,7 @@ export async function buildClientConfigurationForNewtClient(
|
|||||||
.from(siteResources)
|
.from(siteResources)
|
||||||
.where(eq(siteResources.siteId, siteId));
|
.where(eq(siteResources.siteId, siteId));
|
||||||
|
|
||||||
const targetsToSend: SubnetProxyTargetV2[] = [];
|
const targetsToSend: SubnetProxyTarget[] = [];
|
||||||
|
|
||||||
for (const resource of allSiteResources) {
|
for (const resource of allSiteResources) {
|
||||||
// Get clients associated with this specific resource
|
// Get clients associated with this specific resource
|
||||||
@@ -165,14 +151,12 @@ export async function buildClientConfigurationForNewtClient(
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
const resourceTarget = generateSubnetProxyTargetV2(
|
const resourceTargets = generateSubnetProxyTargets(
|
||||||
resource,
|
resource,
|
||||||
resourceClients
|
resourceClients
|
||||||
);
|
);
|
||||||
|
|
||||||
if (resourceTarget) {
|
targetsToSend.push(...resourceTargets);
|
||||||
targetsToSend.push(resourceTarget);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import { db, ExitNode, exitNodes, Newt, sites } from "@server/db";
|
|||||||
import { eq } from "drizzle-orm";
|
import { eq } from "drizzle-orm";
|
||||||
import { sendToExitNode } from "#dynamic/lib/exitNodes";
|
import { sendToExitNode } from "#dynamic/lib/exitNodes";
|
||||||
import { buildClientConfigurationForNewtClient } from "./buildConfiguration";
|
import { buildClientConfigurationForNewtClient } from "./buildConfiguration";
|
||||||
import { convertTargetsIfNessicary } from "../client/targets";
|
|
||||||
|
|
||||||
const inputSchema = z.object({
|
const inputSchema = z.object({
|
||||||
publicKey: z.string(),
|
publicKey: z.string(),
|
||||||
@@ -127,15 +126,13 @@ export const handleGetConfigMessage: MessageHandler = async (context) => {
|
|||||||
exitNode
|
exitNode
|
||||||
);
|
);
|
||||||
|
|
||||||
const targetsToSend = await convertTargetsIfNessicary(newt.newtId, targets);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
message: {
|
message: {
|
||||||
type: "newt/wg/receive-config",
|
type: "newt/wg/receive-config",
|
||||||
data: {
|
data: {
|
||||||
ipAddress: site.address,
|
ipAddress: site.address,
|
||||||
peers,
|
peers,
|
||||||
targets: targetsToSend
|
targets
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
broadcast: false,
|
broadcast: false,
|
||||||
|
|||||||
@@ -292,7 +292,7 @@ export async function createSite(
|
|||||||
if (type == "newt") {
|
if (type == "newt") {
|
||||||
[newSite] = await trx
|
[newSite] = await trx
|
||||||
.insert(sites)
|
.insert(sites)
|
||||||
.values({ // NOTE: NO SUBNET OR EXIT NODE ID PASSED IN HERE BECAUSE ITS NOW CHOSEN ON CONNECT
|
.values({
|
||||||
orgId,
|
orgId,
|
||||||
name,
|
name,
|
||||||
niceId,
|
niceId,
|
||||||
|
|||||||
@@ -88,7 +88,7 @@ const createSiteResourceSchema = z
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
message:
|
message:
|
||||||
"Destination must be a valid IPV4 address or valid domain AND alias is required"
|
"Destination must be a valid IP address or valid domain AND alias is required"
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.refine(
|
.refine(
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ import { updatePeerData, updateTargets } from "@server/routers/client/targets";
|
|||||||
import {
|
import {
|
||||||
generateAliasConfig,
|
generateAliasConfig,
|
||||||
generateRemoteSubnets,
|
generateRemoteSubnets,
|
||||||
generateSubnetProxyTargetV2,
|
generateSubnetProxyTargets,
|
||||||
isIpInCidr,
|
isIpInCidr,
|
||||||
portRangeStringSchema
|
portRangeStringSchema
|
||||||
} from "@server/lib/ip";
|
} from "@server/lib/ip";
|
||||||
@@ -608,18 +608,18 @@ export async function handleMessagingForUpdatedSiteResource(
|
|||||||
|
|
||||||
// Only update targets on newt if destination changed
|
// Only update targets on newt if destination changed
|
||||||
if (destinationChanged || portRangesChanged) {
|
if (destinationChanged || portRangesChanged) {
|
||||||
const oldTarget = generateSubnetProxyTargetV2(
|
const oldTargets = generateSubnetProxyTargets(
|
||||||
existingSiteResource,
|
existingSiteResource,
|
||||||
mergedAllClients
|
mergedAllClients
|
||||||
);
|
);
|
||||||
const newTarget = generateSubnetProxyTargetV2(
|
const newTargets = generateSubnetProxyTargets(
|
||||||
updatedSiteResource,
|
updatedSiteResource,
|
||||||
mergedAllClients
|
mergedAllClients
|
||||||
);
|
);
|
||||||
|
|
||||||
await updateTargets(newt.newtId, {
|
await updateTargets(newt.newtId, {
|
||||||
oldTargets: oldTarget ? [oldTarget] : [],
|
oldTargets: oldTargets,
|
||||||
newTargets: newTarget ? [newTarget] : []
|
newTargets: newTargets
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -69,16 +69,15 @@ export function LayoutMobileMenu({
|
|||||||
<SheetDescription className="sr-only">
|
<SheetDescription className="sr-only">
|
||||||
{t("navbarDescription")}
|
{t("navbarDescription")}
|
||||||
</SheetDescription>
|
</SheetDescription>
|
||||||
<div className="w-full border-b border-border">
|
<div className="flex-1 overflow-y-auto relative">
|
||||||
<div className="px-1 shrink-0">
|
<div className="px-1">
|
||||||
<OrgSelector
|
<OrgSelector
|
||||||
orgId={orgId}
|
orgId={orgId}
|
||||||
orgs={orgs}
|
orgs={orgs}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div className="w-full border-b border-border" />
|
||||||
<div className="flex-1 overflow-y-auto relative">
|
<div className="px-3 pt-3">
|
||||||
<div className="px-3">
|
|
||||||
{!isAdminPage &&
|
{!isAdminPage &&
|
||||||
user.serverAdmin && (
|
user.serverAdmin && (
|
||||||
<div className="mb-1">
|
<div className="mb-1">
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ function getActionsCategories(root: boolean) {
|
|||||||
[t("actionListInvitations")]: "listInvitations",
|
[t("actionListInvitations")]: "listInvitations",
|
||||||
[t("actionRemoveUser")]: "removeUser",
|
[t("actionRemoveUser")]: "removeUser",
|
||||||
[t("actionListUsers")]: "listUsers",
|
[t("actionListUsers")]: "listUsers",
|
||||||
|
[t("actionListOrgDomains")]: "listOrgDomains",
|
||||||
[t("updateOrgUser")]: "updateOrgUser",
|
[t("updateOrgUser")]: "updateOrgUser",
|
||||||
[t("createOrgUser")]: "createOrgUser",
|
[t("createOrgUser")]: "createOrgUser",
|
||||||
[t("actionApplyBlueprint")]: "applyBlueprint",
|
[t("actionApplyBlueprint")]: "applyBlueprint",
|
||||||
@@ -38,16 +39,6 @@ function getActionsCategories(root: boolean) {
|
|||||||
[t("actionGetBlueprint")]: "getBlueprint"
|
[t("actionGetBlueprint")]: "getBlueprint"
|
||||||
},
|
},
|
||||||
|
|
||||||
Domain: {
|
|
||||||
[t("actionListOrgDomains")]: "listOrgDomains",
|
|
||||||
[t("actionGetDomain")]: "getDomain",
|
|
||||||
[t("actionCreateOrgDomain")]: "createOrgDomain",
|
|
||||||
[t("actionUpdateOrgDomain")]: "updateOrgDomain",
|
|
||||||
[t("actionDeleteOrgDomain")]: "deleteOrgDomain",
|
|
||||||
[t("actionGetDNSRecords")]: "getDNSRecords",
|
|
||||||
[t("actionRestartOrgDomain")]: "restartOrgDomain"
|
|
||||||
},
|
|
||||||
|
|
||||||
Site: {
|
Site: {
|
||||||
[t("actionCreateSite")]: "createSite",
|
[t("actionCreateSite")]: "createSite",
|
||||||
[t("actionDeleteSite")]: "deleteSite",
|
[t("actionDeleteSite")]: "deleteSite",
|
||||||
|
|||||||
Reference in New Issue
Block a user