mirror of
https://github.com/Dokploy/dokploy.git
synced 2026-07-21 13:55:33 +02:00
Revert "feat(security): enforce service-level access on docker WebSocket handlers"
This reverts commit 479d851829.
This commit is contained in:
@@ -3,11 +3,9 @@ import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
// Mock the permission + server helpers the wss authorizer composes.
|
||||
const mockHasPermission = vi.hoisted(() => vi.fn());
|
||||
const mockFindMember = vi.hoisted(() => vi.fn());
|
||||
const mockCheckServiceAccess = vi.hoisted(() => vi.fn());
|
||||
vi.mock("@dokploy/server/services/permission", () => ({
|
||||
hasPermission: mockHasPermission,
|
||||
findMemberByUserId: mockFindMember,
|
||||
checkServiceAccess: mockCheckServiceAccess,
|
||||
}));
|
||||
|
||||
const mockGetAccessibleServerIds = vi.hoisted(() => vi.fn());
|
||||
@@ -54,22 +52,6 @@ describe("canAccessDockerOverWss", () => {
|
||||
mockGetAccessibleServerIds.mockResolvedValue(new Set(["srv-1"]));
|
||||
expect(await canAccessDockerOverWss(USER, SESSION, "srv-1")).toBe(true);
|
||||
});
|
||||
|
||||
it("denies when the container belongs to a service the caller cannot access", async () => {
|
||||
mockHasPermission.mockResolvedValue(true);
|
||||
mockCheckServiceAccess.mockRejectedValue(new Error("no access"));
|
||||
expect(await canAccessDockerOverWss(USER, SESSION, null, "svc-1")).toBe(
|
||||
false,
|
||||
);
|
||||
});
|
||||
|
||||
it("allows when the caller has access to the container's service", async () => {
|
||||
mockHasPermission.mockResolvedValue(true);
|
||||
mockCheckServiceAccess.mockResolvedValue(undefined);
|
||||
expect(await canAccessDockerOverWss(USER, SESSION, null, "svc-1")).toBe(
|
||||
true,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("canAccessTerminalOverWss", () => {
|
||||
|
||||
@@ -206,7 +206,6 @@ export const ComposeActions = ({ composeId }: Props) => {
|
||||
appName={data?.appName || ""}
|
||||
serverId={data?.serverId || ""}
|
||||
appType={data?.composeType || "docker-compose"}
|
||||
serviceId={data?.composeId}
|
||||
>
|
||||
<Button
|
||||
variant="outline"
|
||||
|
||||
@@ -10,14 +10,12 @@ interface Props {
|
||||
id: string;
|
||||
containerId?: string;
|
||||
serverId?: string;
|
||||
serviceId?: string;
|
||||
}
|
||||
|
||||
export const DockerTerminal: React.FC<Props> = ({
|
||||
id,
|
||||
containerId,
|
||||
serverId,
|
||||
serviceId,
|
||||
}) => {
|
||||
const termRef = useRef(null);
|
||||
const [activeWay, setActiveWay] = React.useState<string | undefined>("bash");
|
||||
@@ -40,7 +38,7 @@ export const DockerTerminal: React.FC<Props> = ({
|
||||
const addonFit = new FitAddon();
|
||||
const protocol = window.location.protocol === "https:" ? "wss:" : "ws:";
|
||||
|
||||
const wsUrl = `${protocol}//${window.location.host}/docker-container-terminal?containerId=${containerId}&activeWay=${activeWay}${serverId ? `&serverId=${serverId}` : ""}${serviceId ? `&serviceId=${serviceId}` : ""}`;
|
||||
const wsUrl = `${protocol}//${window.location.host}/docker-container-terminal?containerId=${containerId}&activeWay=${activeWay}${serverId ? `&serverId=${serverId}` : ""}`;
|
||||
|
||||
const ws = new WebSocket(wsUrl);
|
||||
|
||||
|
||||
@@ -229,7 +229,6 @@ export const ShowGeneralLibsql = ({ libsqlId }: Props) => {
|
||||
)}
|
||||
<DockerTerminalModal
|
||||
appName={data?.appName || ""}
|
||||
serviceId={data?.libsqlId}
|
||||
serverId={data?.serverId || ""}
|
||||
>
|
||||
<Button
|
||||
|
||||
@@ -236,7 +236,6 @@ export const ShowGeneralMariadb = ({ mariadbId }: Props) => {
|
||||
))}
|
||||
<DockerTerminalModal
|
||||
appName={data?.appName || ""}
|
||||
serviceId={data?.mariadbId}
|
||||
serverId={data?.serverId || ""}
|
||||
>
|
||||
<Button
|
||||
|
||||
@@ -230,7 +230,6 @@ export const ShowGeneralMongo = ({ mongoId }: Props) => {
|
||||
</TooltipProvider>
|
||||
<DockerTerminalModal
|
||||
appName={data?.appName || ""}
|
||||
serviceId={data?.mongoId}
|
||||
serverId={data?.serverId || ""}
|
||||
>
|
||||
<Button
|
||||
|
||||
@@ -228,7 +228,6 @@ export const ShowGeneralMysql = ({ mysqlId }: Props) => {
|
||||
</TooltipProvider>
|
||||
<DockerTerminalModal
|
||||
appName={data?.appName || ""}
|
||||
serviceId={data?.mysqlId}
|
||||
serverId={data?.serverId || ""}
|
||||
>
|
||||
<Button
|
||||
|
||||
@@ -234,7 +234,6 @@ export const ShowGeneralPostgres = ({ postgresId }: Props) => {
|
||||
</TooltipProvider>
|
||||
<DockerTerminalModal
|
||||
appName={data?.appName || ""}
|
||||
serviceId={data?.postgresId}
|
||||
serverId={data?.serverId || ""}
|
||||
>
|
||||
<Button
|
||||
|
||||
@@ -229,7 +229,6 @@ export const ShowGeneralRedis = ({ redisId }: Props) => {
|
||||
</TooltipProvider>
|
||||
<DockerTerminalModal
|
||||
appName={data?.appName || ""}
|
||||
serviceId={data?.redisId}
|
||||
serverId={data?.serverId || ""}
|
||||
>
|
||||
<Button
|
||||
|
||||
@@ -40,7 +40,6 @@ interface Props {
|
||||
children?: React.ReactNode;
|
||||
serverId?: string;
|
||||
appType?: "stack" | "docker-compose";
|
||||
serviceId?: string;
|
||||
}
|
||||
|
||||
export const DockerTerminalModal = ({
|
||||
@@ -48,7 +47,6 @@ export const DockerTerminalModal = ({
|
||||
appName,
|
||||
serverId,
|
||||
appType,
|
||||
serviceId,
|
||||
}: Props) => {
|
||||
const { data, isPending } = api.docker.getContainersByAppNameMatch.useQuery(
|
||||
{
|
||||
@@ -133,7 +131,6 @@ export const DockerTerminalModal = ({
|
||||
serverId={serverId || ""}
|
||||
id="terminal"
|
||||
containerId={containerId || "select-a-container"}
|
||||
serviceId={serviceId}
|
||||
/>
|
||||
<Dialog open={confirmDialogOpen} onOpenChange={setConfirmDialogOpen}>
|
||||
<DialogContent onEscapeKeyDown={(event) => event.preventDefault()}>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { getAccessibleServerIds } from "@dokploy/server";
|
||||
import {
|
||||
checkServiceAccess,
|
||||
findMemberByUserId,
|
||||
hasPermission,
|
||||
} from "@dokploy/server/services/permission";
|
||||
@@ -23,7 +22,6 @@ export const canAccessDockerOverWss = async (
|
||||
user: WssUser,
|
||||
session: WssSession,
|
||||
serverId?: string | null,
|
||||
serviceId?: string | null,
|
||||
): Promise<boolean> => {
|
||||
if (!user || !session?.activeOrganizationId) return false;
|
||||
|
||||
@@ -38,18 +36,6 @@ export const canAccessDockerOverWss = async (
|
||||
if (!accessible.has(serverId)) return false;
|
||||
}
|
||||
|
||||
// When the container belongs to a known Dokploy service (opened from a
|
||||
// service page), enforce service-level access too. Container terminals
|
||||
// opened from the generic Docker overview have no serviceId and fall back to
|
||||
// the docker-permission + server checks above.
|
||||
if (serviceId) {
|
||||
try {
|
||||
await checkServiceAccess(ctx, serviceId, "read");
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
|
||||
@@ -42,7 +42,6 @@ export const setupDockerContainerLogsWebSocketServer = (
|
||||
const since = url.searchParams.get("since") ?? "all";
|
||||
const serverId = url.searchParams.get("serverId");
|
||||
const runType = url.searchParams.get("runType");
|
||||
const serviceId = url.searchParams.get("serviceId");
|
||||
const { user, session } = await validateRequest(req);
|
||||
|
||||
if (!containerId) {
|
||||
@@ -76,7 +75,7 @@ export const setupDockerContainerLogsWebSocketServer = (
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(await canAccessDockerOverWss(user, session, serverId, serviceId))) {
|
||||
if (!(await canAccessDockerOverWss(user, session, serverId))) {
|
||||
ws.close(4003, "Not authorized");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -33,7 +33,6 @@ export const setupDockerContainerTerminalWebSocketServer = (
|
||||
const containerId = url.searchParams.get("containerId");
|
||||
const activeWay = url.searchParams.get("activeWay");
|
||||
const serverId = url.searchParams.get("serverId");
|
||||
const serviceId = url.searchParams.get("serviceId");
|
||||
const { user, session } = await validateRequest(req);
|
||||
|
||||
if (!containerId) {
|
||||
@@ -61,7 +60,7 @@ export const setupDockerContainerTerminalWebSocketServer = (
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(await canAccessDockerOverWss(user, session, serverId, serviceId))) {
|
||||
if (!(await canAccessDockerOverWss(user, session, serverId))) {
|
||||
ws.close(4003, "Not authorized");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -45,7 +45,6 @@ export const setupDockerStatsMonitoringSocketServer = (
|
||||
| "application"
|
||||
| "stack"
|
||||
| "docker-compose";
|
||||
const serviceId = url.searchParams.get("serviceId");
|
||||
const { user, session } = await validateRequest(req);
|
||||
|
||||
if (!appName) {
|
||||
@@ -58,7 +57,7 @@ export const setupDockerStatsMonitoringSocketServer = (
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(await canAccessDockerOverWss(user, session, null, serviceId))) {
|
||||
if (!(await canAccessDockerOverWss(user, session))) {
|
||||
ws.close(4003, "Not authorized");
|
||||
return;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user