Push client identities to newt

This commit is contained in:
Owen
2026-10-12 17:03:27 -04:00
parent b43709903d
commit c6b1e5311e
8 changed files with 474 additions and 10 deletions
@@ -35,6 +35,7 @@ import { LimitId } from "@server/lib/billing";
import { usageService } from "@server/lib/billing/usageService";
import { build } from "@server/build";
import { calculateUserClientsForOrgs } from "@server/lib/calculateUserClientsForOrgs";
import { pushClientIdentitiesForUser } from "@server/lib/clientIdentities";
import { isSubscribed } from "#dynamic/lib/isSubscribed";
import { isLicensedOrSubscribed } from "#dynamic/lib/isLicencedOrSubscribed";
import { tierMatrix } from "@server/lib/billing/tierMatrix";
@@ -642,6 +643,17 @@ export async function validateOidcCallback(
);
});
if (existingUser) {
// The sync above may have changed the user's name, email or
// roles, all of which newt forwards as Remote-* headers.
pushClientIdentitiesForUser(userId!).catch((err) => {
logger.error(
"Error pushing client identities after syncing OIDC user",
{ error: err }
);
});
}
for (const orgCount of orgUserCounts) {
await usageService.updateCount(
orgCount.orgId,
+24 -2
View File
@@ -24,6 +24,10 @@ import {
generateSubnetProxyTargetV2,
SubnetProxyTargetV2
} from "@server/lib/ip";
import {
ClientIdentity,
getClientIdentities
} from "@server/lib/clientIdentities";
export async function buildClientConfigurationForNewtClient(
site: Site,
@@ -166,7 +170,8 @@ export async function buildClientConfigurationForNewtClient(
if (allSiteResources.length === 0) {
return {
peers: validPeers,
targets: targetsToSend
targets: targetsToSend,
clientIdentities: [] as ClientIdentity[]
};
}
@@ -234,9 +239,26 @@ export async function buildClientConfigurationForNewtClient(
}
}
// Only clients that can reach an HTTP resource ever hit newt's HTTP proxy,
// so those are the only identities it needs to set Remote-* headers.
const httpClientIds = new Set<number>();
for (const resource of allSiteResources) {
if (resource.mode !== "http") {
continue;
}
for (const c of clientsByResourceId.get(resource.siteResourceId) ??
[]) {
httpClientIds.add(c.clientId);
}
}
const clientIdentities = Array.from(
(await getClientIdentities(httpClientIds)).values()
);
return {
peers: validPeers,
targets: targetsToSend
targets: targetsToSend,
clientIdentities
};
}
@@ -117,10 +117,8 @@ export const handleNewtGetConfigMessage: MessageHandler = async (context) => {
}
}
const { peers, targets } = await buildClientConfigurationForNewtClient(
site,
exitNode
);
const { peers, targets, clientIdentities } =
await buildClientConfigurationForNewtClient(site, exitNode);
// Older newt clients only understand inline tlsCert/tlsKey on each
// target, so only switch to certId references once we know the client
@@ -144,6 +142,7 @@ export const handleNewtGetConfigMessage: MessageHandler = async (context) => {
peers,
targets: targetsToSend,
certs,
clientIdentities,
chainId: chainId
}
},
+3 -4
View File
@@ -28,10 +28,8 @@ export async function sendNewtSyncMessage(newt: Newt, site: Site) {
.where(eq(exitNodes.exitNodeId, site.exitNodeId))
.limit(1);
}
const { peers, targets } = await buildClientConfigurationForNewtClient(
site,
exitNode
);
const { peers, targets, clientIdentities } =
await buildClientConfigurationForNewtClient(site, exitNode);
// Older newt clients only understand inline tlsCert/tlsKey on each
// target, so only switch to certId references once we know the client
@@ -55,6 +53,7 @@ export async function sendNewtSyncMessage(newt: Newt, site: Site) {
peers: peers,
clientTargets: clientTargets,
certs: certs,
clientIdentities: clientIdentities,
browserGatewayTargets: browserGatewayTargets,
remoteExitNodeSubnets: remoteExitNodeSubnets
}
+11
View File
@@ -12,6 +12,7 @@ import { fromError } from "zod-validation-error";
import { isLicensedOrSubscribed } from "#dynamic/lib/isLicencedOrSubscribed";
import { OpenAPITags, registry } from "@server/openApi";
import { tierMatrix } from "@server/lib/billing/tierMatrix";
import { pushClientIdentitiesForRole } from "@server/lib/clientIdentities";
const updateRoleParamsSchema = z.strictObject({
roleId: z.coerce.number().int().positive()
@@ -216,6 +217,16 @@ export async function updateRole(
);
}
if (updateData.name !== undefined && updateData.name !== role[0].name) {
// Role names are forwarded by newt as the Remote-Role header.
pushClientIdentitiesForRole(roleId, orgId).catch((err) => {
logger.error(
`Error pushing client identities after renaming role ${roleId}`,
err
);
});
}
return response(res, {
data: updatedRole,
success: true,