From 42249726d4f29d73ffacc1ad471f491f470c6863 Mon Sep 17 00:00:00 2001 From: Tuan Dang Date: Sun, 8 Dec 2024 21:23:00 -0800 Subject: [PATCH] Make PR review adjustments, ssh ca public key endpoint, ssh cert template status --- .../db/migrations/20241130015511_ssh-mgmt.ts | 5 +- .../db/schemas/ssh-certificate-templates.ts | 1 + .../v1/ssh-certificate-authority-router.ts | 29 ++++- .../v1/ssh-certificate-template-router.ts | 3 + .../ee/services/audit-log/audit-log-types.ts | 2 + .../ssh-certificate-template-schema.ts | 1 + .../ssh-certificate-template-service.ts | 100 +++++++++++------- .../ssh-certificate-template-types.ts | 6 ++ .../ssh-certificate/ssh-certificate-schema.ts | 4 +- .../ssh/ssh-certificate-authority-fns.ts | 8 +- .../ssh/ssh-certificate-authority-service.ts | 39 +++++++ .../ssh/ssh-certificate-authority-types.ts | 4 + backend/src/lib/api-docs/constants.ts | 3 + .../server/routes/v1/organization-router.ts | 2 +- frontend/src/hooks/api/ca/constants.tsx | 3 +- frontend/src/hooks/api/ssh-ca/types.ts | 2 + .../api/sshCertificateTemplates/index.tsx | 4 +- .../api/sshCertificateTemplates/types.ts | 7 ++ .../components/SshCertificateModal.tsx | 80 ++++++++------ .../SshCertificateTemplatesSection.tsx | 67 +++++++++++- .../SshCertificateTemplatesTable.tsx | 61 ++++++++++- .../components/SshCertificatesTable.tsx | 28 +++-- .../components/SshCertificatesTable.utils.ts | 17 +++ 23 files changed, 380 insertions(+), 96 deletions(-) create mode 100644 frontend/src/views/Org/SshPage/components/SshCertificatesTable.utils.ts diff --git a/backend/src/db/migrations/20241130015511_ssh-mgmt.ts b/backend/src/db/migrations/20241130015511_ssh-mgmt.ts index a96f49a4d..ad38f8ad2 100644 --- a/backend/src/db/migrations/20241130015511_ssh-mgmt.ts +++ b/backend/src/db/migrations/20241130015511_ssh-mgmt.ts @@ -34,6 +34,7 @@ export async function up(knex: Knex): Promise { t.timestamps(true, true, true); t.uuid("sshCaId").notNullable(); t.foreign("sshCaId").references("id").inTable(TableName.SshCertificateAuthority).onDelete("CASCADE"); + t.string("status").notNullable(); // active / disabled t.string("name").notNullable(); t.string("ttl").notNullable(); t.string("maxTTL").notNullable(); @@ -51,7 +52,7 @@ export async function up(knex: Knex): Promise { t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid()); t.timestamps(true, true, true); t.uuid("sshCaId").notNullable(); - t.foreign("sshCaId").references("id").inTable(TableName.SshCertificateAuthority).onDelete("CASCADE"); + t.foreign("sshCaId").references("id").inTable(TableName.SshCertificateAuthority).onDelete("SET NULL"); t.uuid("sshCertificateTemplateId"); t.foreign("sshCertificateTemplateId") .references("id") @@ -65,7 +66,7 @@ export async function up(knex: Knex): Promise { t.datetime("notBefore").notNullable(); t.datetime("notAfter").notNullable(); }); - await createOnUpdateTrigger(knex, TableName.SshCertificateTemplate); + await createOnUpdateTrigger(knex, TableName.SshCertificate); } } diff --git a/backend/src/db/schemas/ssh-certificate-templates.ts b/backend/src/db/schemas/ssh-certificate-templates.ts index 875d66986..6c16c3942 100644 --- a/backend/src/db/schemas/ssh-certificate-templates.ts +++ b/backend/src/db/schemas/ssh-certificate-templates.ts @@ -12,6 +12,7 @@ export const SshCertificateTemplatesSchema = z.object({ createdAt: z.date(), updatedAt: z.date(), sshCaId: z.string().uuid(), + status: z.string(), name: z.string(), ttl: z.string(), maxTTL: z.string(), 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 4f4e166d0..8c75ac4d4 100644 --- a/backend/src/ee/routes/v1/ssh-certificate-authority-router.ts +++ b/backend/src/ee/routes/v1/ssh-certificate-authority-router.ts @@ -109,6 +109,30 @@ export const registerSshCaRouter = async (server: FastifyZodProvider) => { } }); + server.route({ + method: "GET", + url: "/:sshCaId/public-key", + config: { + rateLimit: readLimit + }, + schema: { + description: "Get public key of SSH CA", + params: z.object({ + sshCaId: z.string().trim().describe(SSH_CERTIFICATE_AUTHORITIES.GET_PUBLIC_KEY.sshCaId) + }), + response: { + 200: z.string() + } + }, + handler: async (req) => { + const publicKey = await server.services.sshCertificateAuthority.getSshCaPublicKey({ + caId: req.params.sshCaId + }); + + return publicKey; + } + }); + server.route({ method: "PATCH", url: "/:sshCaId", @@ -123,10 +147,7 @@ export const registerSshCaRouter = async (server: FastifyZodProvider) => { }), body: z.object({ friendlyName: z.string().optional().describe(SSH_CERTIFICATE_AUTHORITIES.UPDATE.friendlyName), - status: z - .enum([SshCaStatus.ACTIVE, SshCaStatus.DISABLED]) - .optional() - .describe(SSH_CERTIFICATE_AUTHORITIES.UPDATE.status) + status: z.nativeEnum(SshCaStatus).optional().describe(SSH_CERTIFICATE_AUTHORITIES.UPDATE.status) }), response: { 200: z.object({ 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 8ab0b57d9..7e828a588 100644 --- a/backend/src/ee/routes/v1/ssh-certificate-template-router.ts +++ b/backend/src/ee/routes/v1/ssh-certificate-template-router.ts @@ -4,6 +4,7 @@ import { z } from "zod"; import { EventType } from "@app/ee/services/audit-log/audit-log-types"; import { sanitizedSshCertificateTemplate } from "@app/ee/services/ssh-certificate-template/ssh-certificate-template-schema"; +import { SshCertTemplateStatus } from "@app/ee/services/ssh-certificate-template/ssh-certificate-template-types"; import { isValidHostPattern, isValidUserPattern @@ -136,6 +137,7 @@ export const registerSshCertificateTemplateRouter = async (server: FastifyZodPro }, schema: { body: z.object({ + status: z.nativeEnum(SshCertTemplateStatus).optional(), name: z .string() .min(1) @@ -191,6 +193,7 @@ export const registerSshCertificateTemplateRouter = async (server: FastifyZodPro event: { type: EventType.UPDATE_SSH_CERTIFICATE_TEMPLATE, metadata: { + status: certificateTemplate.status as SshCertTemplateStatus, certificateTemplateId: certificateTemplate.id, sshCaId: certificateTemplate.sshCaId, name: certificateTemplate.name, diff --git a/backend/src/ee/services/audit-log/audit-log-types.ts b/backend/src/ee/services/audit-log/audit-log-types.ts index 991c46e1e..ac7188c47 100644 --- a/backend/src/ee/services/audit-log/audit-log-types.ts +++ b/backend/src/ee/services/audit-log/audit-log-types.ts @@ -3,6 +3,7 @@ import { TUpdateProjectTemplateDTO } from "@app/ee/services/project-template/project-template-types"; import { SshCaStatus, SshCertType } from "@app/ee/services/ssh/ssh-certificate-authority-types"; +import { SshCertTemplateStatus } from "@app/ee/services/ssh-certificate-template/ssh-certificate-template-types"; import { SymmetricEncryption } from "@app/lib/crypto/cipher"; import { TProjectPermission } from "@app/lib/types"; import { ActorType } from "@app/services/auth/auth-type"; @@ -1238,6 +1239,7 @@ interface UpdateSshCertificateTemplate { certificateTemplateId: string; sshCaId: string; name: string; + status: SshCertTemplateStatus; ttl: string; maxTTL: string; allowedUsers: string[]; diff --git a/backend/src/ee/services/ssh-certificate-template/ssh-certificate-template-schema.ts b/backend/src/ee/services/ssh-certificate-template/ssh-certificate-template-schema.ts index 328530733..fb7a95203 100644 --- a/backend/src/ee/services/ssh-certificate-template/ssh-certificate-template-schema.ts +++ b/backend/src/ee/services/ssh-certificate-template/ssh-certificate-template-schema.ts @@ -3,6 +3,7 @@ import { SshCertificateTemplatesSchema } from "@app/db/schemas"; export const sanitizedSshCertificateTemplate = SshCertificateTemplatesSchema.pick({ id: true, sshCaId: true, + status: true, name: true, ttl: true, maxTTL: true, 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 8553eae5c..a415f42b8 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 @@ -8,6 +8,7 @@ import { BadRequestError, NotFoundError } from "@app/lib/errors"; import { TSshCertificateAuthorityDALFactory } from "../ssh/ssh-certificate-authority-dal"; import { TSshCertificateTemplateDALFactory } from "./ssh-certificate-template-dal"; import { + SshCertTemplateStatus, TCreateSshCertTemplateDTO, TDeleteSshCertTemplateDTO, TGetSshCertTemplateDTO, @@ -15,7 +16,10 @@ import { } from "./ssh-certificate-template-types"; type TSshCertificateTemplateServiceFactoryDep = { - sshCertificateTemplateDAL: TSshCertificateTemplateDALFactory; + sshCertificateTemplateDAL: Pick< + TSshCertificateTemplateDALFactory, + "transaction" | "getByName" | "create" | "updateById" | "deleteById" | "getById" + >; sshCertificateAuthorityDAL: Pick; permissionService: Pick; }; @@ -62,36 +66,45 @@ export const sshCertificateTemplateServiceFactory = ({ OrgPermissionSubjects.SshCertificateTemplates ); - const existingTemplate = await sshCertificateTemplateDAL.getByName(name, ca.orgId); - if (existingTemplate) { - throw new BadRequestError({ - message: `SSH certificate template with name ${name} already exists` - }); - } - if (ms(ttl) > ms(maxTTL)) { throw new BadRequestError({ message: "TTL cannot be greater than max TTL" }); } - const certificateTemplate = await sshCertificateTemplateDAL.create({ - sshCaId, - name, - ttl, - maxTTL, - allowUserCertificates, - allowHostCertificates, - allowedUsers, - allowedHosts, - allowCustomKeyIds + const newCertificateTemplate = await sshCertificateTemplateDAL.transaction(async (tx) => { + const existingTemplate = await sshCertificateTemplateDAL.getByName(name, ca.orgId, tx); + if (existingTemplate) { + throw new BadRequestError({ + message: `SSH certificate template with name ${name} already exists` + }); + } + + const certificateTemplate = await sshCertificateTemplateDAL.create( + { + sshCaId, + name, + ttl, + maxTTL, + allowUserCertificates, + allowHostCertificates, + allowedUsers, + allowedHosts, + allowCustomKeyIds, + status: SshCertTemplateStatus.ACTIVE + }, + tx + ); + + return certificateTemplate; }); - return { certificateTemplate, ca }; + return { certificateTemplate: newCertificateTemplate, ca }; }; const updateSshCertTemplate = async ({ id, + status, name, ttl, maxTTL, @@ -125,34 +138,43 @@ export const sshCertificateTemplateServiceFactory = ({ OrgPermissionSubjects.SshCertificateTemplates ); - if (name) { - const existingTemplate = await sshCertificateTemplateDAL.getByName(name, actorOrgId); - if (existingTemplate && existingTemplate.id !== id) { + const updatedCertificateTemplate = await sshCertificateTemplateDAL.transaction(async (tx) => { + if (name) { + const existingTemplate = await sshCertificateTemplateDAL.getByName(name, actorOrgId, tx); + if (existingTemplate && existingTemplate.id !== id) { + throw new BadRequestError({ + message: `SSH certificate template with name ${name} already exists` + }); + } + } + + if (ms(ttl || certTemplate.ttl) > ms(maxTTL || certTemplate.maxTTL)) { throw new BadRequestError({ - message: `SSH certificate template with name ${name} already exists` + message: "TTL cannot be greater than max TTL" }); } - } - if (ms(ttl || certTemplate.ttl) > ms(maxTTL || certTemplate.maxTTL)) { - throw new BadRequestError({ - message: "TTL cannot be greater than max TTL" - }); - } + const certificateTemplate = await sshCertificateTemplateDAL.updateById( + id, + { + status, + name, + ttl, + maxTTL, + allowUserCertificates, + allowHostCertificates, + allowedUsers, + allowedHosts, + allowCustomKeyIds + }, + tx + ); - const certificateTemplate = await sshCertificateTemplateDAL.updateById(id, { - name, - ttl, - maxTTL, - allowUserCertificates, - allowHostCertificates, - allowedUsers, - allowedHosts, - allowCustomKeyIds + return certificateTemplate; }); return { - certificateTemplate, + certificateTemplate: updatedCertificateTemplate, orgId: certTemplate.orgId }; }; diff --git a/backend/src/ee/services/ssh-certificate-template/ssh-certificate-template-types.ts b/backend/src/ee/services/ssh-certificate-template/ssh-certificate-template-types.ts index 1920f4ca5..64de1bf0c 100644 --- a/backend/src/ee/services/ssh-certificate-template/ssh-certificate-template-types.ts +++ b/backend/src/ee/services/ssh-certificate-template/ssh-certificate-template-types.ts @@ -1,5 +1,10 @@ import { TProjectPermission } from "@app/lib/types"; +export enum SshCertTemplateStatus { + ACTIVE = "active", + DISABLED = "disabled" +} + export type TCreateSshCertTemplateDTO = { sshCaId: string; name: string; @@ -14,6 +19,7 @@ export type TCreateSshCertTemplateDTO = { export type TUpdateSshCertTemplateDTO = { id: string; + status?: SshCertTemplateStatus; name?: string; ttl?: string; maxTTL?: string; diff --git a/backend/src/ee/services/ssh-certificate/ssh-certificate-schema.ts b/backend/src/ee/services/ssh-certificate/ssh-certificate-schema.ts index 27f6ef7ed..9c3b7a392 100644 --- a/backend/src/ee/services/ssh-certificate/ssh-certificate-schema.ts +++ b/backend/src/ee/services/ssh-certificate/ssh-certificate-schema.ts @@ -8,5 +8,7 @@ export const sanitizedSshCertificate = SshCertificatesSchema.pick({ certType: true, publicKey: true, principals: true, - keyId: true + keyId: true, + notBefore: true, + notAfter: true }); diff --git a/backend/src/ee/services/ssh/ssh-certificate-authority-fns.ts b/backend/src/ee/services/ssh/ssh-certificate-authority-fns.ts index 63ef9e7e2..82be81be6 100644 --- a/backend/src/ee/services/ssh/ssh-certificate-authority-fns.ts +++ b/backend/src/ee/services/ssh/ssh-certificate-authority-fns.ts @@ -53,7 +53,9 @@ export const createSshKeyPair = (keyAlgorithm: CertKeyAlgorithm, comment: string keyBits = "384"; break; default: - throw new Error("Failed to produce SSH CA key pair generation command due to unrecognized key algorithm"); + throw new BadRequestError({ + message: "Failed to produce SSH CA key pair generation command due to unrecognized key algorithm" + }); } execSync(`ssh-keygen -t ${keyType} -b ${keyBits} -f ${privateKeyFile} -N '' -C "${comment}"`); @@ -200,7 +202,7 @@ export const validateSshCertificatePrincipals = ( * @param ttl - The TTL to validate * @returns The TTL (in seconds) to use for issuing the SSH certificate */ -export const validateSshCertificateTtl = (template: TSshCertificateTemplates, ttl: string | undefined) => { +export const validateSshCertificateTtl = (template: TSshCertificateTemplates, ttl?: string) => { if (!ttl) { // use default template ttl return ms(template.ttl) / 1000; @@ -249,6 +251,8 @@ export const createSshCert = ({ caPrivateKey, userPublicKey, keyId, principals, const command = `ssh-keygen ${certOptions}`; + console.log("executing command", command); + // Execute the signing process execSync(command); 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 690a71a7b..18b3c712d 100644 --- a/backend/src/ee/services/ssh/ssh-certificate-authority-service.ts +++ b/backend/src/ee/services/ssh/ssh-certificate-authority-service.ts @@ -9,6 +9,7 @@ import { TSshCertificateTemplateDALFactory } from "@app/ee/services/ssh-certific import { BadRequestError, NotFoundError } from "@app/lib/errors"; import { TKmsServiceFactory } from "@app/services/kms/kms-service"; +import { SshCertTemplateStatus } from "../ssh-certificate-template/ssh-certificate-template-types"; import { createSshCert, createSshKeyPair, @@ -23,6 +24,7 @@ import { TDeleteSshCaDTO, TGetSshCaCertificateTemplatesDTO, TGetSshCaDTO, + TGetSshCaPublicKeyDTO, TIssueSshCredsDTO, TSignSshKeyDTO, TUpdateSshCaDTO @@ -147,6 +149,30 @@ export const sshCertificateAuthorityServiceFactory = ({ return { ...ca, publicKey }; }; + /** + * Return public key of SSH CA with id [caId] + */ + const getSshCaPublicKey = async ({ caId }: TGetSshCaPublicKeyDTO) => { + const ca = await sshCertificateAuthorityDAL.findById(caId); + if (!ca) throw new NotFoundError({ message: `SSH CA with ID '${caId}' not found` }); + + const sshCaSecret = await sshCertificateAuthoritySecretDAL.findOne({ sshCaId: ca.id }); + + // decrypt secret + const orgKmsKeyId = await kmsService.getOrgKmsKeyId(ca.orgId); + const kmsDecryptor = await kmsService.decryptWithKmsKey({ + kmsId: orgKmsKeyId + }); + + const decryptedCaPrivateKey = await kmsDecryptor({ + cipherTextBlob: sshCaSecret.encryptedPrivateKey + }); + + const publicKey = getSshPublicKey(decryptedCaPrivateKey.toString("utf-8")); + + return publicKey; + }; + /** * Update SSH CA with id [caId] * Note: Used to enable/disable CA @@ -259,6 +285,12 @@ export const sshCertificateAuthorityServiceFactory = ({ }); } + if (sshCertificateTemplate.status === SshCertTemplateStatus.DISABLED) { + throw new BadRequestError({ + message: "SSH certificate template is disabled" + }); + } + // validate if the requested [certType] is allowed under the template configuration validateSshCertificateType(sshCertificateTemplate, certType); @@ -359,6 +391,12 @@ export const sshCertificateAuthorityServiceFactory = ({ }); } + if (sshCertificateTemplate.status === SshCertTemplateStatus.DISABLED) { + throw new BadRequestError({ + message: "SSH certificate template is disabled" + }); + } + // validate if the requested [certType] is allowed under the template configuration validateSshCertificateType(sshCertificateTemplate, certType); @@ -445,6 +483,7 @@ export const sshCertificateAuthorityServiceFactory = ({ signSshKey, createSshCa, getSshCaById, + getSshCaPublicKey, updateSshCaById, deleteSshCaById, getSshCaCertificateTemplates 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 ba4e5aa54..7d949bfa0 100644 --- a/backend/src/ee/services/ssh/ssh-certificate-authority-types.ts +++ b/backend/src/ee/services/ssh/ssh-certificate-authority-types.ts @@ -20,6 +20,10 @@ export type TGetSshCaDTO = { caId: string; } & Omit; +export type TGetSshCaPublicKeyDTO = { + caId: string; +}; + export type TUpdateSshCaDTO = { caId: string; friendlyName?: string; diff --git a/backend/src/lib/api-docs/constants.ts b/backend/src/lib/api-docs/constants.ts index d881a5233..500611d6f 100644 --- a/backend/src/lib/api-docs/constants.ts +++ b/backend/src/lib/api-docs/constants.ts @@ -1154,6 +1154,9 @@ export const SSH_CERTIFICATE_AUTHORITIES = { GET: { sshCaId: "The ID of the SSH CA to get." }, + GET_PUBLIC_KEY: { + sshCaId: "The ID of the SSH CA to get the public key for." + }, UPDATE: { sshCaId: "The ID of the SSH CA to update.", friendlyName: "A friendly name for the SSH CA to update to.", diff --git a/backend/src/server/routes/v1/organization-router.ts b/backend/src/server/routes/v1/organization-router.ts index c1b8881e9..c249bf2e5 100644 --- a/backend/src/server/routes/v1/organization-router.ts +++ b/backend/src/server/routes/v1/organization-router.ts @@ -425,7 +425,7 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => { response: { 200: z.object({ certificates: z.array(sanitizedSshCertificate), - totalCount: z.number() // TODO + totalCount: z.number() }) } }, diff --git a/frontend/src/hooks/api/ca/constants.tsx b/frontend/src/hooks/api/ca/constants.tsx index 016f7d7b5..bbc75f90d 100644 --- a/frontend/src/hooks/api/ca/constants.tsx +++ b/frontend/src/hooks/api/ca/constants.tsx @@ -1,4 +1,5 @@ import { SshCaStatus } from "@app/hooks/api/ssh-ca"; +import { SshCertTemplateStatus } from "@app/hooks/api/sshCertificateTemplates"; import { CaStatus, CaType } from "./enums"; @@ -13,7 +14,7 @@ export const caStatusToNameMap: { [K in CaStatus]: string } = { [CaStatus.PENDING_CERTIFICATE]: "Pending Certificate" }; -export const getCaStatusBadgeVariant = (status: CaStatus | SshCaStatus) => { +export const getCaStatusBadgeVariant = (status: CaStatus | SshCaStatus | SshCertTemplateStatus) => { switch (status) { case CaStatus.ACTIVE: return "success"; diff --git a/frontend/src/hooks/api/ssh-ca/types.ts b/frontend/src/hooks/api/ssh-ca/types.ts index 747802c32..2873fafeb 100644 --- a/frontend/src/hooks/api/ssh-ca/types.ts +++ b/frontend/src/hooks/api/ssh-ca/types.ts @@ -10,6 +10,8 @@ export type TSshCertificate = { publicKey: string; principals: string[]; keyId: string; + notBefore: string; + notAfter: string; }; export type TSshCertificateAuthority = { diff --git a/frontend/src/hooks/api/sshCertificateTemplates/index.tsx b/frontend/src/hooks/api/sshCertificateTemplates/index.tsx index 89f7ebe14..9efe99c81 100644 --- a/frontend/src/hooks/api/sshCertificateTemplates/index.tsx +++ b/frontend/src/hooks/api/sshCertificateTemplates/index.tsx @@ -1,5 +1,7 @@ export { useCreateSshCertTemplate, useDeleteSshCertTemplate, - useUpdateSshCertTemplate} from "./mutations"; + useUpdateSshCertTemplate +} from "./mutations"; export { useGetSshCertTemplate } from "./queries"; +export * from "./types"; diff --git a/frontend/src/hooks/api/sshCertificateTemplates/types.ts b/frontend/src/hooks/api/sshCertificateTemplates/types.ts index fceaff56d..4099b4b8a 100644 --- a/frontend/src/hooks/api/sshCertificateTemplates/types.ts +++ b/frontend/src/hooks/api/sshCertificateTemplates/types.ts @@ -1,6 +1,12 @@ +export enum SshCertTemplateStatus { + ACTIVE = "active", + DISABLED = "disabled" +} + export type TSshCertificateTemplate = { id: string; sshCaId: string; + status: SshCertTemplateStatus; name: string; ttl: string; maxTTL: string; @@ -25,6 +31,7 @@ export type TCreateSshCertificateTemplateDTO = { export type TUpdateSshCertificateTemplateDTO = { id: string; + status?: SshCertTemplateStatus; name?: string; ttl?: string; maxTTL?: string; diff --git a/frontend/src/views/Org/SshCaPage/components/SshCertificateModal.tsx b/frontend/src/views/Org/SshCaPage/components/SshCertificateModal.tsx index 3238160e9..ea83a95bb 100644 --- a/frontend/src/views/Org/SshCaPage/components/SshCertificateModal.tsx +++ b/frontend/src/views/Org/SshCaPage/components/SshCertificateModal.tsx @@ -14,7 +14,12 @@ import { SelectItem } from "@app/components/v2"; import { useOrganization } from "@app/context"; -import { useIssueSshCreds, useListOrgSshCertificateTemplates, useSignSshKey } from "@app/hooks/api"; +import { + SshCertTemplateStatus, + useGetSshCertTemplate, + useIssueSshCreds, + useListOrgSshCertificateTemplates, + 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"; @@ -22,14 +27,8 @@ import { UsePopUpState } from "@app/hooks/usePopUp"; import { SshCertificateContent } from "./SshCertificateContent"; -/** - * // NOTE (dangtony98): current UI only supports SSH certificate - * issuance via /issue endpoint but should extend to also support - * /sign endpoint as this is already supported in the backend - */ - const schema = z.object({ - templateName: z.string(), + templateId: z.string(), publicKey: z.string().optional(), keyAlgorithm: z.enum([ CertKeyAlgorithm.RSA_2048, @@ -72,7 +71,11 @@ export const SshCertificateModal = ({ popUp, handlePopUpToggle }: Props) => { const { mutateAsync: signSshKey } = useSignSshKey(); const { mutateAsync: issueSshCreds } = useIssueSshCreds(); - const popUpData = popUp?.sshCertificate?.data as { sshCaId: string; templateName: string }; + const popUpData = popUp?.sshCertificate?.data as { + sshCaId: string; + templateName: string; + templateId: string; + }; const { data: templatesData } = useListOrgSshCertificateTemplates({ orgId: currentOrg?.id || "" @@ -83,7 +86,8 @@ export const SshCertificateModal = ({ popUp, handlePopUpToggle }: Props) => { handleSubmit, reset, formState: { isSubmitting }, - setValue + setValue, + watch } = useForm({ resolver: zodResolver(schema), defaultValues: { @@ -92,16 +96,18 @@ export const SshCertificateModal = ({ popUp, handlePopUpToggle }: Props) => { } }); + const templateId = watch("templateId"); + const { data: templateData } = useGetSshCertTemplate(templateId); + useEffect(() => { if (popUpData) { - setValue("templateName", popUpData.templateName); + setValue("templateId", popUpData.templateId); } else if (templatesData && templatesData.certificateTemplates.length > 0) { - setValue("templateName", templatesData.certificateTemplates[0].name); + setValue("templateId", templatesData.certificateTemplates[0].id); } }, [popUpData]); const onFormSubmit = async ({ - templateName, keyAlgorithm, certType, publicKey: existingPublicKey, @@ -110,10 +116,12 @@ export const SshCertificateModal = ({ popUp, handlePopUpToggle }: Props) => { keyId }: FormData) => { try { + if (!templateData) return; + switch (operation) { case SshCertificateOperation.SIGN_SSH_KEY: { const { serialNumber, signedKey } = await signSshKey({ - templateName, + templateName: templateData.name, publicKey: existingPublicKey, certType, principals: principals.split(",").map((user) => user.trim()), @@ -129,7 +137,7 @@ export const SshCertificateModal = ({ popUp, handlePopUpToggle }: Props) => { } case SshCertificateOperation.ISSUE_SSH_CREDS: { const { serialNumber, publicKey, privateKey, signedKey } = await issueSshCreds({ - templateName, + templateName: templateData.name, keyAlgorithm, certType, principals: principals.split(",").map((user) => user.trim()), @@ -179,7 +187,7 @@ export const SshCertificateModal = ({ popUp, handlePopUpToggle }: Props) => {
( { className="w-full" isDisabled={Boolean(popUpData?.sshCaId)} > - {(templatesData?.certificateTemplates || []).map(({ id, name }) => ( - - {name} - - ))} + {(templatesData?.certificateTemplates || []) + .filter((template) => template.status === SshCertTemplateStatus.ACTIVE) + .map(({ id, name }) => ( + + {name} + + ))} )} @@ -235,8 +245,12 @@ export const SshCertificateModal = ({ popUp, handlePopUpToggle }: Props) => { onValueChange={(e) => onChange(e)} className="w-full" > - User - Host + {templateData && templateData.allowUserCertificates && ( + User + )} + {templateData && templateData.allowHostCertificates && ( + Host + )} )} @@ -308,15 +322,17 @@ export const SshCertificateModal = ({ popUp, handlePopUpToggle }: Props) => { )} /> - ( - - - - )} - /> + {templateData && templateData.allowCustomKeyIds && ( + ( + + + + )} + /> + )}