mirror of
https://github.com/Infisical/infisical.git
synced 2025-03-14 10:27:49 +00:00
Merge pull request #3199 from akhilmhdh/feat/webhook-reminder
Added webhook trigger for secret reminder
This commit is contained in:
@ -503,7 +503,7 @@ export const secretApprovalRequestServiceFactory = ({
|
||||
if (!hasMinApproval && !isSoftEnforcement)
|
||||
throw new BadRequestError({ message: "Doesn't have minimum approvals needed" });
|
||||
|
||||
const { botKey, shouldUseSecretV2Bridge } = await projectBotService.getBotKey(projectId);
|
||||
const { botKey, shouldUseSecretV2Bridge, project } = await projectBotService.getBotKey(projectId);
|
||||
let mergeStatus;
|
||||
if (shouldUseSecretV2Bridge) {
|
||||
// this cycle if for bridged secrets
|
||||
@ -861,7 +861,6 @@ export const secretApprovalRequestServiceFactory = ({
|
||||
|
||||
if (isSoftEnforcement) {
|
||||
const cfg = getConfig();
|
||||
const project = await projectDAL.findProjectById(projectId);
|
||||
const env = await projectEnvDAL.findOne({ id: policy.envId });
|
||||
const requestedByUser = await userDAL.findOne({ id: actorId });
|
||||
const approverUsers = await userDAL.find({
|
||||
|
@ -21,6 +21,7 @@ import {
|
||||
TQueueSecretSyncSyncSecretsByIdDTO,
|
||||
TQueueSendSecretSyncActionFailedNotificationsDTO
|
||||
} from "@app/services/secret-sync/secret-sync-types";
|
||||
import { TWebhookPayloads } from "@app/services/webhook/webhook-types";
|
||||
|
||||
export enum QueueName {
|
||||
SecretRotation = "secret-rotation",
|
||||
@ -107,7 +108,7 @@ export type TQueueJobTypes = {
|
||||
};
|
||||
[QueueName.SecretWebhook]: {
|
||||
name: QueueJobs.SecWebhook;
|
||||
payload: { projectId: string; environment: string; secretPath: string; depth?: number };
|
||||
payload: TWebhookPayloads;
|
||||
};
|
||||
|
||||
[QueueName.AccessTokenStatusUpdate]:
|
||||
|
@ -61,6 +61,7 @@ import { SmtpTemplates, TSmtpService } from "../smtp/smtp-service";
|
||||
import { TUserDALFactory } from "../user/user-dal";
|
||||
import { TWebhookDALFactory } from "../webhook/webhook-dal";
|
||||
import { fnTriggerWebhook } from "../webhook/webhook-fns";
|
||||
import { WebhookEvents } from "../webhook/webhook-types";
|
||||
import { TSecretDALFactory } from "./secret-dal";
|
||||
import { interpolateSecrets } from "./secret-fns";
|
||||
import {
|
||||
@ -623,7 +624,14 @@ export const secretQueueFactory = ({
|
||||
await queueService.queue(
|
||||
QueueName.SecretWebhook,
|
||||
QueueJobs.SecWebhook,
|
||||
{ environment, projectId, secretPath },
|
||||
{
|
||||
type: WebhookEvents.SecretModified,
|
||||
payload: {
|
||||
environment,
|
||||
projectId,
|
||||
secretPath
|
||||
}
|
||||
},
|
||||
{
|
||||
jobId: `secret-webhook-${environment}-${projectId}-${secretPath}`,
|
||||
removeOnFail: { count: 5 },
|
||||
@ -1055,6 +1063,8 @@ export const secretQueueFactory = ({
|
||||
|
||||
const organization = await orgDAL.findOrgByProjectId(projectId);
|
||||
const project = await projectDAL.findById(projectId);
|
||||
const secret = await secretV2BridgeDAL.findById(data.secretId);
|
||||
const [folder] = await folderDAL.findSecretPathByFolderIds(project.id, [secret.folderId]);
|
||||
|
||||
if (!organization) {
|
||||
logger.info(`secretReminderQueue.process: [secretDocument=${data.secretId}] no organization found`);
|
||||
@ -1083,6 +1093,19 @@ export const secretQueueFactory = ({
|
||||
organizationName: organization.name
|
||||
}
|
||||
});
|
||||
|
||||
await queueService.queue(QueueName.SecretWebhook, QueueJobs.SecWebhook, {
|
||||
type: WebhookEvents.SecretReminderExpired,
|
||||
payload: {
|
||||
projectName: project.name,
|
||||
projectId: project.id,
|
||||
secretPath: folder?.path,
|
||||
environment: folder?.environmentSlug || "",
|
||||
reminderNote: data.note,
|
||||
secretName: secret?.key,
|
||||
secretId: data.secretId
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const startSecretV2Migration = async (projectId: string) => {
|
||||
@ -1490,14 +1513,17 @@ export const secretQueueFactory = ({
|
||||
queueService.start(QueueName.SecretWebhook, async (job) => {
|
||||
const { decryptor: secretManagerDecryptor } = await kmsService.createCipherPairWithDataKey({
|
||||
type: KmsDataKey.SecretManager,
|
||||
projectId: job.data.projectId
|
||||
projectId: job.data.payload.projectId
|
||||
});
|
||||
|
||||
await fnTriggerWebhook({
|
||||
...job.data,
|
||||
projectId: job.data.payload.projectId,
|
||||
environment: job.data.payload.environment,
|
||||
secretPath: job.data.payload.secretPath || "/",
|
||||
projectEnvDAL,
|
||||
webhookDAL,
|
||||
projectDAL,
|
||||
webhookDAL,
|
||||
event: job.data,
|
||||
secretManagerDecryptor: (value) => secretManagerDecryptor({ cipherTextBlob: value }).toString()
|
||||
});
|
||||
});
|
||||
|
@ -11,7 +11,7 @@ import { logger } from "@app/lib/logger";
|
||||
import { TProjectDALFactory } from "../project/project-dal";
|
||||
import { TProjectEnvDALFactory } from "../project-env/project-env-dal";
|
||||
import { TWebhookDALFactory } from "./webhook-dal";
|
||||
import { WebhookType } from "./webhook-types";
|
||||
import { TWebhookPayloads, WebhookEvents, WebhookType } from "./webhook-types";
|
||||
|
||||
const WEBHOOK_TRIGGER_TIMEOUT = 15 * 1000;
|
||||
|
||||
@ -54,29 +54,64 @@ export const triggerWebhookRequest = async (
|
||||
return req;
|
||||
};
|
||||
|
||||
export const getWebhookPayload = (
|
||||
eventName: string,
|
||||
details: {
|
||||
workspaceName: string;
|
||||
workspaceId: string;
|
||||
environment: string;
|
||||
secretPath?: string;
|
||||
type?: string | null;
|
||||
export const getWebhookPayload = (event: TWebhookPayloads) => {
|
||||
if (event.type === WebhookEvents.SecretModified) {
|
||||
const { projectName, projectId, environment, secretPath, type } = event.payload;
|
||||
|
||||
switch (type) {
|
||||
case WebhookType.SLACK:
|
||||
return {
|
||||
text: "A secret value has been added or modified.",
|
||||
attachments: [
|
||||
{
|
||||
color: "#E7F256",
|
||||
fields: [
|
||||
{
|
||||
title: "Project",
|
||||
value: projectName,
|
||||
short: false
|
||||
},
|
||||
{
|
||||
title: "Environment",
|
||||
value: environment,
|
||||
short: false
|
||||
},
|
||||
{
|
||||
title: "Secret Path",
|
||||
value: secretPath,
|
||||
short: false
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
case WebhookType.GENERAL:
|
||||
default:
|
||||
return {
|
||||
event: event.type,
|
||||
project: {
|
||||
workspaceId: projectId,
|
||||
projectName,
|
||||
environment,
|
||||
secretPath
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
) => {
|
||||
const { workspaceName, workspaceId, environment, secretPath, type } = details;
|
||||
|
||||
const { projectName, projectId, environment, secretPath, type, reminderNote, secretName } = event.payload;
|
||||
|
||||
switch (type) {
|
||||
case WebhookType.SLACK:
|
||||
return {
|
||||
text: "A secret value has been added or modified.",
|
||||
text: "You have a secret reminder",
|
||||
attachments: [
|
||||
{
|
||||
color: "#E7F256",
|
||||
fields: [
|
||||
{
|
||||
title: "Project",
|
||||
value: workspaceName,
|
||||
value: projectName,
|
||||
short: false
|
||||
},
|
||||
{
|
||||
@ -88,6 +123,16 @@ export const getWebhookPayload = (
|
||||
title: "Secret Path",
|
||||
value: secretPath,
|
||||
short: false
|
||||
},
|
||||
{
|
||||
title: "Secret Name",
|
||||
value: secretName,
|
||||
short: false
|
||||
},
|
||||
{
|
||||
title: "Reminder Note",
|
||||
value: reminderNote,
|
||||
short: false
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -96,11 +141,14 @@ export const getWebhookPayload = (
|
||||
case WebhookType.GENERAL:
|
||||
default:
|
||||
return {
|
||||
event: eventName,
|
||||
event: event.type,
|
||||
project: {
|
||||
workspaceId,
|
||||
workspaceId: projectId,
|
||||
projectName,
|
||||
environment,
|
||||
secretPath
|
||||
secretPath,
|
||||
secretName,
|
||||
reminderNote
|
||||
}
|
||||
};
|
||||
}
|
||||
@ -110,6 +158,7 @@ export type TFnTriggerWebhookDTO = {
|
||||
projectId: string;
|
||||
secretPath: string;
|
||||
environment: string;
|
||||
event: TWebhookPayloads;
|
||||
webhookDAL: Pick<TWebhookDALFactory, "findAllWebhooks" | "transaction" | "update" | "bulkUpdate">;
|
||||
projectEnvDAL: Pick<TProjectEnvDALFactory, "findOne">;
|
||||
projectDAL: Pick<TProjectDALFactory, "findById">;
|
||||
@ -124,8 +173,9 @@ export const fnTriggerWebhook = async ({
|
||||
projectId,
|
||||
webhookDAL,
|
||||
projectEnvDAL,
|
||||
projectDAL,
|
||||
secretManagerDecryptor
|
||||
event,
|
||||
secretManagerDecryptor,
|
||||
projectDAL
|
||||
}: TFnTriggerWebhookDTO) => {
|
||||
const webhooks = await webhookDAL.findAllWebhooks(projectId, environment);
|
||||
const toBeTriggeredHooks = webhooks.filter(
|
||||
@ -134,21 +184,20 @@ export const fnTriggerWebhook = async ({
|
||||
);
|
||||
if (!toBeTriggeredHooks.length) return;
|
||||
logger.info({ environment, secretPath, projectId }, "Secret webhook job started");
|
||||
const project = await projectDAL.findById(projectId);
|
||||
let { projectName } = event.payload;
|
||||
if (!projectName) {
|
||||
const project = await projectDAL.findById(event.payload.projectId);
|
||||
projectName = project.name;
|
||||
}
|
||||
|
||||
const webhooksTriggered = await Promise.allSettled(
|
||||
toBeTriggeredHooks.map((hook) =>
|
||||
triggerWebhookRequest(
|
||||
hook,
|
||||
secretManagerDecryptor,
|
||||
getWebhookPayload("secrets.modified", {
|
||||
workspaceName: project.name,
|
||||
workspaceId: projectId,
|
||||
environment,
|
||||
secretPath,
|
||||
type: hook.type
|
||||
})
|
||||
)
|
||||
)
|
||||
toBeTriggeredHooks.map((hook) => {
|
||||
const formattedEvent = {
|
||||
type: event.type,
|
||||
payload: { ...event.payload, type: hook.type, projectName }
|
||||
} as TWebhookPayloads;
|
||||
return triggerWebhookRequest(hook, secretManagerDecryptor, getWebhookPayload(formattedEvent));
|
||||
})
|
||||
);
|
||||
|
||||
// filter hooks by status
|
||||
|
@ -16,7 +16,8 @@ import {
|
||||
TDeleteWebhookDTO,
|
||||
TListWebhookDTO,
|
||||
TTestWebhookDTO,
|
||||
TUpdateWebhookDTO
|
||||
TUpdateWebhookDTO,
|
||||
WebhookEvents
|
||||
} from "./webhook-types";
|
||||
|
||||
type TWebhookServiceFactoryDep = {
|
||||
@ -144,12 +145,15 @@ export const webhookServiceFactory = ({
|
||||
await triggerWebhookRequest(
|
||||
webhook,
|
||||
(value) => secretManagerDecryptor({ cipherTextBlob: value }).toString(),
|
||||
getWebhookPayload("test", {
|
||||
workspaceName: project.name,
|
||||
workspaceId: webhook.projectId,
|
||||
environment: webhook.environment.slug,
|
||||
secretPath: webhook.secretPath,
|
||||
type: webhook.type
|
||||
getWebhookPayload({
|
||||
type: "test" as WebhookEvents.SecretModified,
|
||||
payload: {
|
||||
projectName: project.name,
|
||||
projectId: webhook.projectId,
|
||||
environment: webhook.environment.slug,
|
||||
secretPath: webhook.secretPath,
|
||||
type: webhook.type
|
||||
}
|
||||
})
|
||||
);
|
||||
} catch (err) {
|
||||
|
@ -30,3 +30,36 @@ export enum WebhookType {
|
||||
GENERAL = "general",
|
||||
SLACK = "slack"
|
||||
}
|
||||
|
||||
export enum WebhookEvents {
|
||||
SecretModified = "secrets.modified",
|
||||
SecretReminderExpired = "secrets.reminder-expired",
|
||||
TestEvent = "test"
|
||||
}
|
||||
|
||||
type TWebhookSecretModifiedEventPayload = {
|
||||
type: WebhookEvents.SecretModified;
|
||||
payload: {
|
||||
projectName?: string;
|
||||
projectId: string;
|
||||
environment: string;
|
||||
secretPath?: string;
|
||||
type?: string | null;
|
||||
};
|
||||
};
|
||||
|
||||
type TWebhookSecretReminderEventPayload = {
|
||||
type: WebhookEvents.SecretReminderExpired;
|
||||
payload: {
|
||||
projectName?: string;
|
||||
projectId: string;
|
||||
environment: string;
|
||||
secretPath?: string;
|
||||
type?: string | null;
|
||||
secretName: string;
|
||||
secretId: string;
|
||||
reminderNote?: string | null;
|
||||
};
|
||||
};
|
||||
|
||||
export type TWebhookPayloads = TWebhookSecretModifiedEventPayload | TWebhookSecretReminderEventPayload;
|
||||
|
@ -36,3 +36,18 @@ If the signature in the header matches the signature that you generated, then yo
|
||||
"timestamp": ""
|
||||
}
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"event": "secrets.reminder-expired",
|
||||
"project": {
|
||||
"workspaceId": "the workspace id",
|
||||
"environment": "project environment",
|
||||
"secretPath": "project folder path",
|
||||
"secretName": "name of the secret",
|
||||
"secretId": "id of the secret",
|
||||
"reminderNote": "reminder note of the secret"
|
||||
},
|
||||
"timestamp": ""
|
||||
}
|
||||
```
|
||||
|
Reference in New Issue
Block a user