mirror of
https://github.com/fosrl/pangolin.git
synced 2026-03-06 18:56:39 +00:00
Update blueprints to support new clients
This commit is contained in:
@@ -14,6 +14,7 @@ import {
|
|||||||
import { BlueprintSource } from "@server/routers/blueprints/types";
|
import { BlueprintSource } from "@server/routers/blueprints/types";
|
||||||
import { stringify as stringifyYaml } from "yaml";
|
import { stringify as stringifyYaml } from "yaml";
|
||||||
import { faker } from "@faker-js/faker";
|
import { faker } from "@faker-js/faker";
|
||||||
|
import { handleMessagingForUpdatedSiteResource } from "@server/routers/siteResource";
|
||||||
|
|
||||||
type ApplyBlueprintArgs = {
|
type ApplyBlueprintArgs = {
|
||||||
orgId: string;
|
orgId: string;
|
||||||
@@ -57,22 +58,63 @@ export async function applyBlueprint({
|
|||||||
trx,
|
trx,
|
||||||
siteId
|
siteId
|
||||||
);
|
);
|
||||||
});
|
|
||||||
|
|
||||||
logger.debug(
|
logger.debug(
|
||||||
`Successfully updated proxy resources for org ${orgId}: ${JSON.stringify(proxyResourcesResults)}`
|
`Successfully updated proxy resources for org ${orgId}: ${JSON.stringify(proxyResourcesResults)}`
|
||||||
);
|
);
|
||||||
|
|
||||||
// We need to update the targets on the newts from the successfully updated information
|
// We need to update the targets on the newts from the successfully updated information
|
||||||
for (const result of proxyResourcesResults) {
|
for (const result of proxyResourcesResults) {
|
||||||
for (const target of result.targetsToUpdate) {
|
for (const target of result.targetsToUpdate) {
|
||||||
const [site] = await db
|
const [site] = await trx
|
||||||
|
.select()
|
||||||
|
.from(sites)
|
||||||
|
.innerJoin(newts, eq(sites.siteId, newts.siteId))
|
||||||
|
.where(
|
||||||
|
and(
|
||||||
|
eq(sites.siteId, target.siteId),
|
||||||
|
eq(sites.orgId, orgId),
|
||||||
|
eq(sites.type, "newt"),
|
||||||
|
isNotNull(sites.pubKey)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.limit(1);
|
||||||
|
|
||||||
|
if (site) {
|
||||||
|
logger.debug(
|
||||||
|
`Updating target ${target.targetId} on site ${site.sites.siteId}`
|
||||||
|
);
|
||||||
|
|
||||||
|
// see if you can find a matching target health check from the healthchecksToUpdate array
|
||||||
|
const matchingHealthcheck =
|
||||||
|
result.healthchecksToUpdate.find(
|
||||||
|
(hc) => hc.targetId === target.targetId
|
||||||
|
);
|
||||||
|
|
||||||
|
await addProxyTargets(
|
||||||
|
site.newt.newtId,
|
||||||
|
[target],
|
||||||
|
matchingHealthcheck ? [matchingHealthcheck] : [],
|
||||||
|
result.proxyResource.protocol,
|
||||||
|
result.proxyResource.proxyPort
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.debug(
|
||||||
|
`Successfully updated client resources for org ${orgId}: ${JSON.stringify(clientResourcesResults)}`
|
||||||
|
);
|
||||||
|
|
||||||
|
// We need to update the targets on the newts from the successfully updated information
|
||||||
|
for (const result of clientResourcesResults) {
|
||||||
|
const [site] = await trx
|
||||||
.select()
|
.select()
|
||||||
.from(sites)
|
.from(sites)
|
||||||
.innerJoin(newts, eq(sites.siteId, newts.siteId))
|
.innerJoin(newts, eq(sites.siteId, newts.siteId))
|
||||||
.where(
|
.where(
|
||||||
and(
|
and(
|
||||||
eq(sites.siteId, target.siteId),
|
eq(sites.siteId, result.newSiteResource.siteId),
|
||||||
eq(sites.orgId, orgId),
|
eq(sites.orgId, orgId),
|
||||||
eq(sites.type, "newt"),
|
eq(sites.type, "newt"),
|
||||||
isNotNull(sites.pubKey)
|
isNotNull(sites.pubKey)
|
||||||
@@ -80,60 +122,36 @@ export async function applyBlueprint({
|
|||||||
)
|
)
|
||||||
.limit(1);
|
.limit(1);
|
||||||
|
|
||||||
if (site) {
|
if (!site) {
|
||||||
logger.debug(
|
logger.debug(
|
||||||
`Updating target ${target.targetId} on site ${site.sites.siteId}`
|
`No newt site found for client resource ${result.newSiteResource.siteResourceId}, skipping target update`
|
||||||
);
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
// see if you can find a matching target health check from the healthchecksToUpdate array
|
logger.debug(
|
||||||
const matchingHealthcheck =
|
`Updating client resource ${result.newSiteResource.siteResourceId} on site ${site.sites.siteId}`
|
||||||
result.healthchecksToUpdate.find(
|
);
|
||||||
(hc) => hc.targetId === target.targetId
|
|
||||||
);
|
|
||||||
|
|
||||||
await addProxyTargets(
|
if (result.oldSiteResource) {
|
||||||
site.newt.newtId,
|
// this was an update
|
||||||
[target],
|
await handleMessagingForUpdatedSiteResource(
|
||||||
matchingHealthcheck ? [matchingHealthcheck] : [],
|
result.oldSiteResource,
|
||||||
result.proxyResource.protocol,
|
result.newSiteResource,
|
||||||
result.proxyResource.proxyPort
|
{ siteId: site.sites.siteId, orgId: site.sites.orgId },
|
||||||
|
trx
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// await addClientTargets(
|
||||||
|
// site.newt.newtId,
|
||||||
|
// result.resource.destination,
|
||||||
|
// result.resource.destinationPort,
|
||||||
|
// result.resource.protocol,
|
||||||
|
// result.resource.proxyPort
|
||||||
|
// );
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
|
|
||||||
logger.debug(
|
|
||||||
`Successfully updated client resources for org ${orgId}: ${JSON.stringify(clientResourcesResults)}`
|
|
||||||
);
|
|
||||||
|
|
||||||
// We need to update the targets on the newts from the successfully updated information
|
|
||||||
for (const result of clientResourcesResults) {
|
|
||||||
const [site] = await db
|
|
||||||
.select()
|
|
||||||
.from(sites)
|
|
||||||
.innerJoin(newts, eq(sites.siteId, newts.siteId))
|
|
||||||
.where(
|
|
||||||
and(
|
|
||||||
eq(sites.siteId, result.resource.siteId),
|
|
||||||
eq(sites.orgId, orgId),
|
|
||||||
eq(sites.type, "newt"),
|
|
||||||
isNotNull(sites.pubKey)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.limit(1);
|
|
||||||
|
|
||||||
logger.debug(
|
|
||||||
`Updating client resource ${result.resource.siteResourceId} on site ${site.sites.siteId}`
|
|
||||||
);
|
|
||||||
|
|
||||||
// await addClientTargets(
|
|
||||||
// site.newt.newtId,
|
|
||||||
// result.resource.destination,
|
|
||||||
// result.resource.destinationPort,
|
|
||||||
// result.resource.protocol,
|
|
||||||
// result.resource.proxyPort
|
|
||||||
// );
|
|
||||||
}
|
|
||||||
|
|
||||||
blueprintSucceeded = true;
|
blueprintSucceeded = true;
|
||||||
blueprintMessage = "Blueprint applied successfully";
|
blueprintMessage = "Blueprint applied successfully";
|
||||||
@@ -170,54 +188,4 @@ export async function applyBlueprint({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return blueprint;
|
return blueprint;
|
||||||
}
|
}
|
||||||
|
|
||||||
// await updateDatabaseFromConfig("org_i21aifypnlyxur2", {
|
|
||||||
// resources: {
|
|
||||||
// "resource-nice-id": {
|
|
||||||
// name: "this is my resource",
|
|
||||||
// protocol: "http",
|
|
||||||
// "full-domain": "level1.test.example.com",
|
|
||||||
// "host-header": "example.com",
|
|
||||||
// "tls-server-name": "example.com",
|
|
||||||
// auth: {
|
|
||||||
// pincode: 123456,
|
|
||||||
// password: "sadfasdfadsf",
|
|
||||||
// "sso-enabled": true,
|
|
||||||
// "sso-roles": ["Member"],
|
|
||||||
// "sso-users": ["owen@pangolin.net"],
|
|
||||||
// "whitelist-users": ["owen@pangolin.net"]
|
|
||||||
// },
|
|
||||||
// targets: [
|
|
||||||
// {
|
|
||||||
// site: "glossy-plains-viscacha-rat",
|
|
||||||
// hostname: "localhost",
|
|
||||||
// method: "http",
|
|
||||||
// port: 8000,
|
|
||||||
// healthcheck: {
|
|
||||||
// port: 8000,
|
|
||||||
// hostname: "localhost"
|
|
||||||
// }
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// site: "glossy-plains-viscacha-rat",
|
|
||||||
// hostname: "localhost",
|
|
||||||
// method: "http",
|
|
||||||
// port: 8001
|
|
||||||
// }
|
|
||||||
// ]
|
|
||||||
// },
|
|
||||||
// "resource-nice-id2": {
|
|
||||||
// name: "http server",
|
|
||||||
// protocol: "tcp",
|
|
||||||
// "proxy-port": 3000,
|
|
||||||
// targets: [
|
|
||||||
// {
|
|
||||||
// site: "glossy-plains-viscacha-rat",
|
|
||||||
// hostname: "localhost",
|
|
||||||
// port: 3000,
|
|
||||||
// }
|
|
||||||
// ]
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
@@ -1,17 +1,23 @@
|
|||||||
import {
|
import {
|
||||||
|
clients,
|
||||||
|
clientSiteResources,
|
||||||
|
roles,
|
||||||
|
roleSiteResources,
|
||||||
SiteResource,
|
SiteResource,
|
||||||
siteResources,
|
siteResources,
|
||||||
Transaction,
|
Transaction,
|
||||||
|
userOrgs,
|
||||||
|
users,
|
||||||
|
userSiteResources
|
||||||
} from "@server/db";
|
} from "@server/db";
|
||||||
import { sites } from "@server/db";
|
import { sites } from "@server/db";
|
||||||
import { eq, and } from "drizzle-orm";
|
import { eq, and, ne, inArray } from "drizzle-orm";
|
||||||
import {
|
import { Config } from "./types";
|
||||||
Config,
|
|
||||||
} from "./types";
|
|
||||||
import logger from "@server/logger";
|
import logger from "@server/logger";
|
||||||
|
|
||||||
export type ClientResourcesResults = {
|
export type ClientResourcesResults = {
|
||||||
resource: SiteResource;
|
newSiteResource: SiteResource;
|
||||||
|
oldSiteResource?: SiteResource;
|
||||||
}[];
|
}[];
|
||||||
|
|
||||||
export async function updateClientResources(
|
export async function updateClientResources(
|
||||||
@@ -69,17 +75,22 @@ export async function updateClientResources(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (existingResource) {
|
if (existingResource) {
|
||||||
|
if (existingResource.siteId !== site.siteId) {
|
||||||
|
throw new Error(
|
||||||
|
`You can not change the site of an existing client resource (${resourceNiceId}). Please delete and recreate it instead.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// Update existing resource
|
// Update existing resource
|
||||||
const [updatedResource] = await trx
|
const [updatedResource] = await trx
|
||||||
.update(siteResources)
|
.update(siteResources)
|
||||||
.set({
|
.set({
|
||||||
name: resourceData.name || resourceNiceId,
|
name: resourceData.name || resourceNiceId,
|
||||||
siteId: site.siteId,
|
mode: resourceData.mode,
|
||||||
mode: "port",
|
destination: resourceData.destination,
|
||||||
proxyPort: resourceData["proxy-port"]!,
|
enabled: true, // hardcoded for now
|
||||||
destination: resourceData.hostname,
|
// enabled: resourceData.enabled ?? true,
|
||||||
destinationPort: resourceData["internal-port"],
|
alias: resourceData.alias || null
|
||||||
protocol: resourceData.protocol
|
|
||||||
})
|
})
|
||||||
.where(
|
.where(
|
||||||
eq(
|
eq(
|
||||||
@@ -89,7 +100,110 @@ export async function updateClientResources(
|
|||||||
)
|
)
|
||||||
.returning();
|
.returning();
|
||||||
|
|
||||||
results.push({ resource: updatedResource });
|
const siteResourceId = existingResource.siteResourceId;
|
||||||
|
const orgId = existingResource.orgId;
|
||||||
|
|
||||||
|
await trx
|
||||||
|
.delete(clientSiteResources)
|
||||||
|
.where(eq(clientSiteResources.siteResourceId, siteResourceId));
|
||||||
|
|
||||||
|
if (resourceData.machines.length > 0) {
|
||||||
|
// get clientIds from niceIds
|
||||||
|
const clientsToUpdate = await trx
|
||||||
|
.select()
|
||||||
|
.from(clients)
|
||||||
|
.where(
|
||||||
|
and(
|
||||||
|
inArray(clients.niceId, resourceData.machines),
|
||||||
|
eq(clients.orgId, orgId)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
const clientIds = clientsToUpdate.map(
|
||||||
|
(client) => client.clientId
|
||||||
|
);
|
||||||
|
|
||||||
|
await trx.insert(clientSiteResources).values(
|
||||||
|
clientIds.map((clientId) => ({
|
||||||
|
clientId,
|
||||||
|
siteResourceId
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
await trx
|
||||||
|
.delete(userSiteResources)
|
||||||
|
.where(eq(userSiteResources.siteResourceId, siteResourceId));
|
||||||
|
|
||||||
|
if (resourceData.users.length > 0) {
|
||||||
|
// get userIds from username
|
||||||
|
const usersToUpdate = await trx
|
||||||
|
.select()
|
||||||
|
.from(users)
|
||||||
|
.innerJoin(userOrgs, eq(users.userId, userOrgs.userId))
|
||||||
|
.where(
|
||||||
|
and(
|
||||||
|
inArray(users.username, resourceData.users),
|
||||||
|
eq(userOrgs.orgId, orgId)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
const userIds = usersToUpdate.map((user) => user.user.userId);
|
||||||
|
|
||||||
|
await trx
|
||||||
|
.insert(userSiteResources)
|
||||||
|
.values(
|
||||||
|
userIds.map((userId) => ({ userId, siteResourceId }))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get all admin role IDs for this org to exclude from deletion
|
||||||
|
const adminRoles = await trx
|
||||||
|
.select()
|
||||||
|
.from(roles)
|
||||||
|
.where(and(eq(roles.isAdmin, true), eq(roles.orgId, orgId)));
|
||||||
|
const adminRoleIds = adminRoles.map((role) => role.roleId);
|
||||||
|
|
||||||
|
if (adminRoleIds.length > 0) {
|
||||||
|
await trx.delete(roleSiteResources).where(
|
||||||
|
and(
|
||||||
|
eq(roleSiteResources.siteResourceId, siteResourceId),
|
||||||
|
ne(roleSiteResources.roleId, adminRoleIds[0]) // delete all but the admin role
|
||||||
|
)
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
await trx
|
||||||
|
.delete(roleSiteResources)
|
||||||
|
.where(
|
||||||
|
eq(roleSiteResources.siteResourceId, siteResourceId)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (resourceData.roles.length > 0) {
|
||||||
|
// Re-add specified roles but we need to get the roleIds from the role name in the array
|
||||||
|
const rolesToUpdate = await trx
|
||||||
|
.select()
|
||||||
|
.from(roles)
|
||||||
|
.where(
|
||||||
|
and(
|
||||||
|
eq(roles.orgId, orgId),
|
||||||
|
inArray(roles.name, resourceData.roles)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
const roleIds = rolesToUpdate.map((role) => role.roleId);
|
||||||
|
|
||||||
|
await trx
|
||||||
|
.insert(roleSiteResources)
|
||||||
|
.values(
|
||||||
|
roleIds.map((roleId) => ({ roleId, siteResourceId }))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
results.push({
|
||||||
|
newSiteResource: updatedResource,
|
||||||
|
oldSiteResource: existingResource
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
// Create new resource
|
// Create new resource
|
||||||
const [newResource] = await trx
|
const [newResource] = await trx
|
||||||
@@ -99,19 +213,103 @@ export async function updateClientResources(
|
|||||||
siteId: site.siteId,
|
siteId: site.siteId,
|
||||||
niceId: resourceNiceId,
|
niceId: resourceNiceId,
|
||||||
name: resourceData.name || resourceNiceId,
|
name: resourceData.name || resourceNiceId,
|
||||||
mode: "port",
|
mode: resourceData.mode,
|
||||||
proxyPort: resourceData["proxy-port"]!,
|
destination: resourceData.destination,
|
||||||
destination: resourceData.hostname,
|
enabled: true, // hardcoded for now
|
||||||
destinationPort: resourceData["internal-port"],
|
// enabled: resourceData.enabled ?? true,
|
||||||
protocol: resourceData.protocol
|
alias: resourceData.alias || null
|
||||||
})
|
})
|
||||||
.returning();
|
.returning();
|
||||||
|
|
||||||
|
const siteResourceId = newResource.siteResourceId;
|
||||||
|
|
||||||
|
const [adminRole] = await trx
|
||||||
|
.select()
|
||||||
|
.from(roles)
|
||||||
|
.where(and(eq(roles.isAdmin, true), eq(roles.orgId, orgId)))
|
||||||
|
.limit(1);
|
||||||
|
|
||||||
|
if (!adminRole) {
|
||||||
|
throw new Error(`Admin role not found for org ${orgId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
await trx.insert(roleSiteResources).values({
|
||||||
|
roleId: adminRole.roleId,
|
||||||
|
siteResourceId: siteResourceId
|
||||||
|
});
|
||||||
|
|
||||||
|
if (resourceData.roles.length > 0) {
|
||||||
|
// get roleIds from role names
|
||||||
|
const rolesToUpdate = await trx
|
||||||
|
.select()
|
||||||
|
.from(roles)
|
||||||
|
.where(
|
||||||
|
and(
|
||||||
|
eq(roles.orgId, orgId),
|
||||||
|
inArray(roles.name, resourceData.roles)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
const roleIds = rolesToUpdate.map((role) => role.roleId);
|
||||||
|
|
||||||
|
await trx
|
||||||
|
.insert(roleSiteResources)
|
||||||
|
.values(
|
||||||
|
roleIds.map((roleId) => ({ roleId, siteResourceId }))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (resourceData.users.length > 0) {
|
||||||
|
// get userIds from username
|
||||||
|
const usersToUpdate = await trx
|
||||||
|
.select()
|
||||||
|
.from(users)
|
||||||
|
.innerJoin(userOrgs, eq(users.userId, userOrgs.userId))
|
||||||
|
.where(
|
||||||
|
and(
|
||||||
|
inArray(users.username, resourceData.users),
|
||||||
|
eq(userOrgs.orgId, orgId)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
const userIds = usersToUpdate.map((user) => user.user.userId);
|
||||||
|
|
||||||
|
await trx
|
||||||
|
.insert(userSiteResources)
|
||||||
|
.values(
|
||||||
|
userIds.map((userId) => ({ userId, siteResourceId }))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (resourceData.machines.length > 0) {
|
||||||
|
// get clientIds from niceIds
|
||||||
|
const clientsToUpdate = await trx
|
||||||
|
.select()
|
||||||
|
.from(clients)
|
||||||
|
.where(
|
||||||
|
and(
|
||||||
|
inArray(clients.niceId, resourceData.machines),
|
||||||
|
eq(clients.orgId, orgId)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
const clientIds = clientsToUpdate.map(
|
||||||
|
(client) => client.clientId
|
||||||
|
);
|
||||||
|
|
||||||
|
await trx.insert(clientSiteResources).values(
|
||||||
|
clientIds.map((clientId) => ({
|
||||||
|
clientId,
|
||||||
|
siteResourceId
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
logger.info(
|
logger.info(
|
||||||
`Created new client resource ${newResource.name} (${newResource.siteResourceId}) for org ${orgId}`
|
`Created new client resource ${newResource.name} (${newResource.siteResourceId}) for org ${orgId}`
|
||||||
);
|
);
|
||||||
|
|
||||||
results.push({ resource: newResource });
|
results.push({ newSiteResource: newResource });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -221,7 +221,8 @@ export async function updateProxyResources(
|
|||||||
domainId: domain ? domain.domainId : null,
|
domainId: domain ? domain.domainId : null,
|
||||||
enabled: resourceEnabled,
|
enabled: resourceEnabled,
|
||||||
sso: resourceData.auth?.["sso-enabled"] || false,
|
sso: resourceData.auth?.["sso-enabled"] || false,
|
||||||
skipToIdpId: resourceData.auth?.["auto-login-idp"] || null,
|
skipToIdpId:
|
||||||
|
resourceData.auth?.["auto-login-idp"] || null,
|
||||||
ssl: resourceSsl,
|
ssl: resourceSsl,
|
||||||
setHostHeader: resourceData["host-header"] || null,
|
setHostHeader: resourceData["host-header"] || null,
|
||||||
tlsServerName: resourceData["tls-server-name"] || null,
|
tlsServerName: resourceData["tls-server-name"] || null,
|
||||||
@@ -546,7 +547,8 @@ export async function updateProxyResources(
|
|||||||
if (
|
if (
|
||||||
existingRule.action !== getRuleAction(rule.action) ||
|
existingRule.action !== getRuleAction(rule.action) ||
|
||||||
existingRule.match !== rule.match.toUpperCase() ||
|
existingRule.match !== rule.match.toUpperCase() ||
|
||||||
existingRule.value !== getRuleValue(rule.match.toUpperCase(), rule.value)
|
existingRule.value !==
|
||||||
|
getRuleValue(rule.match.toUpperCase(), rule.value)
|
||||||
) {
|
) {
|
||||||
validateRule(rule);
|
validateRule(rule);
|
||||||
await trx
|
await trx
|
||||||
@@ -554,7 +556,10 @@ export async function updateProxyResources(
|
|||||||
.set({
|
.set({
|
||||||
action: getRuleAction(rule.action),
|
action: getRuleAction(rule.action),
|
||||||
match: rule.match.toUpperCase(),
|
match: rule.match.toUpperCase(),
|
||||||
value: getRuleValue(rule.match.toUpperCase(), rule.value),
|
value: getRuleValue(
|
||||||
|
rule.match.toUpperCase(),
|
||||||
|
rule.value
|
||||||
|
)
|
||||||
})
|
})
|
||||||
.where(
|
.where(
|
||||||
eq(resourceRules.ruleId, existingRule.ruleId)
|
eq(resourceRules.ruleId, existingRule.ruleId)
|
||||||
@@ -566,7 +571,10 @@ export async function updateProxyResources(
|
|||||||
resourceId: existingResource.resourceId,
|
resourceId: existingResource.resourceId,
|
||||||
action: getRuleAction(rule.action),
|
action: getRuleAction(rule.action),
|
||||||
match: rule.match.toUpperCase(),
|
match: rule.match.toUpperCase(),
|
||||||
value: getRuleValue(rule.match.toUpperCase(), rule.value),
|
value: getRuleValue(
|
||||||
|
rule.match.toUpperCase(),
|
||||||
|
rule.value
|
||||||
|
),
|
||||||
priority: index + 1 // start priorities at 1
|
priority: index + 1 // start priorities at 1
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -852,16 +860,16 @@ async function syncUserResources(
|
|||||||
.from(userResources)
|
.from(userResources)
|
||||||
.where(eq(userResources.resourceId, resourceId));
|
.where(eq(userResources.resourceId, resourceId));
|
||||||
|
|
||||||
for (const email of ssoUsers) {
|
for (const username of ssoUsers) {
|
||||||
const [user] = await trx
|
const [user] = await trx
|
||||||
.select()
|
.select()
|
||||||
.from(users)
|
.from(users)
|
||||||
.innerJoin(userOrgs, eq(users.userId, userOrgs.userId))
|
.innerJoin(userOrgs, eq(users.userId, userOrgs.userId))
|
||||||
.where(and(eq(users.email, email), eq(userOrgs.orgId, orgId)))
|
.where(and(eq(users.username, username), eq(userOrgs.orgId, orgId)))
|
||||||
.limit(1);
|
.limit(1);
|
||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
throw new Error(`User not found: ${email} in org ${orgId}`);
|
throw new Error(`User not found: ${username} in org ${orgId}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const existingUserResource = existingUserResources.find(
|
const existingUserResource = existingUserResources.find(
|
||||||
@@ -889,7 +897,11 @@ async function syncUserResources(
|
|||||||
)
|
)
|
||||||
.limit(1);
|
.limit(1);
|
||||||
|
|
||||||
if (user && user.user.email && !ssoUsers.includes(user.user.email)) {
|
if (
|
||||||
|
user &&
|
||||||
|
user.user.username &&
|
||||||
|
!ssoUsers.includes(user.user.username)
|
||||||
|
) {
|
||||||
await trx
|
await trx
|
||||||
.delete(userResources)
|
.delete(userResources)
|
||||||
.where(
|
.where(
|
||||||
|
|||||||
@@ -16,7 +16,11 @@ export const TargetHealthCheckSchema = z.object({
|
|||||||
"unhealthy-interval": z.int().default(30),
|
"unhealthy-interval": z.int().default(30),
|
||||||
unhealthyInterval: z.int().optional(), // deprecated alias
|
unhealthyInterval: z.int().optional(), // deprecated alias
|
||||||
timeout: z.int().default(5),
|
timeout: z.int().default(5),
|
||||||
headers: z.array(z.object({ name: z.string(), value: z.string() })).nullable().optional().default(null),
|
headers: z
|
||||||
|
.array(z.object({ name: z.string(), value: z.string() }))
|
||||||
|
.nullable()
|
||||||
|
.optional()
|
||||||
|
.default(null),
|
||||||
"follow-redirects": z.boolean().default(true),
|
"follow-redirects": z.boolean().default(true),
|
||||||
followRedirects: z.boolean().optional(), // deprecated alias
|
followRedirects: z.boolean().optional(), // deprecated alias
|
||||||
method: z.string().default("GET"),
|
method: z.string().default("GET"),
|
||||||
@@ -36,7 +40,10 @@ export const TargetSchema = z.object({
|
|||||||
healthcheck: TargetHealthCheckSchema.optional(),
|
healthcheck: TargetHealthCheckSchema.optional(),
|
||||||
rewritePath: z.string().optional(), // deprecated alias
|
rewritePath: z.string().optional(), // deprecated alias
|
||||||
"rewrite-path": z.string().optional(),
|
"rewrite-path": z.string().optional(),
|
||||||
"rewrite-match": z.enum(["exact", "prefix", "regex", "stripPrefix"]).optional().nullable(),
|
"rewrite-match": z
|
||||||
|
.enum(["exact", "prefix", "regex", "stripPrefix"])
|
||||||
|
.optional()
|
||||||
|
.nullable(),
|
||||||
priority: z.int().min(1).max(1000).optional().default(100)
|
priority: z.int().min(1).max(1000).optional().default(100)
|
||||||
});
|
});
|
||||||
export type TargetData = z.infer<typeof TargetSchema>;
|
export type TargetData = z.infer<typeof TargetSchema>;
|
||||||
@@ -45,10 +52,12 @@ export const AuthSchema = z.object({
|
|||||||
// pincode has to have 6 digits
|
// pincode has to have 6 digits
|
||||||
pincode: z.number().min(100000).max(999999).optional(),
|
pincode: z.number().min(100000).max(999999).optional(),
|
||||||
password: z.string().min(1).optional(),
|
password: z.string().min(1).optional(),
|
||||||
"basic-auth": z.object({
|
"basic-auth": z
|
||||||
user: z.string().min(1),
|
.object({
|
||||||
password: z.string().min(1)
|
user: z.string().min(1),
|
||||||
}).optional(),
|
password: z.string().min(1)
|
||||||
|
})
|
||||||
|
.optional(),
|
||||||
"sso-enabled": z.boolean().optional().default(false),
|
"sso-enabled": z.boolean().optional().default(false),
|
||||||
"sso-roles": z
|
"sso-roles": z
|
||||||
.array(z.string())
|
.array(z.string())
|
||||||
@@ -59,7 +68,7 @@ export const AuthSchema = z.object({
|
|||||||
}),
|
}),
|
||||||
"sso-users": z.array(z.email()).optional().default([]),
|
"sso-users": z.array(z.email()).optional().default([]),
|
||||||
"whitelist-users": z.array(z.email()).optional().default([]),
|
"whitelist-users": z.array(z.email()).optional().default([]),
|
||||||
"auto-login-idp": z.int().positive().optional(),
|
"auto-login-idp": z.int().positive().optional()
|
||||||
});
|
});
|
||||||
|
|
||||||
export const RuleSchema = z.object({
|
export const RuleSchema = z.object({
|
||||||
@@ -122,7 +131,6 @@ export const ResourceSchema = z
|
|||||||
{
|
{
|
||||||
path: ["targets"],
|
path: ["targets"],
|
||||||
error: "When protocol is 'http', all targets must have a 'method' field"
|
error: "When protocol is 'http', all targets must have a 'method' field"
|
||||||
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.refine(
|
.refine(
|
||||||
@@ -204,23 +212,122 @@ export function isTargetsOnlyResource(resource: any): boolean {
|
|||||||
return Object.keys(resource).length === 1 && resource.targets;
|
return Object.keys(resource).length === 1 && resource.targets;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ClientResourceSchema = z.object({
|
export const ClientResourceSchema = z
|
||||||
name: z.string().min(2).max(100),
|
.object({
|
||||||
site: z.string().min(2).max(100).optional(),
|
name: z.string().min(1).max(255),
|
||||||
protocol: z.enum(["tcp", "udp"]),
|
mode: z.enum(["host", "cidr"]),
|
||||||
"proxy-port": z.number().min(1).max(65535),
|
site: z.string(),
|
||||||
"hostname": z.string().min(1).max(255),
|
// protocol: z.enum(["tcp", "udp"]).optional(),
|
||||||
"internal-port": z.number().min(1).max(65535),
|
// proxyPort: z.int().positive().optional(),
|
||||||
enabled: z.boolean().optional().default(true)
|
// destinationPort: z.int().positive().optional(),
|
||||||
});
|
destination: z.string().min(1),
|
||||||
|
// enabled: z.boolean().default(true),
|
||||||
|
alias: z
|
||||||
|
.string()
|
||||||
|
.regex(
|
||||||
|
/^(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?$/,
|
||||||
|
"Alias must be a fully qualified domain name (e.g., example.com)"
|
||||||
|
)
|
||||||
|
.optional(),
|
||||||
|
roles: z
|
||||||
|
.array(z.string())
|
||||||
|
.optional()
|
||||||
|
.default([])
|
||||||
|
.refine((roles) => !roles.includes("Admin"), {
|
||||||
|
error: "Admin role cannot be included in roles"
|
||||||
|
}),
|
||||||
|
users: z.array(z.email()).optional().default([]),
|
||||||
|
machines: z.array(z.string()).optional().default([])
|
||||||
|
})
|
||||||
|
.refine(
|
||||||
|
(data) => {
|
||||||
|
if (data.mode === "host") {
|
||||||
|
// Check if it's a valid IP address using zod (v4 or v6)
|
||||||
|
const isValidIP = z
|
||||||
|
.union([z.ipv4(), z.ipv6()])
|
||||||
|
.safeParse(data.destination).success;
|
||||||
|
|
||||||
|
if (isValidIP) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if it's a valid domain (hostname pattern, TLD not required)
|
||||||
|
const domainRegex =
|
||||||
|
/^(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)*[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?$/;
|
||||||
|
const isValidDomain = domainRegex.test(data.destination);
|
||||||
|
const isValidAlias = data.alias && domainRegex.test(data.alias);
|
||||||
|
|
||||||
|
return isValidDomain && isValidAlias; // require the alias to be set in the case of domain
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
{
|
||||||
|
message:
|
||||||
|
"Destination must be a valid IP address or valid domain AND alias is required"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.refine(
|
||||||
|
(data) => {
|
||||||
|
if (data.mode === "cidr") {
|
||||||
|
// Check if it's a valid CIDR (v4 or v6)
|
||||||
|
const isValidCIDR = z
|
||||||
|
.union([z.cidrv4(), z.cidrv6()])
|
||||||
|
.safeParse(data.destination).success;
|
||||||
|
return isValidCIDR;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
{
|
||||||
|
message: "Destination must be a valid CIDR notation for cidr mode"
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
// Schema for the entire configuration object
|
// Schema for the entire configuration object
|
||||||
export const ConfigSchema = z
|
export const ConfigSchema = z
|
||||||
.object({
|
.object({
|
||||||
"proxy-resources": z.record(z.string(), ResourceSchema).optional().prefault({}),
|
"proxy-resources": z
|
||||||
"client-resources": z.record(z.string(), ClientResourceSchema).optional().prefault({}),
|
.record(z.string(), ResourceSchema)
|
||||||
|
.optional()
|
||||||
|
.prefault({}),
|
||||||
|
"public-resources": z
|
||||||
|
.record(z.string(), ResourceSchema)
|
||||||
|
.optional()
|
||||||
|
.prefault({}),
|
||||||
|
"client-resources": z
|
||||||
|
.record(z.string(), ClientResourceSchema)
|
||||||
|
.optional()
|
||||||
|
.prefault({}),
|
||||||
|
"private-resources": z
|
||||||
|
.record(z.string(), ClientResourceSchema)
|
||||||
|
.optional()
|
||||||
|
.prefault({}),
|
||||||
sites: z.record(z.string(), SiteSchema).optional().prefault({})
|
sites: z.record(z.string(), SiteSchema).optional().prefault({})
|
||||||
})
|
})
|
||||||
|
.transform((data) => {
|
||||||
|
// Merge public-resources into proxy-resources
|
||||||
|
if (data["public-resources"]) {
|
||||||
|
data["proxy-resources"] = {
|
||||||
|
...data["proxy-resources"],
|
||||||
|
...data["public-resources"]
|
||||||
|
};
|
||||||
|
delete (data as any)["public-resources"];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Merge private-resources into client-resources
|
||||||
|
if (data["private-resources"]) {
|
||||||
|
data["client-resources"] = {
|
||||||
|
...data["client-resources"],
|
||||||
|
...data["private-resources"]
|
||||||
|
};
|
||||||
|
delete (data as any)["private-resources"];
|
||||||
|
}
|
||||||
|
|
||||||
|
return data as {
|
||||||
|
"proxy-resources": Record<string, z.infer<typeof ResourceSchema>>;
|
||||||
|
"client-resources": Record<string, z.infer<typeof ClientResourceSchema>>;
|
||||||
|
sites: Record<string, z.infer<typeof SiteSchema>>;
|
||||||
|
};
|
||||||
|
})
|
||||||
.refine(
|
.refine(
|
||||||
// Enforce the full-domain uniqueness across resources in the same stack
|
// Enforce the full-domain uniqueness across resources in the same stack
|
||||||
(config) => {
|
(config) => {
|
||||||
@@ -278,12 +385,10 @@ export const ConfigSchema = z
|
|||||||
|
|
||||||
const duplicates = Array.from(protocolPortMap.entries())
|
const duplicates = Array.from(protocolPortMap.entries())
|
||||||
.filter(([_, resourceKeys]) => resourceKeys.length > 1)
|
.filter(([_, resourceKeys]) => resourceKeys.length > 1)
|
||||||
.map(
|
.map(([protocolPort, resourceKeys]) => {
|
||||||
([protocolPort, resourceKeys]) => {
|
const [protocol, port] = protocolPort.split(":");
|
||||||
const [protocol, port] = protocolPort.split(':');
|
return `${protocol.toUpperCase()} port ${port} used by proxy-resources: ${resourceKeys.join(", ")}`;
|
||||||
return `${protocol.toUpperCase()} port ${port} used by proxy-resources: ${resourceKeys.join(", ")}`;
|
})
|
||||||
}
|
|
||||||
)
|
|
||||||
.join("; ");
|
.join("; ");
|
||||||
|
|
||||||
if (duplicates.length !== 0) {
|
if (duplicates.length !== 0) {
|
||||||
@@ -295,35 +400,35 @@ export const ConfigSchema = z
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
.refine(
|
.refine(
|
||||||
// Enforce proxy-port uniqueness within client-resources
|
// Enforce alias uniqueness within client-resources
|
||||||
(config) => {
|
(config) => {
|
||||||
// Extract duplicates for error message
|
// Extract duplicates for error message
|
||||||
const proxyPortMap = new Map<number, string[]>();
|
const aliasMap = new Map<string, string[]>();
|
||||||
|
|
||||||
Object.entries(config["client-resources"]).forEach(
|
Object.entries(config["client-resources"]).forEach(
|
||||||
([resourceKey, resource]) => {
|
([resourceKey, resource]) => {
|
||||||
const proxyPort = resource["proxy-port"];
|
const alias = resource.alias;
|
||||||
if (proxyPort !== undefined) {
|
if (alias !== undefined) {
|
||||||
if (!proxyPortMap.has(proxyPort)) {
|
if (!aliasMap.has(alias)) {
|
||||||
proxyPortMap.set(proxyPort, []);
|
aliasMap.set(alias, []);
|
||||||
}
|
}
|
||||||
proxyPortMap.get(proxyPort)!.push(resourceKey);
|
aliasMap.get(alias)!.push(resourceKey);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
const duplicates = Array.from(proxyPortMap.entries())
|
const duplicates = Array.from(aliasMap.entries())
|
||||||
.filter(([_, resourceKeys]) => resourceKeys.length > 1)
|
.filter(([_, resourceKeys]) => resourceKeys.length > 1)
|
||||||
.map(
|
.map(
|
||||||
([proxyPort, resourceKeys]) =>
|
([alias, resourceKeys]) =>
|
||||||
`port ${proxyPort} used by client-resources: ${resourceKeys.join(", ")}`
|
`alias '${alias}' used by client-resources: ${resourceKeys.join(", ")}`
|
||||||
)
|
)
|
||||||
.join("; ");
|
.join("; ");
|
||||||
|
|
||||||
if (duplicates.length !== 0) {
|
if (duplicates.length !== 0) {
|
||||||
return {
|
return {
|
||||||
path: ["client-resources"],
|
path: ["client-resources"],
|
||||||
error: `Duplicate 'proxy-port' values found in client-resources: ${duplicates}`
|
error: `Duplicate 'alias' values found in client-resources: ${duplicates}`
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
roles,
|
roles,
|
||||||
roleSiteResources,
|
roleSiteResources,
|
||||||
sites,
|
sites,
|
||||||
|
Transaction,
|
||||||
userSiteResources
|
userSiteResources
|
||||||
} from "@server/db";
|
} from "@server/db";
|
||||||
import { siteResources, SiteResource } from "@server/db";
|
import { siteResources, SiteResource } from "@server/db";
|
||||||
@@ -296,122 +297,16 @@ export async function updateSiteResource(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { mergedAllClients } =
|
|
||||||
await rebuildClientAssociationsFromSiteResource(
|
|
||||||
existingSiteResource, // we want to rebuild based on the existing resource then we will apply the change to the destination below
|
|
||||||
trx
|
|
||||||
);
|
|
||||||
|
|
||||||
// after everything is rebuilt above we still need to update the targets and remote subnets if the destination changed
|
|
||||||
const destinationChanged =
|
|
||||||
existingSiteResource.destination !==
|
|
||||||
updatedSiteResource.destination;
|
|
||||||
const aliasChanged =
|
|
||||||
existingSiteResource.alias !== updatedSiteResource.alias;
|
|
||||||
|
|
||||||
if (destinationChanged || aliasChanged) {
|
|
||||||
const [newt] = await trx
|
|
||||||
.select()
|
|
||||||
.from(newts)
|
|
||||||
.where(eq(newts.siteId, site.siteId))
|
|
||||||
.limit(1);
|
|
||||||
|
|
||||||
if (!newt) {
|
|
||||||
return next(
|
|
||||||
createHttpError(HttpCode.NOT_FOUND, "Newt not found")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only update targets on newt if destination changed
|
|
||||||
if (destinationChanged) {
|
|
||||||
const oldTargets = generateSubnetProxyTargets(
|
|
||||||
existingSiteResource,
|
|
||||||
mergedAllClients
|
|
||||||
);
|
|
||||||
const newTargets = generateSubnetProxyTargets(
|
|
||||||
updatedSiteResource,
|
|
||||||
mergedAllClients
|
|
||||||
);
|
|
||||||
|
|
||||||
await updateTargets(newt.newtId, {
|
|
||||||
oldTargets: oldTargets,
|
|
||||||
newTargets: newTargets
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const olmJobs: Promise<void>[] = [];
|
|
||||||
for (const client of mergedAllClients) {
|
|
||||||
// does this client have access to another resource on this site that has the same destination still? if so we dont want to remove it from their olm yet
|
|
||||||
// todo: optimize this query if needed
|
|
||||||
const oldDestinationStillInUseSites = await trx
|
|
||||||
.select()
|
|
||||||
.from(siteResources)
|
|
||||||
.innerJoin(
|
|
||||||
clientSiteResourcesAssociationsCache,
|
|
||||||
eq(
|
|
||||||
clientSiteResourcesAssociationsCache.siteResourceId,
|
|
||||||
siteResources.siteResourceId
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.where(
|
|
||||||
and(
|
|
||||||
eq(
|
|
||||||
clientSiteResourcesAssociationsCache.clientId,
|
|
||||||
client.clientId
|
|
||||||
),
|
|
||||||
eq(siteResources.siteId, site.siteId),
|
|
||||||
eq(
|
|
||||||
siteResources.destination,
|
|
||||||
existingSiteResource.destination
|
|
||||||
),
|
|
||||||
ne(
|
|
||||||
siteResources.siteResourceId,
|
|
||||||
existingSiteResource.siteResourceId
|
|
||||||
)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
const oldDestinationStillInUseByASite =
|
|
||||||
oldDestinationStillInUseSites.length > 0;
|
|
||||||
|
|
||||||
// we also need to update the remote subnets on the olms for each client that has access to this site
|
|
||||||
olmJobs.push(
|
|
||||||
updatePeerData(
|
|
||||||
client.clientId,
|
|
||||||
updatedSiteResource.siteId,
|
|
||||||
destinationChanged
|
|
||||||
? {
|
|
||||||
oldRemoteSubnets:
|
|
||||||
!oldDestinationStillInUseByASite
|
|
||||||
? generateRemoteSubnets([
|
|
||||||
existingSiteResource
|
|
||||||
])
|
|
||||||
: [],
|
|
||||||
newRemoteSubnets: generateRemoteSubnets([
|
|
||||||
updatedSiteResource
|
|
||||||
])
|
|
||||||
}
|
|
||||||
: undefined,
|
|
||||||
aliasChanged
|
|
||||||
? {
|
|
||||||
oldAliases: generateAliasConfig([
|
|
||||||
existingSiteResource
|
|
||||||
]),
|
|
||||||
newAliases: generateAliasConfig([
|
|
||||||
updatedSiteResource
|
|
||||||
])
|
|
||||||
}
|
|
||||||
: undefined
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
await Promise.all(olmJobs);
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.info(
|
logger.info(
|
||||||
`Updated site resource ${siteResourceId} for site ${siteId}`
|
`Updated site resource ${siteResourceId} for site ${siteId}`
|
||||||
);
|
);
|
||||||
|
|
||||||
|
await handleMessagingForUpdatedSiteResource(
|
||||||
|
existingSiteResource,
|
||||||
|
updatedSiteResource!,
|
||||||
|
{ siteId: site.siteId, orgId: site.orgId },
|
||||||
|
trx
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
return response(res, {
|
return response(res, {
|
||||||
@@ -431,3 +326,121 @@ export async function updateSiteResource(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function handleMessagingForUpdatedSiteResource(
|
||||||
|
existingSiteResource: SiteResource,
|
||||||
|
updatedSiteResource: SiteResource,
|
||||||
|
site: { siteId: number; orgId: string },
|
||||||
|
trx: Transaction
|
||||||
|
) {
|
||||||
|
const { mergedAllClients } =
|
||||||
|
await rebuildClientAssociationsFromSiteResource(
|
||||||
|
existingSiteResource, // we want to rebuild based on the existing resource then we will apply the change to the destination below
|
||||||
|
trx
|
||||||
|
);
|
||||||
|
|
||||||
|
// after everything is rebuilt above we still need to update the targets and remote subnets if the destination changed
|
||||||
|
const destinationChanged =
|
||||||
|
existingSiteResource.destination !== updatedSiteResource.destination;
|
||||||
|
const aliasChanged =
|
||||||
|
existingSiteResource.alias !== updatedSiteResource.alias;
|
||||||
|
|
||||||
|
if (destinationChanged || aliasChanged) {
|
||||||
|
const [newt] = await trx
|
||||||
|
.select()
|
||||||
|
.from(newts)
|
||||||
|
.where(eq(newts.siteId, site.siteId))
|
||||||
|
.limit(1);
|
||||||
|
|
||||||
|
if (!newt) {
|
||||||
|
throw new Error(
|
||||||
|
"Newt not found for site during site resource update"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only update targets on newt if destination changed
|
||||||
|
if (destinationChanged) {
|
||||||
|
const oldTargets = generateSubnetProxyTargets(
|
||||||
|
existingSiteResource,
|
||||||
|
mergedAllClients
|
||||||
|
);
|
||||||
|
const newTargets = generateSubnetProxyTargets(
|
||||||
|
updatedSiteResource,
|
||||||
|
mergedAllClients
|
||||||
|
);
|
||||||
|
|
||||||
|
await updateTargets(newt.newtId, {
|
||||||
|
oldTargets: oldTargets,
|
||||||
|
newTargets: newTargets
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const olmJobs: Promise<void>[] = [];
|
||||||
|
for (const client of mergedAllClients) {
|
||||||
|
// does this client have access to another resource on this site that has the same destination still? if so we dont want to remove it from their olm yet
|
||||||
|
// todo: optimize this query if needed
|
||||||
|
const oldDestinationStillInUseSites = await trx
|
||||||
|
.select()
|
||||||
|
.from(siteResources)
|
||||||
|
.innerJoin(
|
||||||
|
clientSiteResourcesAssociationsCache,
|
||||||
|
eq(
|
||||||
|
clientSiteResourcesAssociationsCache.siteResourceId,
|
||||||
|
siteResources.siteResourceId
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.where(
|
||||||
|
and(
|
||||||
|
eq(
|
||||||
|
clientSiteResourcesAssociationsCache.clientId,
|
||||||
|
client.clientId
|
||||||
|
),
|
||||||
|
eq(siteResources.siteId, site.siteId),
|
||||||
|
eq(
|
||||||
|
siteResources.destination,
|
||||||
|
existingSiteResource.destination
|
||||||
|
),
|
||||||
|
ne(
|
||||||
|
siteResources.siteResourceId,
|
||||||
|
existingSiteResource.siteResourceId
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
const oldDestinationStillInUseByASite =
|
||||||
|
oldDestinationStillInUseSites.length > 0;
|
||||||
|
|
||||||
|
// we also need to update the remote subnets on the olms for each client that has access to this site
|
||||||
|
olmJobs.push(
|
||||||
|
updatePeerData(
|
||||||
|
client.clientId,
|
||||||
|
updatedSiteResource.siteId,
|
||||||
|
destinationChanged
|
||||||
|
? {
|
||||||
|
oldRemoteSubnets: !oldDestinationStillInUseByASite
|
||||||
|
? generateRemoteSubnets([
|
||||||
|
existingSiteResource
|
||||||
|
])
|
||||||
|
: [],
|
||||||
|
newRemoteSubnets: generateRemoteSubnets([
|
||||||
|
updatedSiteResource
|
||||||
|
])
|
||||||
|
}
|
||||||
|
: undefined,
|
||||||
|
aliasChanged
|
||||||
|
? {
|
||||||
|
oldAliases: generateAliasConfig([
|
||||||
|
existingSiteResource
|
||||||
|
]),
|
||||||
|
newAliases: generateAliasConfig([
|
||||||
|
updatedSiteResource
|
||||||
|
])
|
||||||
|
}
|
||||||
|
: undefined
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
await Promise.all(olmJobs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user