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

This commit is contained in:
=
2024-07-30 22:30:17 +05:30
parent c821bc0e14
commit 9063787772
20 changed files with 398 additions and 414 deletions

View File

@ -1,7 +1,178 @@
/* eslint-disable @typescript-eslint/ban-ts-comment */
import { Knex } from "knex";
import { SecretEncryptionAlgo, SecretKeyEncoding, SecretType, TableName } from "../schemas";
import { infisicalSymmetricDecrypt } from "@app/lib/crypto/encryption";
import { selectAllTableCols } from "@app/lib/knex/select";
import { SecretKeyEncoding, SecretType, TableName } from "../schemas";
import { createJunctionTable, createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils";
import { getSecretManagerDataKey } from "./utils/kms";
const backfillWebhooks = async (knex: Knex) => {
const hasEncryptedSecretKeyWithKms = await knex.schema.hasColumn(TableName.Webhook, "encryptedSecretKeyWithKms");
const hasEncryptedWebhookUrl = 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 hasEncryptedSecretKey = await knex.schema.hasColumn(TableName.Webhook, "encryptedSecretKey");
const hasIV = await knex.schema.hasColumn(TableName.Webhook, "iv");
const hasTag = await knex.schema.hasColumn(TableName.Webhook, "tag");
const hasKeyEncoding = await knex.schema.hasColumn(TableName.Webhook, "keyEncoding");
const hasAlgorithm = await knex.schema.hasColumn(TableName.Webhook, "algorithm");
const hasUrl = await knex.schema.hasColumn(TableName.Webhook, "url");
await knex.schema.alterTable(TableName.Webhook, (t) => {
if (!hasEncryptedSecretKeyWithKms) t.binary("encryptedSecretKeyWithKms");
if (!hasEncryptedWebhookUrl) t.binary("encryptedUrl");
if (hasUrl) t.string("url").nullable().alter();
});
const kmsEncryptorGroupByProjectId: Record<string, Awaited<ReturnType<typeof getSecretManagerDataKey>>["encryptor"]> =
{};
if (hasUrlCipherText && hasUrlIV && hasUrlTag && hasEncryptedSecretKey && hasIV && hasTag) {
// eslint-disable-next-line
const webhooksToFill = await knex(TableName.Webhook)
.join(TableName.Environment, `${TableName.Environment}.id`, `${TableName.Webhook}.envId`)
.whereNull("encryptedUrl")
// eslint-disable-next-line
// @ts-ignore knex migration fails
.select(selectAllTableCols(TableName.Webhook))
.select("projectId");
const updatedWebhooks = [];
for (const webhook of webhooksToFill) {
if (!kmsEncryptorGroupByProjectId[webhook.projectId]) {
// eslint-disable-next-line
const { encryptor } = await getSecretManagerDataKey(knex, webhook.projectId);
kmsEncryptorGroupByProjectId[webhook.projectId] = encryptor;
}
const kmsEncryptor = kmsEncryptorGroupByProjectId[webhook.projectId];
// @ts-ignore post migration fails
let webhookUrl = webhook.url;
let webhookSecretKey;
// @ts-ignore post migration fails
if (webhook.urlTag && webhook.urlCipherText && webhook.urlIV) {
webhookUrl = infisicalSymmetricDecrypt({
// @ts-ignore post migration fails
keyEncoding: webhook.keyEncoding as SecretKeyEncoding,
// @ts-ignore post migration fails
ciphertext: webhook.urlCipherText,
// @ts-ignore post migration fails
iv: webhook.urlIV,
// @ts-ignore post migration fails
tag: webhook.urlTag
});
}
// @ts-ignore post migration fails
if (webhook.encryptedSecretKey && webhook.iv && webhook.tag) {
webhookSecretKey = infisicalSymmetricDecrypt({
// @ts-ignore post migration fails
keyEncoding: webhook.keyEncoding as SecretKeyEncoding,
// @ts-ignore post migration fails
ciphertext: webhook.encryptedSecretKey,
// @ts-ignore post migration fails
iv: webhook.iv,
// @ts-ignore post migration fails
tag: webhook.tag
});
}
const { projectId, ...el } = webhook;
updatedWebhooks.push({
...el,
encryptedSecretKeyWithKms: webhookSecretKey
? kmsEncryptor({ plainText: Buffer.from(webhookSecretKey) }).cipherTextBlob
: null,
encryptedUrl: kmsEncryptor({ plainText: Buffer.from(webhookUrl) }).cipherTextBlob
});
}
if (updatedWebhooks.length) {
// eslint-disable-next-line
await knex(TableName.Webhook).insert(updatedWebhooks).onConflict("id").merge();
}
}
await knex.schema.alterTable(TableName.Webhook, (t) => {
t.binary("encryptedUrl").notNullable().alter();
if (hasUrlIV) t.dropColumn("urlIV");
if (hasUrlCipherText) t.dropColumn("urlCipherText");
if (hasUrlTag) t.dropColumn("urlTag");
if (hasIV) t.dropColumn("iv");
if (hasTag) t.dropColumn("tag");
if (hasEncryptedSecretKey) t.dropColumn("encryptedSecretKey");
if (hasKeyEncoding) t.dropColumn("keyEncoding");
if (hasAlgorithm) t.dropColumn("algorithm");
if (hasUrl) t.dropColumn("url");
});
};
const backfillDynamicSecretConfigs = async (knex: Knex) => {
const hasEncryptedConfig = await knex.schema.hasColumn(TableName.DynamicSecret, "encryptedConfig");
const hasInputCipherText = await knex.schema.hasColumn(TableName.DynamicSecret, "inputCiphertext");
const hasInputIV = await knex.schema.hasColumn(TableName.DynamicSecret, "inputIV");
const hasInputTag = await knex.schema.hasColumn(TableName.DynamicSecret, "inputTag");
const hasKeyEncoding = await knex.schema.hasColumn(TableName.DynamicSecret, "keyEncoding");
const hasAlgorithm = await knex.schema.hasColumn(TableName.DynamicSecret, "algorithm");
await knex.schema.alterTable(TableName.DynamicSecret, (t) => {
if (!hasEncryptedConfig) t.binary("encryptedConfig");
});
const kmsEncryptorGroupByProjectId: Record<string, Awaited<ReturnType<typeof getSecretManagerDataKey>>["encryptor"]> =
{};
if (hasInputCipherText && hasInputIV && hasInputTag) {
// eslint-disable-next-line
const dynamicSecretConfigs = await knex(TableName.DynamicSecret)
.join(TableName.SecretFolder, `${TableName.SecretFolder}.id`, `${TableName.DynamicSecret}.folderId`)
.join(TableName.Environment, `${TableName.SecretFolder}.envId`, `${TableName.Environment}.id`)
.whereNull("encryptedConfig")
// @ts-ignore post migration fails
.select(selectAllTableCols(TableName.DynamicSecret))
.select("projectId");
const updatedConfigs = [];
for (const dynamicSecretConfig of dynamicSecretConfigs) {
if (!kmsEncryptorGroupByProjectId[dynamicSecretConfig.projectId]) {
// eslint-disable-next-line
const { encryptor } = await getSecretManagerDataKey(knex, dynamicSecretConfig.projectId);
kmsEncryptorGroupByProjectId[dynamicSecretConfig.projectId] = encryptor;
}
const kmsEncryptor = kmsEncryptorGroupByProjectId[dynamicSecretConfig.projectId];
const inputConfig = infisicalSymmetricDecrypt({
// @ts-ignore post migration fails
keyEncoding: dynamicSecretConfig.keyEncoding as SecretKeyEncoding,
// @ts-ignore post migration fails
ciphertext: dynamicSecretConfig.inputCiphertext as string,
// @ts-ignore post migration fails
iv: dynamicSecretConfig.inputIV as string,
// @ts-ignore post migration fails
tag: dynamicSecretConfig.inputTag as string
});
const { projectId, ...el } = dynamicSecretConfig;
updatedConfigs.push({
...el,
encryptedConfig: kmsEncryptor({ plainText: Buffer.from(inputConfig) }).cipherTextBlob
});
}
if (updatedConfigs.length) {
// eslint-disable-next-line
await knex(TableName.DynamicSecret).insert(updatedConfigs).onConflict("id").merge();
}
}
await knex.schema.alterTable(TableName.DynamicSecret, (t) => {
t.binary("encryptedConfig").notNullable().alter();
if (hasInputTag) t.dropColumn("inputTag");
if (hasInputIV) t.dropColumn("inputIV");
if (hasInputCipherText) t.dropColumn("inputCiphertext");
if (hasKeyEncoding) t.dropColumn("keyEncoding");
if (hasAlgorithm) t.dropColumn("algorithm");
});
};
export async function up(knex: Knex): Promise<void> {
const doesSecretV2TableExist = await knex.schema.hasTable(TableName.SecretV2);
@ -145,29 +316,11 @@ export async function up(knex: Knex): Promise<void> {
}
if (await knex.schema.hasTable(TableName.Webhook)) {
const hasEncryptedWebhookSecretKey = await knex.schema.hasColumn(TableName.Webhook, "encryptedSecretKeyWithKms");
const hasEncryptedWebhookUrl = await knex.schema.hasColumn(TableName.Webhook, "encryptedUrl");
await knex.schema.alterTable(TableName.Webhook, (t) => {
if (!hasEncryptedWebhookSecretKey) t.binary("encryptedSecretKeyWithKms");
if (!hasEncryptedWebhookUrl) t.binary("encryptedUrl");
});
await backfillWebhooks(knex);
}
if (await knex.schema.hasTable(TableName.DynamicSecret)) {
const hasInputIV = await knex.schema.hasColumn(TableName.DynamicSecret, "inputIV");
const hasInputCipherText = await knex.schema.hasColumn(TableName.DynamicSecret, "inputCiphertext");
const hasInputTag = await knex.schema.hasColumn(TableName.DynamicSecret, "inputTag");
const hasAlgorithm = await knex.schema.hasColumn(TableName.DynamicSecret, "algorithm");
const hasKeyEncoding = await knex.schema.hasColumn(TableName.DynamicSecret, "keyEncoding");
const hasEncryptedConfig = await knex.schema.hasColumn(TableName.DynamicSecret, "encryptedConfig");
await knex.schema.alterTable(TableName.DynamicSecret, (t) => {
if (hasInputIV) t.string("inputIV").alter();
if (hasInputCipherText) t.text("inputCiphertext").alter();
if (hasInputTag) t.string("inputTag").alter();
if (hasAlgorithm) t.string("algorithm").defaultTo(SecretEncryptionAlgo.AES_256_GCM).alter();
if (hasKeyEncoding) t.string("keyEncoding").defaultTo(SecretKeyEncoding.UTF8).alter();
if (!hasEncryptedConfig) t.binary("encryptedConfig");
});
await backfillDynamicSecretConfigs(knex);
}
}
@ -206,16 +359,46 @@ export async function down(knex: Knex): Promise<void> {
if (await knex.schema.hasTable(TableName.Webhook)) {
const hasEncryptedWebhookSecretKey = await knex.schema.hasColumn(TableName.Webhook, "encryptedSecretKeyWithKms");
const hasEncryptedWebhookUrl = 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 hasEncryptedSecretKey = await knex.schema.hasColumn(TableName.Webhook, "encryptedSecretKey");
const hasIV = await knex.schema.hasColumn(TableName.Webhook, "iv");
const hasTag = await knex.schema.hasColumn(TableName.Webhook, "tag");
const hasKeyEncoding = await knex.schema.hasColumn(TableName.Webhook, "keyEncoding");
const hasAlgorithm = await knex.schema.hasColumn(TableName.Webhook, "algorithm");
const hasUrl = await knex.schema.hasColumn(TableName.Webhook, "url");
await knex.schema.alterTable(TableName.Webhook, (t) => {
if (hasEncryptedWebhookSecretKey) t.dropColumn("encryptedSecretKeyWithKms");
if (hasEncryptedWebhookUrl) t.dropColumn("encryptedUrl");
if (!hasUrl) t.string("url");
if (!hasEncryptedSecretKey) t.string("encryptedSecretKey");
if (!hasIV) t.string("iv");
if (!hasTag) t.string("tag");
if (!hasAlgorithm) t.string("algorithm");
if (!hasKeyEncoding) t.string("keyEncoding");
if (!hasUrlCipherText) t.string("urlCipherText");
if (!hasUrlIV) t.string("urlIV");
if (!hasUrlTag) t.string("urlTag");
});
}
if (await knex.schema.hasTable(TableName.DynamicSecret)) {
const hasEncryptedConfig = await knex.schema.hasColumn(TableName.DynamicSecret, "encryptedConfig");
const hasInputIV = await knex.schema.hasColumn(TableName.DynamicSecret, "inputIV");
const hasInputCipherText = await knex.schema.hasColumn(TableName.DynamicSecret, "inputCiphertext");
const hasInputTag = await knex.schema.hasColumn(TableName.DynamicSecret, "inputTag");
const hasAlgorithm = await knex.schema.hasColumn(TableName.DynamicSecret, "algorithm");
const hasKeyEncoding = await knex.schema.hasColumn(TableName.DynamicSecret, "keyEncoding");
await knex.schema.alterTable(TableName.DynamicSecret, (t) => {
if (hasEncryptedConfig) t.dropColumn("encryptedConfig");
if (!hasInputIV) t.string("inputIV");
if (!hasInputCipherText) t.text("inputCiphertext");
if (!hasInputTag) t.string("inputTag");
if (!hasAlgorithm) t.string("algorithm");
if (!hasKeyEncoding) t.string("keyEncoding");
});
}
}

View File

@ -0,0 +1,105 @@
import slugify from "@sindresorhus/slugify";
import { Knex } from "knex";
import { TableName } from "@app/db/schemas";
import { randomSecureBytes } from "@app/lib/crypto";
import { symmetricCipherService, SymmetricEncryption } from "@app/lib/crypto/cipher";
import { alphaNumericNanoId } from "@app/lib/nanoid";
const getInstanceRootKey = async (knex: Knex) => {
const encryptionKey = process.env.ENCRYPTION_KEY || process.env.ROOT_ENCRYPTION_KEY;
// if root key its base64 encoded
const isBase64 = !process.env.ENCRYPTION_KEY;
if (!encryptionKey) throw new Error("ENCRYPTION_KEY variable needed for migration");
const encryptionKeyBuffer = Buffer.from(encryptionKey, isBase64 ? "base64" : "utf8");
const KMS_ROOT_CONFIG_UUID = "00000000-0000-0000-0000-000000000000";
const kmsRootConfig = await knex(TableName.KmsServerRootConfig).where({ id: KMS_ROOT_CONFIG_UUID }).first();
const cipher = symmetricCipherService(SymmetricEncryption.AES_GCM_256);
if (kmsRootConfig) {
const decryptedRootKey = cipher.decrypt(kmsRootConfig.encryptedRootKey, encryptionKeyBuffer);
// set the flag so that other instancen nodes can start
return decryptedRootKey;
}
const newRootKey = randomSecureBytes(32);
const encryptedRootKey = cipher.encrypt(newRootKey, encryptionKeyBuffer);
await knex(TableName.KmsServerRootConfig).insert({
encryptedRootKey,
// eslint-disable-next-line
// @ts-ignore id is kept as fixed for idempotence and to avoid race condition
id: KMS_ROOT_CONFIG_UUID
});
return encryptedRootKey;
};
export const getSecretManagerDataKey = async (knex: Knex, projectId: string) => {
const KMS_VERSION = "v01";
const KMS_VERSION_BLOB_LENGTH = 3;
const cipher = symmetricCipherService(SymmetricEncryption.AES_GCM_256);
const project = await knex(TableName.Project).where({ id: projectId }).first();
if (!project) throw new Error("Missing project id");
const ROOT_ENCRYPTION_KEY = await getInstanceRootKey(knex);
let secretManagerKmsKey;
const projectSecretManagerKmsId = project?.kmsSecretManagerKeyId;
if (projectSecretManagerKmsId) {
const kmsDoc = await knex(TableName.KmsKey)
.leftJoin(TableName.InternalKms, `${TableName.KmsKey}.id`, `${TableName.InternalKms}.kmsKeyId`)
.where({ [`${TableName.KmsKey}.id` as "id"]: projectSecretManagerKmsId })
.first();
if (!kmsDoc) throw new Error("missing kms");
secretManagerKmsKey = cipher.decrypt(kmsDoc.encryptedKey, ROOT_ENCRYPTION_KEY);
} else {
const [kmsDoc] = await knex(TableName.KmsKey)
.insert({
slug: slugify(alphaNumericNanoId(8).toLowerCase()),
orgId: project.orgId,
isReserved: false
})
.returning("*");
secretManagerKmsKey = randomSecureBytes(32);
const encryptedKeyMaterial = cipher.encrypt(secretManagerKmsKey, ROOT_ENCRYPTION_KEY);
await knex(TableName.InternalKms).insert({
version: 1,
encryptedKey: encryptedKeyMaterial,
encryptionAlgorithm: SymmetricEncryption.AES_GCM_256,
kmsKeyId: kmsDoc.id
});
}
const encryptedSecretManagerDataKey = project?.kmsSecretManagerEncryptedDataKey;
let dataKey: Buffer;
if (!encryptedSecretManagerDataKey) {
dataKey = randomSecureBytes();
// the below versioning we do it automatically in kms service
const unversionedDataKey = cipher.encrypt(dataKey, secretManagerKmsKey);
const versionBlob = Buffer.from(KMS_VERSION, "utf8"); // length is 3
await knex(TableName.Project)
.where({ id: projectId })
.update({
kmsSecretManagerEncryptedDataKey: Buffer.concat([unversionedDataKey, versionBlob])
});
} else {
const cipherTextBlob = encryptedSecretManagerDataKey.subarray(0, -KMS_VERSION_BLOB_LENGTH);
dataKey = cipher.decrypt(cipherTextBlob, secretManagerKmsKey);
}
return {
encryptor: ({ plainText }: { plainText: Buffer }) => {
const encryptedPlainTextBlob = cipher.encrypt(plainText, dataKey);
// Buffer#1 encrypted text + Buffer#2 version number
const versionBlob = Buffer.from(KMS_VERSION, "utf8"); // length is 3
const cipherTextBlob = Buffer.concat([encryptedPlainTextBlob, versionBlob]);
return { cipherTextBlob };
},
decryptor: ({ cipherTextBlob: versionedCipherTextBlob }: { cipherTextBlob: Buffer }) => {
const cipherTextBlob = versionedCipherTextBlob.subarray(0, -KMS_VERSION_BLOB_LENGTH);
const decryptedBlob = cipher.decrypt(cipherTextBlob, dataKey);
return decryptedBlob;
}
};
};

View File

@ -16,17 +16,12 @@ export const DynamicSecretsSchema = z.object({
type: z.string(),
defaultTTL: z.string(),
maxTTL: z.string().nullable().optional(),
inputIV: z.string().nullable().optional(),
inputCiphertext: z.string().nullable().optional(),
inputTag: z.string().nullable().optional(),
algorithm: z.string().default("aes-256-gcm").nullable().optional(),
keyEncoding: z.string().default("utf8").nullable().optional(),
folderId: z.string().uuid(),
status: z.string().nullable().optional(),
statusDetails: z.string().nullable().optional(),
createdAt: z.date(),
updatedAt: z.date(),
encryptedConfig: zodBuffer.nullable().optional()
encryptedConfig: zodBuffer
});
export type TDynamicSecrets = z.infer<typeof DynamicSecretsSchema>;

View File

@ -12,24 +12,15 @@ import { TImmutableDBKeys } from "./models";
export const WebhooksSchema = z.object({
id: z.string().uuid(),
secretPath: z.string().default("/"),
url: z.string(),
lastStatus: z.string().nullable().optional(),
lastRunErrorMessage: z.string().nullable().optional(),
isDisabled: z.boolean().default(false),
encryptedSecretKey: z.string().nullable().optional(),
iv: z.string().nullable().optional(),
tag: z.string().nullable().optional(),
algorithm: z.string().nullable().optional(),
keyEncoding: z.string().nullable().optional(),
createdAt: z.date(),
updatedAt: z.date(),
envId: z.string().uuid(),
urlCipherText: z.string().nullable().optional(),
urlIV: z.string().nullable().optional(),
urlTag: z.string().nullable().optional(),
type: z.string().default("general").nullable().optional(),
encryptedSecretKeyWithKms: zodBuffer.nullable().optional(),
encryptedUrl: zodBuffer.nullable().optional()
encryptedUrl: zodBuffer
});
export type TWebhooks = z.infer<typeof WebhooksSchema>;

View File

@ -40,11 +40,6 @@ export const dynamicSecretLeaseDALFactory = (db: TDbClient) => {
db.ref("type").withSchema(TableName.DynamicSecret).as("dynType"),
db.ref("defaultTTL").withSchema(TableName.DynamicSecret).as("dynDefaultTTL"),
db.ref("maxTTL").withSchema(TableName.DynamicSecret).as("dynMaxTTL"),
db.ref("inputIV").withSchema(TableName.DynamicSecret).as("dynInputIV"),
db.ref("inputTag").withSchema(TableName.DynamicSecret).as("dynInputTag"),
db.ref("inputCiphertext").withSchema(TableName.DynamicSecret).as("dynInputCiphertext"),
db.ref("algorithm").withSchema(TableName.DynamicSecret).as("dynAlgorithm"),
db.ref("keyEncoding").withSchema(TableName.DynamicSecret).as("dynKeyEncoding"),
db.ref("folderId").withSchema(TableName.DynamicSecret).as("dynFolderId"),
db.ref("status").withSchema(TableName.DynamicSecret).as("dynStatus"),
db.ref("statusDetails").withSchema(TableName.DynamicSecret).as("dynStatusDetails"),
@ -63,11 +58,6 @@ export const dynamicSecretLeaseDALFactory = (db: TDbClient) => {
type: doc.dynType,
defaultTTL: doc.dynDefaultTTL,
maxTTL: doc.dynMaxTTL,
inputIV: doc.dynInputIV,
inputTag: doc.dynInputTag,
inputCiphertext: doc.dynInputCiphertext,
algorithm: doc.dynAlgorithm,
keyEncoding: doc.dynKeyEncoding,
folderId: doc.dynFolderId,
status: doc.dynStatus,
statusDetails: doc.dynStatusDetails,

View File

@ -1,7 +1,4 @@
import { SecretKeyEncoding } from "@app/db/schemas";
import { DisableRotationErrors } from "@app/ee/services/secret-rotation/secret-rotation-queue";
import { infisicalSymmetricDecrypt } from "@app/lib/crypto/encryption";
import { BadRequestError } from "@app/lib/errors";
import { logger } from "@app/lib/logger";
import { QueueJobs, QueueName, TQueueServiceFactory } from "@app/queue";
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
@ -94,27 +91,9 @@ export const dynamicSecretLeaseQueueServiceFactory = ({
projectId
});
let dynamicSecretInputConfig = "";
if (
dynamicSecretCfg.keyEncoding &&
dynamicSecretCfg.inputCiphertext &&
dynamicSecretCfg.inputTag &&
dynamicSecretCfg.inputIV
) {
dynamicSecretInputConfig = infisicalSymmetricDecrypt({
keyEncoding: dynamicSecretCfg.keyEncoding as SecretKeyEncoding,
ciphertext: dynamicSecretCfg.inputCiphertext,
tag: dynamicSecretCfg.inputTag,
iv: dynamicSecretCfg.inputIV
});
} else if (dynamicSecretCfg.encryptedConfig) {
dynamicSecretInputConfig = secretManagerDecryptor({
cipherTextBlob: dynamicSecretCfg.encryptedConfig
}).toString();
} else {
throw new BadRequestError({ message: "Missing secret input config" });
}
const dynamicSecretInputConfig = secretManagerDecryptor({
cipherTextBlob: dynamicSecretCfg.encryptedConfig
}).toString();
const selectedProvider = dynamicSecretProviders[dynamicSecretCfg.type as DynamicSecretProviders];
const decryptedStoredInput = JSON.parse(dynamicSecretInputConfig) as object;
@ -142,27 +121,10 @@ export const dynamicSecretLeaseQueueServiceFactory = ({
const dynamicSecretLeases = await dynamicSecretLeaseDAL.find({ dynamicSecretId: dynamicSecretCfgId });
if (dynamicSecretLeases.length) {
const selectedProvider = dynamicSecretProviders[dynamicSecretCfg.type as DynamicSecretProviders];
let dynamicSecretInputConfig = "";
if (
dynamicSecretCfg.keyEncoding &&
dynamicSecretCfg.inputCiphertext &&
dynamicSecretCfg.inputTag &&
dynamicSecretCfg.inputIV
) {
dynamicSecretInputConfig = infisicalSymmetricDecrypt({
keyEncoding: dynamicSecretCfg.keyEncoding as SecretKeyEncoding,
ciphertext: dynamicSecretCfg.inputCiphertext,
tag: dynamicSecretCfg.inputTag,
iv: dynamicSecretCfg.inputIV
});
} else if (dynamicSecretCfg.encryptedConfig) {
dynamicSecretInputConfig = secretManagerDecryptor({
cipherTextBlob: dynamicSecretCfg.encryptedConfig
}).toString();
} else {
throw new BadRequestError({ message: "Missing secret input config" });
}
const dynamicSecretInputConfig = secretManagerDecryptor({
cipherTextBlob: dynamicSecretCfg.encryptedConfig
}).toString();
const decryptedStoredInput = JSON.parse(dynamicSecretInputConfig) as object;
await Promise.all(dynamicSecretLeases.map(({ id }) => unsetLeaseRevocation(id)));

View File

@ -1,12 +1,10 @@
import { ForbiddenError, subject } from "@casl/ability";
import ms from "ms";
import { SecretKeyEncoding, TDynamicSecrets } from "@app/db/schemas";
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
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 { infisicalSymmetricDecrypt } from "@app/lib/crypto/encryption";
import { BadRequestError } from "@app/lib/errors";
import { logger } from "@app/lib/logger";
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
@ -52,29 +50,6 @@ export const dynamicSecretLeaseServiceFactory = ({
licenseService,
kmsService
}: TDynamicSecretLeaseServiceFactoryDep) => {
const $getDynamicSecretInputConfig = (dynamicSecretCfg: TDynamicSecrets, decryptValue: (arg: Buffer) => string) => {
if (
dynamicSecretCfg.keyEncoding &&
dynamicSecretCfg.inputCiphertext &&
dynamicSecretCfg.inputTag &&
dynamicSecretCfg.inputIV
) {
return JSON.parse(
infisicalSymmetricDecrypt({
keyEncoding: dynamicSecretCfg.keyEncoding as SecretKeyEncoding,
ciphertext: dynamicSecretCfg.inputCiphertext,
tag: dynamicSecretCfg.inputTag,
iv: dynamicSecretCfg.inputIV
})
) as object;
}
if (dynamicSecretCfg.encryptedConfig) {
return JSON.parse(decryptValue(dynamicSecretCfg.encryptedConfig)) as object;
}
throw new BadRequestError({ message: "Missing secret input config" });
};
const create = async ({
environmentSlug,
path,
@ -125,9 +100,8 @@ export const dynamicSecretLeaseServiceFactory = ({
type: KmsDataKey.SecretManager,
projectId
});
const decryptedStoredInput = $getDynamicSecretInputConfig(dynamicSecretCfg, (value) =>
kmsDecryptor({ cipherTextBlob: value }).toString()
);
const decryptedStoredInputJson = kmsDecryptor({ cipherTextBlob: dynamicSecretCfg.encryptedConfig }).toString();
const decryptedStoredInput = JSON.parse(decryptedStoredInputJson) as object;
const selectedTTL = ttl ?? dynamicSecretCfg.defaultTTL;
const { maxTTL } = dynamicSecretCfg;
@ -194,9 +168,8 @@ export const dynamicSecretLeaseServiceFactory = ({
type: KmsDataKey.SecretManager,
projectId
});
const decryptedStoredInput = $getDynamicSecretInputConfig(dynamicSecretCfg, (value) =>
kmsDecryptor({ cipherTextBlob: value }).toString()
);
const decryptedStoredInputJson = kmsDecryptor({ cipherTextBlob: dynamicSecretCfg.encryptedConfig }).toString();
const decryptedStoredInput = JSON.parse(decryptedStoredInputJson) as object;
const selectedTTL = ttl ?? dynamicSecretCfg.defaultTTL;
const { maxTTL } = dynamicSecretCfg;
@ -260,9 +233,8 @@ export const dynamicSecretLeaseServiceFactory = ({
type: KmsDataKey.SecretManager,
projectId
});
const decryptedStoredInput = $getDynamicSecretInputConfig(dynamicSecretCfg, (value) =>
kmsDecryptor({ cipherTextBlob: value }).toString()
);
const decryptedStoredInputJson = kmsDecryptor({ cipherTextBlob: dynamicSecretCfg.encryptedConfig }).toString();
const decryptedStoredInput = JSON.parse(decryptedStoredInputJson) as object;
const revokeResponse = await selectedProvider
.revoke(decryptedStoredInput, dynamicSecretLease.externalEntityId)

View File

@ -1,10 +1,8 @@
import { ForbiddenError, subject } from "@casl/ability";
import { SecretKeyEncoding } from "@app/db/schemas";
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
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 "@app/services/kms/kms-service";
import { KmsDataKey } from "@app/services/kms/kms-types";
@ -168,32 +166,15 @@ export const dynamicSecretServiceFactory = ({
throw new BadRequestError({ message: "Provided dynamic secret already exist under the folder" });
}
let dynamicSecretInputConfig = "";
const selectedProvider = dynamicSecretProviders[dynamicSecretCfg.type as DynamicSecretProviders];
const { encryptor: secretManagerEncryptor, decryptor: secretManagerDecryptor } =
await kmsService.createCipherPairWithDataKey({
type: KmsDataKey.SecretManager,
projectId
});
if (
dynamicSecretCfg.keyEncoding &&
dynamicSecretCfg.inputCiphertext &&
dynamicSecretCfg.inputTag &&
dynamicSecretCfg.inputIV
) {
dynamicSecretInputConfig = infisicalSymmetricDecrypt({
keyEncoding: dynamicSecretCfg.keyEncoding as SecretKeyEncoding,
ciphertext: dynamicSecretCfg.inputCiphertext,
tag: dynamicSecretCfg.inputTag,
iv: dynamicSecretCfg.inputIV
});
} else if (dynamicSecretCfg.encryptedConfig) {
dynamicSecretInputConfig = secretManagerDecryptor({
cipherTextBlob: dynamicSecretCfg.encryptedConfig
}).toString();
} else {
throw new BadRequestError({ message: "Missing secret input config" });
}
const dynamicSecretInputConfig = secretManagerDecryptor({
cipherTextBlob: dynamicSecretCfg.encryptedConfig
}).toString();
const decryptedStoredInput = JSON.parse(dynamicSecretInputConfig) as object;
const newInput = { ...decryptedStoredInput, ...(inputs || {}) };
@ -208,11 +189,6 @@ export const dynamicSecretServiceFactory = ({
const updatedDynamicCfg = await dynamicSecretDAL.updateById(dynamicSecretCfg.id, {
encryptedConfig,
inputIV: null,
inputTag: null,
keyEncoding: null,
algorithm: null,
inputCiphertext: null,
maxTTL,
defaultTTL,
name: newName ?? name,
@ -318,26 +294,9 @@ export const dynamicSecretServiceFactory = ({
projectId
});
let dynamicSecretInputConfig = "";
if (
dynamicSecretCfg.keyEncoding &&
dynamicSecretCfg.inputCiphertext &&
dynamicSecretCfg.inputTag &&
dynamicSecretCfg.inputIV
) {
dynamicSecretInputConfig = infisicalSymmetricDecrypt({
keyEncoding: dynamicSecretCfg.keyEncoding as SecretKeyEncoding,
ciphertext: dynamicSecretCfg.inputCiphertext,
tag: dynamicSecretCfg.inputTag,
iv: dynamicSecretCfg.inputIV
});
} else if (dynamicSecretCfg.encryptedConfig) {
dynamicSecretInputConfig = secretManagerDecryptor({
cipherTextBlob: dynamicSecretCfg.encryptedConfig
}).toString();
} else {
throw new BadRequestError({ message: "Missing secret input config" });
}
const dynamicSecretInputConfig = secretManagerDecryptor({
cipherTextBlob: dynamicSecretCfg.encryptedConfig
}).toString();
const decryptedStoredInput = JSON.parse(dynamicSecretInputConfig) as object;
const selectedProvider = dynamicSecretProviders[dynamicSecretCfg.type as DynamicSecretProviders];

View File

@ -791,10 +791,19 @@ export const snapshotDALFactory = (db: TDbClient) => {
const deleteSnapshotsAboveLimit = async (folderId: string, n = 15, tx?: Knex) => {
try {
const query = await (tx || db.replicaNode())(TableName.Snapshot)
.orderBy(`${TableName.Snapshot}.createdAt`, "desc")
.where(`${TableName.Snapshot}.folderId`, folderId)
.offset(n)
const query = await (tx || db)
.with("to_delete", (qb) => {
void qb
.select("id")
.from(TableName.Snapshot)
.where("folderId", folderId)
.orderBy("createdAt", "desc")
.offset(n);
})
.from(TableName.Snapshot)
.whereIn("id", (qb) => {
void qb.select("id").from("to_delete");
})
.delete();
return query;
} catch (error) {

View File

@ -226,8 +226,9 @@ export const infisicalSymmetricDecrypt = <T = string>({
keyEncoding: SecretKeyEncoding;
}) => {
const appCfg = getConfig();
const rootEncryptionKey = appCfg.ROOT_ENCRYPTION_KEY;
const encryptionKey = appCfg.ENCRYPTION_KEY;
// the or gate is used used in migration
const rootEncryptionKey = appCfg?.ROOT_ENCRYPTION_KEY || process.env.ROOT_ENCRYPTION_KEY;
const encryptionKey = appCfg?.ENCRYPTION_KEY || process.env.ENCRYPTION_KEY;
if (rootEncryptionKey && keyEncoding === SecretKeyEncoding.BASE64) {
const data = decryptSymmetric({ key: rootEncryptionKey, iv, tag, ciphertext });
return data as T;

View File

@ -725,8 +725,7 @@ export const registerRoutes = async (
integrationAuthDAL,
snapshotDAL,
snapshotSecretV2BridgeDAL,
secretApprovalRequestDAL,
dynamicSecretDAL
secretApprovalRequestDAL
});
const secretImportService = secretImportServiceFactory({
licenseService,

View File

@ -129,11 +129,7 @@ export const SanitizedRoleSchema = ProjectRolesSchema.extend({
});
export const SanitizedDynamicSecretSchema = DynamicSecretsSchema.omit({
inputIV: true,
inputTag: true,
inputCiphertext: true,
keyEncoding: true,
algorithm: true
encryptedConfig: true
});
export const SanitizedAuditLogStreamSchema = z.object({

View File

@ -8,25 +8,24 @@ import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
import { WebhookType } from "@app/services/webhook/webhook-types";
export const sanitizedWebhookSchema = WebhooksSchema.omit({
encryptedSecretKey: true,
iv: true,
tag: true,
algorithm: true,
keyEncoding: true,
urlCipherText: true,
urlIV: true,
urlTag: true
}).merge(
z.object({
projectId: z.string(),
environment: z.object({
id: z.string(),
name: z.string(),
slug: z.string()
})
export const sanitizedWebhookSchema = WebhooksSchema.pick({
id: true,
secretPath: true,
lastStatus: true,
lastRunErrorMessage: true,
isDisabled: true,
createdAt: true,
updatedAt: true,
envId: true,
type: true
}).extend({
projectId: z.string(),
environment: z.object({
id: z.string(),
name: z.string(),
slug: z.string()
})
);
});
export const registerWebhookRouter = async (server: FastifyZodProvider) => {
server.route({
@ -228,7 +227,7 @@ export const registerWebhookRouter = async (server: FastifyZodProvider) => {
response: {
200: z.object({
message: z.string(),
webhooks: sanitizedWebhookSchema.array()
webhooks: sanitizedWebhookSchema.extend({ url: z.string() }).array()
})
}
},

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

View File

@ -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 (

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

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 },

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

View File

@ -22,7 +22,6 @@ enum ProjectUpgradeStatus {
const formSchema = z.object({
isCLIChecked: z.literal(true),
isOperatorChecked: z.literal(true),
doesKnowSnapshotLimit: z.literal(true),
shouldCloseOpenApprovals: z.literal(true)
});
@ -143,22 +142,6 @@ export const SecretV2MigrationSection = () => {
</Checkbox>
)}
/>
<Controller
control={control}
name="doesKnowSnapshotLimit"
defaultValue={false}
render={({ field: { onBlur, value, onChange }, fieldState: { error } }) => (
<Checkbox
id="is-snapshot-checked"
isChecked={value}
onCheckedChange={onChange}
onBlur={onBlur}
isError={Boolean(error?.message)}
>
Folders keep 10 latest snapshots due to migration time limit.
</Checkbox>
)}
/>
<Controller
control={control}
name="shouldCloseOpenApprovals"