mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-14 03:29:35 +00:00
Finish docs for pki alerting + expose endpoints
This commit is contained in:
@@ -38,6 +38,7 @@ export async function up(knex: Knex): Promise<void> {
|
||||
t.string("name").notNullable();
|
||||
t.integer("alertBeforeDays").notNullable();
|
||||
t.string("recipientEmails").notNullable();
|
||||
t.unique(["name", "projectId"]);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1144,6 +1144,63 @@ export const CERTIFICATES = {
|
||||
}
|
||||
};
|
||||
|
||||
export const ALERTS = {
|
||||
CREATE: {
|
||||
projectId: "The ID of the project to create the alert in",
|
||||
pkiCollectionId: "The ID of the PKI collection to bind to the alert",
|
||||
name: "The name of the alert",
|
||||
alertBeforeDays: "The number of days before the certificate expires to trigger the alert",
|
||||
emails: "The email addresses to send the alert email to"
|
||||
},
|
||||
GET: {
|
||||
alertId: "The ID of the alert to get"
|
||||
},
|
||||
UPDATE: {
|
||||
alertId: "The ID of the alert to update",
|
||||
name: "The name of the alert to update to",
|
||||
alertBeforeDays: "The number of days before the certificate expires to trigger the alert to update to",
|
||||
pkiCollectionId: "The ID of the PKI collection to bind to the alert to update to",
|
||||
emails: "The email addresses to send the alert email to update to"
|
||||
},
|
||||
DELETE: {
|
||||
alertId: "The ID of the alert to delete"
|
||||
}
|
||||
};
|
||||
|
||||
export const PKI_COLLECTIONS = {
|
||||
CREATE: {
|
||||
projectId: "The ID of the project to create the PKI collection in",
|
||||
name: "The name of the PKI collection"
|
||||
},
|
||||
GET: {
|
||||
collectionId: "The ID of the PKI collection to get"
|
||||
},
|
||||
UPDATE: {
|
||||
collectionId: "The ID of the PKI collection to update",
|
||||
name: "The name of the PKI collection to update to"
|
||||
},
|
||||
DELETE: {
|
||||
collectionId: "The ID of the PKI collection to delete"
|
||||
},
|
||||
LIST_ITEMS: {
|
||||
collectionId: "The ID of the PKI collection to list items from",
|
||||
type: "The type of the PKI collection item to list",
|
||||
offset: "The offset to start from",
|
||||
limit: "The number of items to return"
|
||||
},
|
||||
ADD_ITEM: {
|
||||
collectionId: "The ID of the PKI collection to add the item to",
|
||||
type: "The type of the PKI collection item to add",
|
||||
itemId: "The resource ID of the PKI collection item to add"
|
||||
},
|
||||
DELETE_ITEM: {
|
||||
collectionId: "The ID of the PKI collection to delete the item from",
|
||||
collectionItemId: "The ID of the PKI collection item to delete",
|
||||
type: "The type of the deleted PKI collection item",
|
||||
itemId: "The resource ID of the deleted PKI collection item"
|
||||
}
|
||||
};
|
||||
|
||||
export const PROJECT_ROLE = {
|
||||
CREATE: {
|
||||
projectSlug: "Slug of the project to create the role for.",
|
||||
|
||||
@@ -639,7 +639,8 @@ export const registerRoutes = async (
|
||||
const pkiAlertService = pkiAlertServiceFactory({
|
||||
pkiAlertDAL,
|
||||
pkiCollectionDAL,
|
||||
permissionService
|
||||
permissionService,
|
||||
smtpService
|
||||
});
|
||||
|
||||
const pkiCollectionService = pkiCollectionServiceFactory({
|
||||
@@ -1049,6 +1050,7 @@ export const registerRoutes = async (
|
||||
const dailyResourceCleanUp = dailyResourceCleanUpQueueServiceFactory({
|
||||
auditLogDAL,
|
||||
queueService,
|
||||
pkiAlertService,
|
||||
secretVersionDAL,
|
||||
secretFolderVersionDAL: folderVersionDAL,
|
||||
snapshotDAL,
|
||||
|
||||
@@ -2,6 +2,7 @@ import { z } from "zod";
|
||||
|
||||
import { PkiAlertsSchema } from "@app/db/schemas";
|
||||
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
||||
import { ALERTS } from "@app/lib/api-docs";
|
||||
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||
import { AuthMode } from "@app/services/auth/auth-type";
|
||||
@@ -17,11 +18,11 @@ export const registerPkiAlertRouter = async (server: FastifyZodProvider) => {
|
||||
schema: {
|
||||
description: "Create PKI alert",
|
||||
body: z.object({
|
||||
projectId: z.string().trim(),
|
||||
pkiCollectionId: z.string().trim(),
|
||||
name: z.string().trim(),
|
||||
alertBeforeDays: z.number(),
|
||||
emails: z.array(z.string())
|
||||
projectId: z.string().trim().describe(ALERTS.CREATE.projectId),
|
||||
pkiCollectionId: z.string().trim().describe(ALERTS.CREATE.pkiCollectionId),
|
||||
name: z.string().trim().describe(ALERTS.CREATE.name),
|
||||
alertBeforeDays: z.number().describe(ALERTS.CREATE.alertBeforeDays),
|
||||
emails: z.array(z.string().trim().email({ message: "Invalid email address" })).describe(ALERTS.CREATE.emails)
|
||||
}),
|
||||
response: {
|
||||
200: PkiAlertsSchema
|
||||
@@ -65,7 +66,7 @@ export const registerPkiAlertRouter = async (server: FastifyZodProvider) => {
|
||||
schema: {
|
||||
description: "Get PKI alert",
|
||||
params: z.object({
|
||||
alertId: z.string().trim()
|
||||
alertId: z.string().trim().describe(ALERTS.GET.alertId)
|
||||
}),
|
||||
response: {
|
||||
200: PkiAlertsSchema
|
||||
@@ -105,13 +106,16 @@ export const registerPkiAlertRouter = async (server: FastifyZodProvider) => {
|
||||
schema: {
|
||||
description: "Update PKI alert",
|
||||
params: z.object({
|
||||
alertId: z.string().trim()
|
||||
alertId: z.string().trim().describe(ALERTS.UPDATE.alertId)
|
||||
}),
|
||||
body: z.object({
|
||||
name: z.string().trim().optional(),
|
||||
alertBeforeDays: z.number().optional(),
|
||||
pkiCollectionId: z.string().trim().optional(),
|
||||
emails: z.array(z.string()).optional()
|
||||
name: z.string().trim().optional().describe(ALERTS.UPDATE.name),
|
||||
alertBeforeDays: z.number().optional().describe(ALERTS.UPDATE.alertBeforeDays),
|
||||
pkiCollectionId: z.string().trim().optional().describe(ALERTS.UPDATE.pkiCollectionId),
|
||||
emails: z
|
||||
.array(z.string().trim().email({ message: "Invalid email address" }))
|
||||
.optional()
|
||||
.describe(ALERTS.UPDATE.emails)
|
||||
}),
|
||||
response: {
|
||||
200: PkiAlertsSchema
|
||||
@@ -156,7 +160,7 @@ export const registerPkiAlertRouter = async (server: FastifyZodProvider) => {
|
||||
schema: {
|
||||
description: "Delete PKI alert",
|
||||
params: z.object({
|
||||
alertId: z.string().trim()
|
||||
alertId: z.string().trim().describe(ALERTS.DELETE.alertId)
|
||||
}),
|
||||
response: {
|
||||
200: PkiAlertsSchema
|
||||
|
||||
@@ -2,6 +2,7 @@ import { z } from "zod";
|
||||
|
||||
import { PkiCollectionItemsSchema, PkiCollectionsSchema } from "@app/db/schemas";
|
||||
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
||||
import { PKI_COLLECTIONS } from "@app/lib/api-docs";
|
||||
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||
import { AuthMode } from "@app/services/auth/auth-type";
|
||||
@@ -18,8 +19,8 @@ export const registerPkiCollectionRouter = async (server: FastifyZodProvider) =>
|
||||
schema: {
|
||||
description: "Create PKI collection",
|
||||
body: z.object({
|
||||
projectId: z.string().trim(),
|
||||
name: z.string().trim()
|
||||
projectId: z.string().trim().describe(PKI_COLLECTIONS.CREATE.projectId),
|
||||
name: z.string().trim().describe(PKI_COLLECTIONS.CREATE.name)
|
||||
}),
|
||||
response: {
|
||||
200: PkiCollectionsSchema
|
||||
@@ -60,7 +61,7 @@ export const registerPkiCollectionRouter = async (server: FastifyZodProvider) =>
|
||||
schema: {
|
||||
description: "Get PKI collection",
|
||||
params: z.object({
|
||||
collectionId: z.string().trim()
|
||||
collectionId: z.string().trim().describe(PKI_COLLECTIONS.GET.collectionId)
|
||||
}),
|
||||
response: {
|
||||
200: PkiCollectionsSchema
|
||||
@@ -100,10 +101,10 @@ export const registerPkiCollectionRouter = async (server: FastifyZodProvider) =>
|
||||
schema: {
|
||||
description: "Update PKI collection",
|
||||
params: z.object({
|
||||
collectionId: z.string().trim()
|
||||
collectionId: z.string().trim().describe(PKI_COLLECTIONS.UPDATE.collectionId)
|
||||
}),
|
||||
body: z.object({
|
||||
name: z.string().trim().optional()
|
||||
name: z.string().trim().optional().describe(PKI_COLLECTIONS.UPDATE.name)
|
||||
}),
|
||||
response: {
|
||||
200: PkiCollectionsSchema
|
||||
@@ -145,7 +146,7 @@ export const registerPkiCollectionRouter = async (server: FastifyZodProvider) =>
|
||||
schema: {
|
||||
description: "Delete PKI collection",
|
||||
params: z.object({
|
||||
collectionId: z.string().trim()
|
||||
collectionId: z.string().trim().describe(PKI_COLLECTIONS.DELETE.collectionId)
|
||||
}),
|
||||
response: {
|
||||
200: PkiCollectionsSchema
|
||||
@@ -185,18 +186,22 @@ export const registerPkiCollectionRouter = async (server: FastifyZodProvider) =>
|
||||
schema: {
|
||||
description: "Get items in PKI collection",
|
||||
params: z.object({
|
||||
collectionId: z.string().trim()
|
||||
collectionId: z.string().trim().describe(PKI_COLLECTIONS.LIST_ITEMS.collectionId)
|
||||
}),
|
||||
querystring: z.object({
|
||||
offset: z.coerce.number().min(0).max(100).default(0),
|
||||
limit: z.coerce.number().min(1).max(100).default(25)
|
||||
type: z.nativeEnum(PkiItemType).optional().describe(PKI_COLLECTIONS.LIST_ITEMS.type),
|
||||
offset: z.coerce.number().min(0).max(100).default(0).describe(PKI_COLLECTIONS.LIST_ITEMS.offset),
|
||||
limit: z.coerce.number().min(1).max(100).default(25).describe(PKI_COLLECTIONS.LIST_ITEMS.limit)
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
collectionItems: z.array(
|
||||
PkiCollectionItemsSchema.omit({ caId: true, certId: true }).extend({
|
||||
type: z.nativeEnum(PkiItemType),
|
||||
itemId: z.string().trim()
|
||||
itemId: z.string().trim(),
|
||||
notBefore: z.date(),
|
||||
notAfter: z.date(),
|
||||
friendlyName: z.string().trim()
|
||||
})
|
||||
),
|
||||
totalCount: z.number()
|
||||
@@ -242,16 +247,16 @@ export const registerPkiCollectionRouter = async (server: FastifyZodProvider) =>
|
||||
schema: {
|
||||
description: "Add item to PKI collection",
|
||||
params: z.object({
|
||||
collectionId: z.string().trim()
|
||||
collectionId: z.string().trim().describe(PKI_COLLECTIONS.ADD_ITEM.collectionId)
|
||||
}),
|
||||
body: z.object({
|
||||
type: z.nativeEnum(PkiItemType),
|
||||
itemId: z.string().trim()
|
||||
type: z.nativeEnum(PkiItemType).describe(PKI_COLLECTIONS.ADD_ITEM.type),
|
||||
itemId: z.string().trim().describe(PKI_COLLECTIONS.ADD_ITEM.itemId)
|
||||
}),
|
||||
response: {
|
||||
200: PkiCollectionItemsSchema.omit({ caId: true, certId: true }).extend({
|
||||
type: z.nativeEnum(PkiItemType),
|
||||
itemId: z.string().trim()
|
||||
type: z.nativeEnum(PkiItemType).describe(PKI_COLLECTIONS.ADD_ITEM.type),
|
||||
itemId: z.string().trim().describe(PKI_COLLECTIONS.ADD_ITEM.itemId)
|
||||
})
|
||||
}
|
||||
},
|
||||
@@ -285,7 +290,7 @@ export const registerPkiCollectionRouter = async (server: FastifyZodProvider) =>
|
||||
|
||||
server.route({
|
||||
method: "DELETE",
|
||||
url: "/:collectionId/items/:itemId",
|
||||
url: "/:collectionId/items/:collectionItemId",
|
||||
config: {
|
||||
rateLimit: writeLimit
|
||||
},
|
||||
@@ -293,20 +298,20 @@ export const registerPkiCollectionRouter = async (server: FastifyZodProvider) =>
|
||||
schema: {
|
||||
description: "Remove item from PKI collection",
|
||||
params: z.object({
|
||||
collectionId: z.string().trim(),
|
||||
itemId: z.string().trim()
|
||||
collectionId: z.string().trim().describe(PKI_COLLECTIONS.DELETE_ITEM.collectionId),
|
||||
collectionItemId: z.string().trim().describe(PKI_COLLECTIONS.DELETE_ITEM.collectionItemId)
|
||||
}),
|
||||
response: {
|
||||
200: PkiCollectionItemsSchema.omit({ caId: true, certId: true }).extend({
|
||||
type: z.nativeEnum(PkiItemType),
|
||||
itemId: z.string().trim()
|
||||
type: z.nativeEnum(PkiItemType).describe(PKI_COLLECTIONS.DELETE_ITEM.type),
|
||||
itemId: z.string().trim().describe(PKI_COLLECTIONS.DELETE_ITEM.itemId)
|
||||
})
|
||||
}
|
||||
},
|
||||
handler: async (req) => {
|
||||
const { pkiCollection, pkiCollectionItem } = await server.services.pkiCollection.removeItemFromPkiCollection({
|
||||
collectionId: req.params.collectionId,
|
||||
itemId: req.params.itemId,
|
||||
itemId: req.params.collectionItemId,
|
||||
actor: req.permission.type,
|
||||
actorId: req.permission.id,
|
||||
actorAuthMethod: req.permission.authMethod,
|
||||
|
||||
@@ -1,12 +1,84 @@
|
||||
import { TDbClient } from "@app/db";
|
||||
import { TableName } from "@app/db/schemas";
|
||||
import { DatabaseError } from "@app/lib/errors";
|
||||
import { ormify } from "@app/lib/knex";
|
||||
|
||||
import { PkiItemType } from "../pki-collection/pki-collection-types";
|
||||
|
||||
export type TPkiAlertDALFactory = ReturnType<typeof pkiAlertDALFactory>;
|
||||
|
||||
export const pkiAlertDALFactory = (db: TDbClient) => {
|
||||
const pkiAlertOrm = ormify(db, TableName.PkiAlert);
|
||||
|
||||
const getExpiringPkiCollectionItemsForAlerting = async () => {
|
||||
try {
|
||||
type AlertItem = {
|
||||
type: PkiItemType;
|
||||
id: string; // id of the CA or certificate
|
||||
expiryDate: Date;
|
||||
serialNumber: string;
|
||||
friendlyName: string;
|
||||
pkiCollectionId: string;
|
||||
alertId: string;
|
||||
alertName: string;
|
||||
alertBeforeDays: number;
|
||||
recipientEmails: string;
|
||||
};
|
||||
|
||||
// gets CAs and certificates as part of PKI collection items
|
||||
const combinedQuery = db
|
||||
.replicaNode()
|
||||
.select(
|
||||
db.raw("? as type", [PkiItemType.CA]),
|
||||
`${PkiItemType.CA}.id`,
|
||||
`${PkiItemType.CA}.notAfter as expiryDate`,
|
||||
`${PkiItemType.CA}.serialNumber`,
|
||||
`${PkiItemType.CA}.friendlyName`,
|
||||
"pci.pkiCollectionId"
|
||||
)
|
||||
.from(`${TableName.CertificateAuthority} as ${PkiItemType.CA}`)
|
||||
.join(`${TableName.PkiCollectionItem} as pci`, `${PkiItemType.CA}.id`, "pci.caId")
|
||||
.unionAll((qb) => {
|
||||
void qb
|
||||
.select(
|
||||
db.raw("? as type", [PkiItemType.CERTIFICATE]),
|
||||
`${PkiItemType.CERTIFICATE}.id`,
|
||||
`${PkiItemType.CERTIFICATE}.notAfter as expiryDate`,
|
||||
`${PkiItemType.CERTIFICATE}.serialNumber`,
|
||||
`${PkiItemType.CERTIFICATE}.friendlyName`,
|
||||
"pci.pkiCollectionId"
|
||||
)
|
||||
.from(`${TableName.Certificate} as ${PkiItemType.CERTIFICATE}`)
|
||||
.join(`${TableName.PkiCollectionItem} as pci`, `${PkiItemType.CERTIFICATE}.id`, "pci.certId");
|
||||
});
|
||||
|
||||
/**
|
||||
* Gets alerts to send based on alertBeforeDays on PKI alerts connected to PKI collection items
|
||||
* Note: Results are clamped to 1-day window to avoid sending multiple alerts for the same item
|
||||
*/
|
||||
const alertQuery = db
|
||||
.replicaNode()
|
||||
.select("combined.*", "pa.id as alertId", "pa.name as alertName", "pa.alertBeforeDays", "pa.recipientEmails")
|
||||
.from(db.raw("(?) as combined", [combinedQuery]))
|
||||
.join(`${TableName.PkiAlert} as pa`, "combined.pkiCollectionId", "pa.pkiCollectionId")
|
||||
.whereRaw(
|
||||
`
|
||||
combined."expiryDate" <= CURRENT_TIMESTAMP + (pa."alertBeforeDays" * INTERVAL '1 day')
|
||||
AND combined."expiryDate" > CURRENT_TIMESTAMP + ((pa."alertBeforeDays" - 1) * INTERVAL '1 day')
|
||||
`
|
||||
)
|
||||
.orderBy("combined.expiryDate");
|
||||
|
||||
const results = (await alertQuery) as AlertItem[];
|
||||
|
||||
return results;
|
||||
} catch (error) {
|
||||
throw new DatabaseError({ error, name: "Get expiring PKI collection items for alerting" });
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
getExpiringPkiCollectionItemsForAlerting,
|
||||
...pkiAlertOrm
|
||||
};
|
||||
};
|
||||
|
||||
@@ -3,7 +3,10 @@ import { ForbiddenError } from "@casl/ability";
|
||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
||||
import { NotFoundError, UnauthorizedError } from "@app/lib/errors";
|
||||
import { groupBy } from "@app/lib/fn";
|
||||
import { TPkiCollectionDALFactory } from "@app/services/pki-collection/pki-collection-dal";
|
||||
import { pkiItemTypeToNameMap } from "@app/services/pki-collection/pki-collection-types";
|
||||
import { SmtpTemplates, TSmtpService } from "@app/services/smtp/smtp-service";
|
||||
|
||||
import { TPkiAlertDALFactory } from "./pki-alert-dal";
|
||||
import { TCreateAlertDTO, TDeleteAlertDTO, TGetAlertByIdDTO, TUpdateAlertDTO } from "./pki-alert-types";
|
||||
@@ -12,6 +15,7 @@ type TPkiAlertServiceFactoryDep = {
|
||||
pkiAlertDAL: TPkiAlertDALFactory;
|
||||
pkiCollectionDAL: TPkiCollectionDALFactory;
|
||||
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
|
||||
smtpService: Pick<TSmtpService, "sendMail">;
|
||||
};
|
||||
|
||||
export type TPkiAlertServiceFactory = ReturnType<typeof pkiAlertServiceFactory>;
|
||||
@@ -19,8 +23,42 @@ export type TPkiAlertServiceFactory = ReturnType<typeof pkiAlertServiceFactory>;
|
||||
export const pkiAlertServiceFactory = ({
|
||||
pkiAlertDAL,
|
||||
pkiCollectionDAL,
|
||||
permissionService
|
||||
permissionService,
|
||||
smtpService
|
||||
}: TPkiAlertServiceFactoryDep) => {
|
||||
const sendPkiItemExpiryNotices = async () => {
|
||||
const allAlertItems = await pkiAlertDAL.getExpiringPkiCollectionItemsForAlerting();
|
||||
|
||||
const flattenedResults = allAlertItems.flatMap(({ recipientEmails, ...item }) =>
|
||||
recipientEmails.split(",").map((email) => ({
|
||||
...item,
|
||||
recipientEmail: email.trim()
|
||||
}))
|
||||
);
|
||||
|
||||
const groupedByEmail = groupBy(flattenedResults, (item) => item.recipientEmail);
|
||||
|
||||
for await (const [email, items] of Object.entries(groupedByEmail)) {
|
||||
const groupedByAlert = groupBy(items, (item) => item.alertId);
|
||||
for await (const [, alertItems] of Object.entries(groupedByAlert)) {
|
||||
await smtpService.sendMail({
|
||||
recipients: [email],
|
||||
subjectLine: `Infisical CA/Certificate expiration notice: ${alertItems[0].alertName}`,
|
||||
substitutions: {
|
||||
alertName: alertItems[0].alertName,
|
||||
alertBeforeDays: items[0].alertBeforeDays,
|
||||
items: alertItems.map((alertItem) => ({
|
||||
...alertItem,
|
||||
type: pkiItemTypeToNameMap[alertItem.type],
|
||||
expiryDate: new Date(alertItem.expiryDate).toString()
|
||||
}))
|
||||
},
|
||||
template: SmtpTemplates.PkiExpirationAlert
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const createPkiAlert = async ({
|
||||
projectId,
|
||||
name,
|
||||
@@ -108,7 +146,7 @@ export const pkiAlertServiceFactory = ({
|
||||
name,
|
||||
alertBeforeDays,
|
||||
...(pkiCollectionId && { pkiCollectionId }),
|
||||
...(emails && { recipientEmails: emails.join(",") }) // TODO: standardize recipient emails
|
||||
...(emails && { recipientEmails: emails.join(",") })
|
||||
});
|
||||
|
||||
return alert;
|
||||
@@ -132,6 +170,7 @@ export const pkiAlertServiceFactory = ({
|
||||
};
|
||||
|
||||
return {
|
||||
sendPkiItemExpiryNotices,
|
||||
createPkiAlert,
|
||||
getPkiAlertById,
|
||||
updatePkiAlert,
|
||||
|
||||
@@ -1,13 +1,72 @@
|
||||
import { TDbClient } from "@app/db";
|
||||
import { TableName } from "@app/db/schemas";
|
||||
import { TableName, TPkiCollectionItems } from "@app/db/schemas";
|
||||
import { DatabaseError } from "@app/lib/errors";
|
||||
import { ormify } from "@app/lib/knex";
|
||||
|
||||
import { PkiItemType } from "./pki-collection-types";
|
||||
|
||||
export type TPkiCollectionItemDALFactory = ReturnType<typeof pkiCollectionItemDALFactory>;
|
||||
|
||||
export const pkiCollectionItemDALFactory = (db: TDbClient) => {
|
||||
const pkiCollectionItemOrm = ormify(db, TableName.PkiCollectionItem);
|
||||
|
||||
const findPkiCollectionItems = async ({
|
||||
collectionId,
|
||||
type,
|
||||
offset,
|
||||
limit
|
||||
}: {
|
||||
collectionId: string;
|
||||
type?: PkiItemType;
|
||||
offset?: number;
|
||||
limit?: number;
|
||||
}) => {
|
||||
try {
|
||||
const query = db
|
||||
.replicaNode()(TableName.PkiCollectionItem)
|
||||
.select(
|
||||
"pki_collection_items.*",
|
||||
db.raw(
|
||||
`COALESCE("${TableName.CertificateAuthority}"."notBefore", "${TableName.Certificate}"."notBefore") as "notBefore"`
|
||||
),
|
||||
db.raw(
|
||||
`COALESCE("${TableName.CertificateAuthority}"."notAfter", "${TableName.Certificate}"."notAfter") as "notAfter"`
|
||||
),
|
||||
db.raw(
|
||||
`COALESCE("${TableName.CertificateAuthority}"."friendlyName", "${TableName.Certificate}"."friendlyName") as "friendlyName"`
|
||||
)
|
||||
)
|
||||
.leftJoin(
|
||||
TableName.CertificateAuthority,
|
||||
`${TableName.PkiCollectionItem}.caId`,
|
||||
`${TableName.CertificateAuthority}.id`
|
||||
)
|
||||
.leftJoin(TableName.Certificate, `${TableName.PkiCollectionItem}.certId`, `${TableName.Certificate}.id`)
|
||||
.where((builder) => {
|
||||
void builder.where(`${TableName.PkiCollectionItem}.pkiCollectionId`, collectionId);
|
||||
if (type === PkiItemType.CA) {
|
||||
void builder.whereNull(`${TableName.PkiCollectionItem}.certId`);
|
||||
} else if (type === PkiItemType.CERTIFICATE) {
|
||||
void builder.whereNull(`${TableName.PkiCollectionItem}.caId`);
|
||||
}
|
||||
});
|
||||
|
||||
if (offset) {
|
||||
void query.offset(offset);
|
||||
}
|
||||
if (limit) {
|
||||
void query.limit(limit);
|
||||
}
|
||||
|
||||
void query.orderBy(`${TableName.PkiCollectionItem}.createdAt`, "desc");
|
||||
|
||||
const result = await query;
|
||||
return result as (TPkiCollectionItems & { notAfter: Date; notBefore: Date; friendlyName: string })[];
|
||||
} catch (error) {
|
||||
throw new DatabaseError({ error, name: "Find all PKI collection items" });
|
||||
}
|
||||
};
|
||||
|
||||
const countItemsInPkiCollection = async (collectionId: string) => {
|
||||
try {
|
||||
interface CountResult {
|
||||
@@ -28,6 +87,7 @@ export const pkiCollectionItemDALFactory = (db: TDbClient) => {
|
||||
|
||||
return {
|
||||
...pkiCollectionItemOrm,
|
||||
findPkiCollectionItems,
|
||||
countItemsInPkiCollection
|
||||
};
|
||||
};
|
||||
|
||||
@@ -144,6 +144,7 @@ export const pkiCollectionServiceFactory = ({
|
||||
|
||||
const getPkiCollectionItems = async ({
|
||||
collectionId,
|
||||
type,
|
||||
offset = 0,
|
||||
limit = 25,
|
||||
actorId,
|
||||
@@ -164,16 +165,23 @@ export const pkiCollectionServiceFactory = ({
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.PkiCollections);
|
||||
|
||||
const pkiCollectionItems = await pkiCollectionItemDAL.find(
|
||||
{ pkiCollectionId: collectionId },
|
||||
{ offset, limit, sort: [["createdAt", "desc"]] }
|
||||
);
|
||||
const pkiCollectionItems = await pkiCollectionItemDAL.findPkiCollectionItems({
|
||||
collectionId,
|
||||
type,
|
||||
offset,
|
||||
limit
|
||||
});
|
||||
|
||||
const count = await pkiCollectionItemDAL.countItemsInPkiCollection(collectionId);
|
||||
|
||||
return {
|
||||
pkiCollection,
|
||||
pkiCollectionItems: pkiCollectionItems.map(transformPkiCollectionItem),
|
||||
pkiCollectionItems: pkiCollectionItems.map((p) => ({
|
||||
...transformPkiCollectionItem(p),
|
||||
notBefore: p.notBefore,
|
||||
notAfter: p.notAfter,
|
||||
friendlyName: p.friendlyName
|
||||
})),
|
||||
totalCount: count
|
||||
};
|
||||
};
|
||||
|
||||
@@ -22,8 +22,14 @@ export enum PkiItemType {
|
||||
CA = "ca"
|
||||
}
|
||||
|
||||
export const pkiItemTypeToNameMap: { [K in PkiItemType]: string } = {
|
||||
[PkiItemType.CA]: "CA",
|
||||
[PkiItemType.CERTIFICATE]: "Certificate"
|
||||
};
|
||||
|
||||
export type TGetPkiCollectionItems = {
|
||||
collectionId: string;
|
||||
type?: PkiItemType;
|
||||
offset: number;
|
||||
limit: number;
|
||||
} & Omit<TProjectPermission, "projectId">;
|
||||
|
||||
@@ -2,6 +2,7 @@ import { TAuditLogDALFactory } from "@app/ee/services/audit-log/audit-log-dal";
|
||||
import { TSnapshotDALFactory } from "@app/ee/services/secret-snapshot/snapshot-dal";
|
||||
import { logger } from "@app/lib/logger";
|
||||
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 { TSecretVersionDALFactory } from "../secret/secret-version-dal";
|
||||
@@ -18,6 +19,7 @@ type TDailyResourceCleanUpQueueServiceFactoryDep = {
|
||||
snapshotDAL: Pick<TSnapshotDALFactory, "pruneExcessSnapshots">;
|
||||
secretSharingDAL: Pick<TSecretSharingDALFactory, "pruneExpiredSharedSecrets">;
|
||||
queueService: TQueueServiceFactory;
|
||||
pkiAlertService: Pick<TPkiAlertServiceFactory, "sendPkiItemExpiryNotices">;
|
||||
};
|
||||
|
||||
export type TDailyResourceCleanUpQueueServiceFactory = ReturnType<typeof dailyResourceCleanUpQueueServiceFactory>;
|
||||
@@ -25,6 +27,7 @@ export type TDailyResourceCleanUpQueueServiceFactory = ReturnType<typeof dailyRe
|
||||
export const dailyResourceCleanUpQueueServiceFactory = ({
|
||||
auditLogDAL,
|
||||
queueService,
|
||||
pkiAlertService,
|
||||
snapshotDAL,
|
||||
secretVersionDAL,
|
||||
secretFolderVersionDAL,
|
||||
@@ -41,6 +44,7 @@ export const dailyResourceCleanUpQueueServiceFactory = ({
|
||||
await secretVersionDAL.pruneExcessVersions();
|
||||
await secretVersionV2DAL.pruneExcessVersions();
|
||||
await secretFolderVersionDAL.pruneExcessVersions();
|
||||
await pkiAlertService.sendPkiItemExpiryNotices();
|
||||
logger.info(`${QueueName.DailyResourceCleanUp}: queue task completed`);
|
||||
});
|
||||
|
||||
|
||||
@@ -31,7 +31,8 @@ export enum SmtpTemplates {
|
||||
ResetPassword = "passwordReset.handlebars",
|
||||
SecretLeakIncident = "secretLeakIncident.handlebars",
|
||||
WorkspaceInvite = "workspaceInvitation.handlebars",
|
||||
ScimUserProvisioned = "scimUserProvisioned.handlebars"
|
||||
ScimUserProvisioned = "scimUserProvisioned.handlebars",
|
||||
PkiExpirationAlert = "pkiExpirationAlert.handlebars"
|
||||
}
|
||||
|
||||
export enum SmtpHost {
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta http-equiv="x-ua-compatible" content="ie=edge" />
|
||||
<title>Infisical CA/Certificate expiration notice</title>
|
||||
</head>
|
||||
<body>
|
||||
<p>Hello,</p>
|
||||
<p>This is an automated alert for "{{alertName}}" triggered for CAs/Certificates expiring in
|
||||
{{alertBeforeDays}}
|
||||
days.</p>
|
||||
|
||||
<p>Expiring Items:</p>
|
||||
<ul>
|
||||
{{#each items}}
|
||||
<li>
|
||||
{{type}}:
|
||||
<strong>{{friendlyName}}</strong>
|
||||
<br />Serial Number:
|
||||
{{serialNumber}}
|
||||
<br />Expires On:
|
||||
{{expiryDate}}
|
||||
</li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
|
||||
<p>Please take necessary actions to renew these items before they expire.</p>
|
||||
|
||||
<p>For more details, please log in to your Infisical account and check your PKI management section.</p>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user