feat: changed webhook and dynamic secret change to migration mode, resolved snapshot deletion issue in update

This commit is contained in:
=
2024-07-30 23:19:33 +05:30
parent c821bc0e14
commit 9063787772
20 changed files with 398 additions and 414 deletions
+2
View File
@@ -575,6 +575,7 @@ export const kmsServiceFactory = ({
// by keeping the decrypted data key in inner scope
// none of the entities outside can interact directly or expose the data key
// NOTICE: If changing here update migrations/utils/kms
const createCipherPairWithDataKey = async (encryptionContext: TEncryptWithKmsDataKeyDTO) => {
const dataKey = await $getDataKey(encryptionContext);
const cipher = symmetricCipherService(SymmetricEncryption.AES_GCM_256);
@@ -753,6 +754,7 @@ export const kmsServiceFactory = ({
return { id, slug, orgId, isExternal };
};
// akhilmhdh: a copy of this is made in migrations/utils/kms
const startService = async () => {
const appCfg = getConfig();
// This will switch to a seal process and HMS flow in future
@@ -36,8 +36,7 @@ export const getBotKeyFnFactory = (
if (!bot || !bot.isActive || !bot.encryptedProjectKey || !bot.encryptedProjectKeyNonce) {
// trying to set bot automatically
const projectV1Keys = await projectBotDAL.findProjectUserWorkspaceKey(projectId);
if (!projectV1Keys)
throw new BadRequestError({ message: "Bot not found. [no-private-key]. Please ask admin user to login" });
if (!projectV1Keys) throw new BadRequestError({ message: "Bot not found. Please ask admin user to login" });
let userPrivateKey = "";
if (
+4 -110
View File
@@ -1,21 +1,13 @@
/* eslint-disable no-await-in-loop */
import { AxiosError } from "axios";
import {
ProjectUpgradeStatus,
ProjectVersion,
SecretKeyEncoding,
TSecretSnapshotSecretsV2,
TSecretVersionsV2
} from "@app/db/schemas";
import { TDynamicSecretDALFactory } from "@app/ee/services/dynamic-secret/dynamic-secret-dal";
import { ProjectUpgradeStatus, ProjectVersion, TSecretSnapshotSecretsV2, TSecretVersionsV2 } from "@app/db/schemas";
import { TSecretApprovalRequestDALFactory } from "@app/ee/services/secret-approval-request/secret-approval-request-dal";
import { TSecretRotationDALFactory } from "@app/ee/services/secret-rotation/secret-rotation-dal";
import { TSnapshotDALFactory } from "@app/ee/services/secret-snapshot/snapshot-dal";
import { TSnapshotSecretV2DALFactory } from "@app/ee/services/secret-snapshot/snapshot-secret-v2-dal";
import { getConfig } from "@app/lib/config/env";
import { decryptSymmetric128BitHexKeyUTF8 } from "@app/lib/crypto";
import { infisicalSymmetricDecrypt } from "@app/lib/crypto/encryption";
import { daysToMillisecond, secondsToMillis } from "@app/lib/dates";
import { BadRequestError } from "@app/lib/errors";
import { groupBy, isSamePath, unique } from "@app/lib/fn";
@@ -68,7 +60,7 @@ type TSecretQueueFactoryDep = {
folderDAL: TSecretFolderDALFactory;
secretDAL: TSecretDALFactory;
secretImportDAL: Pick<TSecretImportDALFactory, "find" | "findByFolderIds">;
webhookDAL: Pick<TWebhookDALFactory, "findAllWebhooks" | "find" | "transaction" | "update" | "bulkUpdate" | "upsert">;
webhookDAL: Pick<TWebhookDALFactory, "findAllWebhooks" | "transaction" | "update" | "bulkUpdate">;
projectEnvDAL: Pick<TProjectEnvDALFactory, "findOne" | "find">;
projectDAL: TProjectDALFactory;
projectBotDAL: TProjectBotDALFactory;
@@ -87,7 +79,6 @@ type TSecretQueueFactoryDep = {
secretApprovalRequestDAL: Pick<TSecretApprovalRequestDALFactory, "deleteByProjectId">;
snapshotDAL: Pick<TSnapshotDALFactory, "findNSecretV1SnapshotByFolderId" | "deleteSnapshotsAboveLimit">;
snapshotSecretV2BridgeDAL: Pick<TSnapshotSecretV2DALFactory, "insertMany">;
dynamicSecretDAL: Pick<TDynamicSecretDALFactory, "find" | "upsert">;
};
export type TGetSecrets = {
@@ -131,8 +122,7 @@ export const secretQueueFactory = ({
secretRotationDAL,
snapshotDAL,
snapshotSecretV2BridgeDAL,
secretApprovalRequestDAL,
dynamicSecretDAL
secretApprovalRequestDAL
}: TSecretQueueFactoryDep) => {
const removeSecretReminder = async (dto: TRemoveSecretReminderDTO) => {
const appCfg = getConfig();
@@ -891,38 +881,7 @@ export const secretQueueFactory = ({
await secretV2BridgeDAL.upsertSecretReferences(secretReferences, tx);
}
const dynamicSecrets = await dynamicSecretDAL.find({ folderId }, { tx });
if (dynamicSecrets.length) {
await dynamicSecretDAL.upsert(
dynamicSecrets.map((el) => {
let { encryptedConfig } = el;
if (!encryptedConfig) {
if (el.keyEncoding && el.inputCiphertext && el.inputTag && el.inputIV) {
const decryptedConfig = infisicalSymmetricDecrypt({
keyEncoding: el.keyEncoding as SecretKeyEncoding,
ciphertext: el.inputCiphertext,
tag: el.inputTag,
iv: el.inputIV
});
encryptedConfig = secretManagerEncryptor({ plainText: Buffer.from(decryptedConfig) }).cipherTextBlob;
}
}
return {
...el,
encryptedConfig,
keyEncoding: null,
inputCiphertext: null,
inputTag: null,
inputIV: null,
algorithm: null
};
}),
"id",
tx
);
}
const SNAPSHOT_BATCH_SIZE = 15;
const SNAPSHOT_BATCH_SIZE = 10;
const snapshots = await snapshotDAL.findNSecretV1SnapshotByFolderId(folderId, SNAPSHOT_BATCH_SIZE, tx);
const projectV3SecretVersionsGroupById: Record<string, TSecretVersionsV2> = {};
const projectV3SecretVersionTags: { secret_versions_v2Id: string; secret_tagsId: string }[] = [];
@@ -1151,71 +1110,6 @@ export const secretQueueFactory = ({
tx
);
/*
* webhooks
* */
const projectV1Webhooks = await webhookDAL.find({ projectId }, tx);
if (projectV1Webhooks.length) {
await webhookDAL.upsert(
projectV1Webhooks.map((el) => {
let { encryptedSecretKeyWithKms, encryptedUrl } = el;
if (!encryptedSecretKeyWithKms) {
if (el.encryptedSecretKey && el.iv && el.tag) {
const webhookSecretKey = infisicalSymmetricDecrypt({
keyEncoding: el.keyEncoding as SecretKeyEncoding,
ciphertext: el.encryptedSecretKey,
iv: el.iv,
tag: el.tag
});
encryptedSecretKeyWithKms = secretManagerEncryptor({
plainText: Buffer.from(webhookSecretKey)
}).cipherTextBlob;
}
}
if (!encryptedUrl) {
if (el.urlTag && el.urlCipherText && el.urlIV) {
const webhookUrl = infisicalSymmetricDecrypt({
keyEncoding: el.keyEncoding as SecretKeyEncoding,
ciphertext: el.urlCipherText,
iv: el.urlIV,
tag: el.urlTag
});
encryptedUrl = secretManagerEncryptor({
plainText: Buffer.from(webhookUrl)
}).cipherTextBlob;
} else {
encryptedUrl = secretManagerEncryptor({
plainText: Buffer.from(el.url)
}).cipherTextBlob;
}
}
return {
id: el.id,
url: el.url,
envId: el.envId,
type: el.type,
isDisabled: el.isDisabled,
lastStatus: el.lastStatus,
secretPath: el.secretPath,
lastRunErrorMessage: el.lastRunErrorMessage,
encryptedSecretKeyWithKms,
encryptedUrl,
urlCipherText: null,
urlIV: null,
urlTag: null,
encryptedSecretKey: null,
iv: null,
tag: null,
keyEncoding: null,
algorithm: null
};
}),
"id",
tx
);
}
/*
* approvals: we will delete all approvals this is because some secret versions may not be added yet
* Thus doesn't make sense for rest to be there
+4 -24
View File
@@ -3,9 +3,7 @@ import crypto from "node:crypto";
import { AxiosError } from "axios";
import picomatch from "picomatch";
import { SecretKeyEncoding } from "@app/db/schemas";
import { request } from "@app/lib/config/request";
import { infisicalSymmetricDecrypt } from "@app/lib/crypto/encryption";
import { BadRequestError } from "@app/lib/errors";
import { logger } from "@app/lib/logger";
@@ -127,28 +125,10 @@ export const fnTriggerWebhook = async ({
const webhooksTriggered = await Promise.allSettled(
toBeTriggeredHooks.map((hook) => {
let webhookUrl = hook.url;
let webhookSecretKey;
if (hook.urlTag && hook.urlCipherText && hook.urlIV) {
webhookUrl = infisicalSymmetricDecrypt({
keyEncoding: hook.keyEncoding as SecretKeyEncoding,
ciphertext: hook.urlCipherText,
iv: hook.urlIV,
tag: hook.urlTag
});
} else if (hook.encryptedUrl) {
webhookUrl = kmsDataKeyDecryptor({ cipherTextBlob: hook.encryptedUrl }).toString();
}
if (hook.encryptedSecretKey && hook.iv && hook.tag) {
webhookSecretKey = infisicalSymmetricDecrypt({
keyEncoding: hook.keyEncoding as SecretKeyEncoding,
ciphertext: hook.encryptedSecretKey,
iv: hook.iv,
tag: hook.tag
});
} else if (hook.encryptedSecretKeyWithKms) {
webhookSecretKey = kmsDataKeyDecryptor({ cipherTextBlob: hook.encryptedSecretKeyWithKms }).toString();
}
const webhookUrl = kmsDataKeyDecryptor({ cipherTextBlob: hook.encryptedUrl }).toString();
const webhookSecretKey = hook.encryptedSecretKeyWithKms
? kmsDataKeyDecryptor({ cipherTextBlob: hook.encryptedSecretKeyWithKms }).toString()
: undefined;
return triggerWebhookRequest(
{ webhookUrl, webhookSecretKey },
+22 -57
View File
@@ -1,9 +1,7 @@
import { ForbiddenError } from "@casl/ability";
import { SecretKeyEncoding, 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 { infisicalSymmetricDecrypt } from "@app/lib/crypto/encryption";
import { BadRequestError } from "@app/lib/errors";
import { TKmsServiceFactory } from "../kms/kms-service";
@@ -60,34 +58,28 @@ export const webhookServiceFactory = ({
const env = await projectEnvDAL.findOne({ projectId, slug: environment });
if (!env) throw new BadRequestError({ message: "Env not found" });
const insertDoc: TWebhooksInsert = {
url: "", // deprecated - we are moving away from plaintext URLs
envId: env.id,
isDisabled: false,
secretPath: secretPath || "/",
type
};
const { encryptor: secretManagerEncryptor } = await kmsService.createCipherPairWithDataKey({
projectId,
type: KmsDataKey.SecretManager
});
if (webhookSecretKey) {
const encryptedSecretKeyWithKms = secretManagerEncryptor({
plainText: Buffer.from(webhookSecretKey)
}).cipherTextBlob;
insertDoc.encryptedSecretKeyWithKms = encryptedSecretKeyWithKms;
}
if (webhookUrl) {
const encryptedUrl = secretManagerEncryptor({
plainText: Buffer.from(webhookUrl)
}).cipherTextBlob;
const encryptedSecretKeyWithKms = webhookSecretKey
? secretManagerEncryptor({
plainText: Buffer.from(webhookSecretKey)
}).cipherTextBlob
: null;
const encryptedUrl = secretManagerEncryptor({
plainText: Buffer.from(webhookUrl)
}).cipherTextBlob;
insertDoc.encryptedUrl = encryptedUrl;
}
const webhook = await webhookDAL.create(insertDoc);
const webhook = await webhookDAL.create({
encryptedUrl,
encryptedSecretKeyWithKms,
envId: env.id,
isDisabled: false,
secretPath: secretPath || "/",
type
});
return { ...webhook, projectId, environment: env };
};
@@ -145,28 +137,11 @@ export const webhookServiceFactory = ({
projectId: project.id,
type: KmsDataKey.SecretManager
});
let webhookUrl = webhook.url;
let webhookSecretKey;
if (webhook.urlTag && webhook.urlCipherText && webhook.urlIV) {
webhookUrl = infisicalSymmetricDecrypt({
keyEncoding: webhook.keyEncoding as SecretKeyEncoding,
ciphertext: webhook.urlCipherText,
iv: webhook.urlIV,
tag: webhook.urlTag
});
} else if (webhook.encryptedUrl) {
webhookUrl = kmsDataKeyDecryptor({ cipherTextBlob: webhook.encryptedUrl }).toString();
}
if (webhook.encryptedSecretKey && webhook.iv && webhook.tag) {
webhookSecretKey = infisicalSymmetricDecrypt({
keyEncoding: webhook.keyEncoding as SecretKeyEncoding,
ciphertext: webhook.encryptedSecretKey,
iv: webhook.iv,
tag: webhook.tag
});
} else if (webhook.encryptedSecretKeyWithKms) {
webhookSecretKey = kmsDataKeyDecryptor({ cipherTextBlob: webhook.encryptedSecretKeyWithKms }).toString();
}
const webhookUrl = kmsDataKeyDecryptor({ cipherTextBlob: webhook.encryptedUrl }).toString();
const webhookSecretKey = webhook.encryptedSecretKeyWithKms
? kmsDataKeyDecryptor({ cipherTextBlob: webhook.encryptedSecretKeyWithKms }).toString()
: undefined;
try {
await triggerWebhookRequest(
{ webhookUrl, webhookSecretKey },
@@ -213,17 +188,7 @@ export const webhookServiceFactory = ({
projectId
});
return webhooks.map((w) => {
let decryptedUrl = w.url;
if (w.urlTag && w.urlCipherText && w.urlIV) {
decryptedUrl = infisicalSymmetricDecrypt({
keyEncoding: w.keyEncoding as SecretKeyEncoding,
ciphertext: w.urlCipherText,
iv: w.urlIV,
tag: w.urlTag
});
} else if (w.encryptedUrl) {
decryptedUrl = kmsDataKeyDecryptor({ cipherTextBlob: w.encryptedUrl }).toString();
}
const decryptedUrl = kmsDataKeyDecryptor({ cipherTextBlob: w.encryptedUrl }).toString();
return {
...w,
url: decryptedUrl