mirror of
https://github.com/Dokploy/dokploy.git
synced 2026-07-21 22:05:23 +02:00
The /terminal, /docker-container-terminal, /docker-container-logs and /docker-stats WebSocket handlers only checked session + organization, so any authenticated member could open a root shell / read logs of any container, and the local (serverId=local) branch reached a root SSH shell on the control-plane host. Adds an authorization layer (server/wss/authorize.ts): - container ops (terminal/logs/stats): require the docker permission (owner/admin or a member granted canAccessToDocker); for a remote server, require the server be accessible to the caller. - host/server terminal: the local host root terminal is restricted to owner/admin; a remote server terminal is gated on server access. Closes GHSA-c68r-7wg9-p7v2, GHSA-899j-cjwp-v4gw, GHSA-7r6p-v9gw-pwc8, GHSA-qf9j-c9p4-r4xp
70 lines
2.2 KiB
TypeScript
70 lines
2.2 KiB
TypeScript
import { getAccessibleServerIds } from "@dokploy/server";
|
|
import {
|
|
findMemberByUserId,
|
|
hasPermission,
|
|
} from "@dokploy/server/services/permission";
|
|
|
|
type WssUser = { id: string } | null | undefined;
|
|
type WssSession = { activeOrganizationId?: string | null } | null | undefined;
|
|
|
|
const buildCtx = (user: { id: string }, activeOrganizationId: string) => ({
|
|
user: { id: user.id },
|
|
session: { activeOrganizationId },
|
|
});
|
|
|
|
// Authorizes docker/container operations opened over a WebSocket (container
|
|
// terminal, container logs, container stats). Requires the docker permission
|
|
// (owner/admin, or a member explicitly granted canAccessToDocker) and, for a
|
|
// remote server, that the server is accessible to the caller. Previously these
|
|
// handlers only checked session + organization, so any member could reach a
|
|
// root shell / logs of any container.
|
|
export const canAccessDockerOverWss = async (
|
|
user: WssUser,
|
|
session: WssSession,
|
|
serverId?: string | null,
|
|
): Promise<boolean> => {
|
|
if (!user || !session?.activeOrganizationId) return false;
|
|
|
|
const ctx = buildCtx(user, session.activeOrganizationId);
|
|
if (!(await hasPermission(ctx, { docker: ["read"] }))) return false;
|
|
|
|
if (serverId && serverId !== "local") {
|
|
const accessible = await getAccessibleServerIds({
|
|
userId: user.id,
|
|
activeOrganizationId: session.activeOrganizationId,
|
|
});
|
|
if (!accessible.has(serverId)) return false;
|
|
}
|
|
|
|
return true;
|
|
};
|
|
|
|
// Authorizes the host/server SSH terminal opened over a WebSocket. The local
|
|
// host terminal is a root shell on the control-plane host, so it is restricted
|
|
// to owner/admin. A remote server terminal is gated on server access.
|
|
export const canAccessTerminalOverWss = async (
|
|
user: WssUser,
|
|
session: WssSession,
|
|
serverId?: string | null,
|
|
): Promise<boolean> => {
|
|
if (!user || !session?.activeOrganizationId) return false;
|
|
|
|
if (serverId && serverId !== "local") {
|
|
const accessible = await getAccessibleServerIds({
|
|
userId: user.id,
|
|
activeOrganizationId: session.activeOrganizationId,
|
|
});
|
|
return accessible.has(serverId);
|
|
}
|
|
|
|
try {
|
|
const member = await findMemberByUserId(
|
|
user.id,
|
|
session.activeOrganizationId,
|
|
);
|
|
return member?.role === "owner" || member?.role === "admin";
|
|
} catch {
|
|
return false;
|
|
}
|
|
};
|