Make PR review adjustments

This commit is contained in:
Tuan Dang
2024-08-17 21:23:25 -07:00
parent 44b42359da
commit 30d6af7760
14 changed files with 124 additions and 51 deletions

View File

@@ -25,7 +25,7 @@ export async function up(knex: Knex): Promise<void> {
if (!hasVersionColumn) { if (!hasVersionColumn) {
await knex.schema.alterTable(TableName.CertificateAuthorityCert, (t) => { await knex.schema.alterTable(TableName.CertificateAuthorityCert, (t) => {
t.integer("version").nullable(); t.integer("version").nullable();
// t.dropUnique(["caId"]); t.dropUnique(["caId"]);
}); });
await knex(TableName.CertificateAuthorityCert).update({ version: 1 }).whereNull("version"); await knex(TableName.CertificateAuthorityCert).update({ version: 1 }).whereNull("version");

View File

@@ -15,6 +15,8 @@ export async function up(knex: Knex): Promise<void> {
}); });
} }
await createOnUpdateTrigger(knex, TableName.PkiCollection);
if (!(await knex.schema.hasTable(TableName.PkiCollectionItem))) { if (!(await knex.schema.hasTable(TableName.PkiCollectionItem))) {
await knex.schema.createTable(TableName.PkiCollectionItem, (t) => { await knex.schema.createTable(TableName.PkiCollectionItem, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid()); t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
@@ -28,6 +30,8 @@ export async function up(knex: Knex): Promise<void> {
}); });
} }
await createOnUpdateTrigger(knex, TableName.PkiCollectionItem);
if (!(await knex.schema.hasTable(TableName.PkiAlert))) { if (!(await knex.schema.hasTable(TableName.PkiAlert))) {
await knex.schema.createTable(TableName.PkiAlert, (t) => { await knex.schema.createTable(TableName.PkiAlert, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid()); t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
@@ -43,7 +47,6 @@ export async function up(knex: Knex): Promise<void> {
}); });
} }
await createOnUpdateTrigger(knex, TableName.PkiCollection);
await createOnUpdateTrigger(knex, TableName.PkiAlert); await createOnUpdateTrigger(knex, TableName.PkiAlert);
} }

View File

@@ -16,6 +16,7 @@ export enum QueueName {
// TODO(akhilmhdh): This will get removed later. For now this is kept to stop the repeatable queue // TODO(akhilmhdh): This will get removed later. For now this is kept to stop the repeatable queue
AuditLogPrune = "audit-log-prune", AuditLogPrune = "audit-log-prune",
DailyResourceCleanUp = "daily-resource-cleanup", DailyResourceCleanUp = "daily-resource-cleanup",
DailyExpiringPkiItemAlert = "daily-expiring-pki-item-alert",
TelemetryInstanceStats = "telemtry-self-hosted-stats", TelemetryInstanceStats = "telemtry-self-hosted-stats",
IntegrationSync = "sync-integrations", IntegrationSync = "sync-integrations",
SecretWebhook = "secret-webhook", SecretWebhook = "secret-webhook",
@@ -36,6 +37,7 @@ export enum QueueJobs {
// TODO(akhilmhdh): This will get removed later. For now this is kept to stop the repeatable queue // TODO(akhilmhdh): This will get removed later. For now this is kept to stop the repeatable queue
AuditLogPrune = "audit-log-prune-job", AuditLogPrune = "audit-log-prune-job",
DailyResourceCleanUp = "daily-resource-cleanup-job", DailyResourceCleanUp = "daily-resource-cleanup-job",
DailyExpiringPkiItemAlert = "daily-expiring-pki-item-alert",
SecWebhook = "secret-webhook-trigger", SecWebhook = "secret-webhook-trigger",
TelemetryInstanceStats = "telemetry-self-hosted-stats", TelemetryInstanceStats = "telemetry-self-hosted-stats",
IntegrationSync = "secret-integration-pull", IntegrationSync = "secret-integration-pull",
@@ -71,6 +73,10 @@ export type TQueueJobTypes = {
name: QueueJobs.DailyResourceCleanUp; name: QueueJobs.DailyResourceCleanUp;
payload: undefined; payload: undefined;
}; };
[QueueName.DailyExpiringPkiItemAlert]: {
name: QueueJobs.DailyExpiringPkiItemAlert;
payload: undefined;
};
[QueueName.AuditLogPrune]: { [QueueName.AuditLogPrune]: {
name: QueueJobs.AuditLogPrune; name: QueueJobs.AuditLogPrune;
payload: undefined; payload: undefined;

View File

@@ -131,6 +131,7 @@ import { orgRoleServiceFactory } from "@app/services/org/org-role-service";
import { orgServiceFactory } from "@app/services/org/org-service"; import { orgServiceFactory } from "@app/services/org/org-service";
import { orgAdminServiceFactory } from "@app/services/org-admin/org-admin-service"; import { orgAdminServiceFactory } from "@app/services/org-admin/org-admin-service";
import { orgMembershipDALFactory } from "@app/services/org-membership/org-membership-dal"; import { orgMembershipDALFactory } from "@app/services/org-membership/org-membership-dal";
import { dailyExpiringPkiItemAlertQueueServiceFactory } from "@app/services/pki-alert/expiring-pki-item-alert-queue";
import { pkiAlertDALFactory } from "@app/services/pki-alert/pki-alert-dal"; import { pkiAlertDALFactory } from "@app/services/pki-alert/pki-alert-dal";
import { pkiAlertServiceFactory } from "@app/services/pki-alert/pki-alert-service"; import { pkiAlertServiceFactory } from "@app/services/pki-alert/pki-alert-service";
import { pkiCollectionDALFactory } from "@app/services/pki-collection/pki-collection-dal"; import { pkiCollectionDALFactory } from "@app/services/pki-collection/pki-collection-dal";
@@ -1063,7 +1064,6 @@ export const registerRoutes = async (
const dailyResourceCleanUp = dailyResourceCleanUpQueueServiceFactory({ const dailyResourceCleanUp = dailyResourceCleanUpQueueServiceFactory({
auditLogDAL, auditLogDAL,
queueService, queueService,
pkiAlertService,
secretVersionDAL, secretVersionDAL,
secretFolderVersionDAL: folderVersionDAL, secretFolderVersionDAL: folderVersionDAL,
snapshotDAL, snapshotDAL,
@@ -1073,6 +1073,11 @@ export const registerRoutes = async (
identityUniversalAuthClientSecretDAL: identityUaClientSecretDAL identityUniversalAuthClientSecretDAL: identityUaClientSecretDAL
}); });
const dailyExpiringPkiItemAlert = dailyExpiringPkiItemAlertQueueServiceFactory({
queueService,
pkiAlertService
});
const oidcService = oidcConfigServiceFactory({ const oidcService = oidcConfigServiceFactory({
orgDAL, orgDAL,
orgMembershipDAL, orgMembershipDAL,
@@ -1097,6 +1102,7 @@ export const registerRoutes = async (
await telemetryQueue.startTelemetryCheck(); await telemetryQueue.startTelemetryCheck();
await dailyResourceCleanUp.startCleanUp(); await dailyResourceCleanUp.startCleanUp();
await dailyExpiringPkiItemAlert.startSendingAlerts();
await kmsService.startService(); await kmsService.startService();
// inject all services // inject all services

View File

@@ -76,8 +76,8 @@ export const registerV1Routes = async (server: FastifyZodProvider) => {
async (pkiRouter) => { async (pkiRouter) => {
await pkiRouter.register(registerCaRouter, { prefix: "/ca" }); await pkiRouter.register(registerCaRouter, { prefix: "/ca" });
await pkiRouter.register(registerCertRouter, { prefix: "/certificates" }); await pkiRouter.register(registerCertRouter, { prefix: "/certificates" });
await server.register(registerPkiAlertRouter, { prefix: "/alerts" }); await pkiRouter.register(registerPkiAlertRouter, { prefix: "/alerts" });
await server.register(registerPkiCollectionRouter, { prefix: "/collections" }); await pkiRouter.register(registerPkiCollectionRouter, { prefix: "/collections" });
}, },
{ prefix: "/pki" } { prefix: "/pki" }
); );

View File

@@ -22,7 +22,11 @@ export const registerPkiAlertRouter = async (server: FastifyZodProvider) => {
pkiCollectionId: z.string().trim().describe(ALERTS.CREATE.pkiCollectionId), pkiCollectionId: z.string().trim().describe(ALERTS.CREATE.pkiCollectionId),
name: z.string().trim().describe(ALERTS.CREATE.name), name: z.string().trim().describe(ALERTS.CREATE.name),
alertBeforeDays: z.number().describe(ALERTS.CREATE.alertBeforeDays), alertBeforeDays: z.number().describe(ALERTS.CREATE.alertBeforeDays),
emails: z.array(z.string().trim().email({ message: "Invalid email address" })).describe(ALERTS.CREATE.emails) emails: z
.array(z.string().trim().email({ message: "Invalid email address" }))
.min(1, { message: "You must specify at least 1 email" })
.max(5, { message: "You can specify a maximum of 5 emails" })
.describe(ALERTS.CREATE.emails)
}), }),
response: { response: {
200: PkiAlertsSchema 200: PkiAlertsSchema
@@ -114,6 +118,8 @@ export const registerPkiAlertRouter = async (server: FastifyZodProvider) => {
pkiCollectionId: z.string().trim().optional().describe(ALERTS.UPDATE.pkiCollectionId), pkiCollectionId: z.string().trim().optional().describe(ALERTS.UPDATE.pkiCollectionId),
emails: z emails: z
.array(z.string().trim().email({ message: "Invalid email address" })) .array(z.string().trim().email({ message: "Invalid email address" }))
.min(1, { message: "You must specify at least 1 email" })
.max(5, { message: "You can specify a maximum of 5 emails" })
.optional() .optional()
.describe(ALERTS.UPDATE.emails) .describe(ALERTS.UPDATE.emails)
}), }),

View File

@@ -1,7 +1,7 @@
import * as x509 from "@peculiar/x509"; import * as x509 from "@peculiar/x509";
import crypto from "crypto"; import crypto from "crypto";
import { BadRequestError } from "@app/lib/errors"; import { NotFoundError } from "@app/lib/errors";
import { getProjectKmsCertificateKeyId } from "@app/services/project/project-fns"; import { getProjectKmsCertificateKeyId } from "@app/services/project/project-fns";
import { CertKeyAlgorithm, CertStatus } from "../certificate/certificate-types"; import { CertKeyAlgorithm, CertStatus } from "../certificate/certificate-types";
@@ -106,10 +106,10 @@ export const getCaCredentials = async ({
kmsService kmsService
}: TGetCaCredentialsDTO) => { }: TGetCaCredentialsDTO) => {
const ca = await certificateAuthorityDAL.findById(caId); const ca = await certificateAuthorityDAL.findById(caId);
if (!ca) throw new BadRequestError({ message: "CA not found" }); if (!ca) throw new NotFoundError({ message: "CA not found" });
const caSecret = await certificateAuthoritySecretDAL.findOne({ caId }); const caSecret = await certificateAuthoritySecretDAL.findOne({ caId });
if (!caSecret) throw new BadRequestError({ message: "CA secret not found" }); if (!caSecret) throw new NotFoundError({ message: "CA secret not found" });
const keyId = await getProjectKmsCertificateKeyId({ const keyId = await getProjectKmsCertificateKeyId({
projectId: ca.projectId, projectId: ca.projectId,
@@ -158,7 +158,7 @@ export const getCaCertChains = async ({
kmsService kmsService
}: TGetCaCertChainsDTO) => { }: TGetCaCertChainsDTO) => {
const ca = await certificateAuthorityDAL.findById(caId); const ca = await certificateAuthorityDAL.findById(caId);
if (!ca) throw new BadRequestError({ message: "CA not found" }); if (!ca) throw new NotFoundError({ message: "CA not found" });
const keyId = await getProjectKmsCertificateKeyId({ const keyId = await getProjectKmsCertificateKeyId({
projectId: ca.projectId, projectId: ca.projectId,
@@ -205,7 +205,7 @@ export const getCaCertChain = async ({
kmsService kmsService
}: TGetCaCertChainDTO) => { }: TGetCaCertChainDTO) => {
const caCert = await certificateAuthorityCertDAL.findById(caCertId); const caCert = await certificateAuthorityCertDAL.findById(caCertId);
if (!caCert) throw new BadRequestError({ message: "CA certificate not found" }); if (!caCert) throw new NotFoundError({ message: "CA certificate not found" });
const ca = await certificateAuthorityDAL.findById(caCert.caId); const ca = await certificateAuthorityDAL.findById(caCert.caId);
const keyId = await getProjectKmsCertificateKeyId({ const keyId = await getProjectKmsCertificateKeyId({
@@ -249,7 +249,7 @@ export const rebuildCaCrl = async ({
kmsService kmsService
}: TRebuildCaCrlDTO) => { }: TRebuildCaCrlDTO) => {
const ca = await certificateAuthorityDAL.findById(caId); const ca = await certificateAuthorityDAL.findById(caId);
if (!ca) throw new BadRequestError({ message: "CA not found" }); if (!ca) throw new NotFoundError({ message: "CA not found" });
const caSecret = await certificateAuthoritySecretDAL.findOne({ caId: ca.id }); const caSecret = await certificateAuthoritySecretDAL.findOne({ caId: ca.id });

View File

@@ -0,0 +1,48 @@
import { logger } from "@app/lib/logger";
import { QueueJobs, QueueName, TQueueServiceFactory } from "@app/queue";
import { TPkiAlertServiceFactory } from "@app/services/pki-alert/pki-alert-service";
type TDailyExpiringPkiItemAlertQueueServiceFactoryDep = {
queueService: TQueueServiceFactory;
pkiAlertService: Pick<TPkiAlertServiceFactory, "sendPkiItemExpiryNotices">;
};
export type TDailyExpiringPkiItemAlertQueueServiceFactory = ReturnType<
typeof dailyExpiringPkiItemAlertQueueServiceFactory
>;
export const dailyExpiringPkiItemAlertQueueServiceFactory = ({
queueService,
pkiAlertService
}: TDailyExpiringPkiItemAlertQueueServiceFactoryDep) => {
queueService.start(QueueName.DailyExpiringPkiItemAlert, async () => {
logger.info(`${QueueName.DailyExpiringPkiItemAlert}: queue task started`);
await pkiAlertService.sendPkiItemExpiryNotices();
logger.info(`${QueueName.DailyExpiringPkiItemAlert}: queue task completed`);
});
// we do a repeat cron job in utc timezone at 12 Midnight each day
const startSendingAlerts = async () => {
// clear previous job
await queueService.stopRepeatableJob(
QueueName.DailyExpiringPkiItemAlert,
QueueJobs.DailyExpiringPkiItemAlert,
{ pattern: "0 0 * * *", utc: true },
QueueName.DailyExpiringPkiItemAlert // just a job id
);
await queueService.queue(QueueName.DailyExpiringPkiItemAlert, QueueJobs.DailyExpiringPkiItemAlert, undefined, {
delay: 5000,
jobId: QueueName.DailyExpiringPkiItemAlert,
repeat: { pattern: "0 0 * * *", utc: true }
});
};
queueService.listen(QueueName.DailyExpiringPkiItemAlert, "failed", (_, err) => {
logger.error(err, `${QueueName.DailyExpiringPkiItemAlert}: Expiring PKI item alert failed`);
});
return {
startSendingAlerts
};
};

View File

@@ -12,8 +12,11 @@ import { TPkiAlertDALFactory } from "./pki-alert-dal";
import { TCreateAlertDTO, TDeleteAlertDTO, TGetAlertByIdDTO, TUpdateAlertDTO } from "./pki-alert-types"; import { TCreateAlertDTO, TDeleteAlertDTO, TGetAlertByIdDTO, TUpdateAlertDTO } from "./pki-alert-types";
type TPkiAlertServiceFactoryDep = { type TPkiAlertServiceFactoryDep = {
pkiAlertDAL: TPkiAlertDALFactory; pkiAlertDAL: Pick<
pkiCollectionDAL: TPkiCollectionDALFactory; TPkiAlertDALFactory,
"create" | "findById" | "updateById" | "deleteById" | "getExpiringPkiCollectionItemsForAlerting"
>;
pkiCollectionDAL: Pick<TPkiCollectionDALFactory, "findById">;
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">; permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
smtpService: Pick<TSmtpService, "sendMail">; smtpService: Pick<TSmtpService, "sendMail">;
}; };

View File

@@ -81,7 +81,7 @@ export const pkiCollectionItemDALFactory = (db: TDbClient) => {
return parseInt((count as unknown as CountResult).count || "0", 10); return parseInt((count as unknown as CountResult).count || "0", 10);
} catch (error) { } catch (error) {
throw new DatabaseError({ error, name: "Count all project certificates" }); throw new DatabaseError({ error, name: "Count all PKI collection items" });
} }
}; };

View File

@@ -22,10 +22,13 @@ import {
} from "./pki-collection-types"; } from "./pki-collection-types";
type TPkiCollectionServiceFactoryDep = { type TPkiCollectionServiceFactoryDep = {
pkiCollectionDAL: TPkiCollectionDALFactory; // TODO: Pick pkiCollectionDAL: Pick<TPkiCollectionDALFactory, "create" | "findById" | "updateById" | "deleteById">;
pkiCollectionItemDAL: TPkiCollectionItemDALFactory; pkiCollectionItemDAL: Pick<
certificateAuthorityDAL: TCertificateAuthorityDALFactory; TPkiCollectionItemDALFactory,
certificateDAL: TCertificateDALFactory; "findOne" | "create" | "deleteById" | "findPkiCollectionItems" | "countItemsInPkiCollection"
>;
certificateAuthorityDAL: Pick<TCertificateAuthorityDALFactory, "find" | "findOne">;
certificateDAL: Pick<TCertificateDALFactory, "find">;
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">; permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
}; };
@@ -139,7 +142,7 @@ export const pkiCollectionServiceFactory = ({
); );
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create, ProjectPermissionActions.Delete,
ProjectPermissionSub.PkiCollections ProjectPermissionSub.PkiCollections
); );
pkiCollection = await pkiCollectionDAL.deleteById(collectionId); pkiCollection = await pkiCollectionDAL.deleteById(collectionId);

View File

@@ -2,7 +2,6 @@ import { TAuditLogDALFactory } from "@app/ee/services/audit-log/audit-log-dal";
import { TSnapshotDALFactory } from "@app/ee/services/secret-snapshot/snapshot-dal"; import { TSnapshotDALFactory } from "@app/ee/services/secret-snapshot/snapshot-dal";
import { logger } from "@app/lib/logger"; import { logger } from "@app/lib/logger";
import { QueueJobs, QueueName, TQueueServiceFactory } from "@app/queue"; import { QueueJobs, QueueName, TQueueServiceFactory } from "@app/queue";
import { TPkiAlertServiceFactory } from "@app/services/pki-alert/pki-alert-service";
import { TIdentityAccessTokenDALFactory } from "../identity-access-token/identity-access-token-dal"; import { TIdentityAccessTokenDALFactory } from "../identity-access-token/identity-access-token-dal";
import { TIdentityUaClientSecretDALFactory } from "../identity-ua/identity-ua-client-secret-dal"; import { TIdentityUaClientSecretDALFactory } from "../identity-ua/identity-ua-client-secret-dal";
@@ -21,7 +20,6 @@ type TDailyResourceCleanUpQueueServiceFactoryDep = {
snapshotDAL: Pick<TSnapshotDALFactory, "pruneExcessSnapshots">; snapshotDAL: Pick<TSnapshotDALFactory, "pruneExcessSnapshots">;
secretSharingDAL: Pick<TSecretSharingDALFactory, "pruneExpiredSharedSecrets">; secretSharingDAL: Pick<TSecretSharingDALFactory, "pruneExpiredSharedSecrets">;
queueService: TQueueServiceFactory; queueService: TQueueServiceFactory;
pkiAlertService: Pick<TPkiAlertServiceFactory, "sendPkiItemExpiryNotices">;
}; };
export type TDailyResourceCleanUpQueueServiceFactory = ReturnType<typeof dailyResourceCleanUpQueueServiceFactory>; export type TDailyResourceCleanUpQueueServiceFactory = ReturnType<typeof dailyResourceCleanUpQueueServiceFactory>;
@@ -29,7 +27,6 @@ export type TDailyResourceCleanUpQueueServiceFactory = ReturnType<typeof dailyRe
export const dailyResourceCleanUpQueueServiceFactory = ({ export const dailyResourceCleanUpQueueServiceFactory = ({
auditLogDAL, auditLogDAL,
queueService, queueService,
pkiAlertService,
snapshotDAL, snapshotDAL,
secretVersionDAL, secretVersionDAL,
secretFolderVersionDAL, secretFolderVersionDAL,
@@ -48,7 +45,6 @@ export const dailyResourceCleanUpQueueServiceFactory = ({
await secretVersionDAL.pruneExcessVersions(); await secretVersionDAL.pruneExcessVersions();
await secretVersionV2DAL.pruneExcessVersions(); await secretVersionV2DAL.pruneExcessVersions();
await secretFolderVersionDAL.pruneExcessVersions(); await secretFolderVersionDAL.pruneExcessVersions();
await pkiAlertService.sendPkiItemExpiryNotices();
logger.info(`${QueueName.DailyResourceCleanUp}: queue task completed`); logger.info(`${QueueName.DailyResourceCleanUp}: queue task completed`);
}); });

View File

@@ -186,32 +186,34 @@ export const CertificateModal = ({ popUp, handlePopUpToggle }: Props) => {
</FormControl> </FormControl>
)} )}
/> />
<Controller {!cert && (
control={control} <Controller
name="collectionId" control={control}
render={({ field: { onChange, ...field }, fieldState: { error } }) => ( name="collectionId"
<FormControl render={({ field: { onChange, ...field }, fieldState: { error } }) => (
label="Certificate Collection (Optional)" <FormControl
errorText={error?.message} label="Certificate Collection (Optional)"
isError={Boolean(error)} errorText={error?.message}
className="mt-4" isError={Boolean(error)}
> className="mt-4"
<Select
defaultValue={field.value}
{...field}
onValueChange={(e) => onChange(e)}
className="w-full"
isDisabled={Boolean(cert)}
> >
{(data?.collections || []).map(({ id, name }) => ( <Select
<SelectItem value={id} key={`pki-collection-${id}`}> defaultValue={field.value}
{name} {...field}
</SelectItem> onValueChange={(e) => onChange(e)}
))} className="w-full"
</Select> isDisabled={Boolean(cert)}
</FormControl> >
)} {(data?.collections || []).map(({ id, name }) => (
/> <SelectItem value={id} key={`pki-collection-${id}`}>
{name}
</SelectItem>
))}
</Select>
</FormControl>
)}
/>
)}
<Controller <Controller
control={control} control={control}
defaultValue="" defaultValue=""

View File

@@ -1,5 +1,5 @@
import { useState } from "react"; import { useState } from "react";
import { faBoxesStacked, faTrash } from "@fortawesome/free-solid-svg-icons"; import { faBoxesStacked, faXmark } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { format } from "date-fns"; import { format } from "date-fns";
@@ -85,7 +85,7 @@ export const PkiCollectionItemsTable = ({ collectionId, type, handlePopUpOpen }:
}); });
}} }}
> >
<FontAwesomeIcon icon={faTrash} /> <FontAwesomeIcon icon={faXmark} />
</IconButton> </IconButton>
</Tooltip> </Tooltip>
)} )}