diff --git a/backend/src/db/migrations/20250718133527_project-unify-revert.ts b/backend/src/db/migrations/20250718133527_project-unify-revert.ts index f61ea3dfc..365395d3b 100644 --- a/backend/src/db/migrations/20250718133527_project-unify-revert.ts +++ b/backend/src/db/migrations/20250718133527_project-unify-revert.ts @@ -208,6 +208,7 @@ const kickOutSecretManagerProject = async (knex: Knex, oldProjectId: string) => if (secret) { const newProjectId = await newProject(knex, oldProjectId, ProjectType.SecretManager); await knex(TableName.IntegrationAuth).where("projectId", oldProjectId).update("projectId", newProjectId); + await knex(TableName.Environment).where("projectId", oldProjectId).update("projectId", newProjectId); await knex(TableName.SecretBlindIndex).where("projectId", oldProjectId).update("projectId", newProjectId); await knex(TableName.SecretSync).where("projectId", oldProjectId).update("projectId", newProjectId); await knex(TableName.SecretTag).where("projectId", oldProjectId).update("projectId", newProjectId); @@ -229,7 +230,7 @@ const kickOutCertManagerProject = async (knex: Knex, oldProjectId: string) => { }; const kickOutSecretScanningProject = async (knex: Knex, oldProjectId: string) => { - const cas = await knex(TableName.SecretScanningConfig).where("projectId", oldProjectId).returning("id").first(); + const cas = await knex(TableName.SecretScanningDataSource).where("projectId", oldProjectId).returning("id").first(); if (cas) { const newProjectId = await newProject(knex, oldProjectId, ProjectType.SecretScanning); await knex(TableName.SecretScanningConfig).where("projectId", oldProjectId).update("projectId", newProjectId); @@ -244,6 +245,7 @@ const kickOutKmsProject = async (knex: Knex, oldProjectId: string) => { .andWhere("isReserved", false) .returning("id") .first(); + if (kmsKeys) { const newProjectId = await newProject(knex, oldProjectId, ProjectType.KMS); await knex(TableName.KmsKey) @@ -255,7 +257,7 @@ const kickOutKmsProject = async (knex: Knex, oldProjectId: string) => { }; const kickOutSshProject = async (knex: Knex, oldProjectId: string) => { - const hosts = await knex(TableName.ProjectSshConfig).where("projectId", oldProjectId).returning("id").first(); + const hosts = await knex(TableName.SshCertificateAuthority).where("projectId", oldProjectId).returning("id").first(); if (hosts) { const newProjectId = await newProject(knex, oldProjectId, ProjectType.SSH); await knex(TableName.SshHost).where("projectId", oldProjectId).update("projectId", newProjectId); @@ -270,6 +272,9 @@ const BATCH_SIZE = 1000; export async function up(knex: Knex): Promise { const hasTemplateTypeColumn = await knex.schema.hasColumn(TableName.ProjectTemplates, "type"); if (hasTemplateTypeColumn) { + await knex(TableName.ProjectTemplates).whereNull("type").update({ + type: ProjectType.SecretManager + }); await knex.schema.alterTable(TableName.ProjectTemplates, (t) => { t.string("type").notNullable().defaultTo(ProjectType.SecretManager).alter(); }); diff --git a/backend/src/ee/services/license/license-fns.ts b/backend/src/ee/services/license/license-fns.ts index 5b755567b..71a5e8105 100644 --- a/backend/src/ee/services/license/license-fns.ts +++ b/backend/src/ee/services/license/license-fns.ts @@ -52,7 +52,7 @@ export const getDefaultOnPremFeatures = (): TFeatureSet => ({ }, pkiEst: false, enforceMfa: false, - projectTemplates: false, + projectTemplates: true, kmip: false, gateway: false, sshHostGroups: false, diff --git a/backend/src/services/pki-alert/pki-alert-service.ts b/backend/src/services/pki-alert/pki-alert-service.ts index 26560cb8c..c35bfbd16 100644 --- a/backend/src/services/pki-alert/pki-alert-service.ts +++ b/backend/src/services/pki-alert/pki-alert-service.ts @@ -137,7 +137,7 @@ export const pkiAlertServiceFactory = ({ projectId: alert.projectId, actorAuthMethod, actorOrgId, - actionProjectType: ActionProjectType.Any + actionProjectType: ActionProjectType.CertificateManager }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.PkiAlerts); diff --git a/backend/src/services/service-token/service-token-service.ts b/backend/src/services/service-token/service-token-service.ts index c8d8dd925..6d61cf898 100644 --- a/backend/src/services/service-token/service-token-service.ts +++ b/backend/src/services/service-token/service-token-service.ts @@ -67,7 +67,7 @@ export const serviceTokenServiceFactory = ({ projectId, actorAuthMethod, actorOrgId, - actionProjectType: ActionProjectType.Any + actionProjectType: ActionProjectType.SecretManager }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.ServiceTokens); @@ -158,7 +158,7 @@ export const serviceTokenServiceFactory = ({ projectId, actorAuthMethod, actorOrgId, - actionProjectType: ActionProjectType.Any + actionProjectType: ActionProjectType.SecretManager }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.ServiceTokens); diff --git a/frontend/src/components/projects/NewProjectModal.tsx b/frontend/src/components/projects/NewProjectModal.tsx index 6d575829e..59ac6083c 100644 --- a/frontend/src/components/projects/NewProjectModal.tsx +++ b/frontend/src/components/projects/NewProjectModal.tsx @@ -33,7 +33,7 @@ import { useSubscription, useUser } from "@app/context"; -import { getProjectHomePage } from "@app/helpers/project"; +import { getProjectHomePage, getProjectLottieIcon } from "@app/helpers/project"; import { useCreateWorkspace, useGetExternalKmsList, useGetUserWorkspaces } from "@app/hooks/api"; import { INTERNAL_KMS_KEY_ID } from "@app/hooks/api/kms/types"; import { InfisicalProjectTemplate, useListProjectTemplates } from "@app/hooks/api/projectTemplates"; @@ -63,28 +63,23 @@ type NewProjectFormProps = Pick; const PROJECT_TYPE_MENU_ITEMS = [ { label: "Secret Manager", - value: ProjectType.SecretManager, - icon: "vault" + value: ProjectType.SecretManager }, { label: "Certificate Manager", - value: ProjectType.CertificateManager, - icon: "note" + value: ProjectType.CertificateManager }, { label: "KMS", - value: ProjectType.KMS, - icon: "unlock" + value: ProjectType.KMS }, { label: "SSH", - value: ProjectType.SSH, - icon: "terminal" + value: ProjectType.SSH }, { label: "Secret Scanning", - value: ProjectType.SecretScanning, - icon: "secret-scan" + value: ProjectType.SecretScanning } ]; @@ -102,18 +97,11 @@ const NewProjectForm = ({ onOpenChange }: NewProjectFormProps) => { OrgPermissionSubjects.ProjectTemplates ); - const { data: projectTemplates = [] } = useListProjectTemplates({ - enabled: Boolean(canReadProjectTemplates && subscription?.projectTemplates) - }); - - const { data: externalKmsList } = useGetExternalKmsList(currentOrg.id, { - enabled: permission.can(OrgPermissionActions.Read, OrgPermissionSubjects.Kms) - }); - const { control, handleSubmit, reset, + watch, formState: { isSubmitting, errors } } = useForm({ resolver: zodResolver(formSchema), @@ -123,6 +111,16 @@ const NewProjectForm = ({ onOpenChange }: NewProjectFormProps) => { } }); + const selectedProjectType = watch("type"); + const { data: projectTemplates = [] } = useListProjectTemplates({ + enabled: Boolean(canReadProjectTemplates && subscription?.projectTemplates), + select: (template) => template.filter((el) => el.type === selectedProjectType) + }); + + const { data: externalKmsList } = useGetExternalKmsList(currentOrg.id, { + enabled: permission.can(OrgPermissionActions.Read, OrgPermissionSubjects.Kms) + }); + useEffect(() => { if (Object.keys(errors).length > 0) { console.log("Current form errors:", errors); @@ -212,7 +210,7 @@ const NewProjectForm = ({ onOpenChange }: NewProjectFormProps) => { } }} > - +
{el.label}
))} diff --git a/frontend/src/helpers/project.ts b/frontend/src/helpers/project.ts index 6db97f995..3b04b263d 100644 --- a/frontend/src/helpers/project.ts +++ b/frontend/src/helpers/project.ts @@ -87,7 +87,7 @@ export const getProjectHomePage = (type: ProjectType) => { export const getProjectTitle = (type: ProjectType) => { const titleConvert = { - [ProjectType.SecretManager]: "Secret Management", + [ProjectType.SecretManager]: "Secrets Management", [ProjectType.KMS]: "Key Management", [ProjectType.CertificateManager]: "Cert Management", [ProjectType.SSH]: "SSH", diff --git a/frontend/src/hooks/api/projectTemplates/types.ts b/frontend/src/hooks/api/projectTemplates/types.ts index e9f76f149..f5ff22dff 100644 --- a/frontend/src/hooks/api/projectTemplates/types.ts +++ b/frontend/src/hooks/api/projectTemplates/types.ts @@ -1,9 +1,12 @@ import { TProjectRole } from "@app/hooks/api/roles/types"; +import { ProjectType } from "../workspace/types"; + export type TProjectTemplate = { id: string; name: string; description?: string; + type: ProjectType; roles: Pick[]; environments?: { name: string; slug: string; position: number }[] | null; createdAt: string; @@ -16,6 +19,7 @@ export type TProjectTemplateResponse = { projectTemplate: TProjectTemplate }; export type TCreateProjectTemplateDTO = { name: string; description?: string; + type?: ProjectType; }; export type TUpdateProjectTemplateDTO = Partial< diff --git a/frontend/src/layouts/SecretScanningLayout/SecretScanningLayout.tsx b/frontend/src/layouts/SecretScanningLayout/SecretScanningLayout.tsx index 9fba9b823..8c54b6a1e 100644 --- a/frontend/src/layouts/SecretScanningLayout/SecretScanningLayout.tsx +++ b/frontend/src/layouts/SecretScanningLayout/SecretScanningLayout.tsx @@ -91,7 +91,7 @@ export const SecretScanningLayout = () => { )}

{name}

-

{description || "Project Template"}

+

+ {`${getProjectTitle(type)} - `} + {description || "Project Template"} +

{!isInfisicalTemplate && (
diff --git a/frontend/src/pages/organization/SettingsPage/components/ProjectTemplatesTab/components/ProjectTemplateDetailsModal.tsx b/frontend/src/pages/organization/SettingsPage/components/ProjectTemplatesTab/components/ProjectTemplateDetailsModal.tsx index 604a37d6d..bb3a03fc1 100644 --- a/frontend/src/pages/organization/SettingsPage/components/ProjectTemplatesTab/components/ProjectTemplateDetailsModal.tsx +++ b/frontend/src/pages/organization/SettingsPage/components/ProjectTemplatesTab/components/ProjectTemplateDetailsModal.tsx @@ -1,5 +1,6 @@ -import { useForm } from "react-hook-form"; +import { Controller, useForm } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; +import { twMerge } from "tailwind-merge"; import { z } from "zod"; import { createNotification } from "@app/components/notifications"; @@ -7,21 +8,25 @@ import { Button, FormControl, Input, + Lottie, Modal, ModalClose, ModalContent, TextArea } from "@app/components/v2"; +import { getProjectLottieIcon } from "@app/helpers/project"; import { TProjectTemplate, useCreateProjectTemplate, useUpdateProjectTemplate } from "@app/hooks/api/projectTemplates"; +import { ProjectType } from "@app/hooks/api/workspace/types"; import { slugSchema } from "@app/lib/schemas"; const formSchema = z.object({ name: slugSchema({ min: 1, max: 64, field: "Name" }), - description: z.string().max(500).optional() + description: z.string().max(500).optional(), + type: z.nativeEnum(ProjectType).optional() }); export type FormData = z.infer; @@ -38,6 +43,29 @@ type FormProps = { onComplete: (template: TProjectTemplate) => void; }; +const PROJECT_TYPE_MENU_ITEMS = [ + { + label: "Secrets Manager", + value: ProjectType.SecretManager + }, + { + label: "Certificates Manager", + value: ProjectType.CertificateManager + }, + { + label: "KMS", + value: ProjectType.KMS + }, + { + label: "SSH", + value: ProjectType.SSH + }, + { + label: "Secret Scanning", + value: ProjectType.SecretScanning + } +]; + const ProjectTemplateForm = ({ onComplete, projectTemplate }: FormProps) => { const createProjectTemplate = useCreateProjectTemplate(); const updateProjectTemplate = useUpdateProjectTemplate(); @@ -45,12 +73,14 @@ const ProjectTemplateForm = ({ onComplete, projectTemplate }: FormProps) => { const { handleSubmit, register, + control, formState: { isSubmitting, errors } } = useForm({ resolver: zodResolver(formSchema), defaultValues: { name: projectTemplate?.name, - description: projectTemplate?.description + description: projectTemplate?.description, + type: ProjectType.SecretManager } }); @@ -90,6 +120,42 @@ const ProjectTemplateForm = ({ onComplete, projectTemplate }: FormProps) => { > + ( + +
+ {PROJECT_TYPE_MENU_ITEMS.map((el) => ( +
field.onChange(el.value)} + role="button" + tabIndex={0} + onKeyDown={(e) => { + if (e.key === "Enter") { + field.onChange(el.value); + } + }} + > + +
{el.label}
+
+ ))} +
+
+ )} + /> { Name + Type Roles - Environments @@ -80,7 +81,7 @@ export const ProjectTemplatesTable = ({ onEdit }: Props) => { /> )} {filteredTemplates.map((template) => { - const { id, name, roles, environments = [], description } = template; + const { id, name, roles, environments = [], description, type } = template; return ( onEdit(template)} @@ -99,6 +100,7 @@ export const ProjectTemplatesTable = ({ onEdit }: Props) => { )} + {getProjectTitle(type)} {roles.length} {roles.length > 0 && ( diff --git a/frontend/src/pages/project/AccessControlPage/AccessControlPage.tsx b/frontend/src/pages/project/AccessControlPage/AccessControlPage.tsx index 4a7eee433..8bb007e88 100644 --- a/frontend/src/pages/project/AccessControlPage/AccessControlPage.tsx +++ b/frontend/src/pages/project/AccessControlPage/AccessControlPage.tsx @@ -5,6 +5,7 @@ import { useNavigate, useSearch } from "@tanstack/react-router"; import { PageHeader, Tab, TabList, TabPanel, Tabs } from "@app/components/v2"; import { useWorkspace } from "@app/context"; import { getProjectBaseURL } from "@app/helpers/project"; +import { ProjectType } from "@app/hooks/api/workspace/types"; import { ProjectAccessControlTabs } from "@app/types/project"; import { @@ -33,6 +34,8 @@ const Page = () => { }); }; + const isSecretManager = currentWorkspace.type === ProjectType.SecretManager; + return (
@@ -49,7 +52,9 @@ const Page = () => {

Machine Identities

- Service Tokens + {isSecretManager && ( + Service Tokens + )} Project Roles @@ -61,9 +66,11 @@ const Page = () => { - - - + {isSecretManager && ( + + + + )} diff --git a/frontend/src/pages/project/RoleDetailsBySlugPage/components/AddPoliciesButton.tsx b/frontend/src/pages/project/RoleDetailsBySlugPage/components/AddPoliciesButton.tsx index cedf07d05..6f770a946 100644 --- a/frontend/src/pages/project/RoleDetailsBySlugPage/components/AddPoliciesButton.tsx +++ b/frontend/src/pages/project/RoleDetailsBySlugPage/components/AddPoliciesButton.tsx @@ -8,6 +8,7 @@ import { DropdownMenuTrigger, IconButton } from "@app/components/v2"; +import { useWorkspace } from "@app/context"; import { usePopUp } from "@app/hooks"; import { PolicySelectionModal } from "@app/pages/project/RoleDetailsBySlugPage/components/PolicySelectionModal"; import { PolicyTemplateModal } from "@app/pages/project/RoleDetailsBySlugPage/components/PolicyTemplateModal"; @@ -17,6 +18,7 @@ type Props = { }; export const AddPoliciesButton = ({ isDisabled }: Props) => { + const { currentWorkspace } = useWorkspace(); const { popUp, handlePopUpToggle, handlePopUpOpen, handlePopUpClose } = usePopUp([ "addPolicy", "addPolicyOptions", @@ -66,10 +68,12 @@ export const AddPoliciesButton = ({ isDisabled }: Props) => { handlePopUpToggle("addPolicy", isOpen)} /> handlePopUpToggle("applyTemplate", isOpen)} /> diff --git a/frontend/src/pages/project/RoleDetailsBySlugPage/components/PolicySelectionModal.tsx b/frontend/src/pages/project/RoleDetailsBySlugPage/components/PolicySelectionModal.tsx index a20bdf9b2..845c5f54e 100644 --- a/frontend/src/pages/project/RoleDetailsBySlugPage/components/PolicySelectionModal.tsx +++ b/frontend/src/pages/project/RoleDetailsBySlugPage/components/PolicySelectionModal.tsx @@ -19,26 +19,31 @@ import { Tr } from "@app/components/v2"; import { ProjectPermissionSub } from "@app/context"; +import { ProjectType } from "@app/hooks/api/workspace/types"; import { EXCLUDED_PERMISSION_SUBS, isConditionalSubjects, PROJECT_PERMISSION_OBJECT, + ProjectTypePermissionSubjects, TFormSchema } from "./ProjectRoleModifySection.utils"; type Props = { isOpen: boolean; onOpenChange: (isOpen: boolean) => void; + type: ProjectType; }; type ContentProps = { onClose: () => void; + + type: ProjectType; }; type TForm = { permissions: Record }; -const Content = ({ onClose }: ContentProps) => { +const Content = ({ onClose, type: projectType }: ContentProps) => { const rootForm = useFormContext(); const [search, setSearch] = useState(""); const { @@ -56,7 +61,12 @@ const Content = ({ onClose }: ContentProps) => { }); const filteredPolicies = Object.entries(PROJECT_PERMISSION_OBJECT) - .filter(([, { title }]) => (search ? title.toLowerCase().includes(search.toLowerCase()) : true)) + .filter( + ([subject, { title }]) => + ProjectTypePermissionSubjects[projectType ?? ProjectType.SecretManager][ + subject as ProjectPermissionSub + ] && (search ? title.toLowerCase().includes(search.toLowerCase()) : true) + ) .filter(([subject]) => !EXCLUDED_PERMISSION_SUBS.includes(subject as ProjectPermissionSub)) .sort((a, b) => a[1].title.localeCompare(b[1].title)) .map(([subject]) => subject); @@ -191,7 +201,7 @@ const Content = ({ onClose }: ContentProps) => { ); }; -export const PolicySelectionModal = ({ isOpen, onOpenChange }: Props) => { +export const PolicySelectionModal = ({ isOpen, onOpenChange, type }: Props) => { return ( { subTitle="Select one or more policies to add to this role." className="max-w-3xl" > - onOpenChange(false)} /> + onOpenChange(false)} type={type} /> ); diff --git a/frontend/src/pages/project/RoleDetailsBySlugPage/components/PolicyTemplateModal.tsx b/frontend/src/pages/project/RoleDetailsBySlugPage/components/PolicyTemplateModal.tsx index 69c43d63a..d9cae6244 100644 --- a/frontend/src/pages/project/RoleDetailsBySlugPage/components/PolicyTemplateModal.tsx +++ b/frontend/src/pages/project/RoleDetailsBySlugPage/components/PolicyTemplateModal.tsx @@ -13,6 +13,7 @@ import { ModalContent } from "@app/components/v2"; import { ProjectPermissionSub } from "@app/context"; +import { ProjectType } from "@app/hooks/api/workspace/types"; import { PROJECT_PERMISSION_OBJECT, @@ -24,19 +25,23 @@ import { type Props = { isOpen: boolean; onOpenChange: (isOpen: boolean) => void; + type: ProjectType; }; type ContentProps = { onClose: () => void; + type: ProjectType; }; -const Content = ({ onClose }: ContentProps) => { +const Content = ({ onClose, type: projectType }: ContentProps) => { const rootForm = useFormContext(); const [selectedTemplate, setSelectedTemplate] = useState(); const [conflictingSubjects, setConflictingSubjects] = useState([]); const [showConflictingSubjects, setShowConflictingSubjects] = useState(false); + const templates = RoleTemplates[projectType ?? ProjectType.SecretManager]; + const onSubmit = (skipConflicting = false) => { if (!selectedTemplate) { createNotification({ type: "error", text: "Please select a template" }); @@ -121,12 +126,12 @@ const Content = ({ onClose }: ContentProps) => { type="single" value={selectedTemplate?.id} onValueChange={(value) => - setSelectedTemplate(RoleTemplates.find((template) => template.id === value)) + setSelectedTemplate(templates.find((template) => template.id === value)) } collapsible className="w-full border-collapse" > - {RoleTemplates.map(({ name, description, permissions, id }) => ( + {templates.map(({ name, description, permissions, id }) => ( { ); }; -export const PolicyTemplateModal = ({ isOpen, onOpenChange }: Props) => { +export const PolicyTemplateModal = ({ isOpen, onOpenChange, type }: Props) => { return ( { subTitle="Select a template with prepopulated policies to get started. You can always add more policies later." className="max-w-3xl" > - onOpenChange(false)} /> + onOpenChange(false)} type={type} /> ); diff --git a/frontend/src/pages/project/RoleDetailsBySlugPage/components/ProjectRoleModifySection.utils.tsx b/frontend/src/pages/project/RoleDetailsBySlugPage/components/ProjectRoleModifySection.utils.tsx index 4e051f1d1..471246d04 100644 --- a/frontend/src/pages/project/RoleDetailsBySlugPage/components/ProjectRoleModifySection.utils.tsx +++ b/frontend/src/pages/project/RoleDetailsBySlugPage/components/ProjectRoleModifySection.utils.tsx @@ -1645,41 +1645,378 @@ export type RoleTemplate = { permissions: { subject: ProjectPermissionSub; actions: string[] }[]; }; -export const RoleTemplates = [ - { - id: "project-manager", - name: "Project Management Policies", - description: "Grants access to manage project members and settings", - permissions: [ +const projectManagerTemplate = ( + additionalPermissions: RoleTemplate["permissions"] = [] +): RoleTemplate => ({ + id: "project-manager", + name: "Project Management Policies", + description: "Grants access to manage project members and settings", + permissions: [ + { + subject: ProjectPermissionSub.AuditLogs, + actions: Object.values(ProjectPermissionActions) + }, + { + subject: ProjectPermissionSub.Groups, + actions: Object.values(ProjectPermissionGroupActions) + }, + { + subject: ProjectPermissionSub.Member, + actions: Object.values(ProjectPermissionMemberActions) + }, + { + subject: ProjectPermissionSub.Identity, + actions: Object.values(ProjectPermissionIdentityActions) + }, + { + subject: ProjectPermissionSub.Project, + actions: [ProjectPermissionActions.Edit, ProjectPermissionActions.Delete] + }, + { subject: ProjectPermissionSub.Role, actions: Object.values(ProjectPermissionActions) }, + { + subject: ProjectPermissionSub.Settings, + actions: [ProjectPermissionActions.Read, ProjectPermissionActions.Edit] + }, + ...additionalPermissions + ] +}); + +export const RoleTemplates: Record = { + [ProjectType.SSH]: [ + { + id: "ssh-viewer", + name: "SSH Viewing Policies", + description: "Grants read access to SSH certificates and hosts", + permissions: [ + { + subject: ProjectPermissionSub.SshCertificateAuthorities, + actions: [ProjectPermissionActions.Read] + }, + { + subject: ProjectPermissionSub.SshCertificates, + actions: [ProjectPermissionActions.Read] + }, + { + subject: ProjectPermissionSub.SshCertificateTemplates, + actions: [ProjectPermissionActions.Read] + }, + { + subject: ProjectPermissionSub.SshHosts, + actions: [ProjectPermissionSshHostActions.Read] + }, + { + subject: ProjectPermissionSub.SshHostGroups, + actions: [ProjectPermissionActions.Read] + } + ] + }, + { + id: "ssh-cert-editor", + name: "SSH Certificate Editing Policies", + description: "Grants read and edit access to SSH certificates", + permissions: [ + { + subject: ProjectPermissionSub.SshCertificateAuthorities, + actions: Object.values(ProjectPermissionActions) + }, + { + subject: ProjectPermissionSub.SshCertificates, + actions: Object.values(ProjectPermissionActions) + }, + { + subject: ProjectPermissionSub.SshCertificateTemplates, + actions: Object.values(ProjectPermissionActions) + } + ] + }, + { + id: "ssh-host-editor", + name: "SSH Host Editing Policies", + description: "Grants read and edit access to SSH hosts", + permissions: [ + { + subject: ProjectPermissionSub.SshHosts, + actions: Object.values(ProjectPermissionSshHostActions) + }, + { + subject: ProjectPermissionSub.SshHostGroups, + actions: Object.values(ProjectPermissionActions) + } + ] + }, + projectManagerTemplate() + ], + [ProjectType.KMS]: [ + { + id: "kms-viewer", + name: "KMS Viewing Policies", + description: "Grants read access to KMS keys and KMIP clients", + permissions: [ + { + subject: ProjectPermissionSub.Cmek, + actions: [ProjectPermissionCmekActions.Read] + }, + { + subject: ProjectPermissionSub.Kmip, + actions: [ProjectPermissionKmipActions.ReadClients] + } + ] + }, + { + id: "key-editor", + name: "KMS Key Editing Policies", + description: "Grants read and edit access to KMS keys", + permissions: [ + { + subject: ProjectPermissionSub.Cmek, + actions: Object.values(ProjectPermissionCmekActions) + } + ] + }, + { + id: "kmip-editor", + name: "KMIP Client Editing Policies", + description: "Grants read and edit access to KMIP clients", + permissions: [ + { + subject: ProjectPermissionSub.Kmip, + actions: Object.values(ProjectPermissionKmipActions) + } + ] + }, + projectManagerTemplate() + ], + [ProjectType.CertificateManager]: [ + { + id: "cert-viewer", + name: "Certificate Viewing Policies", + description: "Grants read access to certificates and related resources", + permissions: [ + { + subject: ProjectPermissionSub.PkiCollections, + actions: [ProjectPermissionActions.Read] + }, + { + subject: ProjectPermissionSub.PkiAlerts, + actions: [ProjectPermissionActions.Read] + }, + { + subject: ProjectPermissionSub.CertificateAuthorities, + actions: [ProjectPermissionActions.Read] + }, + { + subject: ProjectPermissionSub.CertificateTemplates, + actions: [ProjectPermissionActions.Read] + }, + { + subject: ProjectPermissionSub.Certificates, + actions: [ + ProjectPermissionCertificateActions.Read, + ProjectPermissionCertificateActions.ReadPrivateKey + ] + } + ] + }, + { + id: "cert-editor", + name: "Certificate Editing Policies", + description: "Grants read and edit access to certificates and related resources", + permissions: [ + { + subject: ProjectPermissionSub.PkiCollections, + actions: Object.values(ProjectPermissionActions) + }, + { + subject: ProjectPermissionSub.PkiAlerts, + actions: Object.values(ProjectPermissionActions) + }, + { + subject: ProjectPermissionSub.CertificateAuthorities, + actions: Object.values(ProjectPermissionActions) + }, + { + subject: ProjectPermissionSub.CertificateTemplates, + actions: Object.values(ProjectPermissionActions) + }, + { + subject: ProjectPermissionSub.Certificates, + actions: Object.values(ProjectPermissionCertificateActions) + } + ] + }, + projectManagerTemplate() + ], + [ProjectType.SecretScanning]: [ + { + id: "scanning-viewer", + name: "Secret Scanning Viewing Policies", + description: "Grants read access to data sources and findings", + permissions: [ + { + subject: ProjectPermissionSub.SecretScanningDataSources, + actions: [ + ProjectPermissionSecretScanningDataSourceActions.Read, + ProjectPermissionSecretScanningDataSourceActions.ReadResources, + ProjectPermissionSecretScanningDataSourceActions.ReadScans + ] + }, + { + subject: ProjectPermissionSub.SecretScanningFindings, + actions: [ProjectPermissionSecretScanningFindingActions.Read] + }, + { + subject: ProjectPermissionSub.SecretScanningConfigs, + actions: [ProjectPermissionSecretScanningConfigActions.Read] + } + ] + }, + { + id: "scanning-editor", + name: "Secret Scanning Editing Policies", + description: "Grants read and edit access to data sources and findings", + permissions: [ + { + subject: ProjectPermissionSub.SecretScanningDataSources, + actions: Object.values(ProjectPermissionSecretScanningDataSourceActions) + }, + { + subject: ProjectPermissionSub.SecretScanningFindings, + actions: Object.values(ProjectPermissionSecretScanningFindingActions) + }, + { + subject: ProjectPermissionSub.SecretScanningConfigs, + actions: [ProjectPermissionSecretScanningConfigActions.Read] + } + ] + }, + projectManagerTemplate([ { - subject: ProjectPermissionSub.AuditLogs, - actions: Object.values(ProjectPermissionActions) - }, - { - subject: ProjectPermissionSub.Groups, - actions: Object.values(ProjectPermissionGroupActions) - }, - { - subject: ProjectPermissionSub.Member, - actions: Object.values(ProjectPermissionMemberActions) - }, - { - subject: ProjectPermissionSub.Identity, - actions: Object.values(ProjectPermissionIdentityActions) - }, - { - subject: ProjectPermissionSub.Project, - actions: [ProjectPermissionActions.Edit, ProjectPermissionActions.Delete] - }, - { subject: ProjectPermissionSub.Role, actions: Object.values(ProjectPermissionActions) }, - { - subject: ProjectPermissionSub.Settings, - actions: [ProjectPermissionActions.Read, ProjectPermissionActions.Edit] - }, + subject: ProjectPermissionSub.SecretScanningConfigs, + actions: Object.values(ProjectPermissionSecretScanningConfigActions) + } + ]) + ], + [ProjectType.SecretManager]: [ + { + id: "secret-viewer", + name: "Secret Viewing Policies", + description: "Grants read access to secrets and related resources", + permissions: [ + { + subject: ProjectPermissionSub.SecretRollback, + actions: [ProjectPermissionActions.Read] + }, + { + subject: ProjectPermissionSub.SecretImports, + actions: [ProjectPermissionActions.Read] + }, + { + subject: ProjectPermissionSub.Secrets, + actions: [ + ProjectPermissionSecretActions.DescribeSecret, + ProjectPermissionSecretActions.ReadValue + ] + }, + { + subject: ProjectPermissionSub.DynamicSecrets, + actions: [ProjectPermissionDynamicSecretActions.ReadRootCredential] + }, + { + subject: ProjectPermissionSub.Environments, + actions: [ProjectPermissionActions.Read] + }, + { + subject: ProjectPermissionSub.Tags, + actions: [ProjectPermissionActions.Read] + }, + { + subject: ProjectPermissionSub.SecretRotation, + actions: [ProjectPermissionSecretRotationActions.Read] + }, + { + subject: ProjectPermissionSub.Integrations, + actions: [ProjectPermissionActions.Read] + }, + { + subject: ProjectPermissionSub.SecretSyncs, + actions: [ProjectPermissionSecretSyncActions.Read] + }, + { + subject: ProjectPermissionSub.Commits, + actions: [ProjectPermissionCommitsActions.Read] + } + ] + }, + { + id: "secret-editor", + name: "Secret Editing Policies", + description: "Grants read and edit access to secrets and related resources", + permissions: [ + { + subject: ProjectPermissionSub.Environments, + actions: Object.values(ProjectPermissionActions) + }, + { + subject: ProjectPermissionSub.DynamicSecrets, + actions: Object.values(ProjectPermissionDynamicSecretActions) + }, + { + subject: ProjectPermissionSub.Secrets, + actions: [ + ProjectPermissionSecretActions.DescribeSecret, + ProjectPermissionSecretActions.ReadValue, + ProjectPermissionSecretActions.Edit, + ProjectPermissionSecretActions.Create, + ProjectPermissionSecretActions.Delete + ] + }, + { + subject: ProjectPermissionSub.SecretRollback, + actions: [ProjectPermissionActions.Read, ProjectPermissionActions.Create] + }, + { + subject: ProjectPermissionSub.Tags, + actions: Object.values(ProjectPermissionActions) + }, + { + subject: ProjectPermissionSub.SecretImports, + actions: Object.values(ProjectPermissionActions) + }, + { + subject: ProjectPermissionSub.SecretRotation, + actions: Object.values(ProjectPermissionSecretRotationActions) + }, + { + subject: ProjectPermissionSub.SecretFolders, + actions: [ + ProjectPermissionActions.Create, + ProjectPermissionActions.Edit, + ProjectPermissionActions.Delete + ] + }, + { + subject: ProjectPermissionSub.Integrations, + actions: Object.values(ProjectPermissionActions) + }, + { + subject: ProjectPermissionSub.SecretSyncs, + actions: Object.values(ProjectPermissionSecretSyncActions) + }, + { + subject: ProjectPermissionSub.Commits, + actions: Object.values(ProjectPermissionCommitsActions) + } + ] + }, + projectManagerTemplate([ { subject: ProjectPermissionSub.IpAllowList, actions: Object.values(ProjectPermissionActions) }, + { + subject: ProjectPermissionSub.Kms, + actions: [ProjectPermissionActions.Edit] + }, { subject: ProjectPermissionSub.SecretApproval, actions: Object.values(ProjectPermissionActions) @@ -1692,314 +2029,6 @@ export const RoleTemplates = [ subject: ProjectPermissionSub.Webhooks, actions: Object.values(ProjectPermissionActions) } - ] - }, - { - id: "ssh-viewer", - name: "SSH Viewing Policies", - description: "Grants read access to SSH certificates and hosts", - permissions: [ - { - subject: ProjectPermissionSub.SshCertificateAuthorities, - actions: [ProjectPermissionActions.Read] - }, - { - subject: ProjectPermissionSub.SshCertificates, - actions: [ProjectPermissionActions.Read] - }, - { - subject: ProjectPermissionSub.SshCertificateTemplates, - actions: [ProjectPermissionActions.Read] - }, - { - subject: ProjectPermissionSub.SshHosts, - actions: [ProjectPermissionSshHostActions.Read] - }, - { - subject: ProjectPermissionSub.SshHostGroups, - actions: [ProjectPermissionActions.Read] - } - ] - }, - { - id: "ssh-cert-editor", - name: "SSH Certificate Editing Policies", - description: "Grants read and edit access to SSH certificates", - permissions: [ - { - subject: ProjectPermissionSub.SshCertificateAuthorities, - actions: Object.values(ProjectPermissionActions) - }, - { - subject: ProjectPermissionSub.SshCertificates, - actions: Object.values(ProjectPermissionActions) - }, - { - subject: ProjectPermissionSub.SshCertificateTemplates, - actions: Object.values(ProjectPermissionActions) - } - ] - }, - { - id: "ssh-host-editor", - name: "SSH Host Editing Policies", - description: "Grants read and edit access to SSH hosts", - permissions: [ - { - subject: ProjectPermissionSub.SshHosts, - actions: Object.values(ProjectPermissionSshHostActions) - }, - { - subject: ProjectPermissionSub.SshHostGroups, - actions: Object.values(ProjectPermissionActions) - } - ] - }, - { - id: "kms-viewer", - name: "KMS Viewing Policies", - description: "Grants read access to KMS keys and KMIP clients", - permissions: [ - { - subject: ProjectPermissionSub.Cmek, - actions: [ProjectPermissionCmekActions.Read] - }, - { - subject: ProjectPermissionSub.Kmip, - actions: [ProjectPermissionKmipActions.ReadClients] - } - ] - }, - { - id: "key-editor", - name: "KMS Key Editing Policies", - description: "Grants read and edit access to KMS keys", - permissions: [ - { - subject: ProjectPermissionSub.Cmek, - actions: Object.values(ProjectPermissionCmekActions) - } - ] - }, - { - id: "kmip-editor", - name: "KMIP Client Editing Policies", - description: "Grants read and edit access to KMIP clients", - permissions: [ - { - subject: ProjectPermissionSub.Kmip, - actions: Object.values(ProjectPermissionKmipActions) - } - ] - }, - { - id: "cert-viewer", - name: "Certificate Viewing Policies", - description: "Grants read access to certificates and related resources", - permissions: [ - { - subject: ProjectPermissionSub.PkiCollections, - actions: [ProjectPermissionActions.Read] - }, - { - subject: ProjectPermissionSub.PkiAlerts, - actions: [ProjectPermissionActions.Read] - }, - { - subject: ProjectPermissionSub.CertificateAuthorities, - actions: [ProjectPermissionActions.Read] - }, - { - subject: ProjectPermissionSub.CertificateTemplates, - actions: [ProjectPermissionActions.Read] - }, - { - subject: ProjectPermissionSub.Certificates, - actions: [ - ProjectPermissionCertificateActions.Read, - ProjectPermissionCertificateActions.ReadPrivateKey - ] - } - ] - }, - { - id: "cert-editor", - name: "Certificate Editing Policies", - description: "Grants read and edit access to certificates and related resources", - permissions: [ - { - subject: ProjectPermissionSub.PkiCollections, - actions: Object.values(ProjectPermissionActions) - }, - { - subject: ProjectPermissionSub.PkiAlerts, - actions: Object.values(ProjectPermissionActions) - }, - { - subject: ProjectPermissionSub.CertificateAuthorities, - actions: Object.values(ProjectPermissionActions) - }, - { - subject: ProjectPermissionSub.CertificateTemplates, - actions: Object.values(ProjectPermissionActions) - }, - { - subject: ProjectPermissionSub.Certificates, - actions: Object.values(ProjectPermissionCertificateActions) - } - ] - }, - { - id: "scanning-viewer", - name: "Secret Scanning Viewing Policies", - description: "Grants read access to data sources and findings", - permissions: [ - { - subject: ProjectPermissionSub.SecretScanningDataSources, - actions: [ - ProjectPermissionSecretScanningDataSourceActions.Read, - ProjectPermissionSecretScanningDataSourceActions.ReadResources, - ProjectPermissionSecretScanningDataSourceActions.ReadScans - ] - }, - { - subject: ProjectPermissionSub.SecretScanningFindings, - actions: [ProjectPermissionSecretScanningFindingActions.Read] - }, - { - subject: ProjectPermissionSub.SecretScanningConfigs, - actions: [ProjectPermissionSecretScanningConfigActions.Read] - } - ] - }, - { - id: "scanning-editor", - name: "Secret Scanning Editing Policies", - description: "Grants read and edit access to data sources and findings", - permissions: [ - { - subject: ProjectPermissionSub.SecretScanningDataSources, - actions: Object.values(ProjectPermissionSecretScanningDataSourceActions) - }, - { - subject: ProjectPermissionSub.SecretScanningFindings, - actions: Object.values(ProjectPermissionSecretScanningFindingActions) - }, - { - subject: ProjectPermissionSub.SecretScanningConfigs, - actions: [ProjectPermissionSecretScanningConfigActions.Read] - } - ] - }, - { - id: "secret-viewer", - name: "Secret Viewing Policies", - description: "Grants read access to secrets and related resources", - permissions: [ - { - subject: ProjectPermissionSub.SecretRollback, - actions: [ProjectPermissionActions.Read] - }, - { - subject: ProjectPermissionSub.SecretImports, - actions: [ProjectPermissionActions.Read] - }, - { - subject: ProjectPermissionSub.Secrets, - actions: [ - ProjectPermissionSecretActions.DescribeSecret, - ProjectPermissionSecretActions.ReadValue - ] - }, - { - subject: ProjectPermissionSub.DynamicSecrets, - actions: [ProjectPermissionDynamicSecretActions.ReadRootCredential] - }, - { - subject: ProjectPermissionSub.Environments, - actions: [ProjectPermissionActions.Read] - }, - { - subject: ProjectPermissionSub.Tags, - actions: [ProjectPermissionActions.Read] - }, - { - subject: ProjectPermissionSub.SecretRotation, - actions: [ProjectPermissionSecretRotationActions.Read] - }, - { - subject: ProjectPermissionSub.Integrations, - actions: [ProjectPermissionActions.Read] - }, - { - subject: ProjectPermissionSub.SecretSyncs, - actions: [ProjectPermissionSecretSyncActions.Read] - }, - { - subject: ProjectPermissionSub.Commits, - actions: [ProjectPermissionCommitsActions.Read] - } - ] - }, - { - id: "secret-editor", - name: "Secret Editing Policies", - description: "Grants read and edit access to secrets and related resources", - permissions: [ - { - subject: ProjectPermissionSub.Environments, - actions: Object.values(ProjectPermissionActions) - }, - { - subject: ProjectPermissionSub.DynamicSecrets, - actions: Object.values(ProjectPermissionDynamicSecretActions) - }, - { - subject: ProjectPermissionSub.Secrets, - actions: [ - ProjectPermissionSecretActions.DescribeSecret, - ProjectPermissionSecretActions.ReadValue, - ProjectPermissionSecretActions.Edit, - ProjectPermissionSecretActions.Create, - ProjectPermissionSecretActions.Delete - ] - }, - { - subject: ProjectPermissionSub.SecretRollback, - actions: [ProjectPermissionActions.Read, ProjectPermissionActions.Create] - }, - { - subject: ProjectPermissionSub.Tags, - actions: Object.values(ProjectPermissionActions) - }, - { - subject: ProjectPermissionSub.SecretImports, - actions: Object.values(ProjectPermissionActions) - }, - { - subject: ProjectPermissionSub.SecretRotation, - actions: Object.values(ProjectPermissionSecretRotationActions) - }, - { - subject: ProjectPermissionSub.SecretFolders, - actions: [ - ProjectPermissionActions.Create, - ProjectPermissionActions.Edit, - ProjectPermissionActions.Delete - ] - }, - { - subject: ProjectPermissionSub.Integrations, - actions: Object.values(ProjectPermissionActions) - }, - { - subject: ProjectPermissionSub.SecretSyncs, - actions: Object.values(ProjectPermissionSecretSyncActions) - }, - { - subject: ProjectPermissionSub.Commits, - actions: Object.values(ProjectPermissionCommitsActions) - } - ] - } -]; + ]) + ] +}; diff --git a/frontend/src/pages/project/RoleDetailsBySlugPage/components/RolePermissionsSection.tsx b/frontend/src/pages/project/RoleDetailsBySlugPage/components/RolePermissionsSection.tsx index 216d514e4..5e7126083 100644 --- a/frontend/src/pages/project/RoleDetailsBySlugPage/components/RolePermissionsSection.tsx +++ b/frontend/src/pages/project/RoleDetailsBySlugPage/components/RolePermissionsSection.tsx @@ -14,6 +14,7 @@ import { ProjectPermissionSet } from "@app/context/ProjectPermissionContext"; import { evaluatePermissionsAbility } from "@app/helpers/permissions"; import { useGetProjectRoleBySlug, useUpdateProjectRole } from "@app/hooks/api"; import { ProjectMembershipRole } from "@app/hooks/api/roles/types"; +import { ProjectType } from "@app/hooks/api/workspace/types"; import { AddPoliciesButton } from "./AddPoliciesButton"; import { DynamicSecretPermissionConditions } from "./DynamicSecretPermissionConditions"; @@ -121,6 +122,8 @@ export const RolePermissionsSection = ({ roleSlug, isDisabled }: Props) => { (role?.slug ?? "") as ProjectMembershipRole ); + const isSecretManagerProject = currentWorkspace.type === ProjectType.SecretManager; + const permissions = form.watch("permissions"); const formattedPermissions = useMemo( @@ -206,7 +209,7 @@ export const RolePermissionsSection = ({ roleSlug, isDisabled }: Props) => {
- {showAccessTree && ( + {isSecretManagerProject && showAccessTree && (