From 20ebfcefaa999c02405e2a27dd9e8395bb1fefdc Mon Sep 17 00:00:00 2001 From: Tuan Dang Date: Tue, 8 Apr 2025 18:45:16 -0700 Subject: [PATCH] Update permission logic --- .../services/permission/project-permission.ts | 2 - .../ee/services/ssh-host/ssh-host-service.ts | 9 +-- .../src/services/project/project-service.ts | 6 +- .../context/ProjectPermissionContext/types.ts | 1 - frontend/src/hooks/api/sshHost/index.tsx | 2 +- frontend/src/hooks/api/sshHost/queries.tsx | 10 ++- .../layouts/ProjectLayout/ProjectLayout.tsx | 8 +-- .../ProjectRoleModifySection.utils.tsx | 45 +++++++------- .../components/SshCertificatesTable.tsx | 4 +- .../components/SshHostsSection.tsx | 24 ++++---- .../SshHostsPage/components/SshHostsTable.tsx | 61 +++++++++++++++++-- 11 files changed, 109 insertions(+), 63 deletions(-) diff --git a/backend/src/ee/services/permission/project-permission.ts b/backend/src/ee/services/permission/project-permission.ts index 0ebeeb36c..12c9c2bcc 100644 --- a/backend/src/ee/services/permission/project-permission.ts +++ b/backend/src/ee/services/permission/project-permission.ts @@ -68,7 +68,6 @@ export enum ProjectPermissionGroupActions { } export enum ProjectPermissionSshHostActions { - Read = "read", Create = "create", Edit = "edit", Delete = "delete", @@ -629,7 +628,6 @@ const buildAdminPermissionRules = () => { can( [ - ProjectPermissionSshHostActions.Read, ProjectPermissionSshHostActions.Edit, ProjectPermissionSshHostActions.Create, ProjectPermissionSshHostActions.Delete, diff --git a/backend/src/ee/services/ssh-host/ssh-host-service.ts b/backend/src/ee/services/ssh-host/ssh-host-service.ts index 96c7b2ae1..fb8d73da4 100644 --- a/backend/src/ee/services/ssh-host/ssh-host-service.ts +++ b/backend/src/ee/services/ssh-host/ssh-host-service.ts @@ -337,7 +337,7 @@ export const sshHostServiceFactory = ({ }); } - const { permission } = await permissionService.getProjectPermission({ + await permissionService.getProjectPermission({ actor, actorId, projectId: host.projectId, @@ -346,13 +346,6 @@ export const sshHostServiceFactory = ({ actionProjectType: ActionProjectType.SSH }); - ForbiddenError.from(permission).throwUnlessCan( - ProjectPermissionSshHostActions.Read, - subject(ProjectPermissionSub.SshHosts, { - hostname: host.hostname - }) - ); - return host; }; diff --git a/backend/src/services/project/project-service.ts b/backend/src/services/project/project-service.ts index c6cee547d..68e123fe7 100644 --- a/backend/src/services/project/project-service.ts +++ b/backend/src/services/project/project-service.ts @@ -1068,8 +1068,7 @@ export const projectServiceFactory = ({ actor, projectId }: TListProjectSshHostsDTO) => { - console.log("listProjectSshHosts: ", actor, actorId, actorAuthMethod, actorOrgId, projectId); - const { permission } = await permissionService.getProjectPermission({ + await permissionService.getProjectPermission({ actor, actorId, projectId, @@ -1078,10 +1077,7 @@ export const projectServiceFactory = ({ actionProjectType: ActionProjectType.SSH }); - ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SshHosts); - const hosts = await sshHostDAL.findSshHostsWithLoginMappings(projectId); - return hosts; }; diff --git a/frontend/src/context/ProjectPermissionContext/types.ts b/frontend/src/context/ProjectPermissionContext/types.ts index bebf73218..f8b0268af 100644 --- a/frontend/src/context/ProjectPermissionContext/types.ts +++ b/frontend/src/context/ProjectPermissionContext/types.ts @@ -76,7 +76,6 @@ export enum ProjectPermissionGroupActions { } export enum ProjectPermissionSshHostActions { - Read = "read", Create = "create", Edit = "edit", Delete = "delete", diff --git a/frontend/src/hooks/api/sshHost/index.tsx b/frontend/src/hooks/api/sshHost/index.tsx index 2da612286..a4e4da4e1 100644 --- a/frontend/src/hooks/api/sshHost/index.tsx +++ b/frontend/src/hooks/api/sshHost/index.tsx @@ -1,2 +1,2 @@ export { useCreateSshHost, useDeleteSshHost, useUpdateSshHost } from "./mutations"; -export { useGetSshHostById } from "./queries"; +export { fetchSshHostUserCaPublicKey, useGetSshHostById } from "./queries"; diff --git a/frontend/src/hooks/api/sshHost/queries.tsx b/frontend/src/hooks/api/sshHost/queries.tsx index b8fac7270..33974e0de 100644 --- a/frontend/src/hooks/api/sshHost/queries.tsx +++ b/frontend/src/hooks/api/sshHost/queries.tsx @@ -5,7 +5,8 @@ import { apiRequest } from "@app/config/request"; import { TSshHost } from "./types"; export const sshHostKeys = { - getSshHostById: (sshHostId: string) => [{ sshHostId }, "ssh-host"] + getSshHostById: (sshHostId: string) => [{ sshHostId }, "ssh-host"], + getSshHostUserCaPublicKey: (sshHostId: string) => [{ sshHostId }, "ssh-host-user-ca-public-key"] }; export const useGetSshHostById = (sshHostId: string) => { @@ -18,3 +19,10 @@ export const useGetSshHostById = (sshHostId: string) => { enabled: Boolean(sshHostId) }); }; + +export const fetchSshHostUserCaPublicKey = async (sshHostId: string): Promise => { + const { data } = await apiRequest.get( + `/api/v1/ssh/hosts/${sshHostId}/user-ca-public-key` + ); + return data; +}; diff --git a/frontend/src/layouts/ProjectLayout/ProjectLayout.tsx b/frontend/src/layouts/ProjectLayout/ProjectLayout.tsx index 90ba72e3f..81435cc6c 100644 --- a/frontend/src/layouts/ProjectLayout/ProjectLayout.tsx +++ b/frontend/src/layouts/ProjectLayout/ProjectLayout.tsx @@ -133,7 +133,7 @@ export const ProjectLayout = () => { )} - { Certificates )} - - */} + {/* { Certificate Authorities )} - + */} )} {isSecretManager && ( diff --git a/frontend/src/pages/project/RoleDetailsBySlugPage/components/ProjectRoleModifySection.utils.tsx b/frontend/src/pages/project/RoleDetailsBySlugPage/components/ProjectRoleModifySection.utils.tsx index 3b7f9a04b..c62d57411 100644 --- a/frontend/src/pages/project/RoleDetailsBySlugPage/components/ProjectRoleModifySection.utils.tsx +++ b/frontend/src/pages/project/RoleDetailsBySlugPage/components/ProjectRoleModifySection.utils.tsx @@ -100,7 +100,6 @@ const GroupPolicyActionSchema = z.object({ }); const SshHostPolicyActionSchema = z.object({ - [ProjectPermissionSshHostActions.Read]: z.boolean().optional(), [ProjectPermissionSshHostActions.Create]: z.boolean().optional(), [ProjectPermissionSshHostActions.Edit]: z.boolean().optional(), [ProjectPermissionSshHostActions.Delete]: z.boolean().optional(), @@ -559,28 +558,27 @@ export const rolePermission2Form = (permissions: TProjectPermission[] = []) => { } if (subject === ProjectPermissionSub.SshHosts) { - const canRead = action.includes(ProjectPermissionSshHostActions.Read); - const canEdit = action.includes(ProjectPermissionSshHostActions.Edit); - const canDelete = action.includes(ProjectPermissionSshHostActions.Delete); - const canCreate = action.includes(ProjectPermissionSshHostActions.Create); - const canIssueUserCert = action.includes(ProjectPermissionSshHostActions.IssueUserCert); - const canIssueHostCert = action.includes(ProjectPermissionSshHostActions.IssueHostCert); + if (!formVal[subject]) formVal[subject] = []; - if (!formVal[subject]) formVal[subject] = [{ conditions: [] }]; - - if (canRead) formVal[subject]![0][ProjectPermissionSshHostActions.Read] = true; - if (canEdit) formVal[subject]![0][ProjectPermissionSshHostActions.Edit] = true; - if (canDelete) formVal[subject]![0][ProjectPermissionSshHostActions.Delete] = true; - if (canCreate) formVal[subject]![0][ProjectPermissionSshHostActions.Create] = true; - if (canIssueUserCert) - formVal[subject]![0][ProjectPermissionSshHostActions.IssueUserCert] = true; - if (canIssueHostCert) - formVal[subject]![0][ProjectPermissionSshHostActions.IssueHostCert] = true; - - formVal[subject]![0].conditions = conditions - ? convertCaslConditionToFormOperator(conditions) - : []; - formVal[subject]![0].inverted = inverted; + formVal[subject]!.push({ + [ProjectPermissionSshHostActions.Edit]: action.includes( + ProjectPermissionSshHostActions.Edit + ), + [ProjectPermissionSshHostActions.Delete]: action.includes( + ProjectPermissionSshHostActions.Delete + ), + [ProjectPermissionSshHostActions.Create]: action.includes( + ProjectPermissionSshHostActions.Create + ), + [ProjectPermissionSshHostActions.IssueUserCert]: action.includes( + ProjectPermissionSshHostActions.IssueUserCert + ), + [ProjectPermissionSshHostActions.IssueHostCert]: action.includes( + ProjectPermissionSshHostActions.IssueHostCert + ), + conditions: conditions ? convertCaslConditionToFormOperator(conditions) : [], + inverted + }); } }); @@ -910,11 +908,10 @@ export const PROJECT_PERMISSION_OBJECT: TProjectPermissionObject = { [ProjectPermissionSub.SshHosts]: { title: "SSH Hosts", actions: [ - { label: "Read", value: ProjectPermissionSshHostActions.Read }, { label: "Create", value: ProjectPermissionSshHostActions.Create }, { label: "Modify", value: ProjectPermissionSshHostActions.Edit }, { label: "Remove", value: ProjectPermissionSshHostActions.Delete }, - { label: "Connect", value: ProjectPermissionSshHostActions.IssueUserCert }, + { label: "Issue User Certificate", value: ProjectPermissionSshHostActions.IssueUserCert }, { label: "Issue Host Certificate", value: ProjectPermissionSshHostActions.IssueHostCert } ] }, diff --git a/frontend/src/pages/ssh/SshCertsPage/components/SshCertificatesTable.tsx b/frontend/src/pages/ssh/SshCertsPage/components/SshCertificatesTable.tsx index 158d1de80..c48b407ac 100644 --- a/frontend/src/pages/ssh/SshCertsPage/components/SshCertificatesTable.tsx +++ b/frontend/src/pages/ssh/SshCertsPage/components/SshCertificatesTable.tsx @@ -57,12 +57,12 @@ export const SshCertificatesTable = () => { {certificate.notBefore - ? format(new Date(certificate.notBefore), "yyyy-MM-dd") + ? format(new Date(certificate.notBefore), "yyyy-MM-dd | HH:mm:ss") : "-"} {certificate.notAfter - ? format(new Date(certificate.notAfter), "yyyy-MM-dd") + ? format(new Date(certificate.notAfter), "yyyy-MM-dd | HH:mm:ss") : "-"} diff --git a/frontend/src/pages/ssh/SshHostsPage/components/SshHostsSection.tsx b/frontend/src/pages/ssh/SshHostsPage/components/SshHostsSection.tsx index 5b43b3443..c15cf5d04 100644 --- a/frontend/src/pages/ssh/SshHostsPage/components/SshHostsSection.tsx +++ b/frontend/src/pages/ssh/SshHostsPage/components/SshHostsSection.tsx @@ -43,17 +43,19 @@ export const SshHostsSection = () => {

Hosts

- {(isAllowed) => ( - - )} + {(isAllowed) => + isAllowed && ( + + ) + }
diff --git a/frontend/src/pages/ssh/SshHostsPage/components/SshHostsTable.tsx b/frontend/src/pages/ssh/SshHostsPage/components/SshHostsTable.tsx index 8b68cb581..4fe971ed4 100644 --- a/frontend/src/pages/ssh/SshHostsPage/components/SshHostsTable.tsx +++ b/frontend/src/pages/ssh/SshHostsPage/components/SshHostsTable.tsx @@ -1,7 +1,15 @@ -import { faEllipsis, faPencil, faServer, faTrash } from "@fortawesome/free-solid-svg-icons"; +import { + faDownload, + faEllipsis, + faPencil, + faServer, + faTrash +} from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import FileSaver from "file-saver"; import { twMerge } from "tailwind-merge"; +import { createNotification } from "@app/components/notifications"; import { ProjectPermissionCan } from "@app/components/permissions"; import { DropdownMenu, @@ -20,7 +28,7 @@ import { Tr } from "@app/components/v2"; import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context"; -import { useListWorkspaceSshHosts } from "@app/hooks/api"; +import { fetchSshHostUserCaPublicKey, useListWorkspaceSshHosts } from "@app/hooks/api"; import { UsePopUpState } from "@app/hooks/usePopUp"; type Props = { @@ -34,6 +42,24 @@ export const SshHostsTable = ({ handlePopUpOpen }: Props) => { const { currentWorkspace } = useWorkspace(); const { data, isPending } = useListWorkspaceSshHosts(currentWorkspace?.id || ""); + const downloadTxtFile = (filename: string, content: string) => { + const blob = new Blob([content], { type: "text/plain;charset=utf-8" }); + FileSaver.saveAs(blob, filename); + }; + + const handleDownloadUserCaKey = async (sshHostId: string) => { + try { + const publicKey = await fetchSshHostUserCaPublicKey(sshHostId); + downloadTxtFile("infisical_user_ca.pub", publicKey); + } catch (err) { + console.error("Failed to download User CA public key", err); + createNotification({ + type: "error", + text: "Failed to download User CA public key" + }); + } + }; + return (
@@ -41,6 +67,7 @@ export const SshHostsTable = ({ handlePopUpOpen }: Props) => { Hostname + Login User - Authorized Principals Mapping @@ -52,11 +79,28 @@ export const SshHostsTable = ({ handlePopUpOpen }: Props) => { data.map((host) => { return ( {host.hostname} - + + {host.loginMappings.length === 0 ? ( + None + ) : ( + host.loginMappings.map(({ loginUser, allowedPrincipals }) => ( +
+
{loginUser}
+ {allowedPrincipals.map((principal) => ( +
+ └─ {principal} +
+ ))} +
+ )) + )} + +
@@ -66,6 +110,15 @@ export const SshHostsTable = ({ handlePopUpOpen }: Props) => {
+ { + e.stopPropagation(); + handleDownloadUserCaKey(host.id); + }} + icon={} + > + Download User CA Public Key +