From cb664bb0426aeea4b3d66a4ca82969a6fd9fa553 Mon Sep 17 00:00:00 2001 From: Sheen Capadngan Date: Thu, 4 Jul 2024 13:33:32 +0800 Subject: [PATCH] misc: addressed review comments --- ...0240702055253_add-encrypted-webhook-url.ts | 64 +++++++++--------- backend/src/db/schemas/webhooks.ts | 2 +- .../src/server/routes/v1/webhook-router.ts | 2 +- backend/src/services/webhook/webhook-fns.ts | 67 ++++++------------- .../src/services/webhook/webhook-service.ts | 47 ++++--------- frontend/src/hooks/api/webhooks/types.ts | 7 ++ .../components/WebhooksTab/AddWebhookForm.tsx | 7 +- 7 files changed, 77 insertions(+), 119 deletions(-) diff --git a/backend/src/db/migrations/20240702055253_add-encrypted-webhook-url.ts b/backend/src/db/migrations/20240702055253_add-encrypted-webhook-url.ts index c2ca86d348..8762dde10a 100644 --- a/backend/src/db/migrations/20240702055253_add-encrypted-webhook-url.ts +++ b/backend/src/db/migrations/20240702055253_add-encrypted-webhook-url.ts @@ -5,45 +5,49 @@ import { WebhookType } from "@app/services/webhook/webhook-types"; import { TableName } from "../schemas"; export async function up(knex: Knex): Promise { - const hasEncryptedURL = await knex.schema.hasColumn(TableName.Webhook, "encryptedUrl"); + const hasUrlCipherText = await knex.schema.hasColumn(TableName.Webhook, "urlCipherText"); const hasUrlIV = await knex.schema.hasColumn(TableName.Webhook, "urlIV"); const hasUrlTag = await knex.schema.hasColumn(TableName.Webhook, "urlTag"); const hasType = await knex.schema.hasColumn(TableName.Webhook, "type"); - await knex.schema.alterTable(TableName.Webhook, (tb) => { - if (!hasEncryptedURL) { - tb.text("encryptedUrl"); - } - if (!hasUrlIV) { - tb.string("urlIV"); - } - if (!hasUrlTag) { - tb.string("urlTag"); - } - if (!hasType) { - tb.string("type").defaultTo(WebhookType.GENERAL); - } - }); + if (await knex.schema.hasTable(TableName.Webhook)) { + await knex.schema.alterTable(TableName.Webhook, (tb) => { + if (!hasUrlCipherText) { + tb.text("urlCipherText"); + } + if (!hasUrlIV) { + tb.string("urlIV"); + } + if (!hasUrlTag) { + tb.string("urlTag"); + } + if (!hasType) { + tb.string("type").defaultTo(WebhookType.GENERAL); + } + }); + } } export async function down(knex: Knex): Promise { - const hasEncryptedURL = await knex.schema.hasColumn(TableName.Webhook, "encryptedUrl"); + const hasUrlCipherText = await knex.schema.hasColumn(TableName.Webhook, "urlCipherText"); const hasUrlIV = await knex.schema.hasColumn(TableName.Webhook, "urlIV"); const hasUrlTag = await knex.schema.hasColumn(TableName.Webhook, "urlTag"); const hasType = await knex.schema.hasColumn(TableName.Webhook, "type"); - await knex.schema.alterTable(TableName.Webhook, (t) => { - if (hasEncryptedURL) { - t.dropColumn("encryptedUrl"); - } - if (hasUrlIV) { - t.dropColumn("urlIV"); - } - if (hasUrlTag) { - t.dropColumn("urlTag"); - } - if (hasType) { - t.dropColumn("type"); - } - }); + if (await knex.schema.hasTable(TableName.Webhook)) { + await knex.schema.alterTable(TableName.Webhook, (t) => { + if (hasUrlCipherText) { + t.dropColumn("urlCipherText"); + } + if (hasUrlIV) { + t.dropColumn("urlIV"); + } + if (hasUrlTag) { + t.dropColumn("urlTag"); + } + if (hasType) { + t.dropColumn("type"); + } + }); + } } diff --git a/backend/src/db/schemas/webhooks.ts b/backend/src/db/schemas/webhooks.ts index 49b64662c9..a7aac29339 100644 --- a/backend/src/db/schemas/webhooks.ts +++ b/backend/src/db/schemas/webhooks.ts @@ -22,7 +22,7 @@ export const WebhooksSchema = z.object({ createdAt: z.date(), updatedAt: z.date(), envId: z.string().uuid(), - encryptedUrl: z.string().nullable().optional(), + urlCipherText: z.string().nullable().optional(), urlIV: z.string().nullable().optional(), urlTag: z.string().nullable().optional(), type: z.string().default("general").nullable().optional() diff --git a/backend/src/server/routes/v1/webhook-router.ts b/backend/src/server/routes/v1/webhook-router.ts index 4124d9150e..0bb668b5fa 100644 --- a/backend/src/server/routes/v1/webhook-router.ts +++ b/backend/src/server/routes/v1/webhook-router.ts @@ -14,7 +14,7 @@ export const sanitizedWebhookSchema = WebhooksSchema.omit({ tag: true, algorithm: true, keyEncoding: true, - encryptedUrl: true, + urlCipherText: true, urlIV: true, urlTag: true }).merge( diff --git a/backend/src/services/webhook/webhook-fns.ts b/backend/src/services/webhook/webhook-fns.ts index fba5e3a49d..2439c7d653 100644 --- a/backend/src/services/webhook/webhook-fns.ts +++ b/backend/src/services/webhook/webhook-fns.ts @@ -4,9 +4,8 @@ import { AxiosError } from "axios"; import picomatch from "picomatch"; import { SecretKeyEncoding, TWebhooks } from "@app/db/schemas"; -import { getConfig } from "@app/lib/config/env"; import { request } from "@app/lib/config/request"; -import { decryptSymmetric, decryptSymmetric128BitHexKeyUTF8 } from "@app/lib/crypto"; +import { infisicalSymmetricDecrypt } from "@app/lib/crypto/encryption"; import { BadRequestError } from "@app/lib/errors"; import { logger } from "@app/lib/logger"; @@ -17,53 +16,27 @@ import { WebhookType } from "./webhook-types"; const WEBHOOK_TRIGGER_TIMEOUT = 15 * 1000; export const decryptWebhookDetails = (webhook: TWebhooks) => { - const appCfg = getConfig(); - const { keyEncoding, iv, encryptedSecretKey, tag, encryptedUrl, urlIV, urlTag, url } = webhook; - - const encryptionKey = appCfg.ENCRYPTION_KEY; - const rootEncryptionKey = appCfg.ROOT_ENCRYPTION_KEY; + const { keyEncoding, iv, encryptedSecretKey, tag, urlCipherText, urlIV, urlTag, url } = webhook; let decryptedSecretKey = ""; let decryptedUrl = url; - if (rootEncryptionKey && keyEncoding === SecretKeyEncoding.BASE64) { - // case: encoding scheme is base64 - if (encryptedSecretKey) { - decryptedSecretKey = decryptSymmetric({ - ciphertext: encryptedSecretKey, - iv: iv as string, - tag: tag as string, - key: rootEncryptionKey - }); - } + if (encryptedSecretKey) { + decryptedSecretKey = infisicalSymmetricDecrypt({ + keyEncoding: keyEncoding as SecretKeyEncoding, + ciphertext: encryptedSecretKey, + iv: iv as string, + tag: tag as string + }); + } - if (encryptedUrl) { - decryptedUrl = decryptSymmetric({ - ciphertext: encryptedUrl, - iv: urlIV as string, - tag: urlTag as string, - key: rootEncryptionKey - }); - } - } else if (encryptionKey && keyEncoding === SecretKeyEncoding.UTF8) { - // case: encoding scheme is utf8 - if (encryptedSecretKey) { - decryptedSecretKey = decryptSymmetric128BitHexKeyUTF8({ - ciphertext: encryptedSecretKey, - iv: iv as string, - tag: tag as string, - key: encryptionKey - }); - } - - if (encryptedUrl) { - decryptedUrl = decryptSymmetric128BitHexKeyUTF8({ - ciphertext: encryptedUrl, - iv: urlIV as string, - tag: urlTag as string, - key: encryptionKey - }); - } + if (urlCipherText) { + decryptedUrl = infisicalSymmetricDecrypt({ + keyEncoding: keyEncoding as SecretKeyEncoding, + ciphertext: urlCipherText, + iv: urlIV as string, + tag: urlTag as string + }); } return { @@ -109,17 +82,17 @@ export const getWebhookPayload = ( { title: "Workspace ID", value: workspaceId, - short: true + short: false }, { title: "Environment", value: environment, - short: true + short: false }, { title: "Secret Path", value: secretPath, - short: true + short: false } ] } diff --git a/backend/src/services/webhook/webhook-service.ts b/backend/src/services/webhook/webhook-service.ts index acf4e2d750..272c9de90c 100644 --- a/backend/src/services/webhook/webhook-service.ts +++ b/backend/src/services/webhook/webhook-service.ts @@ -1,10 +1,9 @@ import { ForbiddenError } from "@casl/ability"; -import { SecretEncryptionAlgo, SecretKeyEncoding, TWebhooksInsert } from "@app/db/schemas"; +import { TWebhooksInsert } from "@app/db/schemas"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service"; import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission"; -import { getConfig } from "@app/lib/config/env"; -import { encryptSymmetric, encryptSymmetric128BitHexKeyUTF8 } from "@app/lib/crypto"; +import { infisicalSymmetricEncypt } from "@app/lib/crypto/encryption"; import { BadRequestError } from "@app/lib/errors"; import { TProjectEnvDALFactory } from "../project-env/project-env-dal"; @@ -39,10 +38,6 @@ export const webhookServiceFactory = ({ webhookDAL, projectEnvDAL, permissionSer webhookSecretKey, type }: TCreateWebhookDTO) => { - const appCfg = getConfig(); - const encryptionKey = appCfg.ENCRYPTION_KEY; - const rootEncryptionKey = appCfg.ROOT_ENCRYPTION_KEY; - const { permission } = await permissionService.getProjectPermission( actor, actorId, @@ -63,37 +58,21 @@ export const webhookServiceFactory = ({ webhookDAL, projectEnvDAL, permissionSer }; if (webhookSecretKey) { - if (rootEncryptionKey) { - const { ciphertext, iv, tag } = encryptSymmetric(webhookSecretKey, rootEncryptionKey); - insertDoc.encryptedSecretKey = ciphertext; - insertDoc.iv = iv; - insertDoc.tag = tag; - insertDoc.algorithm = SecretEncryptionAlgo.AES_256_GCM; - insertDoc.keyEncoding = SecretKeyEncoding.BASE64; - } else if (encryptionKey) { - const { ciphertext, iv, tag } = encryptSymmetric128BitHexKeyUTF8(webhookSecretKey, encryptionKey); - insertDoc.encryptedSecretKey = ciphertext; - insertDoc.iv = iv; - insertDoc.tag = tag; - insertDoc.algorithm = SecretEncryptionAlgo.AES_256_GCM; - insertDoc.keyEncoding = SecretKeyEncoding.UTF8; - } + const { ciphertext, iv, tag, algorithm, encoding } = infisicalSymmetricEncypt(webhookSecretKey); + insertDoc.encryptedSecretKey = ciphertext; + insertDoc.iv = iv; + insertDoc.tag = tag; + insertDoc.algorithm = algorithm; + insertDoc.keyEncoding = encoding; } - if (rootEncryptionKey) { - const { ciphertext, iv, tag } = encryptSymmetric(webhookUrl, rootEncryptionKey); - insertDoc.encryptedUrl = ciphertext; + if (webhookUrl) { + const { ciphertext, iv, tag, algorithm, encoding } = infisicalSymmetricEncypt(webhookUrl); + insertDoc.urlCipherText = ciphertext; insertDoc.urlIV = iv; insertDoc.urlTag = tag; - insertDoc.algorithm = SecretEncryptionAlgo.AES_256_GCM; - insertDoc.keyEncoding = SecretKeyEncoding.BASE64; - } else if (encryptionKey) { - const { ciphertext, iv, tag } = encryptSymmetric128BitHexKeyUTF8(webhookUrl, encryptionKey); - insertDoc.encryptedUrl = ciphertext; - insertDoc.urlIV = iv; - insertDoc.urlTag = tag; - insertDoc.algorithm = SecretEncryptionAlgo.AES_256_GCM; - insertDoc.keyEncoding = SecretKeyEncoding.UTF8; + insertDoc.algorithm = algorithm; + insertDoc.keyEncoding = encoding; } const webhook = await webhookDAL.create(insertDoc); diff --git a/frontend/src/hooks/api/webhooks/types.ts b/frontend/src/hooks/api/webhooks/types.ts index 447ed4fc56..86183bf1ba 100644 --- a/frontend/src/hooks/api/webhooks/types.ts +++ b/frontend/src/hooks/api/webhooks/types.ts @@ -1,5 +1,11 @@ +export enum WebhookType { + GENERAL = "general", + SLACK = "slack" +} + export type TWebhook = { id: string; + type: WebhookType; projectId: string; environment: { slug: string; @@ -22,6 +28,7 @@ export type TCreateWebhookDto = { webhookUrl: string; webhookSecretKey?: string; secretPath: string; + type: WebhookType; }; export type TUpdateWebhookDto = { diff --git a/frontend/src/views/Settings/ProjectSettingsPage/components/WebhooksTab/AddWebhookForm.tsx b/frontend/src/views/Settings/ProjectSettingsPage/components/WebhooksTab/AddWebhookForm.tsx index aac9a458a1..6e4af22444 100644 --- a/frontend/src/views/Settings/ProjectSettingsPage/components/WebhooksTab/AddWebhookForm.tsx +++ b/frontend/src/views/Settings/ProjectSettingsPage/components/WebhooksTab/AddWebhookForm.tsx @@ -14,11 +14,7 @@ import { SelectItem } from "@app/components/v2"; import { SecretPathInput } from "@app/components/v2/SecretPathInput"; - -enum WebhookType { - GENERAL = "general", - SLACK = "slack" -} +import { WebhookType } from "@app/hooks/api/webhooks/types"; const formSchema = z .object({ @@ -116,7 +112,6 @@ export const AddWebhookForm = ({ (