Files
dokploy/apps/dokploy/server/api/routers/cluster.ts
Mauricio Siu 232ccc9139 feat: add organization-level authorization checks to WebSocket servers
- Implemented checks in the WebSocket server setups for Docker container logs, terminal, and deployment logs to ensure users can only access resources associated with their active organization.
- Enhanced security by closing WebSocket connections if the organization ID does not match the session's active organization ID.
2026-04-24 12:47:51 -06:00

143 lines
3.9 KiB
TypeScript

import {
type DockerNode,
execAsync,
execAsyncRemote,
findServerById,
getRemoteDocker,
} from "@dokploy/server";
import { TRPCError } from "@trpc/server";
import { z } from "zod";
import { audit } from "@/server/api/utils/audit";
import { getLocalServerIp } from "@/server/wss/terminal";
import { createTRPCRouter, withPermission } from "../trpc";
export const clusterRouter = createTRPCRouter({
getNodes: withPermission("server", "read")
.input(
z.object({
serverId: z.string().optional(),
}),
)
.query(async ({ input, ctx }) => {
if (input.serverId) {
const targetServer = await findServerById(input.serverId);
if (targetServer.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You don't have access to this server.",
});
}
}
const docker = await getRemoteDocker(input.serverId);
const workers: DockerNode[] = await docker.listNodes();
return workers;
}),
removeWorker: withPermission("server", "delete")
.input(
z.object({
nodeId: z.string(),
serverId: z.string().optional(),
}),
)
.mutation(async ({ input, ctx }) => {
if (input.serverId) {
const targetServer = await findServerById(input.serverId);
if (targetServer.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You don't have access to this server.",
});
}
}
try {
const drainCommand = `docker node update --availability drain ${input.nodeId}`;
const removeCommand = `docker node rm ${input.nodeId} --force`;
if (input.serverId) {
await execAsyncRemote(input.serverId, drainCommand);
await execAsyncRemote(input.serverId, removeCommand);
} else {
await execAsync(drainCommand);
await execAsync(removeCommand);
}
await audit(ctx, {
action: "delete",
resourceType: "cluster",
resourceId: input.nodeId,
resourceName: input.nodeId,
});
return true;
} catch (error) {
throw new TRPCError({
code: "INTERNAL_SERVER_ERROR",
message: "Error removing the node",
cause: error,
});
}
}),
addWorker: withPermission("server", "create")
.input(
z.object({
serverId: z.string().optional(),
}),
)
.query(async ({ input, ctx }) => {
if (input.serverId) {
const targetServer = await findServerById(input.serverId);
if (targetServer.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You don't have access to this server.",
});
}
}
const docker = await getRemoteDocker(input.serverId);
const result = await docker.swarmInspect();
const docker_version = await docker.version();
let ip = await getLocalServerIp();
if (input.serverId) {
const server = await findServerById(input.serverId);
ip = server?.ipAddress;
}
return {
command: `docker swarm join --token ${result.JoinTokens.Worker} ${ip}:2377`,
version: docker_version.Version,
};
}),
addManager: withPermission("server", "create")
.input(
z.object({
serverId: z.string().optional(),
}),
)
.query(async ({ input, ctx }) => {
if (input.serverId) {
const targetServer = await findServerById(input.serverId);
if (targetServer.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You don't have access to this server.",
});
}
}
const docker = await getRemoteDocker(input.serverId);
const result = await docker.swarmInspect();
const docker_version = await docker.version();
let ip = await getLocalServerIp();
if (input.serverId) {
const server = await findServerById(input.serverId);
ip = server?.ipAddress;
}
return {
command: `docker swarm join --token ${result.JoinTokens.Manager} ${ip}:2377`,
version: docker_version.Version,
};
}),
});