mirror of
https://github.com/Infisical/infisical.git
synced 2026-01-09 15:38:03 -05:00
improvement: add webhook triggered audit log
This commit is contained in:
@@ -44,6 +44,7 @@ import {
|
||||
TSecretSyncRaw,
|
||||
TUpdateSecretSyncDTO
|
||||
} 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 { KmipPermission } from "../kmip/kmip-enum";
|
||||
@@ -206,6 +207,7 @@ export enum EventType {
|
||||
CREATE_WEBHOOK = "create-webhook",
|
||||
UPDATE_WEBHOOK_STATUS = "update-webhook-status",
|
||||
DELETE_WEBHOOK = "delete-webhook",
|
||||
WEBHOOK_TRIGGERED = "webhook-triggered",
|
||||
GET_SECRET_IMPORTS = "get-secret-imports",
|
||||
GET_SECRET_IMPORT = "get-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 {
|
||||
type: EventType.GET_SECRET_IMPORTS;
|
||||
metadata: {
|
||||
@@ -3221,6 +3231,7 @@ export type Event =
|
||||
| CreateWebhookEvent
|
||||
| UpdateWebhookStatusEvent
|
||||
| DeleteWebhookEvent
|
||||
| WebhookTriggeredEvent
|
||||
| GetSecretImportsEvent
|
||||
| GetSecretImportEvent
|
||||
| CreateSecretImportEvent
|
||||
|
||||
@@ -1581,6 +1581,7 @@ export const secretQueueFactory = ({
|
||||
projectDAL,
|
||||
webhookDAL,
|
||||
event: job.data,
|
||||
auditLogService,
|
||||
secretManagerDecryptor: (value) => secretManagerDecryptor({ cipherTextBlob: value }).toString()
|
||||
});
|
||||
});
|
||||
|
||||
@@ -4,9 +4,12 @@ import { AxiosError } from "axios";
|
||||
import picomatch from "picomatch";
|
||||
|
||||
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 { NotFoundError } from "@app/lib/errors";
|
||||
import { logger } from "@app/lib/logger";
|
||||
import { ActorType } from "@app/services/auth/auth-type";
|
||||
|
||||
import { TProjectDALFactory } from "../project/project-dal";
|
||||
import { TProjectEnvDALFactory } from "../project-env/project-env-dal";
|
||||
@@ -163,6 +166,7 @@ export type TFnTriggerWebhookDTO = {
|
||||
projectEnvDAL: Pick<TProjectEnvDALFactory, "findOne">;
|
||||
projectDAL: Pick<TProjectDALFactory, "findById">;
|
||||
secretManagerDecryptor: (value: Buffer) => string;
|
||||
auditLogService: Pick<TAuditLogServiceFactory, "createAuditLog">;
|
||||
};
|
||||
|
||||
// this is reusable function
|
||||
@@ -175,7 +179,8 @@ export const fnTriggerWebhook = async ({
|
||||
projectEnvDAL,
|
||||
event,
|
||||
secretManagerDecryptor,
|
||||
projectDAL
|
||||
projectDAL,
|
||||
auditLogService
|
||||
}: TFnTriggerWebhookDTO) => {
|
||||
const webhooks = await webhookDAL.findAllWebhooks(projectId, environment);
|
||||
const toBeTriggeredHooks = webhooks.filter(
|
||||
@@ -200,16 +205,43 @@ export const fnTriggerWebhook = async ({
|
||||
})
|
||||
);
|
||||
|
||||
const eventPayloads: WebhookTriggeredEvent["metadata"][] = [];
|
||||
// filter hooks by status
|
||||
const successWebhooks = webhooksTriggered
|
||||
.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
|
||||
.filter(({ status }) => status === "rejected")
|
||||
.map((data, i) => ({
|
||||
id: toBeTriggeredHooks[i].id,
|
||||
error: data.status === "rejected" ? (data.reason as AxiosError).message : ""
|
||||
}));
|
||||
.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,
|
||||
error: data.status === "rejected" ? (data.reason as AxiosError).message : ""
|
||||
};
|
||||
});
|
||||
|
||||
await webhookDAL.transaction(async (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");
|
||||
};
|
||||
|
||||
@@ -27,7 +27,7 @@ If the signature in the header matches the signature that you generated, then yo
|
||||
|
||||
```json
|
||||
{
|
||||
"event": "secret.modified",
|
||||
"event": "secrets.modified",
|
||||
"project": {
|
||||
"workspaceId": "the workspace id",
|
||||
"environment": "project environment",
|
||||
|
||||
@@ -52,6 +52,7 @@ export const eventToNameMap: { [K in EventType]: string } = {
|
||||
[EventType.CREATE_WEBHOOK]: "Create webhook",
|
||||
[EventType.UPDATE_WEBHOOK_STATUS]: "Update webhook status",
|
||||
[EventType.DELETE_WEBHOOK]: "Delete webhook",
|
||||
[EventType.WEBHOOK_TRIGGERED]: "Webhook event",
|
||||
[EventType.GET_SECRET_IMPORTS]: "List secret imports",
|
||||
[EventType.CREATE_SECRET_IMPORT]: "Create secret import",
|
||||
[EventType.UPDATE_SECRET_IMPORT]: "Update secret import",
|
||||
|
||||
@@ -65,6 +65,7 @@ export enum EventType {
|
||||
CREATE_WEBHOOK = "create-webhook",
|
||||
UPDATE_WEBHOOK_STATUS = "update-webhook-status",
|
||||
DELETE_WEBHOOK = "delete-webhook",
|
||||
WEBHOOK_TRIGGERED = "webhook-triggered",
|
||||
GET_SECRET_IMPORTS = "get-secret-imports",
|
||||
CREATE_SECRET_IMPORT = "create-secret-import",
|
||||
UPDATE_SECRET_IMPORT = "update-secret-import",
|
||||
|
||||
@@ -194,6 +194,7 @@ export const WebhooksTab = withProjectPermission(
|
||||
<Tr key={id}>
|
||||
<Td className="max-w-xs overflow-hidden text-ellipsis hover:overflow-auto hover:break-all">
|
||||
{url}
|
||||
<p className="text-xs text-mineshaft-400">{id}</p>
|
||||
</Td>
|
||||
<Td>{environment.slug}</Td>
|
||||
<Td>{secretPath}</Td>
|
||||
|
||||
Reference in New Issue
Block a user