From 7cf297344b3b2ca18a427b07dd2c3c081514b876 Mon Sep 17 00:00:00 2001 From: Tuan Dang Date: Mon, 9 Dec 2024 21:36:42 -0800 Subject: [PATCH] Move ssh back to project level --- backend/src/@types/knex.d.ts | 8 + .../db/migrations/20241130015511_ssh-mgmt.ts | 20 +- backend/src/db/schemas/index.ts | 1 + backend/src/db/schemas/models.ts | 1 + .../db/schemas/ssh-certificate-authorities.ts | 2 +- .../src/db/schemas/ssh-certificate-bodies.ts | 22 ++ backend/src/db/schemas/ssh-certificates.ts | 1 - .../v1/ssh-certificate-authority-router.ts | 11 +- .../v1/ssh-certificate-template-router.ts | 10 +- backend/src/ee/routes/v1/ssh-router.ts | 2 + .../ee/services/permission/org-permission.ts | 28 +-- .../services/permission/project-permission.ts | 41 +++- .../ssh-certificate-template-dal.ts | 15 +- .../ssh-certificate-template-service.ts | 42 ++-- .../ssh-certificate-body-dal.ts | 10 + .../ssh-certificate/ssh-certificate-dal.ts | 10 +- .../ssh/ssh-certificate-authority-schema.ts | 2 +- .../ssh/ssh-certificate-authority-service.ts | 231 +++++++++++------- .../ssh/ssh-certificate-authority-types.ts | 16 +- backend/src/lib/api-docs/constants.ts | 24 +- backend/src/server/routes/index.ts | 11 +- .../server/routes/v1/organization-router.ts | 100 -------- .../src/server/routes/v2/project-router.ts | 100 ++++++++ backend/src/services/org/org-service.ts | 102 +------- backend/src/services/org/org-types.ts | 7 - .../src/services/project/project-service.ts | 124 ++++++++++ backend/src/services/project/project-types.ts | 7 + .../src/context/OrgPermissionContext/types.ts | 10 +- .../context/ProjectPermissionContext/types.ts | 6 + frontend/src/hooks/api/organization/index.ts | 6 +- .../src/hooks/api/organization/queries.tsx | 69 +----- frontend/src/hooks/api/ssh-ca/mutations.tsx | 22 +- frontend/src/hooks/api/ssh-ca/types.ts | 6 +- frontend/src/hooks/api/workspace/index.tsx | 3 + frontend/src/hooks/api/workspace/queries.tsx | 63 +++++ .../src/hooks/api/workspace/query-keys.tsx | 16 +- frontend/src/layouts/AppLayout/AppLayout.tsx | 20 +- frontend/src/pages/org/[id]/ssh/index.tsx | 26 -- .../[id]/ssh/ca/[caId]/index.tsx | 2 +- frontend/src/pages/project/[id]/ssh/index.tsx | 23 ++ .../components/OrgRoleModifySection.utils.ts | 5 +- .../RolePermissionsSection.tsx | 14 +- .../ProjectRoleModifySection.utils.tsx | 35 +++ .../{Org => Project}/SshCaPage/SshCaPage.tsx | 27 +- .../components/SshCaDetailsSection.tsx | 14 +- .../components/SshCertificateContent.tsx | 0 .../components/SshCertificateModal.tsx | 17 +- .../SshCertificateTemplateModal.tsx | 10 +- .../SshCertificateTemplatesSection.tsx | 15 +- .../SshCertificateTemplatesTable.tsx | 38 +-- .../SshCaPage/components/index.tsx | 0 .../{Org => Project}/SshCaPage/index.tsx | 0 .../{Org => Project}/SshPage/SshPage.tsx | 8 +- .../SshPage/components/SshCaModal.tsx | 6 + .../SshPage/components/SshCaSection.tsx | 12 +- .../SshPage/components/SshCaTable.tsx | 30 ++- .../components/SshCertificatesSection.tsx | 11 +- .../components/SshCertificatesTable.tsx | 10 +- .../components/SshCertificatesTable.utils.ts | 0 .../SshPage/components/index.tsx | 0 .../views/{Org => Project}/SshPage/index.tsx | 0 61 files changed, 837 insertions(+), 635 deletions(-) create mode 100644 backend/src/db/schemas/ssh-certificate-bodies.ts create mode 100644 backend/src/ee/services/ssh-certificate/ssh-certificate-body-dal.ts delete mode 100644 frontend/src/pages/org/[id]/ssh/index.tsx rename frontend/src/pages/{org => project}/[id]/ssh/ca/[caId]/index.tsx (84%) create mode 100644 frontend/src/pages/project/[id]/ssh/index.tsx rename frontend/src/views/{Org => Project}/SshCaPage/SshCaPage.tsx (84%) rename frontend/src/views/{Org => Project}/SshCaPage/components/SshCaDetailsSection.tsx (91%) rename frontend/src/views/{Org => Project}/SshCaPage/components/SshCertificateContent.tsx (100%) rename frontend/src/views/{Org => Project}/SshCaPage/components/SshCertificateModal.tsx (96%) rename frontend/src/views/{Org => Project}/SshCaPage/components/SshCertificateTemplateModal.tsx (98%) rename frontend/src/views/{Org => Project}/SshCaPage/components/SshCertificateTemplatesSection.tsx (93%) rename frontend/src/views/{Org => Project}/SshCaPage/components/SshCertificateTemplatesTable.tsx (85%) rename frontend/src/views/{Org => Project}/SshCaPage/components/index.tsx (100%) rename frontend/src/views/{Org => Project}/SshCaPage/index.tsx (100%) rename frontend/src/views/{Org => Project}/SshPage/SshPage.tsx (86%) rename frontend/src/views/{Org => Project}/SshPage/components/SshCaModal.tsx (96%) rename frontend/src/views/{Org => Project}/SshPage/components/SshCaSection.tsx (92%) rename frontend/src/views/{Org => Project}/SshPage/components/SshCaTable.tsx (84%) rename frontend/src/views/{Org => Project}/SshPage/components/SshCertificatesSection.tsx (79%) rename frontend/src/views/{Org => Project}/SshPage/components/SshCertificatesTable.tsx (89%) rename frontend/src/views/{Org => Project}/SshPage/components/SshCertificatesTable.utils.ts (100%) rename frontend/src/views/{Org => Project}/SshPage/components/index.tsx (100%) rename frontend/src/views/{Org => Project}/SshPage/index.tsx (100%) diff --git a/backend/src/@types/knex.d.ts b/backend/src/@types/knex.d.ts index c0a2c0eaa..0fa1f3c2e 100644 --- a/backend/src/@types/knex.d.ts +++ b/backend/src/@types/knex.d.ts @@ -317,6 +317,9 @@ import { TSshCertificateAuthoritySecrets, TSshCertificateAuthoritySecretsInsert, TSshCertificateAuthoritySecretsUpdate, + TSshCertificateBodies, + TSshCertificateBodiesInsert, + TSshCertificateBodiesUpdate, TSshCertificates, TSshCertificatesInsert, TSshCertificatesUpdate, @@ -404,6 +407,11 @@ declare module "knex/types/tables" { TSshCertificatesInsert, TSshCertificatesUpdate >; + [TableName.SshCertificateBody]: KnexOriginal.CompositeTableType< + TSshCertificateBodies, + TSshCertificateBodiesInsert, + TSshCertificateBodiesUpdate + >; [TableName.CertificateAuthority]: KnexOriginal.CompositeTableType< TCertificateAuthorities, TCertificateAuthoritiesInsert, diff --git a/backend/src/db/migrations/20241130015511_ssh-mgmt.ts b/backend/src/db/migrations/20241130015511_ssh-mgmt.ts index ad38f8ad2..92831d382 100644 --- a/backend/src/db/migrations/20241130015511_ssh-mgmt.ts +++ b/backend/src/db/migrations/20241130015511_ssh-mgmt.ts @@ -8,8 +8,8 @@ export async function up(knex: Knex): Promise { await knex.schema.createTable(TableName.SshCertificateAuthority, (t) => { t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid()); t.timestamps(true, true, true); - t.uuid("orgId").notNullable(); - t.foreign("orgId").references("id").inTable(TableName.Organization).onDelete("CASCADE"); + t.string("projectId").notNullable(); + t.foreign("projectId").references("id").inTable(TableName.Project).onDelete("CASCADE"); t.string("status").notNullable(); // active / disabled t.string("friendlyName").notNullable(); t.string("keyAlgorithm").notNullable(); @@ -60,7 +60,6 @@ export async function up(knex: Knex): Promise { .onDelete("SET NULL"); t.string("serialNumber").notNullable().unique(); t.string("certType").notNullable(); // user or host - t.text("publicKey").notNullable(); // public key in OpenSSH format t.specificType("principals", "text[]").notNullable(); t.string("keyId").notNullable(); t.datetime("notBefore").notNullable(); @@ -68,9 +67,24 @@ export async function up(knex: Knex): Promise { }); await createOnUpdateTrigger(knex, TableName.SshCertificate); } + + if (!(await knex.schema.hasTable(TableName.SshCertificateBody))) { + await knex.schema.createTable(TableName.SshCertificateBody, (t) => { + t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid()); + t.timestamps(true, true, true); + t.uuid("sshCertId").notNullable().unique(); + t.foreign("sshCertId").references("id").inTable(TableName.SshCertificate).onDelete("CASCADE"); + t.binary("encryptedCertificate").notNullable(); + }); + + await createOnUpdateTrigger(knex, TableName.SshCertificateBody); + } } export async function down(knex: Knex): Promise { + await knex.schema.dropTableIfExists(TableName.SshCertificateBody); + await dropOnUpdateTrigger(knex, TableName.SshCertificateBody); + await knex.schema.dropTableIfExists(TableName.SshCertificate); await dropOnUpdateTrigger(knex, TableName.SshCertificate); diff --git a/backend/src/db/schemas/index.ts b/backend/src/db/schemas/index.ts index 348c39c70..c5dcecfd8 100644 --- a/backend/src/db/schemas/index.ts +++ b/backend/src/db/schemas/index.ts @@ -107,6 +107,7 @@ export * from "./service-tokens"; export * from "./slack-integrations"; export * from "./ssh-certificate-authorities"; export * from "./ssh-certificate-authority-secrets"; +export * from "./ssh-certificate-bodies"; export * from "./ssh-certificate-templates"; export * from "./ssh-certificates"; export * from "./super-admin"; diff --git a/backend/src/db/schemas/models.ts b/backend/src/db/schemas/models.ts index b7de13188..0f2c1ae49 100644 --- a/backend/src/db/schemas/models.ts +++ b/backend/src/db/schemas/models.ts @@ -6,6 +6,7 @@ export enum TableName { SshCertificateAuthoritySecret = "ssh_certificate_authority_secrets", SshCertificateTemplate = "ssh_certificate_templates", SshCertificate = "ssh_certificates", + SshCertificateBody = "ssh_certificate_bodies", CertificateAuthority = "certificate_authorities", CertificateTemplateEstConfig = "certificate_template_est_configs", CertificateAuthorityCert = "certificate_authority_certs", diff --git a/backend/src/db/schemas/ssh-certificate-authorities.ts b/backend/src/db/schemas/ssh-certificate-authorities.ts index d70b09a8a..81e789288 100644 --- a/backend/src/db/schemas/ssh-certificate-authorities.ts +++ b/backend/src/db/schemas/ssh-certificate-authorities.ts @@ -11,7 +11,7 @@ export const SshCertificateAuthoritiesSchema = z.object({ id: z.string().uuid(), createdAt: z.date(), updatedAt: z.date(), - orgId: z.string().uuid(), + projectId: z.string(), status: z.string(), friendlyName: z.string(), keyAlgorithm: z.string() diff --git a/backend/src/db/schemas/ssh-certificate-bodies.ts b/backend/src/db/schemas/ssh-certificate-bodies.ts new file mode 100644 index 000000000..baafb773b --- /dev/null +++ b/backend/src/db/schemas/ssh-certificate-bodies.ts @@ -0,0 +1,22 @@ +// Code generated by automation script, DO NOT EDIT. +// Automated by pulling database and generating zod schema +// To update. Just run npm run generate:schema +// Written by akhilmhdh. + +import { z } from "zod"; + +import { zodBuffer } from "@app/lib/zod"; + +import { TImmutableDBKeys } from "./models"; + +export const SshCertificateBodiesSchema = z.object({ + id: z.string().uuid(), + createdAt: z.date(), + updatedAt: z.date(), + sshCertId: z.string().uuid(), + encryptedCertificate: zodBuffer +}); + +export type TSshCertificateBodies = z.infer; +export type TSshCertificateBodiesInsert = Omit, TImmutableDBKeys>; +export type TSshCertificateBodiesUpdate = Partial, TImmutableDBKeys>>; diff --git a/backend/src/db/schemas/ssh-certificates.ts b/backend/src/db/schemas/ssh-certificates.ts index 238a71e00..6fe5bc261 100644 --- a/backend/src/db/schemas/ssh-certificates.ts +++ b/backend/src/db/schemas/ssh-certificates.ts @@ -15,7 +15,6 @@ export const SshCertificatesSchema = z.object({ sshCertificateTemplateId: z.string().uuid().nullable().optional(), serialNumber: z.string(), certType: z.string(), - publicKey: z.string(), principals: z.string().array(), keyId: z.string(), notBefore: z.date(), diff --git a/backend/src/ee/routes/v1/ssh-certificate-authority-router.ts b/backend/src/ee/routes/v1/ssh-certificate-authority-router.ts index 8c75ac4d4..ab80888d7 100644 --- a/backend/src/ee/routes/v1/ssh-certificate-authority-router.ts +++ b/backend/src/ee/routes/v1/ssh-certificate-authority-router.ts @@ -21,6 +21,7 @@ export const registerSshCaRouter = async (server: FastifyZodProvider) => { schema: { description: "Create SSH CA", body: z.object({ + projectId: z.string().describe(SSH_CERTIFICATE_AUTHORITIES.CREATE.projectId), friendlyName: z.string().describe(SSH_CERTIFICATE_AUTHORITIES.CREATE.friendlyName), keyAlgorithm: z .nativeEnum(CertKeyAlgorithm) @@ -46,7 +47,7 @@ export const registerSshCaRouter = async (server: FastifyZodProvider) => { await server.services.auditLog.createAuditLog({ ...req.auditLogInfo, - orgId: ca.orgId, + projectId: ca.projectId, event: { type: EventType.CREATE_SSH_CA, metadata: { @@ -93,7 +94,7 @@ export const registerSshCaRouter = async (server: FastifyZodProvider) => { await server.services.auditLog.createAuditLog({ ...req.auditLogInfo, - orgId: ca.orgId, + projectId: ca.projectId, event: { type: EventType.GET_SSH_CA, metadata: { @@ -169,7 +170,7 @@ export const registerSshCaRouter = async (server: FastifyZodProvider) => { await server.services.auditLog.createAuditLog({ ...req.auditLogInfo, - orgId: ca.orgId, + projectId: ca.projectId, event: { type: EventType.UPDATE_SSH_CA, metadata: { @@ -215,7 +216,7 @@ export const registerSshCaRouter = async (server: FastifyZodProvider) => { await server.services.auditLog.createAuditLog({ ...req.auditLogInfo, - orgId: ca.orgId, + projectId: ca.projectId, event: { type: EventType.DELETE_SSH_CA, metadata: { @@ -260,7 +261,7 @@ export const registerSshCaRouter = async (server: FastifyZodProvider) => { await server.services.auditLog.createAuditLog({ ...req.auditLogInfo, - orgId: ca.orgId, + projectId: ca.projectId, event: { type: EventType.GET_SSH_CA_CERTIFICATE_TEMPLATES, metadata: { diff --git a/backend/src/ee/routes/v1/ssh-certificate-template-router.ts b/backend/src/ee/routes/v1/ssh-certificate-template-router.ts index 7e828a588..14e1e0dc7 100644 --- a/backend/src/ee/routes/v1/ssh-certificate-template-router.ts +++ b/backend/src/ee/routes/v1/ssh-certificate-template-router.ts @@ -41,7 +41,7 @@ export const registerSshCertificateTemplateRouter = async (server: FastifyZodPro await server.services.auditLog.createAuditLog({ ...req.auditLogInfo, - orgId: certificateTemplate.orgId, + projectId: certificateTemplate.projectId, event: { type: EventType.GET_SSH_CERTIFICATE_TEMPLATE, metadata: { @@ -107,7 +107,7 @@ export const registerSshCertificateTemplateRouter = async (server: FastifyZodPro await server.services.auditLog.createAuditLog({ ...req.auditLogInfo, - orgId: ca.orgId, + projectId: ca.projectId, event: { type: EventType.CREATE_SSH_CERTIFICATE_TEMPLATE, metadata: { @@ -178,7 +178,7 @@ export const registerSshCertificateTemplateRouter = async (server: FastifyZodPro }, onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), handler: async (req) => { - const { certificateTemplate, orgId } = await server.services.sshCertificateTemplate.updateSshCertTemplate({ + const { certificateTemplate, projectId } = await server.services.sshCertificateTemplate.updateSshCertTemplate({ ...req.body, id: req.params.certificateTemplateId, actor: req.permission.type, @@ -189,7 +189,7 @@ export const registerSshCertificateTemplateRouter = async (server: FastifyZodPro await server.services.auditLog.createAuditLog({ ...req.auditLogInfo, - orgId, + projectId, event: { type: EventType.UPDATE_SSH_CERTIFICATE_TEMPLATE, metadata: { @@ -238,7 +238,7 @@ export const registerSshCertificateTemplateRouter = async (server: FastifyZodPro await server.services.auditLog.createAuditLog({ ...req.auditLogInfo, - orgId: certificateTemplate.orgId, + projectId: certificateTemplate.projectId, event: { type: EventType.DELETE_SSH_CERTIFICATE_TEMPLATE, metadata: { diff --git a/backend/src/ee/routes/v1/ssh-router.ts b/backend/src/ee/routes/v1/ssh-router.ts index 566cbb423..8a7226014 100644 --- a/backend/src/ee/routes/v1/ssh-router.ts +++ b/backend/src/ee/routes/v1/ssh-router.ts @@ -20,6 +20,7 @@ export const registerSshRouter = async (server: FastifyZodProvider) => { schema: { description: "Sign SSH public key", body: z.object({ + projectId: z.string().trim().describe(SSH_CERTIFICATE_AUTHORITIES.SIGN_SSH_KEY.projectId), templateName: z.string().trim().describe(SSH_CERTIFICATE_AUTHORITIES.SIGN_SSH_KEY.templateName), publicKey: z.string().trim().describe(SSH_CERTIFICATE_AUTHORITIES.SIGN_SSH_KEY.publicKey), certType: z @@ -86,6 +87,7 @@ export const registerSshRouter = async (server: FastifyZodProvider) => { schema: { description: "Issue SSH credentials (certificate + key)", body: z.object({ + projectId: z.string().trim().describe(SSH_CERTIFICATE_AUTHORITIES.ISSUE_SSH_CREDENTIALS.projectId), templateName: z.string().trim().describe(SSH_CERTIFICATE_AUTHORITIES.ISSUE_SSH_CREDENTIALS.templateName), keyAlgorithm: z .nativeEnum(CertKeyAlgorithm) diff --git a/backend/src/ee/services/permission/org-permission.ts b/backend/src/ee/services/permission/org-permission.ts index 89c4459d8..aac45b2d5 100644 --- a/backend/src/ee/services/permission/org-permission.ts +++ b/backend/src/ee/services/permission/org-permission.ts @@ -27,10 +27,7 @@ export enum OrgPermissionSubjects { Kms = "kms", AdminConsole = "organization-admin-console", AuditLogs = "audit-logs", - ProjectTemplates = "project-templates", - SshCertificates = "ssh-certificates", - SshCertificateAuthorities = "ssh-certificate-authorities", - SshCertificateTemplates = "ssh-certificate-templates" + ProjectTemplates = "project-templates" } export type OrgPermissionSet = @@ -49,10 +46,7 @@ export type OrgPermissionSet = | [OrgPermissionActions, OrgPermissionSubjects.Kms] | [OrgPermissionActions, OrgPermissionSubjects.AuditLogs] | [OrgPermissionActions, OrgPermissionSubjects.ProjectTemplates] - | [OrgPermissionAdminConsoleAction, OrgPermissionSubjects.AdminConsole] - | [OrgPermissionActions, OrgPermissionSubjects.SshCertificateAuthorities] - | [OrgPermissionActions, OrgPermissionSubjects.SshCertificates] - | [OrgPermissionActions, OrgPermissionSubjects.SshCertificateTemplates]; + | [OrgPermissionAdminConsoleAction, OrgPermissionSubjects.AdminConsole]; const buildAdminPermission = () => { const { can, rules } = new AbilityBuilder>(createMongoAbility); @@ -129,19 +123,6 @@ const buildAdminPermission = () => { can(OrgPermissionActions.Edit, OrgPermissionSubjects.ProjectTemplates); can(OrgPermissionActions.Delete, OrgPermissionSubjects.ProjectTemplates); - can(OrgPermissionActions.Read, OrgPermissionSubjects.SshCertificates); - can(OrgPermissionActions.Create, OrgPermissionSubjects.SshCertificates); - - can(OrgPermissionActions.Read, OrgPermissionSubjects.SshCertificateAuthorities); - can(OrgPermissionActions.Create, OrgPermissionSubjects.SshCertificateAuthorities); - can(OrgPermissionActions.Edit, OrgPermissionSubjects.SshCertificateAuthorities); - can(OrgPermissionActions.Delete, OrgPermissionSubjects.SshCertificateAuthorities); - - can(OrgPermissionActions.Read, OrgPermissionSubjects.SshCertificateTemplates); - can(OrgPermissionActions.Create, OrgPermissionSubjects.SshCertificateTemplates); - can(OrgPermissionActions.Edit, OrgPermissionSubjects.SshCertificateTemplates); - can(OrgPermissionActions.Delete, OrgPermissionSubjects.SshCertificateTemplates); - can(OrgPermissionAdminConsoleAction.AccessAllProjects, OrgPermissionSubjects.AdminConsole); return rules; @@ -172,11 +153,6 @@ const buildMemberPermission = () => { can(OrgPermissionActions.Read, OrgPermissionSubjects.AuditLogs); - can(OrgPermissionActions.Read, OrgPermissionSubjects.SshCertificateAuthorities); - can(OrgPermissionActions.Read, OrgPermissionSubjects.SshCertificates); - can(OrgPermissionActions.Create, OrgPermissionSubjects.SshCertificates); - can(OrgPermissionActions.Read, OrgPermissionSubjects.SshCertificateTemplates); - return rules; }; diff --git a/backend/src/ee/services/permission/project-permission.ts b/backend/src/ee/services/permission/project-permission.ts index c6e574fb1..145ec0d7f 100644 --- a/backend/src/ee/services/permission/project-permission.ts +++ b/backend/src/ee/services/permission/project-permission.ts @@ -54,6 +54,9 @@ export enum ProjectPermissionSub { CertificateAuthorities = "certificate-authorities", Certificates = "certificates", CertificateTemplates = "certificate-templates", + SshCertificateAuthorities = "ssh-certificate-authorities", + SshCertificates = "ssh-certificates", + SshCertificateTemplates = "ssh-certificate-templates", PkiAlerts = "pki-alerts", PkiCollections = "pki-collections", Kms = "kms", @@ -125,6 +128,9 @@ export type ProjectPermissionSet = | [ProjectPermissionActions, ProjectPermissionSub.CertificateAuthorities] | [ProjectPermissionActions, ProjectPermissionSub.Certificates] | [ProjectPermissionActions, ProjectPermissionSub.CertificateTemplates] + | [ProjectPermissionActions, ProjectPermissionSub.SshCertificateAuthorities] + | [ProjectPermissionActions, ProjectPermissionSub.SshCertificates] + | [ProjectPermissionActions, ProjectPermissionSub.SshCertificateTemplates] | [ProjectPermissionActions, ProjectPermissionSub.PkiAlerts] | [ProjectPermissionActions, ProjectPermissionSub.PkiCollections] | [ProjectPermissionCmekActions, ProjectPermissionSub.Cmek] @@ -322,6 +328,28 @@ const GeneralPermissionSchema = [ "Describe what action an entity can take." ) }), + z.object({ + subject: z + .literal(ProjectPermissionSub.SshCertificateAuthorities) + .describe("The entity this permission pertains to."), + action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionActions).describe( + "Describe what action an entity can take." + ) + }), + z.object({ + subject: z.literal(ProjectPermissionSub.SshCertificates).describe("The entity this permission pertains to."), + action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionActions).describe( + "Describe what action an entity can take." + ) + }), + z.object({ + subject: z + .literal(ProjectPermissionSub.SshCertificateTemplates) + .describe("The entity this permission pertains to."), + action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionActions).describe( + "Describe what action an entity can take." + ) + }), z.object({ subject: z.literal(ProjectPermissionSub.PkiAlerts).describe("The entity this permission pertains to."), action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionActions).describe( @@ -448,7 +476,10 @@ const buildAdminPermissionRules = () => { ProjectPermissionSub.Certificates, ProjectPermissionSub.CertificateTemplates, ProjectPermissionSub.PkiAlerts, - ProjectPermissionSub.PkiCollections + ProjectPermissionSub.PkiCollections, + ProjectPermissionSub.SshCertificateAuthorities, + ProjectPermissionSub.SshCertificates, + ProjectPermissionSub.SshCertificateTemplates ].forEach((el) => { can( [ @@ -633,6 +664,11 @@ const buildMemberPermissionRules = () => { can([ProjectPermissionActions.Read], ProjectPermissionSub.PkiAlerts); can([ProjectPermissionActions.Read], ProjectPermissionSub.PkiCollections); + can([ProjectPermissionActions.Read], ProjectPermissionSub.SshCertificateAuthorities); + can([ProjectPermissionActions.Read], ProjectPermissionSub.SshCertificates); + can([ProjectPermissionActions.Create], ProjectPermissionSub.SshCertificates); + can([ProjectPermissionActions.Read], ProjectPermissionSub.SshCertificateTemplates); + can( [ ProjectPermissionCmekActions.Create, @@ -675,6 +711,9 @@ const buildViewerPermissionRules = () => { can(ProjectPermissionActions.Read, ProjectPermissionSub.CertificateAuthorities); can(ProjectPermissionActions.Read, ProjectPermissionSub.Certificates); can(ProjectPermissionCmekActions.Read, ProjectPermissionSub.Cmek); + can(ProjectPermissionActions.Read, ProjectPermissionSub.SshCertificateAuthorities); + can(ProjectPermissionActions.Read, ProjectPermissionSub.SshCertificates); + can(ProjectPermissionActions.Read, ProjectPermissionSub.SshCertificateTemplates); return rules; }; diff --git a/backend/src/ee/services/ssh-certificate-template/ssh-certificate-template-dal.ts b/backend/src/ee/services/ssh-certificate-template/ssh-certificate-template-dal.ts index 62b6323ca..b8afa0df2 100644 --- a/backend/src/ee/services/ssh-certificate-template/ssh-certificate-template-dal.ts +++ b/backend/src/ee/services/ssh-certificate-template/ssh-certificate-template-dal.ts @@ -18,11 +18,11 @@ export const sshCertificateTemplateDALFactory = (db: TDbClient) => { `${TableName.SshCertificateAuthority}.id`, `${TableName.SshCertificateTemplate}.sshCaId` ) - .join(TableName.Organization, `${TableName.Organization}.id`, `${TableName.SshCertificateAuthority}.orgId`) + .join(TableName.Project, `${TableName.Project}.id`, `${TableName.SshCertificateAuthority}.projectId`) .where(`${TableName.SshCertificateTemplate}.id`, "=", id) .select(selectAllTableCols(TableName.SshCertificateTemplate)) .select( - db.ref("orgId").withSchema(TableName.SshCertificateAuthority), + db.ref("projectId").withSchema(TableName.SshCertificateAuthority), db.ref("friendlyName").as("caName").withSchema(TableName.SshCertificateAuthority), db.ref("status").as("caStatus").withSchema(TableName.SshCertificateAuthority) ) @@ -34,7 +34,10 @@ export const sshCertificateTemplateDALFactory = (db: TDbClient) => { } }; - const getByName = async (name: string, orgId: string, tx?: Knex) => { + /** + * Returns the SSH certificate template named [name] within project with id [projectId] + */ + const getByName = async (name: string, projectId: string, tx?: Knex) => { try { const certTemplate = await (tx || db.replicaNode())(TableName.SshCertificateTemplate) .join( @@ -42,12 +45,12 @@ export const sshCertificateTemplateDALFactory = (db: TDbClient) => { `${TableName.SshCertificateAuthority}.id`, `${TableName.SshCertificateTemplate}.sshCaId` ) - .join(TableName.Organization, `${TableName.Organization}.id`, `${TableName.SshCertificateAuthority}.orgId`) + .join(TableName.Project, `${TableName.Project}.id`, `${TableName.SshCertificateAuthority}.projectId`) .where(`${TableName.SshCertificateTemplate}.name`, "=", name) - .where(`${TableName.Organization}.id`, "=", orgId) + .where(`${TableName.Project}.id`, "=", projectId) .select(selectAllTableCols(TableName.SshCertificateTemplate)) .select( - db.ref("orgId").withSchema(TableName.SshCertificateAuthority), + db.ref("projectId").withSchema(TableName.SshCertificateAuthority), db.ref("friendlyName").as("caName").withSchema(TableName.SshCertificateAuthority), db.ref("status").as("caStatus").withSchema(TableName.SshCertificateAuthority) ) diff --git a/backend/src/ee/services/ssh-certificate-template/ssh-certificate-template-service.ts b/backend/src/ee/services/ssh-certificate-template/ssh-certificate-template-service.ts index a415f42b8..e68eb991a 100644 --- a/backend/src/ee/services/ssh-certificate-template/ssh-certificate-template-service.ts +++ b/backend/src/ee/services/ssh-certificate-template/ssh-certificate-template-service.ts @@ -1,8 +1,8 @@ import { ForbiddenError } from "@casl/ability"; import ms from "ms"; -import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service"; +import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission"; import { BadRequestError, NotFoundError } from "@app/lib/errors"; import { TSshCertificateAuthorityDALFactory } from "../ssh/ssh-certificate-authority-dal"; @@ -21,7 +21,7 @@ type TSshCertificateTemplateServiceFactoryDep = { "transaction" | "getByName" | "create" | "updateById" | "deleteById" | "getById" >; sshCertificateAuthorityDAL: Pick; - permissionService: Pick; + permissionService: Pick; }; export type TSshCertificateTemplateServiceFactory = ReturnType; @@ -53,17 +53,17 @@ export const sshCertificateTemplateServiceFactory = ({ }); } - const { permission } = await permissionService.getOrgPermission( + const { permission } = await permissionService.getProjectPermission( actor, actorId, - ca.orgId, + ca.projectId, actorAuthMethod, actorOrgId ); ForbiddenError.from(permission).throwUnlessCan( - OrgPermissionActions.Create, - OrgPermissionSubjects.SshCertificateTemplates + ProjectPermissionActions.Create, + ProjectPermissionSub.SshCertificateTemplates ); if (ms(ttl) > ms(maxTTL)) { @@ -73,7 +73,7 @@ export const sshCertificateTemplateServiceFactory = ({ } const newCertificateTemplate = await sshCertificateTemplateDAL.transaction(async (tx) => { - const existingTemplate = await sshCertificateTemplateDAL.getByName(name, ca.orgId, tx); + const existingTemplate = await sshCertificateTemplateDAL.getByName(name, ca.projectId, tx); if (existingTemplate) { throw new BadRequestError({ message: `SSH certificate template with name ${name} already exists` @@ -125,22 +125,22 @@ export const sshCertificateTemplateServiceFactory = ({ }); } - const { permission } = await permissionService.getOrgPermission( + const { permission } = await permissionService.getProjectPermission( actor, actorId, - certTemplate.orgId, + certTemplate.projectId, actorAuthMethod, actorOrgId ); ForbiddenError.from(permission).throwUnlessCan( - OrgPermissionActions.Edit, - OrgPermissionSubjects.SshCertificateTemplates + ProjectPermissionActions.Edit, + ProjectPermissionSub.SshCertificateTemplates ); const updatedCertificateTemplate = await sshCertificateTemplateDAL.transaction(async (tx) => { if (name) { - const existingTemplate = await sshCertificateTemplateDAL.getByName(name, actorOrgId, tx); + const existingTemplate = await sshCertificateTemplateDAL.getByName(name, certTemplate.projectId, tx); if (existingTemplate && existingTemplate.id !== id) { throw new BadRequestError({ message: `SSH certificate template with name ${name} already exists` @@ -175,7 +175,7 @@ export const sshCertificateTemplateServiceFactory = ({ return { certificateTemplate: updatedCertificateTemplate, - orgId: certTemplate.orgId + projectId: certTemplate.projectId }; }; @@ -193,17 +193,17 @@ export const sshCertificateTemplateServiceFactory = ({ }); } - const { permission } = await permissionService.getOrgPermission( + const { permission } = await permissionService.getProjectPermission( actor, actorId, - certificateTemplate.orgId, + certificateTemplate.projectId, actorAuthMethod, actorOrgId ); ForbiddenError.from(permission).throwUnlessCan( - OrgPermissionActions.Delete, - OrgPermissionSubjects.SshCertificateTemplates + ProjectPermissionActions.Delete, + ProjectPermissionSub.SshCertificateTemplates ); await sshCertificateTemplateDAL.deleteById(certificateTemplate.id); @@ -219,17 +219,17 @@ export const sshCertificateTemplateServiceFactory = ({ }); } - const { permission } = await permissionService.getOrgPermission( + const { permission } = await permissionService.getProjectPermission( actor, actorId, - certTemplate.orgId, + certTemplate.projectId, actorAuthMethod, actorOrgId ); ForbiddenError.from(permission).throwUnlessCan( - OrgPermissionActions.Read, - OrgPermissionSubjects.SshCertificateTemplates + ProjectPermissionActions.Read, + ProjectPermissionSub.SshCertificateTemplates ); return certTemplate; diff --git a/backend/src/ee/services/ssh-certificate/ssh-certificate-body-dal.ts b/backend/src/ee/services/ssh-certificate/ssh-certificate-body-dal.ts new file mode 100644 index 000000000..c3d16a39e --- /dev/null +++ b/backend/src/ee/services/ssh-certificate/ssh-certificate-body-dal.ts @@ -0,0 +1,10 @@ +import { TDbClient } from "@app/db"; +import { TableName } from "@app/db/schemas"; +import { ormify } from "@app/lib/knex"; + +export type TSshCertificateBodyDALFactory = ReturnType; + +export const sshCertificateBodyDALFactory = (db: TDbClient) => { + const sshCertificateBodyOrm = ormify(db, TableName.SshCertificateBody); + return sshCertificateBodyOrm; +}; diff --git a/backend/src/ee/services/ssh-certificate/ssh-certificate-dal.ts b/backend/src/ee/services/ssh-certificate/ssh-certificate-dal.ts index 95cc4766e..9c5bd1d3e 100644 --- a/backend/src/ee/services/ssh-certificate/ssh-certificate-dal.ts +++ b/backend/src/ee/services/ssh-certificate/ssh-certificate-dal.ts @@ -8,7 +8,7 @@ export type TSshCertificateDALFactory = ReturnType { const sshCertificateOrm = ormify(db, TableName.SshCertificate); - const countSshCertificatesInOrg = async (orgId: string) => { + const countSshCertificatesInProject = async (projectId: string) => { try { interface CountResult { count: string; @@ -21,18 +21,18 @@ export const sshCertificateDALFactory = (db: TDbClient) => { `${TableName.SshCertificate}.sshCaId`, `${TableName.SshCertificateAuthority}.id` ) - .join(TableName.Organization, `${TableName.SshCertificateAuthority}.orgId`, `${TableName.Organization}.id`) - .where(`${TableName.Organization}.id`, orgId); + .join(TableName.Project, `${TableName.SshCertificateAuthority}.projectId`, `${TableName.Project}.id`) + .where(`${TableName.Project}.id`, projectId); const count = await query.count("*").first(); return parseInt((count as unknown as CountResult).count || "0", 10); } catch (error) { - throw new DatabaseError({ error, name: "Count all SSH certificates in organization" }); + throw new DatabaseError({ error, name: "Count all SSH certificates in project" }); } }; return { ...sshCertificateOrm, - countSshCertificatesInOrg + countSshCertificatesInProject }; }; diff --git a/backend/src/ee/services/ssh/ssh-certificate-authority-schema.ts b/backend/src/ee/services/ssh/ssh-certificate-authority-schema.ts index 82561dda6..9ff76efbc 100644 --- a/backend/src/ee/services/ssh/ssh-certificate-authority-schema.ts +++ b/backend/src/ee/services/ssh/ssh-certificate-authority-schema.ts @@ -2,7 +2,7 @@ import { SshCertificateAuthoritiesSchema } from "@app/db/schemas"; export const sanitizedSshCa = SshCertificateAuthoritiesSchema.pick({ id: true, - orgId: true, + projectId: true, friendlyName: true, status: true, keyAlgorithm: true diff --git a/backend/src/ee/services/ssh/ssh-certificate-authority-service.ts b/backend/src/ee/services/ssh/ssh-certificate-authority-service.ts index 18b3c712d..2844407b2 100644 --- a/backend/src/ee/services/ssh/ssh-certificate-authority-service.ts +++ b/backend/src/ee/services/ssh/ssh-certificate-authority-service.ts @@ -1,13 +1,15 @@ import { ForbiddenError } from "@casl/ability"; -import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service"; +import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission"; import { TSshCertificateAuthorityDALFactory } from "@app/ee/services/ssh/ssh-certificate-authority-dal"; import { TSshCertificateAuthoritySecretDALFactory } from "@app/ee/services/ssh/ssh-certificate-authority-secret-dal"; +import { TSshCertificateBodyDALFactory } from "@app/ee/services/ssh-certificate/ssh-certificate-body-dal"; import { TSshCertificateDALFactory } from "@app/ee/services/ssh-certificate/ssh-certificate-dal"; import { TSshCertificateTemplateDALFactory } from "@app/ee/services/ssh-certificate-template/ssh-certificate-template-dal"; import { BadRequestError, NotFoundError } from "@app/lib/errors"; import { TKmsServiceFactory } from "@app/services/kms/kms-service"; +import { KmsDataKey } from "@app/services/kms/kms-types"; import { SshCertTemplateStatus } from "../ssh-certificate-template/ssh-certificate-template-types"; import { @@ -37,18 +39,25 @@ type TSshCertificateAuthorityServiceFactoryDep = { >; sshCertificateAuthoritySecretDAL: Pick; sshCertificateTemplateDAL: Pick; - sshCertificateDAL: Pick; - kmsService: Pick; - permissionService: Pick; + sshCertificateDAL: Pick; + sshCertificateBodyDAL: Pick; + kmsService: Pick< + TKmsServiceFactory, + "generateKmsKey" | "encryptWithKmsKey" | "decryptWithKmsKey" | "getOrgKmsKeyId" | "createCipherPairWithDataKey" + >; + permissionService: Pick; }; export type TSshCertificateAuthorityServiceFactory = ReturnType; +// TODO: secretManagerEncryptor -> sshEncryptor (cc akhil) + export const sshCertificateAuthorityServiceFactory = ({ sshCertificateAuthorityDAL, sshCertificateAuthoritySecretDAL, sshCertificateTemplateDAL, sshCertificateDAL, + sshCertificateBodyDAL, kmsService, permissionService }: TSshCertificateAuthorityServiceFactoryDep) => { @@ -56,6 +65,7 @@ export const sshCertificateAuthorityServiceFactory = ({ * Generates a new SSH CA */ const createSshCa = async ({ + projectId, friendlyName, keyAlgorithm, actorId, @@ -63,23 +73,23 @@ export const sshCertificateAuthorityServiceFactory = ({ actor, actorOrgId }: TCreateSshCaDTO) => { - const { permission } = await permissionService.getOrgPermission( + const { permission } = await permissionService.getProjectPermission( actor, actorId, - actorOrgId, + projectId, actorAuthMethod, actorOrgId ); ForbiddenError.from(permission).throwUnlessCan( - OrgPermissionActions.Create, - OrgPermissionSubjects.SshCertificateAuthorities + ProjectPermissionActions.Create, + ProjectPermissionSub.SshCertificateAuthorities ); const newCa = await sshCertificateAuthorityDAL.transaction(async (tx) => { const ca = await sshCertificateAuthorityDAL.create( { - orgId: actorOrgId, + projectId, friendlyName, status: SshCaStatus.ACTIVE, keyAlgorithm @@ -89,19 +99,16 @@ export const sshCertificateAuthorityServiceFactory = ({ const { publicKey, privateKey } = createSshKeyPair(keyAlgorithm, ca.friendlyName); - const orgKmsKeyId = await kmsService.getOrgKmsKeyId(actorOrgId); - const kmsEncryptor = await kmsService.encryptWithKmsKey({ - kmsId: orgKmsKeyId - }); - - const { cipherTextBlob: encryptedPrivateKey } = await kmsEncryptor({ - plainText: Buffer.from(privateKey, "utf8") + // TODO: update to sshEncryptor + const { encryptor: secretManagerEncryptor } = await kmsService.createCipherPairWithDataKey({ + type: KmsDataKey.SecretManager, + projectId }); await sshCertificateAuthoritySecretDAL.create( { sshCaId: ca.id, - encryptedPrivateKey + encryptedPrivateKey: secretManagerEncryptor({ plainText: Buffer.from(privateKey, "utf8") }).cipherTextBlob }, tx ); @@ -119,28 +126,28 @@ export const sshCertificateAuthorityServiceFactory = ({ const ca = await sshCertificateAuthorityDAL.findById(caId); if (!ca) throw new NotFoundError({ message: `SSH CA with ID '${caId}' not found` }); - const { permission } = await permissionService.getOrgPermission( + const { permission } = await permissionService.getProjectPermission( actor, actorId, - ca.orgId, + ca.projectId, actorAuthMethod, actorOrgId ); ForbiddenError.from(permission).throwUnlessCan( - OrgPermissionActions.Read, - OrgPermissionSubjects.SshCertificateAuthorities + ProjectPermissionActions.Read, + ProjectPermissionSub.SshCertificateAuthorities ); const sshCaSecret = await sshCertificateAuthoritySecretDAL.findOne({ sshCaId: ca.id }); - // decrypt secret - const orgKmsKeyId = await kmsService.getOrgKmsKeyId(actorOrgId); - const kmsDecryptor = await kmsService.decryptWithKmsKey({ - kmsId: orgKmsKeyId + // TODO: update to sshDecryptor + const { decryptor: secretManagerDecryptor } = await kmsService.createCipherPairWithDataKey({ + type: KmsDataKey.SecretManager, + projectId: ca.projectId }); - const decryptedCaPrivateKey = await kmsDecryptor({ + const decryptedCaPrivateKey = secretManagerDecryptor({ cipherTextBlob: sshCaSecret.encryptedPrivateKey }); @@ -158,13 +165,13 @@ export const sshCertificateAuthorityServiceFactory = ({ const sshCaSecret = await sshCertificateAuthoritySecretDAL.findOne({ sshCaId: ca.id }); - // decrypt secret - const orgKmsKeyId = await kmsService.getOrgKmsKeyId(ca.orgId); - const kmsDecryptor = await kmsService.decryptWithKmsKey({ - kmsId: orgKmsKeyId + // TODO: update to sshDecryptor + const { decryptor: secretManagerDecryptor } = await kmsService.createCipherPairWithDataKey({ + type: KmsDataKey.SecretManager, + projectId: ca.projectId }); - const decryptedCaPrivateKey = await kmsDecryptor({ + const decryptedCaPrivateKey = secretManagerDecryptor({ cipherTextBlob: sshCaSecret.encryptedPrivateKey }); @@ -189,30 +196,30 @@ export const sshCertificateAuthorityServiceFactory = ({ const ca = await sshCertificateAuthorityDAL.findById(caId); if (!ca) throw new NotFoundError({ message: `SSH CA with ID '${caId}' not found` }); - const { permission } = await permissionService.getOrgPermission( + const { permission } = await permissionService.getProjectPermission( actor, actorId, - ca.orgId, + ca.projectId, actorAuthMethod, actorOrgId ); ForbiddenError.from(permission).throwUnlessCan( - OrgPermissionActions.Edit, - OrgPermissionSubjects.SshCertificateAuthorities + ProjectPermissionActions.Edit, + ProjectPermissionSub.SshCertificateAuthorities ); const updatedCa = await sshCertificateAuthorityDAL.updateById(caId, { friendlyName, status }); const sshCaSecret = await sshCertificateAuthoritySecretDAL.findOne({ sshCaId: ca.id }); - // decrypt secret - const orgKmsKeyId = await kmsService.getOrgKmsKeyId(actorOrgId); - const kmsDecryptor = await kmsService.decryptWithKmsKey({ - kmsId: orgKmsKeyId + // TODO: update to sshDecryptor + const { decryptor: secretManagerDecryptor } = await kmsService.createCipherPairWithDataKey({ + type: KmsDataKey.SecretManager, + projectId: ca.projectId }); - const decryptedCaPrivateKey = await kmsDecryptor({ + const decryptedCaPrivateKey = secretManagerDecryptor({ cipherTextBlob: sshCaSecret.encryptedPrivateKey }); @@ -228,17 +235,17 @@ export const sshCertificateAuthorityServiceFactory = ({ const ca = await sshCertificateAuthorityDAL.findById(caId); if (!ca) throw new NotFoundError({ message: `SSH CA with ID '${caId}' not found` }); - const { permission } = await permissionService.getOrgPermission( + const { permission } = await permissionService.getProjectPermission( actor, actorId, - ca.orgId, + ca.projectId, actorAuthMethod, actorOrgId ); ForbiddenError.from(permission).throwUnlessCan( - OrgPermissionActions.Delete, - OrgPermissionSubjects.SshCertificateAuthorities + ProjectPermissionActions.Delete, + ProjectPermissionSub.SshCertificateAuthorities ); const deletedCa = await sshCertificateAuthorityDAL.deleteById(caId); @@ -251,6 +258,7 @@ export const sshCertificateAuthorityServiceFactory = ({ * SSH public key is signed using CA behind SSH certificate with name [templateName]. */ const issueSshCreds = async ({ + projectId, templateName, keyAlgorithm, certType, @@ -262,22 +270,25 @@ export const sshCertificateAuthorityServiceFactory = ({ actorAuthMethod, actorOrgId }: TIssueSshCredsDTO) => { - const sshCertificateTemplate = await sshCertificateTemplateDAL.getByName(templateName, actorOrgId); + const sshCertificateTemplate = await sshCertificateTemplateDAL.getByName(templateName, projectId); if (!sshCertificateTemplate) { throw new NotFoundError({ message: "No SSH certificate template found with specified name" }); } - const { permission } = await permissionService.getOrgPermission( + const { permission } = await permissionService.getProjectPermission( actor, actorId, - actorOrgId, + sshCertificateTemplate.projectId, actorAuthMethod, actorOrgId ); - ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.SshCertificates); + ForbiddenError.from(permission).throwUnlessCan( + ProjectPermissionActions.Create, + ProjectPermissionSub.SshCertificates + ); if (sshCertificateTemplate.caStatus === SshCaStatus.DISABLED) { throw new BadRequestError({ @@ -307,13 +318,13 @@ export const sshCertificateAuthorityServiceFactory = ({ const sshCaSecret = await sshCertificateAuthoritySecretDAL.findOne({ sshCaId: sshCertificateTemplate.sshCaId }); - // decrypt secret - const orgKmsKeyId = await kmsService.getOrgKmsKeyId(actorOrgId); - const kmsDecryptor = await kmsService.decryptWithKmsKey({ - kmsId: orgKmsKeyId + // TODO: update to sshDecryptor + const { decryptor: secretManagerDecryptor } = await kmsService.createCipherPairWithDataKey({ + type: KmsDataKey.SecretManager, + projectId }); - const decryptedCaPrivateKey = await kmsDecryptor({ + const decryptedCaPrivateKey = secretManagerDecryptor({ cipherTextBlob: sshCaSecret.encryptedPrivateKey }); @@ -329,16 +340,38 @@ export const sshCertificateAuthorityServiceFactory = ({ certType }); - await sshCertificateDAL.create({ - sshCaId: sshCertificateTemplate.sshCaId, - sshCertificateTemplateId: sshCertificateTemplate.id, - serialNumber, - certType, - publicKey, - principals, - keyId, - notBefore: new Date(), - notAfter: new Date(Date.now() + ttl * 1000) + // TODO: update to sshEncryptor + const { encryptor: secretManagerEncryptor } = await kmsService.createCipherPairWithDataKey({ + type: KmsDataKey.SecretManager, + projectId: sshCertificateTemplate.projectId + }); + + const encryptedCertificate = secretManagerEncryptor({ + plainText: Buffer.from(signedPublicKey, "utf8") + }).cipherTextBlob; + + await sshCertificateDAL.transaction(async (tx) => { + const cert = await sshCertificateDAL.create( + { + sshCaId: sshCertificateTemplate.sshCaId, + sshCertificateTemplateId: sshCertificateTemplate.id, + serialNumber, + certType, + principals, + keyId, + notBefore: new Date(), + notAfter: new Date(Date.now() + ttl * 1000) + }, + tx + ); + + await sshCertificateBodyDAL.create( + { + sshCertId: cert.id, + encryptedCertificate + }, + tx + ); }); return { @@ -357,6 +390,7 @@ export const sshCertificateAuthorityServiceFactory = ({ * using CA behind SSH certificate template with name [templateName] */ const signSshKey = async ({ + projectId, templateName, publicKey, certType, @@ -368,22 +402,25 @@ export const sshCertificateAuthorityServiceFactory = ({ actorAuthMethod, actorOrgId }: TSignSshKeyDTO) => { - const sshCertificateTemplate = await sshCertificateTemplateDAL.getByName(templateName, actorOrgId); + const sshCertificateTemplate = await sshCertificateTemplateDAL.getByName(templateName, projectId); if (!sshCertificateTemplate) { throw new NotFoundError({ message: "No SSH certificate template found with specified name" }); } - const { permission } = await permissionService.getOrgPermission( + const { permission } = await permissionService.getProjectPermission( actor, actorId, - actorOrgId, + sshCertificateTemplate.projectId, actorAuthMethod, actorOrgId ); - ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.SshCertificates); + ForbiddenError.from(permission).throwUnlessCan( + ProjectPermissionActions.Create, + ProjectPermissionSub.SshCertificates + ); if (sshCertificateTemplate.caStatus === SshCaStatus.DISABLED) { throw new BadRequestError({ @@ -413,13 +450,13 @@ export const sshCertificateAuthorityServiceFactory = ({ const sshCaSecret = await sshCertificateAuthoritySecretDAL.findOne({ sshCaId: sshCertificateTemplate.sshCaId }); - // decrypt secret - const orgKmsKeyId = await kmsService.getOrgKmsKeyId(actorOrgId); - const kmsDecryptor = await kmsService.decryptWithKmsKey({ - kmsId: orgKmsKeyId + // TODO: update to sshDecryptor + const { decryptor: secretManagerDecryptor } = await kmsService.createCipherPairWithDataKey({ + type: KmsDataKey.SecretManager, + projectId }); - const decryptedCaPrivateKey = await kmsDecryptor({ + const decryptedCaPrivateKey = secretManagerDecryptor({ cipherTextBlob: sshCaSecret.encryptedPrivateKey }); @@ -432,16 +469,38 @@ export const sshCertificateAuthorityServiceFactory = ({ certType }); - await sshCertificateDAL.create({ - sshCaId: sshCertificateTemplate.sshCaId, - sshCertificateTemplateId: sshCertificateTemplate.id, - serialNumber, - certType, - publicKey, - principals, - keyId, - notBefore: new Date(), - notAfter: new Date(Date.now() + ttl * 1000) + // TODO: update to sshEncryptor + const { encryptor: secretManagerEncryptor } = await kmsService.createCipherPairWithDataKey({ + type: KmsDataKey.SecretManager, + projectId: sshCertificateTemplate.projectId + }); + + const encryptedCertificate = secretManagerEncryptor({ + plainText: Buffer.from(signedPublicKey, "utf8") + }).cipherTextBlob; + + await sshCertificateDAL.transaction(async (tx) => { + const cert = await sshCertificateDAL.create( + { + sshCaId: sshCertificateTemplate.sshCaId, + sshCertificateTemplateId: sshCertificateTemplate.id, + serialNumber, + certType, + principals, + keyId, + notBefore: new Date(), + notAfter: new Date(Date.now() + ttl * 1000) + }, + tx + ); + + await sshCertificateBodyDAL.create( + { + sshCertId: cert.id, + encryptedCertificate + }, + tx + ); }); return { serialNumber, signedPublicKey, certificateTemplate: sshCertificateTemplate, ttl, keyId }; @@ -457,17 +516,17 @@ export const sshCertificateAuthorityServiceFactory = ({ const ca = await sshCertificateAuthorityDAL.findById(caId); if (!ca) throw new NotFoundError({ message: `SSH CA with ID '${caId}' not found` }); - const { permission } = await permissionService.getOrgPermission( + const { permission } = await permissionService.getProjectPermission( actor, actorId, - actorOrgId, + ca.projectId, actorAuthMethod, actorOrgId ); ForbiddenError.from(permission).throwUnlessCan( - OrgPermissionActions.Read, - OrgPermissionSubjects.SshCertificateTemplates + ProjectPermissionActions.Read, + ProjectPermissionSub.SshCertificateTemplates ); const certificateTemplates = await sshCertificateTemplateDAL.find({ sshCaId: caId }); diff --git a/backend/src/ee/services/ssh/ssh-certificate-authority-types.ts b/backend/src/ee/services/ssh/ssh-certificate-authority-types.ts index 7d949bfa0..0f94f5037 100644 --- a/backend/src/ee/services/ssh/ssh-certificate-authority-types.ts +++ b/backend/src/ee/services/ssh/ssh-certificate-authority-types.ts @@ -1,4 +1,4 @@ -import { TOrgPermission } from "@app/lib/types"; +import { TProjectPermission } from "@app/lib/types"; import { CertKeyAlgorithm } from "@app/services/certificate/certificate-types"; export enum SshCaStatus { @@ -14,11 +14,11 @@ export enum SshCertType { export type TCreateSshCaDTO = { friendlyName: string; keyAlgorithm: CertKeyAlgorithm; -} & Omit; +} & TProjectPermission; export type TGetSshCaDTO = { caId: string; -} & Omit; +} & Omit; export type TGetSshCaPublicKeyDTO = { caId: string; @@ -28,11 +28,11 @@ export type TUpdateSshCaDTO = { caId: string; friendlyName?: string; status?: SshCaStatus; -} & Omit; +} & Omit; export type TDeleteSshCaDTO = { caId: string; -} & Omit; +} & Omit; export type TIssueSshCredsDTO = { templateName: string; @@ -41,7 +41,7 @@ export type TIssueSshCredsDTO = { principals: string[]; ttl?: string; keyId?: string; -} & Omit; +} & TProjectPermission; export type TSignSshKeyDTO = { templateName: string; @@ -50,11 +50,11 @@ export type TSignSshKeyDTO = { principals: string[]; ttl?: string; keyId?: string; -} & Omit; +} & TProjectPermission; export type TGetSshCaCertificateTemplatesDTO = { caId: string; -} & Omit; +} & Omit; export type TCreateSshCertDTO = { caPrivateKey: string; diff --git a/backend/src/lib/api-docs/constants.ts b/backend/src/lib/api-docs/constants.ts index 500611d6f..c4902dc27 100644 --- a/backend/src/lib/api-docs/constants.ts +++ b/backend/src/lib/api-docs/constants.ts @@ -384,17 +384,6 @@ export const ORGANIZATIONS = { }, LIST_GROUPS: { organizationId: "The ID of the organization to list groups for." - }, - LIST_SSH_CAS: { - organizationId: "The ID of the organization to list SSH CAs for." - }, - LIST_SSH_CERTIFICATES: { - organizationId: "The ID of the organization to list SSH certificates for.", - offset: "The offset to start from. If you enter 10, it will start from the 10th SSH certificate.", - limit: "The number of SSH certificates to return." - }, - LIST_SSH_CERTIFICATE_TEMPLATES: { - organizationId: "The ID of the organization to list SSH certificate templates for." } } as const; @@ -455,7 +444,15 @@ export const PROJECTS = { workspaceId: "The ID of the project to list integration auths for." }, LIST_SSH_CAS: { - slug: "The slug of the project to list SSH CAs for." + projectId: "The ID of the project to list SSH CAs for." + }, + LIST_SSH_CERTIFICATES: { + projectId: "The ID of the project to list SSH certificates for.", + offset: "The offset to start from. If you enter 10, it will start from the 10th SSH certificate.", + limit: "The number of SSH certificates to return." + }, + LIST_SSH_CERTIFICATE_TEMPLATES: { + projectId: "The ID of the project to list SSH certificate templates for." }, LIST_CAS: { slug: "The slug of the project to list CAs for.", @@ -1148,6 +1145,7 @@ export const AUDIT_LOG_STREAMS = { export const SSH_CERTIFICATE_AUTHORITIES = { CREATE: { + projectId: "The ID of the project to create the SSH CA in.", friendlyName: "A friendly name for the SSH CA.", keyAlgorithm: "The type of public key algorithm and size, in bits, of the key pair for the SSH CA." }, @@ -1169,6 +1167,7 @@ export const SSH_CERTIFICATE_AUTHORITIES = { sshCaId: "The ID of the SSH CA to get the certificate templates for." }, SIGN_SSH_KEY: { + projectId: "The ID of the project to sign the SSH public key for.", templateName: "The name of the SSH certificate template to sign the SSH public key with.", publicKey: "The SSH public key to sign.", certType: "The type of certificate to issue. This can be one of user or host.", @@ -1179,6 +1178,7 @@ export const SSH_CERTIFICATE_AUTHORITIES = { signedKey: "The SSH certificate or signed SSH public key." }, ISSUE_SSH_CREDENTIALS: { + projectId: "The ID of the project to issue the SSH credentials for.", templateName: "The name of the SSH certificate template to issue the SSH credentials with.", keyAlgorithm: "The type of public key algorithm and size, in bits, of the key pair for the SSH CA.", certType: "The type of certificate to issue. This can be one of user or host.", diff --git a/backend/src/server/routes/index.ts b/backend/src/server/routes/index.ts index 02ba4eba2..709d47abd 100644 --- a/backend/src/server/routes/index.ts +++ b/backend/src/server/routes/index.ts @@ -78,6 +78,7 @@ import { snapshotSecretV2DALFactory } from "@app/ee/services/secret-snapshot/sna import { sshCertificateAuthorityDALFactory } from "@app/ee/services/ssh/ssh-certificate-authority-dal"; import { sshCertificateAuthoritySecretDALFactory } from "@app/ee/services/ssh/ssh-certificate-authority-secret-dal"; import { sshCertificateAuthorityServiceFactory } from "@app/ee/services/ssh/ssh-certificate-authority-service"; +import { sshCertificateBodyDALFactory } from "@app/ee/services/ssh-certificate/ssh-certificate-body-dal"; import { sshCertificateDALFactory } from "@app/ee/services/ssh-certificate/ssh-certificate-dal"; import { sshCertificateTemplateDALFactory } from "@app/ee/services/ssh-certificate-template/ssh-certificate-template-dal"; import { sshCertificateTemplateServiceFactory } from "@app/ee/services/ssh-certificate-template/ssh-certificate-template-service"; @@ -349,6 +350,7 @@ export const registerRoutes = async ( const dynamicSecretLeaseDAL = dynamicSecretLeaseDALFactory(db); const sshCertificateDAL = sshCertificateDALFactory(db); + const sshCertificateBodyDAL = sshCertificateBodyDALFactory(db); const sshCertificateAuthorityDAL = sshCertificateAuthorityDALFactory(db); const sshCertificateAuthoritySecretDAL = sshCertificateAuthoritySecretDALFactory(db); const sshCertificateTemplateDAL = sshCertificateTemplateDALFactory(db); @@ -565,10 +567,7 @@ export const registerRoutes = async ( groupDAL, orgBotDAL, oidcConfigDAL, - projectBotService, - sshCertificateAuthorityDAL, - sshCertificateDAL, - sshCertificateTemplateDAL + projectBotService }); const signupService = authSignupServiceFactory({ tokenService, @@ -721,6 +720,7 @@ export const registerRoutes = async ( sshCertificateAuthoritySecretDAL, sshCertificateTemplateDAL, sshCertificateDAL, + sshCertificateBodyDAL, kmsService, permissionService }); @@ -817,6 +817,9 @@ export const registerRoutes = async ( certificateDAL, pkiAlertDAL, pkiCollectionDAL, + sshCertificateAuthorityDAL, + sshCertificateDAL, + sshCertificateTemplateDAL, projectUserMembershipRoleDAL, identityProjectMembershipRoleDAL, keyStore, diff --git a/backend/src/server/routes/v1/organization-router.ts b/backend/src/server/routes/v1/organization-router.ts index c249bf2e5..07f795779 100644 --- a/backend/src/server/routes/v1/organization-router.ts +++ b/backend/src/server/routes/v1/organization-router.ts @@ -11,9 +11,6 @@ import { UsersSchema } from "@app/db/schemas"; import { EventType, UserAgentType } from "@app/ee/services/audit-log/audit-log-types"; -import { sanitizedSshCa } from "@app/ee/services/ssh/ssh-certificate-authority-schema"; -import { sanitizedSshCertificate } from "@app/ee/services/ssh-certificate/ssh-certificate-schema"; -import { sanitizedSshCertificateTemplate } from "@app/ee/services/ssh-certificate-template/ssh-certificate-template-schema"; import { AUDIT_LOGS, ORGANIZATIONS } from "@app/lib/api-docs"; import { getLastMidnightDateISO } from "@app/lib/fn"; import { readLimit, writeLimit } from "@app/server/config/rateLimiter"; @@ -407,101 +404,4 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => { return { groups }; } }); - - server.route({ - method: "GET", - url: "/:organizationId/ssh-certificates", - config: { - rateLimit: readLimit - }, - schema: { - params: z.object({ - organizationId: z.string().trim().describe(ORGANIZATIONS.LIST_SSH_CAS.organizationId) - }), - querystring: z.object({ - offset: z.coerce.number().default(0).describe(ORGANIZATIONS.LIST_SSH_CERTIFICATES.offset), - limit: z.coerce.number().default(25).describe(ORGANIZATIONS.LIST_SSH_CERTIFICATES.limit) - }), - response: { - 200: z.object({ - certificates: z.array(sanitizedSshCertificate), - totalCount: z.number() - }) - } - }, - onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), - handler: async (req) => { - const { certificates, totalCount } = await server.services.org.listOrgSshCertificates({ - actorId: req.permission.id, - actorOrgId: req.permission.orgId, - actorAuthMethod: req.permission.authMethod, - actor: req.permission.type, - orgId: req.params.organizationId, - offset: req.query.offset, - limit: req.query.limit - }); - - return { certificates, totalCount }; - } - }); - - server.route({ - method: "GET", - url: "/:organizationId/ssh-certificate-templates", - config: { - rateLimit: readLimit - }, - schema: { - params: z.object({ - organizationId: z.string().trim().describe(ORGANIZATIONS.LIST_SSH_CERTIFICATE_TEMPLATES.organizationId) - }), - response: { - 200: z.object({ - certificateTemplates: z.array(sanitizedSshCertificateTemplate) - }) - } - }, - onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), - handler: async (req) => { - const { certificateTemplates } = await server.services.org.listOrgSshCertificateTemplates({ - actorId: req.permission.id, - actorOrgId: req.permission.orgId, - actorAuthMethod: req.permission.authMethod, - actor: req.permission.type, - orgId: req.params.organizationId - }); - - return { certificateTemplates }; - } - }); - - server.route({ - method: "GET", - url: "/:organizationId/ssh-cas", - config: { - rateLimit: readLimit - }, - schema: { - params: z.object({ - organizationId: z.string().trim().describe(ORGANIZATIONS.LIST_SSH_CAS.organizationId) - }), - response: { - 200: z.object({ - cas: z.array(sanitizedSshCa) - }) - } - }, - onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), - handler: async (req) => { - const cas = await server.services.org.listOrgSshCas({ - actorId: req.permission.id, - actorOrgId: req.permission.orgId, - actorAuthMethod: req.permission.authMethod, - actor: req.permission.type, - orgId: req.params.organizationId - }); - - return { cas }; - } - }); }; diff --git a/backend/src/server/routes/v2/project-router.ts b/backend/src/server/routes/v2/project-router.ts index 0e271eb0e..2ab3621ed 100644 --- a/backend/src/server/routes/v2/project-router.ts +++ b/backend/src/server/routes/v2/project-router.ts @@ -10,6 +10,9 @@ import { } from "@app/db/schemas"; import { EventType } from "@app/ee/services/audit-log/audit-log-types"; import { InfisicalProjectTemplate } from "@app/ee/services/project-template/project-template-types"; +import { sanitizedSshCa } from "@app/ee/services/ssh/ssh-certificate-authority-schema"; +import { sanitizedSshCertificate } from "@app/ee/services/ssh-certificate/ssh-certificate-schema"; +import { sanitizedSshCertificateTemplate } from "@app/ee/services/ssh-certificate-template/ssh-certificate-template-schema"; import { PROJECTS } from "@app/lib/api-docs"; import { readLimit, writeLimit } from "@app/server/config/rateLimiter"; import { getTelemetryDistinctId } from "@app/server/lib/telemetry"; @@ -517,4 +520,101 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { return { certificateTemplates }; } }); + + server.route({ + method: "GET", + url: "/:projectId/ssh-certificates", + config: { + rateLimit: readLimit + }, + schema: { + params: z.object({ + projectId: z.string().trim().describe(PROJECTS.LIST_SSH_CAS.projectId) + }), + querystring: z.object({ + offset: z.coerce.number().default(0).describe(PROJECTS.LIST_SSH_CERTIFICATES.offset), + limit: z.coerce.number().default(25).describe(PROJECTS.LIST_SSH_CERTIFICATES.limit) + }), + response: { + 200: z.object({ + certificates: z.array(sanitizedSshCertificate), + totalCount: z.number() + }) + } + }, + onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), + handler: async (req) => { + const { certificates, totalCount } = await server.services.project.listProjectSshCertificates({ + actorId: req.permission.id, + actorOrgId: req.permission.orgId, + actorAuthMethod: req.permission.authMethod, + actor: req.permission.type, + projectId: req.params.projectId, + offset: req.query.offset, + limit: req.query.limit + }); + + return { certificates, totalCount }; + } + }); + + server.route({ + method: "GET", + url: "/:projectId/ssh-certificate-templates", + config: { + rateLimit: readLimit + }, + schema: { + params: z.object({ + projectId: z.string().trim().describe(PROJECTS.LIST_SSH_CERTIFICATE_TEMPLATES.projectId) + }), + response: { + 200: z.object({ + certificateTemplates: z.array(sanitizedSshCertificateTemplate) + }) + } + }, + onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), + handler: async (req) => { + const { certificateTemplates } = await server.services.project.listProjectSshCertificateTemplates({ + actorId: req.permission.id, + actorOrgId: req.permission.orgId, + actorAuthMethod: req.permission.authMethod, + actor: req.permission.type, + projectId: req.params.projectId + }); + + return { certificateTemplates }; + } + }); + + server.route({ + method: "GET", + url: "/:projectId/ssh-cas", + config: { + rateLimit: readLimit + }, + schema: { + params: z.object({ + projectId: z.string().trim().describe(PROJECTS.LIST_SSH_CAS.projectId) + }), + response: { + 200: z.object({ + cas: z.array(sanitizedSshCa) + }) + } + }, + onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), + handler: async (req) => { + const cas = await server.services.project.listProjectSshCas({ + actorId: req.permission.id, + actorOrgId: req.permission.orgId, + actorAuthMethod: req.permission.authMethod, + actor: req.permission.type, + projectId: req.params.projectId + }); + + return { cas }; + } + }); }; diff --git a/backend/src/services/org/org-service.ts b/backend/src/services/org/org-service.ts index 0871b9766..9741220f8 100644 --- a/backend/src/services/org/org-service.ts +++ b/backend/src/services/org/org-service.ts @@ -24,9 +24,6 @@ import { TPermissionServiceFactory } from "@app/ee/services/permission/permissio import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission"; import { TProjectUserAdditionalPrivilegeDALFactory } from "@app/ee/services/project-user-additional-privilege/project-user-additional-privilege-dal"; import { TSamlConfigDALFactory } from "@app/ee/services/saml-config/saml-config-dal"; -import { TSshCertificateAuthorityDALFactory } from "@app/ee/services/ssh/ssh-certificate-authority-dal"; -import { TSshCertificateDALFactory } from "@app/ee/services/ssh-certificate/ssh-certificate-dal"; -import { TSshCertificateTemplateDALFactory } from "@app/ee/services/ssh-certificate-template/ssh-certificate-template-dal"; import { getConfig } from "@app/lib/config/env"; import { generateAsymmetricKeyPair } from "@app/lib/crypto"; import { generateSymmetricKey, infisicalSymmetricDecrypt, infisicalSymmetricEncypt } from "@app/lib/crypto/encryption"; @@ -65,9 +62,6 @@ import { TGetOrgGroupsDTO, TGetOrgMembershipDTO, TInviteUserToOrgDTO, - TListOrgSshCasDTO, - TListOrgSshCertificatesDTO, - TListOrgSshCertificateTemplatesDTO, TListProjectMembershipsByOrgMembershipIdDTO, TUpdateOrgDTO, TUpdateOrgMembershipDTO, @@ -104,9 +98,6 @@ type TOrgServiceFactoryDep = { projectBotDAL: Pick; projectUserMembershipRoleDAL: Pick; projectBotService: Pick; - sshCertificateAuthorityDAL: Pick; - sshCertificateDAL: Pick; - sshCertificateTemplateDAL: Pick; }; export type TOrgServiceFactory = ReturnType; @@ -134,9 +125,6 @@ export const orgServiceFactory = ({ projectBotDAL, projectUserMembershipRoleDAL, identityMetadataDAL, - sshCertificateAuthorityDAL, - sshCertificateDAL, - sshCertificateTemplateDAL, projectBotService }: TOrgServiceFactoryDep) => { /* @@ -1139,91 +1127,6 @@ export const orgServiceFactory = ({ return incidentContact; }; - /** - * Return list of SSH CAs for organization - */ - const listOrgSshCas = async ({ actorId, actorOrgId, actorAuthMethod, actor, orgId }: TListOrgSshCasDTO) => { - const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId); - - ForbiddenError.from(permission).throwUnlessCan( - OrgPermissionActions.Read, - OrgPermissionSubjects.SshCertificateAuthorities - ); - - const cas = await sshCertificateAuthorityDAL.find( - { - orgId - }, - { sort: [["updatedAt", "desc"]] } - ); - - return cas; - }; - - /** - * Return list of SSH certificates for organization - */ - const listOrgSshCertificates = async ({ - limit = 25, - offset = 0, - actorId, - actorOrgId, - actorAuthMethod, - actor, - orgId - }: TListOrgSshCertificatesDTO) => { - const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId); - - ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.SshCertificates); - - const cas = await sshCertificateAuthorityDAL.find({ - orgId - }); - - const certificates = await sshCertificateDAL.find( - { - $in: { - sshCaId: cas.map((ca) => ca.id) - } - }, - { offset, limit, sort: [["updatedAt", "desc"]] } - ); - - const count = await sshCertificateDAL.countSshCertificatesInOrg(orgId); - - return { certificates, totalCount: count }; - }; - - /** - * Return list of SSH certificate templates for organization - */ - const listOrgSshCertificateTemplates = async ({ - actorId, - actorOrgId, - actorAuthMethod, - actor, - orgId - }: TListOrgSshCertificateTemplatesDTO) => { - const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId); - - ForbiddenError.from(permission).throwUnlessCan( - OrgPermissionActions.Read, - OrgPermissionSubjects.SshCertificateTemplates - ); - - const cas = await sshCertificateAuthorityDAL.find({ - orgId - }); - - const certificateTemplates = await sshCertificateTemplateDAL.find({ - $in: { - sshCaId: cas.map((ca) => ca.id) - } - }); - - return { certificateTemplates }; - }; - return { findOrganizationById, findAllOrgMembers, @@ -1245,9 +1148,6 @@ export const orgServiceFactory = ({ deleteIncidentContact, getOrgGroups, listProjectMembershipsByOrgMembershipId, - findOrgBySlug, - listOrgSshCas, - listOrgSshCertificates, - listOrgSshCertificateTemplates + findOrgBySlug }; }; diff --git a/backend/src/services/org/org-types.ts b/backend/src/services/org/org-types.ts index 868d3345c..05df9429e 100644 --- a/backend/src/services/org/org-types.ts +++ b/backend/src/services/org/org-types.ts @@ -75,13 +75,6 @@ export type TListProjectMembershipsByOrgMembershipIdDTO = { orgMembershipId: string; } & TOrgPermission; -export type TListOrgSshCasDTO = TOrgPermission; -export type TListOrgSshCertificateTemplatesDTO = TOrgPermission; -export type TListOrgSshCertificatesDTO = { - offset: number; - limit: number; -} & TOrgPermission; - export enum OrgAuthMethod { OIDC = "oidc", SAML = "saml" diff --git a/backend/src/services/project/project-service.ts b/backend/src/services/project/project-service.ts index 53e934716..49ee43eda 100644 --- a/backend/src/services/project/project-service.ts +++ b/backend/src/services/project/project-service.ts @@ -8,6 +8,9 @@ import { TPermissionServiceFactory } from "@app/ee/services/permission/permissio import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission"; import { TProjectTemplateServiceFactory } from "@app/ee/services/project-template/project-template-service"; import { InfisicalProjectTemplate } from "@app/ee/services/project-template/project-template-types"; +import { TSshCertificateAuthorityDALFactory } from "@app/ee/services/ssh/ssh-certificate-authority-dal"; +import { TSshCertificateDALFactory } from "@app/ee/services/ssh-certificate/ssh-certificate-dal"; +import { TSshCertificateTemplateDALFactory } from "@app/ee/services/ssh-certificate-template/ssh-certificate-template-dal"; import { TKeyStoreFactory } from "@app/keystore/keystore"; import { isAtLeastAsPrivileged } from "@app/lib/casl"; import { infisicalSymmetricEncypt } from "@app/lib/crypto/encryption"; @@ -53,6 +56,9 @@ import { TListProjectCertificateTemplatesDTO, TListProjectCertsDTO, TListProjectsDTO, + TListProjectSshCasDTO, + TListProjectSshCertificatesDTO, + TListProjectSshCertificateTemplatesDTO, TLoadProjectKmsBackupDTO, TToggleProjectAutoCapitalizationDTO, TUpdateAuditLogsRetentionDTO, @@ -90,6 +96,9 @@ type TProjectServiceFactoryDep = { certificateTemplateDAL: Pick; pkiAlertDAL: Pick; pkiCollectionDAL: Pick; + sshCertificateAuthorityDAL: Pick; + sshCertificateDAL: Pick; + sshCertificateTemplateDAL: Pick; permissionService: TPermissionServiceFactory; orgService: Pick; licenseService: Pick; @@ -133,6 +142,9 @@ export const projectServiceFactory = ({ certificateTemplateDAL, pkiCollectionDAL, pkiAlertDAL, + sshCertificateAuthorityDAL, + sshCertificateDAL, + sshCertificateTemplateDAL, keyStore, kmsService, projectBotDAL, @@ -859,6 +871,115 @@ export const projectServiceFactory = ({ }; }; + /** + * Return list of SSH CAs for project + */ + const listProjectSshCas = async ({ + actorId, + actorOrgId, + actorAuthMethod, + actor, + projectId + }: TListProjectSshCasDTO) => { + const { permission } = await permissionService.getProjectPermission( + actor, + actorId, + projectId, + actorAuthMethod, + actorOrgId + ); + + ForbiddenError.from(permission).throwUnlessCan( + ProjectPermissionActions.Read, + ProjectPermissionSub.SshCertificateAuthorities + ); + + const cas = await sshCertificateAuthorityDAL.find( + { + projectId + }, + { sort: [["updatedAt", "desc"]] } + ); + + return cas; + }; + + /** + * Return list of SSH certificates for organization + */ + const listProjectSshCertificates = async ({ + limit = 25, + offset = 0, + actorId, + actorOrgId, + actorAuthMethod, + actor, + projectId + }: TListProjectSshCertificatesDTO) => { + const { permission } = await permissionService.getProjectPermission( + actor, + actorId, + projectId, + actorAuthMethod, + actorOrgId + ); + + ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SshCertificates); + + const cas = await sshCertificateAuthorityDAL.find({ + projectId + }); + + const certificates = await sshCertificateDAL.find( + { + $in: { + sshCaId: cas.map((ca) => ca.id) + } + }, + { offset, limit, sort: [["updatedAt", "desc"]] } + ); + + const count = await sshCertificateDAL.countSshCertificatesInProject(projectId); + + return { certificates, totalCount: count }; + }; + + /** + * Return list of SSH certificate templates for organization + */ + const listProjectSshCertificateTemplates = async ({ + actorId, + actorOrgId, + actorAuthMethod, + actor, + projectId + }: TListProjectSshCertificateTemplatesDTO) => { + const { permission } = await permissionService.getProjectPermission( + actor, + actorId, + projectId, + actorAuthMethod, + actorOrgId + ); + + ForbiddenError.from(permission).throwUnlessCan( + ProjectPermissionActions.Read, + ProjectPermissionSub.SshCertificateTemplates + ); + + const cas = await sshCertificateAuthorityDAL.find({ + projectId + }); + + const certificateTemplates = await sshCertificateTemplateDAL.find({ + $in: { + sshCaId: cas.map((ca) => ca.id) + } + }); + + return { certificateTemplates }; + }; + const updateProjectKmsKey = async ({ projectId, kms, @@ -1092,6 +1213,9 @@ export const projectServiceFactory = ({ listProjectAlerts, listProjectPkiCollections, listProjectCertificateTemplates, + listProjectSshCas, + listProjectSshCertificates, + listProjectSshCertificateTemplates, updateVersionLimit, updateAuditLogsRetention, updateProjectKmsKey, diff --git a/backend/src/services/project/project-types.ts b/backend/src/services/project/project-types.ts index b826f2a6a..6e9d85eb0 100644 --- a/backend/src/services/project/project-types.ts +++ b/backend/src/services/project/project-types.ts @@ -130,6 +130,13 @@ export type TGetProjectKmsKey = TProjectPermission; export type TListProjectCertificateTemplatesDTO = TProjectPermission; +export type TListProjectSshCasDTO = TProjectPermission; +export type TListProjectSshCertificateTemplatesDTO = TProjectPermission; +export type TListProjectSshCertificatesDTO = { + offset: number; + limit: number; +} & TProjectPermission; + export type TGetProjectSlackConfig = TProjectPermission; export type TUpdateProjectSlackConfig = { diff --git a/frontend/src/context/OrgPermissionContext/types.ts b/frontend/src/context/OrgPermissionContext/types.ts index ae2c95736..41a2e7e3c 100644 --- a/frontend/src/context/OrgPermissionContext/types.ts +++ b/frontend/src/context/OrgPermissionContext/types.ts @@ -23,10 +23,7 @@ export enum OrgPermissionSubjects { Kms = "kms", AdminConsole = "organization-admin-console", AuditLogs = "audit-logs", - ProjectTemplates = "project-templates", - SshCertificateAuthorities = "ssh-certificate-authorities", - SshCertificateTemplates = "ssh-certificate-templates", - SshCertificates = "ssh-certificates" + ProjectTemplates = "project-templates" } export enum OrgPermissionAdminConsoleAction { @@ -50,9 +47,6 @@ export type OrgPermissionSet = | [OrgPermissionActions, OrgPermissionSubjects.Kms] | [OrgPermissionAdminConsoleAction, OrgPermissionSubjects.AdminConsole] | [OrgPermissionActions, OrgPermissionSubjects.AuditLogs] - | [OrgPermissionActions, OrgPermissionSubjects.ProjectTemplates] - | [OrgPermissionActions, OrgPermissionSubjects.SshCertificateAuthorities] - | [OrgPermissionActions, OrgPermissionSubjects.SshCertificates] - | [OrgPermissionActions, OrgPermissionSubjects.SshCertificateTemplates]; + | [OrgPermissionActions, OrgPermissionSubjects.ProjectTemplates]; export type TOrgPermission = MongoAbility; diff --git a/frontend/src/context/ProjectPermissionContext/types.ts b/frontend/src/context/ProjectPermissionContext/types.ts index 8f10d5f21..e3fe9732b 100644 --- a/frontend/src/context/ProjectPermissionContext/types.ts +++ b/frontend/src/context/ProjectPermissionContext/types.ts @@ -81,6 +81,9 @@ export enum ProjectPermissionSub { CertificateAuthorities = "certificate-authorities", Certificates = "certificates", CertificateTemplates = "certificate-templates", + SshCertificateAuthorities = "ssh-certificate-authorities", + SshCertificateTemplates = "ssh-certificate-templates", + SshCertificates = "ssh-certificates", PkiAlerts = "pki-alerts", PkiCollections = "pki-collections", Kms = "kms", @@ -155,6 +158,9 @@ export type ProjectPermissionSet = | [ProjectPermissionActions, ProjectPermissionSub.CertificateAuthorities] | [ProjectPermissionActions, ProjectPermissionSub.Certificates] | [ProjectPermissionActions, ProjectPermissionSub.CertificateTemplates] + | [ProjectPermissionActions, ProjectPermissionSub.SshCertificateAuthorities] + | [ProjectPermissionActions, ProjectPermissionSub.SshCertificateTemplates] + | [ProjectPermissionActions, ProjectPermissionSub.SshCertificates] | [ProjectPermissionActions, ProjectPermissionSub.PkiAlerts] | [ProjectPermissionActions, ProjectPermissionSub.PkiCollections] | [ProjectPermissionActions.Delete, ProjectPermissionSub.Project] diff --git a/frontend/src/hooks/api/organization/index.ts b/frontend/src/hooks/api/organization/index.ts index 477b3bd8b..fece19e5f 100644 --- a/frontend/src/hooks/api/organization/index.ts +++ b/frontend/src/hooks/api/organization/index.ts @@ -19,8 +19,6 @@ export { useGetOrgPmtMethods, useGetOrgTaxIds, useGetOrgTrialUrl, - useListOrgSshCas, - useListOrgSshCertificates, - useListOrgSshCertificateTemplates, useUpdateOrg, - useUpdateOrgBillingDetails} from "./queries"; + useUpdateOrgBillingDetails +} from "./queries"; diff --git a/frontend/src/hooks/api/organization/queries.tsx b/frontend/src/hooks/api/organization/queries.tsx index 9f4c83c1c..4923177ba 100644 --- a/frontend/src/hooks/api/organization/queries.tsx +++ b/frontend/src/hooks/api/organization/queries.tsx @@ -4,8 +4,6 @@ import { apiRequest } from "@app/config/request"; import { OrderByDirection } from "@app/hooks/api/generic/types"; import { TGroupOrgMembership } from "../groups/types"; -import { TSshCertificate,TSshCertificateAuthority } from "../ssh-ca/types"; -import { TSshCertificateTemplate } from "../sshCertificateTemplates/types"; import { IntegrationAuth } from "../types"; import { BillingDetails, @@ -43,12 +41,7 @@ export const organizationKeys = { }: TListOrgIdentitiesDTO) => [...organizationKeys.getOrgIdentityMemberships(orgId), params] as const, getOrgGroups: (orgId: string) => [{ orgId }, "organization-groups"] as const, - getOrgIntegrationAuths: (orgId: string) => [{ orgId }, "integration-auths"] as const, - getOrgSshCas: ({ orgId }: { orgId: string }) => [{ orgId }, "org-ssh-cas"] as const, - allOrgSshCertificates: () => ["org-ssh-certificates"] as const, - specificOrgSshCertificates: ({ offset, limit }: { offset: number; limit: number }) => - [...organizationKeys.allOrgSshCertificates(), { offset, limit }] as const, - getOrgSshCertificateTemplates: () => ["org-ssh-certificate-templates"] as const + getOrgIntegrationAuths: (orgId: string) => [{ orgId }, "integration-auths"] as const }; export const fetchOrganizations = async () => { @@ -502,63 +495,3 @@ export const useGetOrgIntegrationAuths = ( select }); }; - -export const useListOrgSshCas = ({ orgId }: { orgId: string }) => { - return useQuery({ - queryKey: organizationKeys.getOrgSshCas({ orgId }), - queryFn: async () => { - const { - data: { cas } - } = await apiRequest.get<{ cas: Omit[] }>( - `/api/v1/organization/${orgId}/ssh-cas` - ); - return cas; - }, - enabled: Boolean(orgId) - }); -}; - -export const useListOrgSshCertificates = ({ - orgId, - offset, - limit -}: { - orgId: string; - offset: number; - limit: number; -}) => { - return useQuery({ - queryKey: organizationKeys.specificOrgSshCertificates({ - offset, - limit - }), - queryFn: async () => { - const params = new URLSearchParams({ - offset: String(offset), - limit: String(limit) - }); - - const { data } = await apiRequest.get<{ - certificates: TSshCertificate[]; - totalCount: number; - }>(`/api/v1/organization/${orgId}/ssh-certificates`, { - params - }); - return data; - }, - enabled: Boolean(orgId) - }); -}; - -export const useListOrgSshCertificateTemplates = ({ orgId }: { orgId: string }) => { - return useQuery({ - queryKey: organizationKeys.getOrgSshCertificateTemplates(), - queryFn: async () => { - const { data } = await apiRequest.get<{ certificateTemplates: TSshCertificateTemplate[] }>( - `/api/v1/organization/${orgId}/ssh-certificate-templates` - ); - return data; - }, - enabled: Boolean(orgId) - }); -}; diff --git a/frontend/src/hooks/api/ssh-ca/mutations.tsx b/frontend/src/hooks/api/ssh-ca/mutations.tsx index 811e09d18..e8c5731b6 100644 --- a/frontend/src/hooks/api/ssh-ca/mutations.tsx +++ b/frontend/src/hooks/api/ssh-ca/mutations.tsx @@ -2,7 +2,7 @@ import { useMutation, useQueryClient } from "@tanstack/react-query"; import { apiRequest } from "@app/config/request"; -import { organizationKeys } from "../organization/queries"; +import { workspaceKeys } from "../workspace/query-keys"; import { TCreateSshCaDTO, TDeleteSshCaDTO, @@ -27,8 +27,8 @@ export const useCreateSshCa = () => { } = await apiRequest.post<{ ca: TSshCertificateAuthority }>("/api/v1/ssh/ca/", body); return ca; }, - onSuccess: ({ orgId }) => { - queryClient.invalidateQueries(organizationKeys.getOrgSshCas({ orgId })); + onSuccess: ({ projectId }) => { + queryClient.invalidateQueries(workspaceKeys.getWorkspaceSshCas(projectId)); } }); }; @@ -42,8 +42,8 @@ export const useUpdateSshCa = () => { } = await apiRequest.patch<{ ca: TSshCertificateAuthority }>(`/api/v1/ssh/ca/${caId}`, body); return ca; }, - onSuccess: ({ orgId }, { caId }) => { - queryClient.invalidateQueries(organizationKeys.getOrgSshCas({ orgId })); + onSuccess: ({ projectId }, { caId }) => { + queryClient.invalidateQueries(workspaceKeys.getWorkspaceSshCas(projectId)); queryClient.invalidateQueries(sshCaKeys.getSshCaById(caId)); } }); @@ -58,8 +58,8 @@ export const useDeleteSshCa = () => { } = await apiRequest.delete<{ ca: TSshCertificateAuthority }>(`/api/v1/ssh/ca/${caId}`); return ca; }, - onSuccess: ({ orgId }) => { - queryClient.invalidateQueries(organizationKeys.getOrgSshCas({ orgId })); + onSuccess: ({ projectId }) => { + queryClient.invalidateQueries(workspaceKeys.getWorkspaceSshCas(projectId)); } }); }; @@ -71,8 +71,8 @@ export const useSignSshKey = () => { const { data } = await apiRequest.post("/api/v1/ssh/sign", body); return data; }, - onSuccess: () => { - queryClient.invalidateQueries(organizationKeys.allOrgSshCertificates()); + onSuccess: (_, { projectId }) => { + queryClient.invalidateQueries(workspaceKeys.allWorkspaceSshCertificates(projectId)); } }); }; @@ -84,8 +84,8 @@ export const useIssueSshCreds = () => { const { data } = await apiRequest.post("/api/v1/ssh/issue", body); return data; }, - onSuccess: () => { - queryClient.invalidateQueries(organizationKeys.allOrgSshCertificates()); + onSuccess: (_, { projectId }) => { + queryClient.invalidateQueries(workspaceKeys.allWorkspaceSshCertificates(projectId)); } }); }; diff --git a/frontend/src/hooks/api/ssh-ca/types.ts b/frontend/src/hooks/api/ssh-ca/types.ts index 2873fafeb..06089fffc 100644 --- a/frontend/src/hooks/api/ssh-ca/types.ts +++ b/frontend/src/hooks/api/ssh-ca/types.ts @@ -7,7 +7,6 @@ export type TSshCertificate = { sshCertificateTemplateId: string; serialNumber: string; certType: SshCertType; - publicKey: string; principals: string[]; keyId: string; notBefore: string; @@ -16,7 +15,7 @@ export type TSshCertificate = { export type TSshCertificateAuthority = { id: string; - orgId: string; + projectId: string; status: SshCaStatus; friendlyName: string; keyAlgorithm: CertKeyAlgorithm; @@ -26,6 +25,7 @@ export type TSshCertificateAuthority = { }; export type TCreateSshCaDTO = { + projectId: string; friendlyName?: string; keyAlgorithm: CertKeyAlgorithm; }; @@ -41,6 +41,7 @@ export type TDeleteSshCaDTO = { }; export type TSignSshKeyDTO = { + projectId: string; templateName: string; publicKey?: string; certType: SshCertType; @@ -55,6 +56,7 @@ export type TSignSshKeyResponse = { }; export type TIssueSshCredsDTO = { + projectId: string; templateName: string; keyAlgorithm: CertKeyAlgorithm; certType: SshCertType; diff --git a/frontend/src/hooks/api/workspace/index.tsx b/frontend/src/hooks/api/workspace/index.tsx index cd652970a..665462e7d 100644 --- a/frontend/src/hooks/api/workspace/index.tsx +++ b/frontend/src/hooks/api/workspace/index.tsx @@ -32,6 +32,9 @@ export { useListWorkspaceGroups, useListWorkspacePkiAlerts, useListWorkspacePkiCollections, + useListWorkspaceSshCas, + useListWorkspaceSshCertificates, + useListWorkspaceSshCertificateTemplates, useNameWorkspaceSecrets, useToggleAutoCapitalization, useUpdateIdentityWorkspaceRole, diff --git a/frontend/src/hooks/api/workspace/queries.tsx b/frontend/src/hooks/api/workspace/queries.tsx index ec887a401..3385344cd 100644 --- a/frontend/src/hooks/api/workspace/queries.tsx +++ b/frontend/src/hooks/api/workspace/queries.tsx @@ -15,6 +15,8 @@ import { TIntegration } from "../integrations/types"; import { TPkiAlert } from "../pkiAlerts/types"; import { TPkiCollection } from "../pkiCollections/types"; import { EncryptedSecret } from "../secrets/types"; +import { TSshCertificate, TSshCertificateAuthority } from "../ssh-ca/types"; +import { TSshCertificateTemplate } from "../sshCertificateTemplates/types"; import { userKeys } from "../users/query-keys"; import { TWorkspaceUser } from "../users/types"; import { ProjectSlackConfig } from "../workflowIntegrations/types"; @@ -713,6 +715,67 @@ export const useListWorkspaceCertificateTemplates = ({ workspaceId }: { workspac }); }; +export const useListWorkspaceSshCertificates = ({ + offset, + limit, + projectId +}: { + offset: number; + limit: number; + projectId: string; +}) => { + return useQuery({ + queryKey: workspaceKeys.specificWorkspaceSshCertificates({ + offset, + limit, + projectId + }), + queryFn: async () => { + const params = new URLSearchParams({ + offset: String(offset), + limit: String(limit) + }); + + const { data } = await apiRequest.get<{ + certificates: TSshCertificate[]; + totalCount: number; + }>(`/api/v2/workspace/${projectId}/ssh-certificates`, { + params + }); + return data; + }, + enabled: Boolean(projectId) + }); +}; + +export const useListWorkspaceSshCas = (projectId: string) => { + return useQuery({ + queryKey: workspaceKeys.getWorkspaceSshCas(projectId), + queryFn: async () => { + const { + data: { cas } + } = await apiRequest.get<{ cas: Omit[] }>( + `/api/v2/workspace/${projectId}/ssh-cas` + ); + return cas; + }, + enabled: Boolean(projectId) + }); +}; + +export const useListWorkspaceSshCertificateTemplates = (projectId: string) => { + return useQuery({ + queryKey: workspaceKeys.getWorkspaceSshCertificateTemplates(projectId), + queryFn: async () => { + const { data } = await apiRequest.get<{ certificateTemplates: TSshCertificateTemplate[] }>( + `/api/v2/workspace/${projectId}/ssh-certificate-templates` + ); + return data; + }, + enabled: Boolean(projectId) + }); +}; + export const useGetWorkspaceSlackConfig = ({ workspaceId }: { workspaceId: string }) => { return useQuery({ queryKey: workspaceKeys.getWorkspaceSlackConfig(workspaceId), diff --git a/frontend/src/hooks/api/workspace/query-keys.tsx b/frontend/src/hooks/api/workspace/query-keys.tsx index f5a02ec2b..bec32ceeb 100644 --- a/frontend/src/hooks/api/workspace/query-keys.tsx +++ b/frontend/src/hooks/api/workspace/query-keys.tsx @@ -52,5 +52,19 @@ export const workspaceKeys = { getWorkspaceCertificateTemplates: (workspaceId: string) => [{ workspaceId }, "workspace-certificate-templates"] as const, getWorkspaceSlackConfig: (workspaceId: string) => - [{ workspaceId }, "workspace-slack-config"] as const + [{ workspaceId }, "workspace-slack-config"] as const, + getWorkspaceSshCas: (projectId: string) => [{ projectId }, "workspace-ssh-cas"] as const, + allWorkspaceSshCertificates: (projectId: string) => + [{ projectId }, "workspace-ssh-certificates"] as const, + specificWorkspaceSshCertificates: ({ + offset, + limit, + projectId + }: { + offset: number; + limit: number; + projectId: string; + }) => [...workspaceKeys.allWorkspaceSshCertificates(projectId), { offset, limit }] as const, + getWorkspaceSshCertificateTemplates: (projectId: string) => + [{ projectId }, "workspace-ssh-certificate-templates"] as const }; diff --git a/frontend/src/layouts/AppLayout/AppLayout.tsx b/frontend/src/layouts/AppLayout/AppLayout.tsx index 575695b2c..b7ff48788 100644 --- a/frontend/src/layouts/AppLayout/AppLayout.tsx +++ b/frontend/src/layouts/AppLayout/AppLayout.tsx @@ -424,6 +424,16 @@ export const AppLayout = ({ children }: LayoutProps) => { + + + + SSH + + + { - - - - SSH - - - { - const { t } = useTranslation(); - return ( - <> - - {t("common.head-title", { title: t("approval.title") })} - - - - - -
- -
- - ); -}; - -export default Ssh; - -Ssh.requireAuth = true; diff --git a/frontend/src/pages/org/[id]/ssh/ca/[caId]/index.tsx b/frontend/src/pages/project/[id]/ssh/ca/[caId]/index.tsx similarity index 84% rename from frontend/src/pages/org/[id]/ssh/ca/[caId]/index.tsx rename to frontend/src/pages/project/[id]/ssh/ca/[caId]/index.tsx index de06fac46..cdaa4d0b2 100644 --- a/frontend/src/pages/org/[id]/ssh/ca/[caId]/index.tsx +++ b/frontend/src/pages/project/[id]/ssh/ca/[caId]/index.tsx @@ -1,7 +1,7 @@ /* eslint-disable @typescript-eslint/no-unused-vars */ import Head from "next/head"; -import { SshCaPage } from "@app/views/Org/SshCaPage"; +import { SshCaPage } from "@app/views/Project/SshCaPage"; export default function SshCa() { return ( diff --git a/frontend/src/pages/project/[id]/ssh/index.tsx b/frontend/src/pages/project/[id]/ssh/index.tsx new file mode 100644 index 000000000..f574566c1 --- /dev/null +++ b/frontend/src/pages/project/[id]/ssh/index.tsx @@ -0,0 +1,23 @@ +import { useTranslation } from "react-i18next"; +import Head from "next/head"; + +import { SshPage } from "@app/views/Project/SshPage"; + +const Ssh = () => { + const { t } = useTranslation(); + + return ( +
+ + {t("common.head-title", { title: "Certificates" })} + + + + +
+ ); +}; + +export default Ssh; + +Ssh.requireAuth = true; diff --git a/frontend/src/views/Org/RolePage/components/OrgRoleModifySection.utils.ts b/frontend/src/views/Org/RolePage/components/OrgRoleModifySection.utils.ts index b9da49ca7..aa8c4d7ec 100644 --- a/frontend/src/views/Org/RolePage/components/OrgRoleModifySection.utils.ts +++ b/frontend/src/views/Org/RolePage/components/OrgRoleModifySection.utils.ts @@ -49,10 +49,7 @@ export const formSchema = z.object({ identity: generalPermissionSchema, "organization-admin-console": adminConsolePermissionSchmea, [OrgPermissionSubjects.Kms]: generalPermissionSchema, - [OrgPermissionSubjects.ProjectTemplates]: generalPermissionSchema, - [OrgPermissionSubjects.SshCertificateAuthorities]: generalPermissionSchema, - [OrgPermissionSubjects.SshCertificates]: generalPermissionSchema, - [OrgPermissionSubjects.SshCertificateTemplates]: generalPermissionSchema + [OrgPermissionSubjects.ProjectTemplates]: generalPermissionSchema }) .optional() }); diff --git a/frontend/src/views/Org/RolePage/components/RolePermissionsSection/RolePermissionsSection.tsx b/frontend/src/views/Org/RolePage/components/RolePermissionsSection/RolePermissionsSection.tsx index f4fcc84f9..976b35636 100644 --- a/frontend/src/views/Org/RolePage/components/RolePermissionsSection/RolePermissionsSection.tsx +++ b/frontend/src/views/Org/RolePage/components/RolePermissionsSection/RolePermissionsSection.tsx @@ -69,19 +69,7 @@ const SIMPLE_PERMISSION_OPTIONS = [ title: "External KMS", formName: OrgPermissionSubjects.Kms }, - { title: "Project Templates", formName: OrgPermissionSubjects.ProjectTemplates }, - { - title: "SSH Certificate Authorities", - formName: OrgPermissionSubjects.SshCertificateAuthorities - }, - { - title: "SSH Certificates", - formName: OrgPermissionSubjects.SshCertificates - }, - { - title: "SSH Certificate Templates", - formName: OrgPermissionSubjects.SshCertificateTemplates - } + { title: "Project Templates", formName: OrgPermissionSubjects.ProjectTemplates } ] as const; type Props = { diff --git a/frontend/src/views/Project/RolePage/components/RolePermissionsSection/ProjectRoleModifySection.utils.tsx b/frontend/src/views/Project/RolePage/components/RolePermissionsSection/ProjectRoleModifySection.utils.tsx index e298281f8..3c1310de2 100644 --- a/frontend/src/views/Project/RolePage/components/RolePermissionsSection/ProjectRoleModifySection.utils.tsx +++ b/frontend/src/views/Project/RolePage/components/RolePermissionsSection/ProjectRoleModifySection.utils.tsx @@ -121,6 +121,11 @@ export const projectRoleFormSchema = z.object({ [ProjectPermissionSub.PkiAlerts]: GeneralPolicyActionSchema.array().default([]), [ProjectPermissionSub.PkiCollections]: GeneralPolicyActionSchema.array().default([]), [ProjectPermissionSub.CertificateTemplates]: GeneralPolicyActionSchema.array().default([]), + [ProjectPermissionSub.SshCertificateAuthorities]: GeneralPolicyActionSchema.array().default( + [] + ), + [ProjectPermissionSub.SshCertificates]: GeneralPolicyActionSchema.array().default([]), + [ProjectPermissionSub.SshCertificateTemplates]: GeneralPolicyActionSchema.array().default([]), [ProjectPermissionSub.SecretApproval]: GeneralPolicyActionSchema.array().default([]), [ProjectPermissionSub.SecretRollback]: SecretRollbackPolicyActionSchema.array().default([]), [ProjectPermissionSub.Project]: WorkspacePolicyActionSchema.array().default([]), @@ -203,6 +208,9 @@ export const rolePermission2Form = (permissions: TProjectPermission[] = []) => { ProjectPermissionSub.PkiAlerts, ProjectPermissionSub.PkiCollections, ProjectPermissionSub.CertificateTemplates, + ProjectPermissionSub.SshCertificateAuthorities, + ProjectPermissionSub.SshCertificates, + ProjectPermissionSub.SshCertificateTemplates, ProjectPermissionSub.SecretApproval, ProjectPermissionSub.Tags, ProjectPermissionSub.SecretRotation, @@ -589,6 +597,33 @@ export const PROJECT_PERMISSION_OBJECT: TProjectPermissionObject = { { label: "Remove", value: "delete" } ] }, + [ProjectPermissionSub.SshCertificateAuthorities]: { + title: "SSH Certificate Authorities", + actions: [ + { label: "Read", value: "read" }, + { label: "Create", value: "create" }, + { label: "Modify", value: "edit" }, + { label: "Remove", value: "delete" } + ] + }, + [ProjectPermissionSub.SshCertificates]: { + title: "SSH Certificates", + actions: [ + { label: "Read", value: "read" }, + { label: "Create", value: "create" }, + { label: "Modify", value: "edit" }, + { label: "Remove", value: "delete" } + ] + }, + [ProjectPermissionSub.SshCertificateTemplates]: { + title: "SSH Certificate Templates", + actions: [ + { label: "Read", value: "read" }, + { label: "Create", value: "create" }, + { label: "Modify", value: "edit" }, + { label: "Remove", value: "delete" } + ] + }, [ProjectPermissionSub.PkiCollections]: { title: "PKI Collections", actions: [ diff --git a/frontend/src/views/Org/SshCaPage/SshCaPage.tsx b/frontend/src/views/Project/SshCaPage/SshCaPage.tsx similarity index 84% rename from frontend/src/views/Org/SshCaPage/SshCaPage.tsx rename to frontend/src/views/Project/SshCaPage/SshCaPage.tsx index 34a869faa..97ca965a4 100644 --- a/frontend/src/views/Org/SshCaPage/SshCaPage.tsx +++ b/frontend/src/views/Project/SshCaPage/SshCaPage.tsx @@ -5,7 +5,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { twMerge } from "tailwind-merge"; import { createNotification } from "@app/components/notifications"; -import { OrgPermissionCan } from "@app/components/permissions"; +import { ProjectPermissionCan } from "@app/components/permissions"; import { Button, DeleteActionModal, @@ -15,17 +15,18 @@ import { DropdownMenuTrigger, Tooltip } from "@app/components/v2"; -import { OrgPermissionActions, OrgPermissionSubjects, useOrganization } from "@app/context"; -import { withPermission } from "@app/hoc"; +import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context"; +import { withProjectPermission } from "@app/hoc"; import { useDeleteSshCa, useGetSshCaById } from "@app/hooks/api"; import { usePopUp } from "@app/hooks/usePopUp"; import { SshCaModal } from "../SshPage/components/SshCaModal"; import { SshCaDetailsSection, SshCertificateTemplatesSection } from "./components"; -export const SshCaPage = withPermission( +export const SshCaPage = withProjectPermission( () => { - const { currentOrg } = useOrganization(); + const { currentWorkspace } = useWorkspace(); + const projectId = currentWorkspace?.id || ""; const router = useRouter(); const caId = router.query.caId as string; const { data } = useGetSshCaById(caId); @@ -39,7 +40,7 @@ export const SshCaPage = withPermission( const onRemoveCaSubmit = async (caIdToDelete: string) => { try { - if (!currentOrg?.id) return; + if (!projectId) return; await deleteSshCa({ caId: caIdToDelete }); @@ -49,7 +50,7 @@ export const SshCaPage = withPermission( }); handlePopUpClose("deleteSshCa"); - router.push(`/org/${currentOrg.id}/ssh`); + router.push(`/project/${projectId}/ssh`); } catch (err) { console.error(err); createNotification({ @@ -67,7 +68,7 @@ export const SshCaPage = withPermission( variant="link" type="submit" leftIcon={} - onClick={() => router.push(`/org/${currentOrg?.id}/ssh`)} + onClick={() => router.push(`/project/${projectId}/ssh`)} className="mb-4" > SSH Certificate Authorities @@ -83,9 +84,9 @@ export const SshCaPage = withPermission( - {(isAllowed) => ( )} - + @@ -131,5 +132,5 @@ export const SshCaPage = withPermission( ); }, - { action: OrgPermissionActions.Read, subject: OrgPermissionSubjects.SshCertificateAuthorities } + { action: ProjectPermissionActions.Read, subject: ProjectPermissionSub.SshCertificateAuthorities } ); diff --git a/frontend/src/views/Org/SshCaPage/components/SshCaDetailsSection.tsx b/frontend/src/views/Project/SshCaPage/components/SshCaDetailsSection.tsx similarity index 91% rename from frontend/src/views/Org/SshCaPage/components/SshCaDetailsSection.tsx rename to frontend/src/views/Project/SshCaPage/components/SshCaDetailsSection.tsx index 475478124..812ac02fe 100644 --- a/frontend/src/views/Org/SshCaPage/components/SshCaDetailsSection.tsx +++ b/frontend/src/views/Project/SshCaPage/components/SshCaDetailsSection.tsx @@ -1,10 +1,10 @@ -import { faCheck, faCopy, faDownload,faPencil } from "@fortawesome/free-solid-svg-icons"; +import { faCheck, faCopy, faDownload, faPencil } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import FileSaver from "file-saver"; -import { OrgPermissionCan } from "@app/components/permissions"; +import { ProjectPermissionCan } from "@app/components/permissions"; import { IconButton, Tooltip } from "@app/components/v2"; -import { OrgPermissionActions, OrgPermissionSubjects } from "@app/context"; +import { ProjectPermissionActions, ProjectPermissionSub } from "@app/context"; import { useTimedReset } from "@app/hooks"; import { useGetSshCaById } from "@app/hooks/api"; import { caStatusToNameMap } from "@app/hooks/api/ca/constants"; @@ -35,9 +35,9 @@ export const SshCaDetailsSection = ({ caId, handlePopUpOpen }: Props) => {

SSH CA Details

- {(isAllowed) => { return ( @@ -59,7 +59,7 @@ export const SshCaDetailsSection = ({ caId, handlePopUpOpen }: Props) => { ); }} - +
diff --git a/frontend/src/views/Org/SshCaPage/components/SshCertificateContent.tsx b/frontend/src/views/Project/SshCaPage/components/SshCertificateContent.tsx similarity index 100% rename from frontend/src/views/Org/SshCaPage/components/SshCertificateContent.tsx rename to frontend/src/views/Project/SshCaPage/components/SshCertificateContent.tsx diff --git a/frontend/src/views/Org/SshCaPage/components/SshCertificateModal.tsx b/frontend/src/views/Project/SshCaPage/components/SshCertificateModal.tsx similarity index 96% rename from frontend/src/views/Org/SshCaPage/components/SshCertificateModal.tsx rename to frontend/src/views/Project/SshCaPage/components/SshCertificateModal.tsx index ea83a95bb..9495ab141 100644 --- a/frontend/src/views/Org/SshCaPage/components/SshCertificateModal.tsx +++ b/frontend/src/views/Project/SshCaPage/components/SshCertificateModal.tsx @@ -13,13 +13,14 @@ import { Select, SelectItem } from "@app/components/v2"; -import { useOrganization } from "@app/context"; +import { useWorkspace } from "@app/context"; import { SshCertTemplateStatus, useGetSshCertTemplate, useIssueSshCreds, - useListOrgSshCertificateTemplates, - useSignSshKey} from "@app/hooks/api"; + useListWorkspaceSshCertificateTemplates, + useSignSshKey +} from "@app/hooks/api"; import { certKeyAlgorithms } from "@app/hooks/api/certificates/constants"; import { CertKeyAlgorithm } from "@app/hooks/api/certificates/enums"; import { SshCertType } from "@app/hooks/api/ssh-ca/constants"; @@ -62,7 +63,8 @@ enum SshCertificateOperation { } export const SshCertificateModal = ({ popUp, handlePopUpToggle }: Props) => { - const { currentOrg } = useOrganization(); + const { currentWorkspace } = useWorkspace(); + const projectId = currentWorkspace?.id || ""; const [operation, setOperation] = useState( SshCertificateOperation.SIGN_SSH_KEY ); @@ -77,9 +79,7 @@ export const SshCertificateModal = ({ popUp, handlePopUpToggle }: Props) => { templateId: string; }; - const { data: templatesData } = useListOrgSshCertificateTemplates({ - orgId: currentOrg?.id || "" - }); + const { data: templatesData } = useListWorkspaceSshCertificateTemplates(projectId); const { control, @@ -117,10 +117,12 @@ export const SshCertificateModal = ({ popUp, handlePopUpToggle }: Props) => { }: FormData) => { try { if (!templateData) return; + if (!projectId) return; switch (operation) { case SshCertificateOperation.SIGN_SSH_KEY: { const { serialNumber, signedKey } = await signSshKey({ + projectId: currentWorkspace?.id || "", templateName: templateData.name, publicKey: existingPublicKey, certType, @@ -137,6 +139,7 @@ export const SshCertificateModal = ({ popUp, handlePopUpToggle }: Props) => { } case SshCertificateOperation.ISSUE_SSH_CREDS: { const { serialNumber, publicKey, privateKey, signedKey } = await issueSshCreds({ + projectId, templateName: templateData.name, keyAlgorithm, certType, diff --git a/frontend/src/views/Org/SshCaPage/components/SshCertificateTemplateModal.tsx b/frontend/src/views/Project/SshCaPage/components/SshCertificateTemplateModal.tsx similarity index 98% rename from frontend/src/views/Org/SshCaPage/components/SshCertificateTemplateModal.tsx rename to frontend/src/views/Project/SshCaPage/components/SshCertificateTemplateModal.tsx index 1090b4b43..7bdcd319c 100644 --- a/frontend/src/views/Org/SshCaPage/components/SshCertificateTemplateModal.tsx +++ b/frontend/src/views/Project/SshCaPage/components/SshCertificateTemplateModal.tsx @@ -14,12 +14,12 @@ import { SelectItem, Switch } from "@app/components/v2"; -import { useOrganization } from "@app/context"; +import { useWorkspace } from "@app/context"; import { useCreateSshCertTemplate, useGetSshCaById, useGetSshCertTemplate, - useListOrgSshCas, + useListWorkspaceSshCas, useUpdateSshCertTemplate } from "@app/hooks/api"; import { UsePopUpState } from "@app/hooks/usePopUp"; @@ -48,7 +48,7 @@ type Props = { }; export const SshCertificateTemplateModal = ({ popUp, handlePopUpToggle, sshCaId }: Props) => { - const { currentOrg } = useOrganization(); + const { currentWorkspace } = useWorkspace(); const { data: ca } = useGetSshCaById(sshCaId); @@ -56,9 +56,7 @@ export const SshCertificateTemplateModal = ({ popUp, handlePopUpToggle, sshCaId (popUp?.sshCertificateTemplate?.data as { id: string })?.id || "" ); - const { data: cas } = useListOrgSshCas({ - orgId: currentOrg?.id ?? "" - }); + const { data: cas } = useListWorkspaceSshCas(currentWorkspace?.id || ""); const { mutateAsync: createSshCertTemplate } = useCreateSshCertTemplate(); const { mutateAsync: updateSshCertTemplate } = useUpdateSshCertTemplate(); diff --git a/frontend/src/views/Org/SshCaPage/components/SshCertificateTemplatesSection.tsx b/frontend/src/views/Project/SshCaPage/components/SshCertificateTemplatesSection.tsx similarity index 93% rename from frontend/src/views/Org/SshCaPage/components/SshCertificateTemplatesSection.tsx rename to frontend/src/views/Project/SshCaPage/components/SshCertificateTemplatesSection.tsx index f82ab7442..b6bdde96c 100644 --- a/frontend/src/views/Org/SshCaPage/components/SshCertificateTemplatesSection.tsx +++ b/frontend/src/views/Project/SshCaPage/components/SshCertificateTemplatesSection.tsx @@ -2,14 +2,15 @@ import { faPlus } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { createNotification } from "@app/components/notifications"; -import { OrgPermissionCan } from "@app/components/permissions"; +import { ProjectPermissionCan } from "@app/components/permissions"; import { DeleteActionModal, IconButton } from "@app/components/v2"; -import { OrgPermissionActions, OrgPermissionSubjects } from "@app/context"; +import { ProjectPermissionActions, ProjectPermissionSub } from "@app/context"; import { usePopUp } from "@app/hooks"; import { SshCertTemplateStatus, useDeleteSshCertTemplate, - useUpdateSshCertTemplate} from "@app/hooks/api"; + useUpdateSshCertTemplate +} from "@app/hooks/api"; import { SshCertificateModal } from "./SshCertificateModal"; import { SshCertificateTemplateModal } from "./SshCertificateTemplateModal"; @@ -85,9 +86,9 @@ export const SshCertificateTemplatesSection = ({ caId }: Props) => {

Certificate Templates

- {(isAllowed) => ( { )} - +
diff --git a/frontend/src/views/Org/SshCaPage/components/SshCertificateTemplatesTable.tsx b/frontend/src/views/Project/SshCaPage/components/SshCertificateTemplatesTable.tsx similarity index 85% rename from frontend/src/views/Org/SshCaPage/components/SshCertificateTemplatesTable.tsx rename to frontend/src/views/Project/SshCaPage/components/SshCertificateTemplatesTable.tsx index 2fefc898d..bc8e751fc 100644 --- a/frontend/src/views/Org/SshCaPage/components/SshCertificateTemplatesTable.tsx +++ b/frontend/src/views/Project/SshCaPage/components/SshCertificateTemplatesTable.tsx @@ -8,7 +8,7 @@ import { import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { twMerge } from "tailwind-merge"; -import { OrgPermissionCan } from "@app/components/permissions"; +import { ProjectPermissionCan } from "@app/components/permissions"; import { Badge, DropdownMenu, @@ -26,8 +26,8 @@ import { Tooltip, Tr } from "@app/components/v2"; -import { OrgPermissionActions, OrgPermissionSubjects } from "@app/context"; -import { SshCertTemplateStatus,useGetSshCaCertTemplates } from "@app/hooks/api"; +import { ProjectPermissionActions, ProjectPermissionSub } from "@app/context"; +import { SshCertTemplateStatus, useGetSshCaCertTemplates } from "@app/hooks/api"; import { caStatusToNameMap, getCaStatusBadgeVariant } from "@app/hooks/api/ca/constants"; import { UsePopUpState } from "@app/hooks/usePopUp"; @@ -89,9 +89,9 @@ export const SshCertificateTemplatesTable = ({ handlePopUpOpen, sshCaId }: Props
- {(isAllowed) => ( )} - - + { @@ -136,10 +136,10 @@ export const SshCertificateTemplatesTable = ({ handlePopUpOpen, sshCaId }: Props > Issue Certificate - - + @@ -151,10 +151,10 @@ export const SshCertificateTemplatesTable = ({ handlePopUpOpen, sshCaId }: Props > Edit Template - - + {(isAllowed) => ( )} - + diff --git a/frontend/src/views/Org/SshCaPage/components/index.tsx b/frontend/src/views/Project/SshCaPage/components/index.tsx similarity index 100% rename from frontend/src/views/Org/SshCaPage/components/index.tsx rename to frontend/src/views/Project/SshCaPage/components/index.tsx diff --git a/frontend/src/views/Org/SshCaPage/index.tsx b/frontend/src/views/Project/SshCaPage/index.tsx similarity index 100% rename from frontend/src/views/Org/SshCaPage/index.tsx rename to frontend/src/views/Project/SshCaPage/index.tsx diff --git a/frontend/src/views/Org/SshPage/SshPage.tsx b/frontend/src/views/Project/SshPage/SshPage.tsx similarity index 86% rename from frontend/src/views/Org/SshPage/SshPage.tsx rename to frontend/src/views/Project/SshPage/SshPage.tsx index ff373749a..294170fb5 100644 --- a/frontend/src/views/Org/SshPage/SshPage.tsx +++ b/frontend/src/views/Project/SshPage/SshPage.tsx @@ -1,8 +1,8 @@ import { motion } from "framer-motion"; import { Tab, TabList, TabPanel, Tabs } from "@app/components/v2"; -import { OrgPermissionActions, OrgPermissionSubjects } from "@app/context"; -import { withPermission } from "@app/hoc"; +import { ProjectPermissionActions, ProjectPermissionSub } from "@app/context"; +import { withProjectPermission } from "@app/hoc"; import { SshCaSection, SshCertificatesSection } from "./components"; @@ -11,7 +11,7 @@ enum TabSections { SshCertificates = "ssh-certificates" } -export const SshPage = withPermission( +export const SshPage = withProjectPermission( () => { return (
@@ -49,5 +49,5 @@ export const SshPage = withPermission(
); }, - { action: OrgPermissionActions.Read, subject: OrgPermissionSubjects.SshCertificateAuthorities } + { action: ProjectPermissionActions.Read, subject: ProjectPermissionSub.SshCertificateAuthorities } ); diff --git a/frontend/src/views/Org/SshPage/components/SshCaModal.tsx b/frontend/src/views/Project/SshPage/components/SshCaModal.tsx similarity index 96% rename from frontend/src/views/Org/SshPage/components/SshCaModal.tsx rename to frontend/src/views/Project/SshPage/components/SshCaModal.tsx index d57633801..f0a2eb685 100644 --- a/frontend/src/views/Org/SshPage/components/SshCaModal.tsx +++ b/frontend/src/views/Project/SshPage/components/SshCaModal.tsx @@ -13,6 +13,7 @@ import { Select, SelectItem } from "@app/components/v2"; +import { useWorkspace } from "@app/context"; import { useCreateSshCa, useGetSshCaById, useUpdateSshCa } from "@app/hooks/api"; import { certKeyAlgorithms } from "@app/hooks/api/certificates/constants"; import { CertKeyAlgorithm } from "@app/hooks/api/certificates/enums"; @@ -38,6 +39,8 @@ const schema = z export type FormData = z.infer; export const SshCaModal = ({ popUp, handlePopUpToggle }: Props) => { + const { currentWorkspace } = useWorkspace(); + const projectId = currentWorkspace?.id || ""; const { data: ca } = useGetSshCaById((popUp?.sshCa?.data as { caId: string })?.caId || ""); const { mutateAsync: createMutateAsync } = useCreateSshCa(); @@ -72,6 +75,8 @@ export const SshCaModal = ({ popUp, handlePopUpToggle }: Props) => { const onFormSubmit = async ({ friendlyName, keyAlgorithm }: FormData) => { try { + if (!projectId) return; + if (ca) { await updateMutateAsync({ caId: ca.id, @@ -79,6 +84,7 @@ export const SshCaModal = ({ popUp, handlePopUpToggle }: Props) => { }); } else { await createMutateAsync({ + projectId, friendlyName, keyAlgorithm }); diff --git a/frontend/src/views/Org/SshPage/components/SshCaSection.tsx b/frontend/src/views/Project/SshPage/components/SshCaSection.tsx similarity index 92% rename from frontend/src/views/Org/SshPage/components/SshCaSection.tsx rename to frontend/src/views/Project/SshPage/components/SshCaSection.tsx index c25ac7eae..98855a450 100644 --- a/frontend/src/views/Org/SshPage/components/SshCaSection.tsx +++ b/frontend/src/views/Project/SshPage/components/SshCaSection.tsx @@ -2,9 +2,9 @@ import { faPlus } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { createNotification } from "@app/components/notifications"; -import { OrgPermissionCan } from "@app/components/permissions"; +import { ProjectPermissionCan } from "@app/components/permissions"; import { Button, DeleteActionModal } from "@app/components/v2"; -import { OrgPermissionActions, OrgPermissionSubjects } from "@app/context"; +import { ProjectPermissionActions, ProjectPermissionSub } from "@app/context"; import { SshCaStatus, useDeleteSshCa, useUpdateSshCa } from "@app/hooks/api"; import { usePopUp } from "@app/hooks/usePopUp"; @@ -64,9 +64,9 @@ export const SshCaSection = () => {

Certificate Authorities

- {(isAllowed) => ( )} - +
diff --git a/frontend/src/views/Org/SshPage/components/SshCaTable.tsx b/frontend/src/views/Project/SshPage/components/SshCaTable.tsx similarity index 84% rename from frontend/src/views/Org/SshPage/components/SshCaTable.tsx rename to frontend/src/views/Project/SshPage/components/SshCaTable.tsx index 1fe9dd91d..ab2d33dcf 100644 --- a/frontend/src/views/Org/SshPage/components/SshCaTable.tsx +++ b/frontend/src/views/Project/SshPage/components/SshCaTable.tsx @@ -3,7 +3,7 @@ import { faBan, faCertificate, faEllipsis, faTrash } from "@fortawesome/free-sol import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { twMerge } from "tailwind-merge"; -import { OrgPermissionCan } from "@app/components/permissions"; +import { ProjectPermissionCan } from "@app/components/permissions"; import { Badge, DropdownMenu, @@ -21,8 +21,8 @@ import { Tooltip, Tr } from "@app/components/v2"; -import { OrgPermissionActions, OrgPermissionSubjects, useOrganization } from "@app/context"; -import { SshCaStatus , useListOrgSshCas } from "@app/hooks/api"; +import { ProjectPermissionActions, ProjectPermissionSub,useWorkspace } from "@app/context"; +import { SshCaStatus, useListWorkspaceSshCas } from "@app/hooks/api"; import { caStatusToNameMap, getCaStatusBadgeVariant } from "@app/hooks/api/ca/constants"; import { UsePopUpState } from "@app/hooks/usePopUp"; @@ -35,10 +35,8 @@ type Props = { export const SshCaTable = ({ handlePopUpOpen }: Props) => { const router = useRouter(); - const { currentOrg } = useOrganization(); - const { data, isLoading } = useListOrgSshCas({ - orgId: currentOrg?.id ?? "" - }); + const { currentWorkspace } = useWorkspace(); + const { data, isLoading } = useListWorkspaceSshCas(currentWorkspace?.id || ""); return (
@@ -61,7 +59,7 @@ export const SshCaTable = ({ handlePopUpOpen }: Props) => { router.push(`/org/${currentOrg?.id}/ssh/ca/${ca.id}`)} + onClick={() => router.push(`/project/${currentWorkspace?.id}/ssh/ca/${ca.id}`)} > {ca.friendlyName} @@ -81,9 +79,9 @@ export const SshCaTable = ({ handlePopUpOpen }: Props) => { {(ca.status === SshCaStatus.ACTIVE || ca.status === SshCaStatus.DISABLED) && ( - {(isAllowed) => ( { } SSH CA`} )} - + )} - {(isAllowed) => ( { Delete SSH CA )} - + diff --git a/frontend/src/views/Org/SshPage/components/SshCertificatesSection.tsx b/frontend/src/views/Project/SshPage/components/SshCertificatesSection.tsx similarity index 79% rename from frontend/src/views/Org/SshPage/components/SshCertificatesSection.tsx rename to frontend/src/views/Project/SshPage/components/SshCertificatesSection.tsx index 49b4cfc4a..5d2e7f5fe 100644 --- a/frontend/src/views/Org/SshPage/components/SshCertificatesSection.tsx +++ b/frontend/src/views/Project/SshPage/components/SshCertificatesSection.tsx @@ -1,9 +1,9 @@ import { faPlus } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { OrgPermissionCan } from "@app/components/permissions"; +import { ProjectPermissionCan } from "@app/components/permissions"; import { Button } from "@app/components/v2"; -import { OrgPermissionActions, OrgPermissionSubjects } from "@app/context"; +import { ProjectPermissionActions, ProjectPermissionSub } from "@app/context"; import { usePopUp } from "@app/hooks/usePopUp"; import { SshCertificateModal } from "../../SshCaPage/components/SshCertificateModal"; @@ -15,7 +15,10 @@ export const SshCertificatesSection = () => {

Certificates

- + {(isAllowed) => ( )} - +
diff --git a/frontend/src/views/Org/SshPage/components/SshCertificatesTable.tsx b/frontend/src/views/Project/SshPage/components/SshCertificatesTable.tsx similarity index 89% rename from frontend/src/views/Org/SshPage/components/SshCertificatesTable.tsx rename to frontend/src/views/Project/SshPage/components/SshCertificatesTable.tsx index c7435826b..c8310cc71 100644 --- a/frontend/src/views/Org/SshPage/components/SshCertificatesTable.tsx +++ b/frontend/src/views/Project/SshPage/components/SshCertificatesTable.tsx @@ -15,20 +15,20 @@ import { THead, Tr } from "@app/components/v2"; -import { useOrganization } from "@app/context"; -import { useListOrgSshCertificates } from "@app/hooks/api"; +import { useWorkspace } from "@app/context"; +import { useListWorkspaceSshCertificates } from "@app/hooks/api"; import { getSshCertStatusBadgeDetails } from "./SshCertificatesTable.utils"; const PER_PAGE_INIT = 25; export const SshCertificatesTable = () => { - const { currentOrg } = useOrganization(); + const { currentWorkspace } = useWorkspace(); const [page, setPage] = useState(1); const [perPage, setPerPage] = useState(PER_PAGE_INIT); - const { data, isLoading } = useListOrgSshCertificates({ - orgId: currentOrg?.id ?? "", + const { data, isLoading } = useListWorkspaceSshCertificates({ + projectId: currentWorkspace?.id || "", offset: (page - 1) * perPage, limit: perPage }); diff --git a/frontend/src/views/Org/SshPage/components/SshCertificatesTable.utils.ts b/frontend/src/views/Project/SshPage/components/SshCertificatesTable.utils.ts similarity index 100% rename from frontend/src/views/Org/SshPage/components/SshCertificatesTable.utils.ts rename to frontend/src/views/Project/SshPage/components/SshCertificatesTable.utils.ts diff --git a/frontend/src/views/Org/SshPage/components/index.tsx b/frontend/src/views/Project/SshPage/components/index.tsx similarity index 100% rename from frontend/src/views/Org/SshPage/components/index.tsx rename to frontend/src/views/Project/SshPage/components/index.tsx diff --git a/frontend/src/views/Org/SshPage/index.tsx b/frontend/src/views/Project/SshPage/index.tsx similarity index 100% rename from frontend/src/views/Org/SshPage/index.tsx rename to frontend/src/views/Project/SshPage/index.tsx