misc: addressed review comments

This commit is contained in:
Sheen Capadngan
2025-05-19 20:08:43 +08:00
parent ba8fcb6891
commit 2b4a6ad907
16 changed files with 42 additions and 31 deletions
@@ -12,12 +12,8 @@ export async function up(knex: Knex): Promise<void> {
t.uuid("certificateAuthorityId").nullable(); t.uuid("certificateAuthorityId").nullable();
}); });
const caRows = await knex(TableName.CertificateAuthority).select("*"); // @ts-expect-error intentional: migration
if (caRows.length > 0) { await knex(TableName.InternalCertificateAuthority).insert(knex(TableName.CertificateAuthority).select("*"));
// @ts-expect-error intentional: migration
await knex(TableName.InternalCertificateAuthority).insert(caRows);
}
await knex(TableName.InternalCertificateAuthority).update("certificateAuthorityId", knex.ref("id")); await knex(TableName.InternalCertificateAuthority).update("certificateAuthorityId", knex.ref("id"));
await knex.schema.alterTable(TableName.InternalCertificateAuthority, (t) => { await knex.schema.alterTable(TableName.InternalCertificateAuthority, (t) => {
@@ -90,7 +86,7 @@ export async function up(knex: Knex): Promise<void> {
t.string("ttl").nullable().alter(); t.string("ttl").nullable().alter();
t.string("lastOperationStatus"); t.string("lastOperationStatus");
t.text("lastOperationMessage"); t.text("lastOperationMessage");
t.string("lastOperationAt"); t.dateTime("lastOperationAt");
}); });
} }
} }
+1 -1
View File
@@ -22,7 +22,7 @@ export const PkiSubscribersSchema = z.object({
status: z.string(), status: z.string(),
lastOperationStatus: z.string().nullable().optional(), lastOperationStatus: z.string().nullable().optional(),
lastOperationMessage: z.string().nullable().optional(), lastOperationMessage: z.string().nullable().optional(),
lastOperationAt: z.string().nullable().optional() lastOperationAt: z.date().nullable().optional()
}); });
export type TPkiSubscribers = z.infer<typeof PkiSubscribersSchema>; export type TPkiSubscribers = z.infer<typeof PkiSubscribersSchema>;
@@ -204,7 +204,7 @@ export const registerCertificateAuthorityEndpoints = <
server.route({ server.route({
method: "DELETE", method: "DELETE",
url: `/:certificateAuthorityId`, url: "/:certificateAuthorityId",
config: { config: {
rateLimit: writeLimit rateLimit: writeLimit
}, },
@@ -1,4 +1,3 @@
/* eslint-disable @typescript-eslint/no-floating-promises */
import { z } from "zod"; import { z } from "zod";
import { EventType } from "@app/ee/services/audit-log/audit-log-types"; import { EventType } from "@app/ee/services/audit-log/audit-log-types";
@@ -6,6 +6,7 @@ import { KeyObject } from "crypto";
import { TableName } from "@app/db/schemas"; import { TableName } from "@app/db/schemas";
import { BadRequestError, NotFoundError } from "@app/lib/errors"; import { BadRequestError, NotFoundError } from "@app/lib/errors";
import { OrgServiceActor } from "@app/lib/types"; import { OrgServiceActor } from "@app/lib/types";
import { blockLocalAndPrivateIpAddresses } from "@app/lib/validator";
import { TAppConnectionDALFactory } from "@app/services/app-connection/app-connection-dal"; import { TAppConnectionDALFactory } from "@app/services/app-connection/app-connection-dal";
import { AppConnection, AWSRegion } from "@app/services/app-connection/app-connection-enums"; import { AppConnection, AWSRegion } from "@app/services/app-connection/app-connection-enums";
import { decryptAppConnection } from "@app/services/app-connection/app-connection-fns"; import { decryptAppConnection } from "@app/services/app-connection/app-connection-fns";
@@ -406,6 +407,8 @@ export const AcmeCertificateAuthorityFns = ({
); );
} }
await blockLocalAndPrivateIpAddresses(acmeCa.configuration.directoryUrl);
const acmeClient = new acme.Client({ const acmeClient = new acme.Client({
directoryUrl: acmeCa.configuration.directoryUrl, directoryUrl: acmeCa.configuration.directoryUrl,
accountKey accountKey
@@ -11,13 +11,13 @@ import {
import { AcmeDnsProvider } from "./acme-certificate-authority-enums"; import { AcmeDnsProvider } from "./acme-certificate-authority-enums";
export const AcmeCertificateAuthorityConfigurationSchema = z.object({ export const AcmeCertificateAuthorityConfigurationSchema = z.object({
dnsAppConnectionId: z.string().trim().describe(CertificateAuthorities.CONFIGURATIONS.ACME.dnsAppConnectionId), dnsAppConnectionId: z.string().uuid().trim().describe(CertificateAuthorities.CONFIGURATIONS.ACME.dnsAppConnectionId),
// soon, differentiate via the provider property // soon, differentiate via the provider property
dnsProviderConfig: z.object({ dnsProviderConfig: z.object({
provider: z.nativeEnum(AcmeDnsProvider).describe(CertificateAuthorities.CONFIGURATIONS.ACME.provider), provider: z.nativeEnum(AcmeDnsProvider).describe(CertificateAuthorities.CONFIGURATIONS.ACME.provider),
hostedZoneId: z.string().trim().min(1).describe(CertificateAuthorities.CONFIGURATIONS.ACME.hostedZoneId) hostedZoneId: z.string().trim().min(1).describe(CertificateAuthorities.CONFIGURATIONS.ACME.hostedZoneId)
}), }),
directoryUrl: z.string().trim().min(1).describe(CertificateAuthorities.CONFIGURATIONS.ACME.directoryUrl), directoryUrl: z.string().url().trim().min(1).describe(CertificateAuthorities.CONFIGURATIONS.ACME.directoryUrl),
accountEmail: z.string().trim().min(1).describe(CertificateAuthorities.CONFIGURATIONS.ACME.accountEmail) accountEmail: z.string().trim().min(1).describe(CertificateAuthorities.CONFIGURATIONS.ACME.accountEmail)
}); });
@@ -156,7 +156,7 @@ export const certificateAuthorityQueueFactory = ({
await pkiSubscriberDAL.updateById(subscriberId, { await pkiSubscriberDAL.updateById(subscriberId, {
lastOperationStatus: SubscriberOperationStatus.SUCCESS, lastOperationStatus: SubscriberOperationStatus.SUCCESS,
lastOperationMessage: "Certificate ordered successfully", lastOperationMessage: "Certificate ordered successfully",
lastOperationAt: new Date().toISOString() lastOperationAt: new Date()
}); });
} }
} catch (e: unknown) { } catch (e: unknown) {
@@ -164,7 +164,7 @@ export const certificateAuthorityQueueFactory = ({
await pkiSubscriberDAL.updateById(subscriberId, { await pkiSubscriberDAL.updateById(subscriberId, {
lastOperationStatus: SubscriberOperationStatus.FAILED, lastOperationStatus: SubscriberOperationStatus.FAILED,
lastOperationMessage: e.message, lastOperationMessage: e.message,
lastOperationAt: new Date().toISOString() lastOperationAt: new Date()
}); });
} }
logger.error(e, `CaOrderCertificate Failed [subscriberId=${subscriberId}] [job=${job.name}]`); logger.error(e, `CaOrderCertificate Failed [subscriberId=${subscriberId}] [job=${job.name}]`);
@@ -140,7 +140,7 @@ export const certificateAuthorityServiceFactory = ({
type, type,
disableDirectIssuance: ca.disableDirectIssuance, disableDirectIssuance: ca.disableDirectIssuance,
name: ca.internalCa?.friendlyName, name: ca.internalCa?.friendlyName,
projectId, projectId: finalProjectId,
status, status,
configuration: ca.internalCa configuration: ca.internalCa
} as TCertificateAuthority; } as TCertificateAuthority;
@@ -11,14 +11,18 @@ export const certificateDALFactory = (db: TDbClient) => {
const certificateOrm = ormify(db, TableName.Certificate); const certificateOrm = ormify(db, TableName.Certificate);
const findLatestActiveCertForSubscriber = async ({ subscriberId }: { subscriberId: string }) => { const findLatestActiveCertForSubscriber = async ({ subscriberId }: { subscriberId: string }) => {
const cert = await db try {
.replicaNode()(TableName.Certificate) const cert = await db
.where({ pkiSubscriberId: subscriberId, status: CertStatus.ACTIVE }) .replicaNode()(TableName.Certificate)
.where("notAfter", ">", new Date()) .where({ pkiSubscriberId: subscriberId, status: CertStatus.ACTIVE })
.orderBy("notBefore", "desc") .where("notAfter", ">", new Date())
.first(); .orderBy("notBefore", "desc")
.first();
return cert; return cert;
} catch (error) {
throw new DatabaseError({ error, name: "Find latest active certificate for subscriber" });
}
}; };
const countCertificatesInProject = async ({ const countCertificatesInProject = async ({
@@ -710,6 +710,10 @@ export const pkiSubscriberServiceFactory = ({
projectId projectId
}); });
if (!subscriber) {
throw new NotFoundError({ message: `PKI subscriber named '${subscriberName}' not found` });
}
const { permission } = await permissionService.getProjectPermission({ const { permission } = await permissionService.getProjectPermission({
actor, actor,
actorId, actorId,
@@ -1,4 +1,4 @@
--- ---
title: "Issue Certificate" title: "Issue Certificate"
openapi: "POST /api/v1/pki/subscribers/{subscriberName}/issue-cert" openapi: "POST /api/v1/pki/subscribers/{subscriberName}/issue-certificate"
--- ---
@@ -0,0 +1,4 @@
---
title: "Order Certificate"
openapi: "POST /api/v1/pki/subscribers/{subscriberName}/order-certificate"
---
+1
View File
@@ -1469,6 +1469,7 @@
"api-reference/endpoints/pki/subscribers/delete", "api-reference/endpoints/pki/subscribers/delete",
"api-reference/endpoints/pki/subscribers/issue-cert", "api-reference/endpoints/pki/subscribers/issue-cert",
"api-reference/endpoints/pki/subscribers/sign-cert", "api-reference/endpoints/pki/subscribers/sign-cert",
"api-reference/endpoints/pki/subscribers/order-cert",
"api-reference/endpoints/pki/subscribers/get-active-cert-bundle" "api-reference/endpoints/pki/subscribers/get-active-cert-bundle"
] ]
}, },
@@ -17,7 +17,7 @@ export type TPkiSubscriber = {
name: string; name: string;
commonName: string; commonName: string;
status: PkiSubscriberStatus; status: PkiSubscriberStatus;
ttl: string; ttl?: string;
subjectAlternativeNames: string[]; subjectAlternativeNames: string[];
keyUsages: CertKeyUsage[]; keyUsages: CertKeyUsage[];
extendedKeyUsages: CertExtendedKeyUsage[]; extendedKeyUsages: CertExtendedKeyUsage[];
@@ -26,7 +26,7 @@ export const ExternalCaSection = () => {
const onRemoveCaSubmit = async (caId: string, type: CaType) => { const onRemoveCaSubmit = async (caId: string, type: CaType) => {
try { try {
if (!currentWorkspace?.slug) return; if (!currentWorkspace?.id) return;
await deleteCa({ caId, type, projectId: currentWorkspace.id }); await deleteCa({ caId, type, projectId: currentWorkspace.id });
@@ -67,7 +67,7 @@ export const ExternalCaSection = () => {
} catch (err) { } catch (err) {
console.error(err); console.error(err);
createNotification({ createNotification({
text: `Failed to ${status === CaStatus.ACTIVE ? "enabled" : "disabled"} CA`, text: `Failed to ${status === CaStatus.ACTIVE ? "enable" : "disable"} CA`,
type: "error" type: "error"
}); });
} }
@@ -84,7 +84,6 @@ export const ExternalCaSection = () => {
{(isAllowed) => ( {(isAllowed) => (
<Button <Button
colorSchema="primary" colorSchema="primary"
type="submit"
leftIcon={<FontAwesomeIcon icon={faPlus} />} leftIcon={<FontAwesomeIcon icon={faPlus} />}
onClick={() => handlePopUpOpen("ca")} onClick={() => handlePopUpOpen("ca")}
isDisabled={!isAllowed} isDisabled={!isAllowed}
@@ -66,7 +66,8 @@ export const PkiSubscriberDetailsSection = ({ subscriberName, handlePopUpOpen }:
const { mutateAsync: issuePkiSubscriberCert, isPending: isIssuingCert } = const { mutateAsync: issuePkiSubscriberCert, isPending: isIssuingCert } =
useIssuePkiSubscriberCert(); useIssuePkiSubscriberCert();
const { mutateAsync: orderPkiSubscriberCert } = useOrderPkiSubscriberCert(); const { mutateAsync: orderPkiSubscriberCert, isPending: isOrderingCert } =
useOrderPkiSubscriberCert();
const onIssuePkiSubscriberCert = async () => { const onIssuePkiSubscriberCert = async () => {
try { try {
@@ -90,8 +91,8 @@ export const PkiSubscriberDetailsSection = ({ subscriberName, handlePopUpOpen }:
await orderPkiSubscriberCert({ subscriberName, projectId }); await orderPkiSubscriberCert({ subscriberName, projectId });
createNotification({ createNotification({
text: "Successfully ordered certificate. It will be issued after CA processing.", text: "Successfully ordered certificate. It will be issued after CA processing which could take a few minutes.",
type: "success" type: "info"
}); });
} }
} catch (err) { } catch (err) {
@@ -198,7 +199,7 @@ export const PkiSubscriberDetailsSection = ({ subscriberName, handlePopUpOpen }:
className="mt-2 w-full" className="mt-2 w-full"
colorSchema="primary" colorSchema="primary"
type="button" type="button"
isLoading={isIssuingCert} isLoading={isIssuingCert || isOrderingCert}
onClick={() => { onClick={() => {
onIssuePkiSubscriberCert(); onIssuePkiSubscriberCert();
}} }}