improvement: add webhook triggered audit log

This commit is contained in:
Scott Wilson
2025-06-06 16:06:29 -07:00
parent ae00e74c17
commit 219aa3c641
7 changed files with 70 additions and 7 deletions
@@ -44,6 +44,7 @@ import {
TSecretSyncRaw, TSecretSyncRaw,
TUpdateSecretSyncDTO TUpdateSecretSyncDTO
} from "@app/services/secret-sync/secret-sync-types"; } from "@app/services/secret-sync/secret-sync-types";
import { TWebhookPayloads } from "@app/services/webhook/webhook-types";
import { WorkflowIntegration } from "@app/services/workflow-integration/workflow-integration-types"; import { WorkflowIntegration } from "@app/services/workflow-integration/workflow-integration-types";
import { KmipPermission } from "../kmip/kmip-enum"; import { KmipPermission } from "../kmip/kmip-enum";
@@ -206,6 +207,7 @@ export enum EventType {
CREATE_WEBHOOK = "create-webhook", CREATE_WEBHOOK = "create-webhook",
UPDATE_WEBHOOK_STATUS = "update-webhook-status", UPDATE_WEBHOOK_STATUS = "update-webhook-status",
DELETE_WEBHOOK = "delete-webhook", DELETE_WEBHOOK = "delete-webhook",
WEBHOOK_TRIGGERED = "webhook-triggered",
GET_SECRET_IMPORTS = "get-secret-imports", GET_SECRET_IMPORTS = "get-secret-imports",
GET_SECRET_IMPORT = "get-secret-import", GET_SECRET_IMPORT = "get-secret-import",
CREATE_SECRET_IMPORT = "create-secret-import", CREATE_SECRET_IMPORT = "create-secret-import",
@@ -1440,6 +1442,14 @@ interface DeleteWebhookEvent {
}; };
} }
export interface WebhookTriggeredEvent {
type: EventType.WEBHOOK_TRIGGERED;
metadata: {
webhookId: string;
status: string;
} & TWebhookPayloads;
}
interface GetSecretImportsEvent { interface GetSecretImportsEvent {
type: EventType.GET_SECRET_IMPORTS; type: EventType.GET_SECRET_IMPORTS;
metadata: { metadata: {
@@ -3221,6 +3231,7 @@ export type Event =
| CreateWebhookEvent | CreateWebhookEvent
| UpdateWebhookStatusEvent | UpdateWebhookStatusEvent
| DeleteWebhookEvent | DeleteWebhookEvent
| WebhookTriggeredEvent
| GetSecretImportsEvent | GetSecretImportsEvent
| GetSecretImportEvent | GetSecretImportEvent
| CreateSecretImportEvent | CreateSecretImportEvent
@@ -1581,6 +1581,7 @@ export const secretQueueFactory = ({
projectDAL, projectDAL,
webhookDAL, webhookDAL,
event: job.data, event: job.data,
auditLogService,
secretManagerDecryptor: (value) => secretManagerDecryptor({ cipherTextBlob: value }).toString() secretManagerDecryptor: (value) => secretManagerDecryptor({ cipherTextBlob: value }).toString()
}); });
}); });
+52 -4
View File
@@ -4,9 +4,12 @@ import { AxiosError } from "axios";
import picomatch from "picomatch"; import picomatch from "picomatch";
import { TWebhooks } from "@app/db/schemas"; import { TWebhooks } from "@app/db/schemas";
import { TAuditLogServiceFactory } from "@app/ee/services/audit-log/audit-log-service";
import { EventType, WebhookTriggeredEvent } from "@app/ee/services/audit-log/audit-log-types";
import { request } from "@app/lib/config/request"; import { request } from "@app/lib/config/request";
import { NotFoundError } from "@app/lib/errors"; import { NotFoundError } from "@app/lib/errors";
import { logger } from "@app/lib/logger"; import { logger } from "@app/lib/logger";
import { ActorType } from "@app/services/auth/auth-type";
import { TProjectDALFactory } from "../project/project-dal"; import { TProjectDALFactory } from "../project/project-dal";
import { TProjectEnvDALFactory } from "../project-env/project-env-dal"; import { TProjectEnvDALFactory } from "../project-env/project-env-dal";
@@ -163,6 +166,7 @@ export type TFnTriggerWebhookDTO = {
projectEnvDAL: Pick<TProjectEnvDALFactory, "findOne">; projectEnvDAL: Pick<TProjectEnvDALFactory, "findOne">;
projectDAL: Pick<TProjectDALFactory, "findById">; projectDAL: Pick<TProjectDALFactory, "findById">;
secretManagerDecryptor: (value: Buffer) => string; secretManagerDecryptor: (value: Buffer) => string;
auditLogService: Pick<TAuditLogServiceFactory, "createAuditLog">;
}; };
// this is reusable function // this is reusable function
@@ -175,7 +179,8 @@ export const fnTriggerWebhook = async ({
projectEnvDAL, projectEnvDAL,
event, event,
secretManagerDecryptor, secretManagerDecryptor,
projectDAL projectDAL,
auditLogService
}: TFnTriggerWebhookDTO) => { }: TFnTriggerWebhookDTO) => {
const webhooks = await webhookDAL.findAllWebhooks(projectId, environment); const webhooks = await webhookDAL.findAllWebhooks(projectId, environment);
const toBeTriggeredHooks = webhooks.filter( const toBeTriggeredHooks = webhooks.filter(
@@ -200,16 +205,43 @@ export const fnTriggerWebhook = async ({
}) })
); );
const eventPayloads: WebhookTriggeredEvent["metadata"][] = [];
// filter hooks by status // filter hooks by status
const successWebhooks = webhooksTriggered const successWebhooks = webhooksTriggered
.filter(({ status }) => status === "fulfilled") .filter(({ status }) => status === "fulfilled")
.map((_, i) => toBeTriggeredHooks[i].id); .map((_, i) => {
eventPayloads.push({
webhookId: toBeTriggeredHooks[i].id,
type: event.type,
payload: {
type: toBeTriggeredHooks[i].type!,
...event.payload,
projectName
},
status: "success"
} as WebhookTriggeredEvent["metadata"]);
return toBeTriggeredHooks[i].id;
});
const failedWebhooks = webhooksTriggered const failedWebhooks = webhooksTriggered
.filter(({ status }) => status === "rejected") .filter(({ status }) => status === "rejected")
.map((data, i) => ({ .map((data, i) => {
eventPayloads.push({
webhookId: toBeTriggeredHooks[i].id,
type: event.type,
payload: {
type: toBeTriggeredHooks[i].type!,
...event.payload,
projectName
},
status: "failed"
} as WebhookTriggeredEvent["metadata"]);
return {
id: toBeTriggeredHooks[i].id, id: toBeTriggeredHooks[i].id,
error: data.status === "rejected" ? (data.reason as AxiosError).message : "" error: data.status === "rejected" ? (data.reason as AxiosError).message : ""
})); };
});
await webhookDAL.transaction(async (tx) => { await webhookDAL.transaction(async (tx) => {
const env = await projectEnvDAL.findOne({ projectId, slug: environment }, tx); const env = await projectEnvDAL.findOne({ projectId, slug: environment }, tx);
@@ -236,5 +268,21 @@ export const fnTriggerWebhook = async ({
); );
} }
}); });
for (const eventPayload of eventPayloads) {
// eslint-disable-next-line no-await-in-loop
await auditLogService.createAuditLog({
actor: {
type: ActorType.PLATFORM,
metadata: {}
},
projectId,
event: {
type: EventType.WEBHOOK_TRIGGERED,
metadata: eventPayload
}
});
}
logger.info({ environment, secretPath, projectId }, "Secret webhook job ended"); logger.info({ environment, secretPath, projectId }, "Secret webhook job ended");
}; };
+1 -1
View File
@@ -27,7 +27,7 @@ If the signature in the header matches the signature that you generated, then yo
```json ```json
{ {
"event": "secret.modified", "event": "secrets.modified",
"project": { "project": {
"workspaceId": "the workspace id", "workspaceId": "the workspace id",
"environment": "project environment", "environment": "project environment",
@@ -52,6 +52,7 @@ export const eventToNameMap: { [K in EventType]: string } = {
[EventType.CREATE_WEBHOOK]: "Create webhook", [EventType.CREATE_WEBHOOK]: "Create webhook",
[EventType.UPDATE_WEBHOOK_STATUS]: "Update webhook status", [EventType.UPDATE_WEBHOOK_STATUS]: "Update webhook status",
[EventType.DELETE_WEBHOOK]: "Delete webhook", [EventType.DELETE_WEBHOOK]: "Delete webhook",
[EventType.WEBHOOK_TRIGGERED]: "Webhook event",
[EventType.GET_SECRET_IMPORTS]: "List secret imports", [EventType.GET_SECRET_IMPORTS]: "List secret imports",
[EventType.CREATE_SECRET_IMPORT]: "Create secret import", [EventType.CREATE_SECRET_IMPORT]: "Create secret import",
[EventType.UPDATE_SECRET_IMPORT]: "Update secret import", [EventType.UPDATE_SECRET_IMPORT]: "Update secret import",
@@ -65,6 +65,7 @@ export enum EventType {
CREATE_WEBHOOK = "create-webhook", CREATE_WEBHOOK = "create-webhook",
UPDATE_WEBHOOK_STATUS = "update-webhook-status", UPDATE_WEBHOOK_STATUS = "update-webhook-status",
DELETE_WEBHOOK = "delete-webhook", DELETE_WEBHOOK = "delete-webhook",
WEBHOOK_TRIGGERED = "webhook-triggered",
GET_SECRET_IMPORTS = "get-secret-imports", GET_SECRET_IMPORTS = "get-secret-imports",
CREATE_SECRET_IMPORT = "create-secret-import", CREATE_SECRET_IMPORT = "create-secret-import",
UPDATE_SECRET_IMPORT = "update-secret-import", UPDATE_SECRET_IMPORT = "update-secret-import",
@@ -194,6 +194,7 @@ export const WebhooksTab = withProjectPermission(
<Tr key={id}> <Tr key={id}>
<Td className="max-w-xs overflow-hidden text-ellipsis hover:overflow-auto hover:break-all"> <Td className="max-w-xs overflow-hidden text-ellipsis hover:overflow-auto hover:break-all">
{url} {url}
<p className="text-xs text-mineshaft-400">{id}</p>
</Td> </Td>
<Td>{environment.slug}</Td> <Td>{environment.slug}</Td>
<Td>{secretPath}</Td> <Td>{secretPath}</Td>