mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-09-22 13:39:35 +00:00
Update permission logic
This commit is contained in:
@@ -68,7 +68,6 @@ export enum ProjectPermissionGroupActions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export enum ProjectPermissionSshHostActions {
|
export enum ProjectPermissionSshHostActions {
|
||||||
Read = "read",
|
|
||||||
Create = "create",
|
Create = "create",
|
||||||
Edit = "edit",
|
Edit = "edit",
|
||||||
Delete = "delete",
|
Delete = "delete",
|
||||||
@@ -629,7 +628,6 @@ const buildAdminPermissionRules = () => {
|
|||||||
|
|
||||||
can(
|
can(
|
||||||
[
|
[
|
||||||
ProjectPermissionSshHostActions.Read,
|
|
||||||
ProjectPermissionSshHostActions.Edit,
|
ProjectPermissionSshHostActions.Edit,
|
||||||
ProjectPermissionSshHostActions.Create,
|
ProjectPermissionSshHostActions.Create,
|
||||||
ProjectPermissionSshHostActions.Delete,
|
ProjectPermissionSshHostActions.Delete,
|
||||||
|
|||||||
@@ -337,7 +337,7 @@ export const sshHostServiceFactory = ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const { permission } = await permissionService.getProjectPermission({
|
await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
projectId: host.projectId,
|
projectId: host.projectId,
|
||||||
@@ -346,13 +346,6 @@ export const sshHostServiceFactory = ({
|
|||||||
actionProjectType: ActionProjectType.SSH
|
actionProjectType: ActionProjectType.SSH
|
||||||
});
|
});
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
|
||||||
ProjectPermissionSshHostActions.Read,
|
|
||||||
subject(ProjectPermissionSub.SshHosts, {
|
|
||||||
hostname: host.hostname
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
return host;
|
return host;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1068,8 +1068,7 @@ export const projectServiceFactory = ({
|
|||||||
actor,
|
actor,
|
||||||
projectId
|
projectId
|
||||||
}: TListProjectSshHostsDTO) => {
|
}: TListProjectSshHostsDTO) => {
|
||||||
console.log("listProjectSshHosts: ", actor, actorId, actorAuthMethod, actorOrgId, projectId);
|
await permissionService.getProjectPermission({
|
||||||
const { permission } = await permissionService.getProjectPermission({
|
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
projectId,
|
projectId,
|
||||||
@@ -1078,10 +1077,7 @@ export const projectServiceFactory = ({
|
|||||||
actionProjectType: ActionProjectType.SSH
|
actionProjectType: ActionProjectType.SSH
|
||||||
});
|
});
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SshHosts);
|
|
||||||
|
|
||||||
const hosts = await sshHostDAL.findSshHostsWithLoginMappings(projectId);
|
const hosts = await sshHostDAL.findSshHostsWithLoginMappings(projectId);
|
||||||
|
|
||||||
return hosts;
|
return hosts;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -76,7 +76,6 @@ export enum ProjectPermissionGroupActions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export enum ProjectPermissionSshHostActions {
|
export enum ProjectPermissionSshHostActions {
|
||||||
Read = "read",
|
|
||||||
Create = "create",
|
Create = "create",
|
||||||
Edit = "edit",
|
Edit = "edit",
|
||||||
Delete = "delete",
|
Delete = "delete",
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
export { useCreateSshHost, useDeleteSshHost, useUpdateSshHost } from "./mutations";
|
export { useCreateSshHost, useDeleteSshHost, useUpdateSshHost } from "./mutations";
|
||||||
export { useGetSshHostById } from "./queries";
|
export { fetchSshHostUserCaPublicKey, useGetSshHostById } from "./queries";
|
||||||
|
|||||||
@@ -5,7 +5,8 @@ import { apiRequest } from "@app/config/request";
|
|||||||
import { TSshHost } from "./types";
|
import { TSshHost } from "./types";
|
||||||
|
|
||||||
export const sshHostKeys = {
|
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) => {
|
export const useGetSshHostById = (sshHostId: string) => {
|
||||||
@@ -18,3 +19,10 @@ export const useGetSshHostById = (sshHostId: string) => {
|
|||||||
enabled: Boolean(sshHostId)
|
enabled: Boolean(sshHostId)
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const fetchSshHostUserCaPublicKey = async (sshHostId: string): Promise<string> => {
|
||||||
|
const { data } = await apiRequest.get<string>(
|
||||||
|
`/api/v1/ssh/hosts/${sshHostId}/user-ca-public-key`
|
||||||
|
);
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
|||||||
@@ -133,7 +133,7 @@ export const ProjectLayout = () => {
|
|||||||
</MenuItem>
|
</MenuItem>
|
||||||
)}
|
)}
|
||||||
</Link>
|
</Link>
|
||||||
<Link
|
{/* <Link
|
||||||
to={`/${ProjectType.SSH}/$projectId/certificates` as const}
|
to={`/${ProjectType.SSH}/$projectId/certificates` as const}
|
||||||
params={{
|
params={{
|
||||||
projectId: currentWorkspace.id
|
projectId: currentWorkspace.id
|
||||||
@@ -144,8 +144,8 @@ export const ProjectLayout = () => {
|
|||||||
Certificates
|
Certificates
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
)}
|
)}
|
||||||
</Link>
|
</Link> */}
|
||||||
<Link
|
{/* <Link
|
||||||
to={`/${ProjectType.SSH}/$projectId/cas` as const}
|
to={`/${ProjectType.SSH}/$projectId/cas` as const}
|
||||||
params={{
|
params={{
|
||||||
projectId: currentWorkspace.id
|
projectId: currentWorkspace.id
|
||||||
@@ -160,7 +160,7 @@ export const ProjectLayout = () => {
|
|||||||
Certificate Authorities
|
Certificate Authorities
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
)}
|
)}
|
||||||
</Link>
|
</Link> */}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{isSecretManager && (
|
{isSecretManager && (
|
||||||
|
|||||||
@@ -100,7 +100,6 @@ const GroupPolicyActionSchema = z.object({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const SshHostPolicyActionSchema = z.object({
|
const SshHostPolicyActionSchema = z.object({
|
||||||
[ProjectPermissionSshHostActions.Read]: z.boolean().optional(),
|
|
||||||
[ProjectPermissionSshHostActions.Create]: z.boolean().optional(),
|
[ProjectPermissionSshHostActions.Create]: z.boolean().optional(),
|
||||||
[ProjectPermissionSshHostActions.Edit]: z.boolean().optional(),
|
[ProjectPermissionSshHostActions.Edit]: z.boolean().optional(),
|
||||||
[ProjectPermissionSshHostActions.Delete]: z.boolean().optional(),
|
[ProjectPermissionSshHostActions.Delete]: z.boolean().optional(),
|
||||||
@@ -559,28 +558,27 @@ export const rolePermission2Form = (permissions: TProjectPermission[] = []) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (subject === ProjectPermissionSub.SshHosts) {
|
if (subject === ProjectPermissionSub.SshHosts) {
|
||||||
const canRead = action.includes(ProjectPermissionSshHostActions.Read);
|
if (!formVal[subject]) formVal[subject] = [];
|
||||||
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] = [{ conditions: [] }];
|
formVal[subject]!.push({
|
||||||
|
[ProjectPermissionSshHostActions.Edit]: action.includes(
|
||||||
if (canRead) formVal[subject]![0][ProjectPermissionSshHostActions.Read] = true;
|
ProjectPermissionSshHostActions.Edit
|
||||||
if (canEdit) formVal[subject]![0][ProjectPermissionSshHostActions.Edit] = true;
|
),
|
||||||
if (canDelete) formVal[subject]![0][ProjectPermissionSshHostActions.Delete] = true;
|
[ProjectPermissionSshHostActions.Delete]: action.includes(
|
||||||
if (canCreate) formVal[subject]![0][ProjectPermissionSshHostActions.Create] = true;
|
ProjectPermissionSshHostActions.Delete
|
||||||
if (canIssueUserCert)
|
),
|
||||||
formVal[subject]![0][ProjectPermissionSshHostActions.IssueUserCert] = true;
|
[ProjectPermissionSshHostActions.Create]: action.includes(
|
||||||
if (canIssueHostCert)
|
ProjectPermissionSshHostActions.Create
|
||||||
formVal[subject]![0][ProjectPermissionSshHostActions.IssueHostCert] = true;
|
),
|
||||||
|
[ProjectPermissionSshHostActions.IssueUserCert]: action.includes(
|
||||||
formVal[subject]![0].conditions = conditions
|
ProjectPermissionSshHostActions.IssueUserCert
|
||||||
? convertCaslConditionToFormOperator(conditions)
|
),
|
||||||
: [];
|
[ProjectPermissionSshHostActions.IssueHostCert]: action.includes(
|
||||||
formVal[subject]![0].inverted = inverted;
|
ProjectPermissionSshHostActions.IssueHostCert
|
||||||
|
),
|
||||||
|
conditions: conditions ? convertCaslConditionToFormOperator(conditions) : [],
|
||||||
|
inverted
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -910,11 +908,10 @@ export const PROJECT_PERMISSION_OBJECT: TProjectPermissionObject = {
|
|||||||
[ProjectPermissionSub.SshHosts]: {
|
[ProjectPermissionSub.SshHosts]: {
|
||||||
title: "SSH Hosts",
|
title: "SSH Hosts",
|
||||||
actions: [
|
actions: [
|
||||||
{ label: "Read", value: ProjectPermissionSshHostActions.Read },
|
|
||||||
{ label: "Create", value: ProjectPermissionSshHostActions.Create },
|
{ label: "Create", value: ProjectPermissionSshHostActions.Create },
|
||||||
{ label: "Modify", value: ProjectPermissionSshHostActions.Edit },
|
{ label: "Modify", value: ProjectPermissionSshHostActions.Edit },
|
||||||
{ label: "Remove", value: ProjectPermissionSshHostActions.Delete },
|
{ label: "Remove", value: ProjectPermissionSshHostActions.Delete },
|
||||||
{ label: "Connect", value: ProjectPermissionSshHostActions.IssueUserCert },
|
{ label: "Issue User Certificate", value: ProjectPermissionSshHostActions.IssueUserCert },
|
||||||
{ label: "Issue Host Certificate", value: ProjectPermissionSshHostActions.IssueHostCert }
|
{ label: "Issue Host Certificate", value: ProjectPermissionSshHostActions.IssueHostCert }
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -57,12 +57,12 @@ export const SshCertificatesTable = () => {
|
|||||||
</Td>
|
</Td>
|
||||||
<Td>
|
<Td>
|
||||||
{certificate.notBefore
|
{certificate.notBefore
|
||||||
? format(new Date(certificate.notBefore), "yyyy-MM-dd")
|
? format(new Date(certificate.notBefore), "yyyy-MM-dd | HH:mm:ss")
|
||||||
: "-"}
|
: "-"}
|
||||||
</Td>
|
</Td>
|
||||||
<Td>
|
<Td>
|
||||||
{certificate.notAfter
|
{certificate.notAfter
|
||||||
? format(new Date(certificate.notAfter), "yyyy-MM-dd")
|
? format(new Date(certificate.notAfter), "yyyy-MM-dd | HH:mm:ss")
|
||||||
: "-"}
|
: "-"}
|
||||||
</Td>
|
</Td>
|
||||||
</Tr>
|
</Tr>
|
||||||
|
|||||||
@@ -43,17 +43,19 @@ export const SshHostsSection = () => {
|
|||||||
<div className="mb-4 flex justify-between">
|
<div className="mb-4 flex justify-between">
|
||||||
<p className="text-xl font-semibold text-mineshaft-100">Hosts</p>
|
<p className="text-xl font-semibold text-mineshaft-100">Hosts</p>
|
||||||
<ProjectPermissionCan I={ProjectPermissionActions.Create} a={ProjectPermissionSub.SshHosts}>
|
<ProjectPermissionCan I={ProjectPermissionActions.Create} a={ProjectPermissionSub.SshHosts}>
|
||||||
{(isAllowed) => (
|
{(isAllowed) =>
|
||||||
<Button
|
isAllowed && (
|
||||||
colorSchema="primary"
|
<Button
|
||||||
type="submit"
|
colorSchema="primary"
|
||||||
leftIcon={<FontAwesomeIcon icon={faPlus} />}
|
type="submit"
|
||||||
onClick={() => handlePopUpOpen("sshHost")}
|
leftIcon={<FontAwesomeIcon icon={faPlus} />}
|
||||||
isDisabled={!isAllowed}
|
onClick={() => handlePopUpOpen("sshHost")}
|
||||||
>
|
isDisabled={!isAllowed}
|
||||||
Add Host
|
>
|
||||||
</Button>
|
Add Host
|
||||||
)}
|
</Button>
|
||||||
|
)
|
||||||
|
}
|
||||||
</ProjectPermissionCan>
|
</ProjectPermissionCan>
|
||||||
</div>
|
</div>
|
||||||
<SshHostsTable handlePopUpOpen={handlePopUpOpen} />
|
<SshHostsTable handlePopUpOpen={handlePopUpOpen} />
|
||||||
|
|||||||
@@ -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 { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
import FileSaver from "file-saver";
|
||||||
import { twMerge } from "tailwind-merge";
|
import { twMerge } from "tailwind-merge";
|
||||||
|
|
||||||
|
import { createNotification } from "@app/components/notifications";
|
||||||
import { ProjectPermissionCan } from "@app/components/permissions";
|
import { ProjectPermissionCan } from "@app/components/permissions";
|
||||||
import {
|
import {
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
@@ -20,7 +28,7 @@ import {
|
|||||||
Tr
|
Tr
|
||||||
} from "@app/components/v2";
|
} from "@app/components/v2";
|
||||||
import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context";
|
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";
|
import { UsePopUpState } from "@app/hooks/usePopUp";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@@ -34,6 +42,24 @@ export const SshHostsTable = ({ handlePopUpOpen }: Props) => {
|
|||||||
const { currentWorkspace } = useWorkspace();
|
const { currentWorkspace } = useWorkspace();
|
||||||
const { data, isPending } = useListWorkspaceSshHosts(currentWorkspace?.id || "");
|
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 (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<TableContainer>
|
<TableContainer>
|
||||||
@@ -41,6 +67,7 @@ export const SshHostsTable = ({ handlePopUpOpen }: Props) => {
|
|||||||
<THead>
|
<THead>
|
||||||
<Tr>
|
<Tr>
|
||||||
<Th>Hostname</Th>
|
<Th>Hostname</Th>
|
||||||
|
<Th>Login User - Authorized Principals Mapping</Th>
|
||||||
<Th />
|
<Th />
|
||||||
</Tr>
|
</Tr>
|
||||||
</THead>
|
</THead>
|
||||||
@@ -52,11 +79,28 @@ export const SshHostsTable = ({ handlePopUpOpen }: Props) => {
|
|||||||
data.map((host) => {
|
data.map((host) => {
|
||||||
return (
|
return (
|
||||||
<Tr
|
<Tr
|
||||||
className="h-10 cursor-pointer transition-colors duration-100 hover:bg-mineshaft-700"
|
// className="h-10 cursor-pointer transition-colors duration-100 hover:bg-mineshaft-700"
|
||||||
|
className="h-10"
|
||||||
key={`ssh-host-${host.id}`}
|
key={`ssh-host-${host.id}`}
|
||||||
>
|
>
|
||||||
<Td>{host.hostname}</Td>
|
<Td>{host.hostname}</Td>
|
||||||
<Td className="flex justify-end">
|
<Td>
|
||||||
|
{host.loginMappings.length === 0 ? (
|
||||||
|
<span className="italic text-mineshaft-400">None</span>
|
||||||
|
) : (
|
||||||
|
host.loginMappings.map(({ loginUser, allowedPrincipals }) => (
|
||||||
|
<div key={`${host.id}-${loginUser}`} className="mb-2">
|
||||||
|
<div className="text-mineshaft-200">{loginUser}</div>
|
||||||
|
{allowedPrincipals.map((principal) => (
|
||||||
|
<div key={principal} className="ml-4">
|
||||||
|
└─ {principal}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
</Td>
|
||||||
|
<Td className="text-right align-middle">
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger asChild className="rounded-lg">
|
<DropdownMenuTrigger asChild className="rounded-lg">
|
||||||
<div className="hover:text-primary-400 data-[state=open]:text-primary-400">
|
<div className="hover:text-primary-400 data-[state=open]:text-primary-400">
|
||||||
@@ -66,6 +110,15 @@ export const SshHostsTable = ({ handlePopUpOpen }: Props) => {
|
|||||||
</div>
|
</div>
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent align="start" className="p-1">
|
<DropdownMenuContent align="start" className="p-1">
|
||||||
|
<DropdownMenuItem
|
||||||
|
onClick={async (e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
handleDownloadUserCaKey(host.id);
|
||||||
|
}}
|
||||||
|
icon={<FontAwesomeIcon icon={faDownload} />}
|
||||||
|
>
|
||||||
|
Download User CA Public Key
|
||||||
|
</DropdownMenuItem>
|
||||||
<ProjectPermissionCan
|
<ProjectPermissionCan
|
||||||
I={ProjectPermissionActions.Edit}
|
I={ProjectPermissionActions.Edit}
|
||||||
a={ProjectPermissionSub.SshHosts}
|
a={ProjectPermissionSub.SshHosts}
|
||||||
|
|||||||
Reference in New Issue
Block a user