mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-06 21:27:10 +00:00
feat: updated codebase for new field changes made on project level enc migration
This commit is contained in:
@@ -33,7 +33,18 @@ export async function up(knex: Knex): Promise<void> {
|
|||||||
const webhooks = await knex(TableName.Webhook)
|
const webhooks = await knex(TableName.Webhook)
|
||||||
.where({})
|
.where({})
|
||||||
.leftJoin(TableName.Environment, `${TableName.Environment}.id`, `${TableName.Webhook}.envId`)
|
.leftJoin(TableName.Environment, `${TableName.Environment}.id`, `${TableName.Webhook}.envId`)
|
||||||
.select("url", "encryptedSecretKey", "iv", "tag", "keyEncoding", "urlCipherText", "urlIV", "urlTag", "id", "envId")
|
.select(
|
||||||
|
"url",
|
||||||
|
"encryptedSecretKey",
|
||||||
|
"iv",
|
||||||
|
"tag",
|
||||||
|
"keyEncoding",
|
||||||
|
"urlCipherText",
|
||||||
|
"urlIV",
|
||||||
|
"urlTag",
|
||||||
|
knex.ref("id").withSchema(TableName.Webhook),
|
||||||
|
"envId"
|
||||||
|
)
|
||||||
.select(knex.ref("projectId").withSchema(TableName.Environment));
|
.select(knex.ref("projectId").withSchema(TableName.Environment));
|
||||||
|
|
||||||
const updatedWebhooks = await Promise.all(
|
const updatedWebhooks = await Promise.all(
|
||||||
@@ -55,7 +66,9 @@ export async function up(knex: Knex): Promise<void> {
|
|||||||
tag: el.tag,
|
tag: el.tag,
|
||||||
ciphertext: el.encryptedSecretKey
|
ciphertext: el.encryptedSecretKey
|
||||||
});
|
});
|
||||||
encryptedSecretKey = projectKmsService.encryptor({ plainText: Buffer.from(decyptedSecretKey, "utf8") });
|
encryptedSecretKey = projectKmsService.encryptor({
|
||||||
|
plainText: Buffer.from(decyptedSecretKey, "utf8")
|
||||||
|
}).cipherTextBlob;
|
||||||
}
|
}
|
||||||
|
|
||||||
const decryptedUrl =
|
const decryptedUrl =
|
||||||
@@ -68,7 +81,9 @@ export async function up(knex: Knex): Promise<void> {
|
|||||||
})
|
})
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
const encryptedUrl = projectKmsService.encryptor({ plainText: Buffer.from(decryptedUrl || el.url) });
|
const encryptedUrl = projectKmsService.encryptor({
|
||||||
|
plainText: Buffer.from(decryptedUrl || el.url)
|
||||||
|
}).cipherTextBlob;
|
||||||
return { id: el.id, encryptedUrl, encryptedSecretKey, envId: el.envId };
|
return { id: el.id, encryptedUrl, encryptedSecretKey, envId: el.envId };
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { Knex } from "knex";
|
|||||||
import { inMemoryKeyStore } from "@app/keystore/memory";
|
import { inMemoryKeyStore } from "@app/keystore/memory";
|
||||||
import { infisicalSymmetricDecrypt } from "@app/lib/crypto/encryption";
|
import { infisicalSymmetricDecrypt } from "@app/lib/crypto/encryption";
|
||||||
import { selectAllTableCols } from "@app/lib/knex";
|
import { selectAllTableCols } from "@app/lib/knex";
|
||||||
|
import { initLogger } from "@app/lib/logger";
|
||||||
import { KmsDataKey } from "@app/services/kms/kms-types";
|
import { KmsDataKey } from "@app/services/kms/kms-types";
|
||||||
|
|
||||||
import { SecretKeyEncoding, TableName } from "../schemas";
|
import { SecretKeyEncoding, TableName } from "../schemas";
|
||||||
@@ -21,6 +22,7 @@ export async function up(knex: Knex): Promise<void> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await initLogger();
|
||||||
const envConfig = getMigrationEnvConfig();
|
const envConfig = getMigrationEnvConfig();
|
||||||
const keyStore = inMemoryKeyStore();
|
const keyStore = inMemoryKeyStore();
|
||||||
const { kmsService } = await getMigrationEncryptionServices({ envConfig, keyStore, db: knex });
|
const { kmsService } = await getMigrationEncryptionServices({ envConfig, keyStore, db: knex });
|
||||||
@@ -33,31 +35,39 @@ export async function up(knex: Knex): Promise<void> {
|
|||||||
.select(knex.ref("projectId").withSchema(TableName.Environment));
|
.select(knex.ref("projectId").withSchema(TableName.Environment));
|
||||||
|
|
||||||
const updatedRotationData = await Promise.all(
|
const updatedRotationData = await Promise.all(
|
||||||
secretRotations.map(async (el) => {
|
secretRotations.map(async ({ projectId, ...el }) => {
|
||||||
let projectKmsService = projectEncryptionRingBuffer.getItem(el.projectId);
|
let projectKmsService = projectEncryptionRingBuffer.getItem(projectId);
|
||||||
if (!projectKmsService) {
|
if (!projectKmsService) {
|
||||||
projectKmsService = await kmsService.createCipherPairWithDataKey({
|
projectKmsService = await kmsService.createCipherPairWithDataKey({
|
||||||
type: KmsDataKey.SecretManager,
|
type: KmsDataKey.SecretManager,
|
||||||
projectId: el.projectId
|
projectId
|
||||||
});
|
});
|
||||||
projectEncryptionRingBuffer.push(el.projectId, projectKmsService);
|
projectEncryptionRingBuffer.push(projectId, projectKmsService);
|
||||||
}
|
}
|
||||||
|
|
||||||
const decryptedRotationData =
|
const decryptedRotationData =
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore This will be removed in next cycle so ignore the ts missing error
|
||||||
el.encryptedDataTag && el.encryptedDataIV && el.encryptedData && el.keyEncoding
|
el.encryptedDataTag && el.encryptedDataIV && el.encryptedData && el.keyEncoding
|
||||||
? infisicalSymmetricDecrypt({
|
? infisicalSymmetricDecrypt({
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore This will be removed in next cycle so ignore the ts missing error
|
||||||
keyEncoding: el.keyEncoding as SecretKeyEncoding,
|
keyEncoding: el.keyEncoding as SecretKeyEncoding,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore This will be removed in next cycle so ignore the ts missing error
|
||||||
iv: el.encryptedDataIV,
|
iv: el.encryptedDataIV,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore This will be removed in next cycle so ignore the ts missing error
|
||||||
tag: el.encryptedDataTag,
|
tag: el.encryptedDataTag,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore This will be removed in next cycle so ignore the ts missing error
|
||||||
ciphertext: el.encryptedData
|
ciphertext: el.encryptedData
|
||||||
})
|
})
|
||||||
: null;
|
: "";
|
||||||
|
|
||||||
const encryptedRotationData = decryptedRotationData
|
const encryptedRotationData = projectKmsService.encryptor({
|
||||||
? projectKmsService.encryptor({
|
plainText: Buffer.from(decryptedRotationData)
|
||||||
plainText: Buffer.from(decryptedRotationData)
|
}).cipherTextBlob;
|
||||||
})
|
|
||||||
: null;
|
|
||||||
return { ...el, encryptedRotationData };
|
return { ...el, encryptedRotationData };
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { Knex } from "knex";
|
|||||||
import { inMemoryKeyStore } from "@app/keystore/memory";
|
import { inMemoryKeyStore } from "@app/keystore/memory";
|
||||||
import { infisicalSymmetricDecrypt } from "@app/lib/crypto/encryption";
|
import { infisicalSymmetricDecrypt } from "@app/lib/crypto/encryption";
|
||||||
import { selectAllTableCols } from "@app/lib/knex";
|
import { selectAllTableCols } from "@app/lib/knex";
|
||||||
|
import { initLogger } from "@app/lib/logger";
|
||||||
import { KmsDataKey } from "@app/services/kms/kms-types";
|
import { KmsDataKey } from "@app/services/kms/kms-types";
|
||||||
|
|
||||||
import { SecretKeyEncoding, TableName } from "../schemas";
|
import { SecretKeyEncoding, TableName } from "../schemas";
|
||||||
@@ -21,6 +22,7 @@ export async function up(knex: Knex): Promise<void> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await initLogger();
|
||||||
const envConfig = getMigrationEnvConfig();
|
const envConfig = getMigrationEnvConfig();
|
||||||
const keyStore = inMemoryKeyStore();
|
const keyStore = inMemoryKeyStore();
|
||||||
const { kmsService } = await getMigrationEncryptionServices({ envConfig, keyStore, db: knex });
|
const { kmsService } = await getMigrationEncryptionServices({ envConfig, keyStore, db: knex });
|
||||||
@@ -34,31 +36,40 @@ export async function up(knex: Knex): Promise<void> {
|
|||||||
.select(knex.ref("projectId").withSchema(TableName.Environment));
|
.select(knex.ref("projectId").withSchema(TableName.Environment));
|
||||||
|
|
||||||
const updatedDynamicSecrets = await Promise.all(
|
const updatedDynamicSecrets = await Promise.all(
|
||||||
dynamicSecretRootCredentials.map(async (el) => {
|
dynamicSecretRootCredentials.map(async ({ projectId, ...el }) => {
|
||||||
let projectKmsService = projectEncryptionRingBuffer.getItem(el.projectId);
|
let projectKmsService = projectEncryptionRingBuffer.getItem(projectId);
|
||||||
if (!projectKmsService) {
|
if (!projectKmsService) {
|
||||||
projectKmsService = await kmsService.createCipherPairWithDataKey({
|
projectKmsService = await kmsService.createCipherPairWithDataKey({
|
||||||
type: KmsDataKey.SecretManager,
|
type: KmsDataKey.SecretManager,
|
||||||
projectId: el.projectId
|
projectId
|
||||||
});
|
});
|
||||||
projectEncryptionRingBuffer.push(el.projectId, projectKmsService);
|
projectEncryptionRingBuffer.push(projectId, projectKmsService);
|
||||||
}
|
}
|
||||||
|
|
||||||
const decryptedInputData =
|
const decryptedInputData =
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore This will be removed in next cycle so ignore the ts missing error
|
||||||
el.inputIV && el.inputTag && el.inputCiphertext && el.keyEncoding
|
el.inputIV && el.inputTag && el.inputCiphertext && el.keyEncoding
|
||||||
? infisicalSymmetricDecrypt({
|
? infisicalSymmetricDecrypt({
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore This will be removed in next cycle so ignore the ts missing error
|
||||||
keyEncoding: el.keyEncoding as SecretKeyEncoding,
|
keyEncoding: el.keyEncoding as SecretKeyEncoding,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore This will be removed in next cycle so ignore the ts missing error
|
||||||
iv: el.inputIV,
|
iv: el.inputIV,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore This will be removed in next cycle so ignore the ts missing error
|
||||||
tag: el.inputTag,
|
tag: el.inputTag,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore This will be removed in next cycle so ignore the ts missing error
|
||||||
ciphertext: el.inputCiphertext
|
ciphertext: el.inputCiphertext
|
||||||
})
|
})
|
||||||
: null;
|
: "";
|
||||||
|
|
||||||
|
const encryptedInput = projectKmsService.encryptor({
|
||||||
|
plainText: Buffer.from(decryptedInputData)
|
||||||
|
}).cipherTextBlob;
|
||||||
|
|
||||||
const encryptedInput = decryptedInputData
|
|
||||||
? projectKmsService.encryptor({
|
|
||||||
plainText: Buffer.from(decryptedInputData)
|
|
||||||
})
|
|
||||||
: null;
|
|
||||||
return { ...el, encryptedInput };
|
return { ...el, encryptedInput };
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -19,6 +19,8 @@ type TDependencies = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const getMigrationEncryptionServices = async ({ envConfig, db, keyStore }: TDependencies) => {
|
export const getMigrationEncryptionServices = async ({ envConfig, db, keyStore }: TDependencies) => {
|
||||||
|
// eslint-disable-next-line no-param-reassign
|
||||||
|
db.replicaNode = () => db;
|
||||||
const hsmModule = initializeHsmModule(envConfig);
|
const hsmModule = initializeHsmModule(envConfig);
|
||||||
hsmModule.initialize();
|
hsmModule.initialize();
|
||||||
|
|
||||||
@@ -45,6 +47,7 @@ export const getMigrationEncryptionServices = async ({ envConfig, db, keyStore }
|
|||||||
});
|
});
|
||||||
|
|
||||||
await hsmService.startService();
|
await hsmService.startService();
|
||||||
|
await kmsService.startService();
|
||||||
|
|
||||||
return { kmsService };
|
return { kmsService };
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -5,6 +5,8 @@
|
|||||||
|
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { zodBuffer } from "@app/lib/zod";
|
||||||
|
|
||||||
import { TImmutableDBKeys } from "./models";
|
import { TImmutableDBKeys } from "./models";
|
||||||
|
|
||||||
export const DynamicSecretsSchema = z.object({
|
export const DynamicSecretsSchema = z.object({
|
||||||
@@ -23,7 +25,8 @@ export const DynamicSecretsSchema = z.object({
|
|||||||
status: z.string().nullable().optional(),
|
status: z.string().nullable().optional(),
|
||||||
statusDetails: z.string().nullable().optional(),
|
statusDetails: z.string().nullable().optional(),
|
||||||
createdAt: z.date(),
|
createdAt: z.date(),
|
||||||
updatedAt: z.date()
|
updatedAt: z.date(),
|
||||||
|
encryptedInput: zodBuffer
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TDynamicSecrets = z.infer<typeof DynamicSecretsSchema>;
|
export type TDynamicSecrets = z.infer<typeof DynamicSecretsSchema>;
|
||||||
|
|||||||
@@ -5,6 +5,8 @@
|
|||||||
|
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { zodBuffer } from "@app/lib/zod";
|
||||||
|
|
||||||
import { TImmutableDBKeys } from "./models";
|
import { TImmutableDBKeys } from "./models";
|
||||||
|
|
||||||
export const SecretRotationsSchema = z.object({
|
export const SecretRotationsSchema = z.object({
|
||||||
@@ -22,7 +24,8 @@ export const SecretRotationsSchema = z.object({
|
|||||||
keyEncoding: z.string().nullable().optional(),
|
keyEncoding: z.string().nullable().optional(),
|
||||||
envId: z.string().uuid(),
|
envId: z.string().uuid(),
|
||||||
createdAt: z.date(),
|
createdAt: z.date(),
|
||||||
updatedAt: z.date()
|
updatedAt: z.date(),
|
||||||
|
encryptedRotationData: zodBuffer
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TSecretRotations = z.infer<typeof SecretRotationsSchema>;
|
export type TSecretRotations = z.infer<typeof SecretRotationsSchema>;
|
||||||
|
|||||||
@@ -5,6 +5,8 @@
|
|||||||
|
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { zodBuffer } from "@app/lib/zod";
|
||||||
|
|
||||||
import { TImmutableDBKeys } from "./models";
|
import { TImmutableDBKeys } from "./models";
|
||||||
|
|
||||||
export const WebhooksSchema = z.object({
|
export const WebhooksSchema = z.object({
|
||||||
@@ -25,7 +27,9 @@ export const WebhooksSchema = z.object({
|
|||||||
urlCipherText: z.string().nullable().optional(),
|
urlCipherText: z.string().nullable().optional(),
|
||||||
urlIV: z.string().nullable().optional(),
|
urlIV: z.string().nullable().optional(),
|
||||||
urlTag: z.string().nullable().optional(),
|
urlTag: z.string().nullable().optional(),
|
||||||
type: z.string().default("general").nullable().optional()
|
type: z.string().default("general").nullable().optional(),
|
||||||
|
encryptedPassKey: zodBuffer.nullable().optional(),
|
||||||
|
encryptedUrl: zodBuffer
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TWebhooks = z.infer<typeof WebhooksSchema>;
|
export type TWebhooks = z.infer<typeof WebhooksSchema>;
|
||||||
|
|||||||
@@ -37,11 +37,7 @@ export const dynamicSecretLeaseDALFactory = (db: TDbClient) => {
|
|||||||
db.ref("type").withSchema(TableName.DynamicSecret).as("dynType"),
|
db.ref("type").withSchema(TableName.DynamicSecret).as("dynType"),
|
||||||
db.ref("defaultTTL").withSchema(TableName.DynamicSecret).as("dynDefaultTTL"),
|
db.ref("defaultTTL").withSchema(TableName.DynamicSecret).as("dynDefaultTTL"),
|
||||||
db.ref("maxTTL").withSchema(TableName.DynamicSecret).as("dynMaxTTL"),
|
db.ref("maxTTL").withSchema(TableName.DynamicSecret).as("dynMaxTTL"),
|
||||||
db.ref("inputIV").withSchema(TableName.DynamicSecret).as("dynInputIV"),
|
db.ref("encryptedInput").withSchema(TableName.DynamicSecret).as("dynEncryptedInput"),
|
||||||
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("folderId").withSchema(TableName.DynamicSecret).as("dynFolderId"),
|
||||||
db.ref("status").withSchema(TableName.DynamicSecret).as("dynStatus"),
|
db.ref("status").withSchema(TableName.DynamicSecret).as("dynStatus"),
|
||||||
db.ref("statusDetails").withSchema(TableName.DynamicSecret).as("dynStatusDetails"),
|
db.ref("statusDetails").withSchema(TableName.DynamicSecret).as("dynStatusDetails"),
|
||||||
@@ -59,11 +55,7 @@ export const dynamicSecretLeaseDALFactory = (db: TDbClient) => {
|
|||||||
type: doc.dynType,
|
type: doc.dynType,
|
||||||
defaultTTL: doc.dynDefaultTTL,
|
defaultTTL: doc.dynDefaultTTL,
|
||||||
maxTTL: doc.dynMaxTTL,
|
maxTTL: doc.dynMaxTTL,
|
||||||
inputIV: doc.dynInputIV,
|
encryptedInput: doc.dynEncryptedInput,
|
||||||
inputTag: doc.dynInputTag,
|
|
||||||
inputCiphertext: doc.dynInputCiphertext,
|
|
||||||
algorithm: doc.dynAlgorithm,
|
|
||||||
keyEncoding: doc.dynKeyEncoding,
|
|
||||||
folderId: doc.dynFolderId,
|
folderId: doc.dynFolderId,
|
||||||
status: doc.dynStatus,
|
status: doc.dynStatus,
|
||||||
statusDetails: doc.dynStatusDetails,
|
statusDetails: doc.dynStatusDetails,
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
import { SecretKeyEncoding } from "@app/db/schemas";
|
|
||||||
import { DisableRotationErrors } from "@app/ee/services/secret-rotation/secret-rotation-queue";
|
import { DisableRotationErrors } from "@app/ee/services/secret-rotation/secret-rotation-queue";
|
||||||
import { infisicalSymmetricDecrypt } from "@app/lib/crypto/encryption";
|
import { NotFoundError } from "@app/lib/errors";
|
||||||
import { logger } from "@app/lib/logger";
|
import { logger } from "@app/lib/logger";
|
||||||
import { QueueJobs, QueueName, TQueueServiceFactory } from "@app/queue";
|
import { QueueJobs, QueueName, TQueueServiceFactory } from "@app/queue";
|
||||||
|
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
|
||||||
|
import { KmsDataKey } from "@app/services/kms/kms-types";
|
||||||
|
import { TSecretFolderDALFactory } from "@app/services/secret-folder/secret-folder-dal";
|
||||||
|
|
||||||
import { TDynamicSecretDALFactory } from "../dynamic-secret/dynamic-secret-dal";
|
import { TDynamicSecretDALFactory } from "../dynamic-secret/dynamic-secret-dal";
|
||||||
import { DynamicSecretStatus } from "../dynamic-secret/dynamic-secret-types";
|
import { DynamicSecretStatus } from "../dynamic-secret/dynamic-secret-types";
|
||||||
@@ -14,6 +16,8 @@ type TDynamicSecretLeaseQueueServiceFactoryDep = {
|
|||||||
dynamicSecretLeaseDAL: Pick<TDynamicSecretLeaseDALFactory, "findById" | "deleteById" | "find" | "updateById">;
|
dynamicSecretLeaseDAL: Pick<TDynamicSecretLeaseDALFactory, "findById" | "deleteById" | "find" | "updateById">;
|
||||||
dynamicSecretDAL: Pick<TDynamicSecretDALFactory, "findById" | "deleteById" | "updateById">;
|
dynamicSecretDAL: Pick<TDynamicSecretDALFactory, "findById" | "deleteById" | "updateById">;
|
||||||
dynamicSecretProviders: Record<DynamicSecretProviders, TDynamicProviderFns>;
|
dynamicSecretProviders: Record<DynamicSecretProviders, TDynamicProviderFns>;
|
||||||
|
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">;
|
||||||
|
folderDAL: Pick<TSecretFolderDALFactory, "findById">;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TDynamicSecretLeaseQueueServiceFactory = ReturnType<typeof dynamicSecretLeaseQueueServiceFactory>;
|
export type TDynamicSecretLeaseQueueServiceFactory = ReturnType<typeof dynamicSecretLeaseQueueServiceFactory>;
|
||||||
@@ -22,7 +26,9 @@ export const dynamicSecretLeaseQueueServiceFactory = ({
|
|||||||
queueService,
|
queueService,
|
||||||
dynamicSecretDAL,
|
dynamicSecretDAL,
|
||||||
dynamicSecretProviders,
|
dynamicSecretProviders,
|
||||||
dynamicSecretLeaseDAL
|
dynamicSecretLeaseDAL,
|
||||||
|
kmsService,
|
||||||
|
folderDAL
|
||||||
}: TDynamicSecretLeaseQueueServiceFactoryDep) => {
|
}: TDynamicSecretLeaseQueueServiceFactoryDep) => {
|
||||||
const pruneDynamicSecret = async (dynamicSecretCfgId: string) => {
|
const pruneDynamicSecret = async (dynamicSecretCfgId: string) => {
|
||||||
await queueService.queue(
|
await queueService.queue(
|
||||||
@@ -76,15 +82,21 @@ export const dynamicSecretLeaseQueueServiceFactory = ({
|
|||||||
const dynamicSecretLease = await dynamicSecretLeaseDAL.findById(leaseId);
|
const dynamicSecretLease = await dynamicSecretLeaseDAL.findById(leaseId);
|
||||||
if (!dynamicSecretLease) throw new DisableRotationErrors({ message: "Dynamic secret lease not found" });
|
if (!dynamicSecretLease) throw new DisableRotationErrors({ message: "Dynamic secret lease not found" });
|
||||||
|
|
||||||
|
const folder = await folderDAL.findById(dynamicSecretLease.dynamicSecret.folderId);
|
||||||
|
if (!folder)
|
||||||
|
throw new NotFoundError({
|
||||||
|
message: `Failed to find folder with ${dynamicSecretLease.dynamicSecret.folderId}`
|
||||||
|
});
|
||||||
|
|
||||||
|
const { decryptor: secretManagerDecryptor } = await kmsService.createCipherPairWithDataKey({
|
||||||
|
type: KmsDataKey.SecretManager,
|
||||||
|
projectId: folder.projectId
|
||||||
|
});
|
||||||
|
|
||||||
const dynamicSecretCfg = dynamicSecretLease.dynamicSecret;
|
const dynamicSecretCfg = dynamicSecretLease.dynamicSecret;
|
||||||
const selectedProvider = dynamicSecretProviders[dynamicSecretCfg.type as DynamicSecretProviders];
|
const selectedProvider = dynamicSecretProviders[dynamicSecretCfg.type as DynamicSecretProviders];
|
||||||
const decryptedStoredInput = JSON.parse(
|
const decryptedStoredInput = JSON.parse(
|
||||||
infisicalSymmetricDecrypt({
|
secretManagerDecryptor({ cipherTextBlob: dynamicSecretCfg.encryptedInput }).toString()
|
||||||
keyEncoding: dynamicSecretCfg.keyEncoding as SecretKeyEncoding,
|
|
||||||
ciphertext: dynamicSecretCfg.inputCiphertext,
|
|
||||||
tag: dynamicSecretCfg.inputTag,
|
|
||||||
iv: dynamicSecretCfg.inputIV
|
|
||||||
})
|
|
||||||
) as object;
|
) as object;
|
||||||
|
|
||||||
await selectedProvider.revoke(decryptedStoredInput, dynamicSecretLease.externalEntityId);
|
await selectedProvider.revoke(decryptedStoredInput, dynamicSecretLease.externalEntityId);
|
||||||
@@ -100,16 +112,22 @@ export const dynamicSecretLeaseQueueServiceFactory = ({
|
|||||||
if ((dynamicSecretCfg.status as DynamicSecretStatus) !== DynamicSecretStatus.Deleting)
|
if ((dynamicSecretCfg.status as DynamicSecretStatus) !== DynamicSecretStatus.Deleting)
|
||||||
throw new DisableRotationErrors({ message: "Document not deleted" });
|
throw new DisableRotationErrors({ message: "Document not deleted" });
|
||||||
|
|
||||||
|
const folder = await folderDAL.findById(dynamicSecretCfg.folderId);
|
||||||
|
if (!folder)
|
||||||
|
throw new NotFoundError({
|
||||||
|
message: `Failed to find folder with ${dynamicSecretCfg.folderId}`
|
||||||
|
});
|
||||||
|
|
||||||
|
const { decryptor: secretManagerDecryptor } = await kmsService.createCipherPairWithDataKey({
|
||||||
|
type: KmsDataKey.SecretManager,
|
||||||
|
projectId: folder.projectId
|
||||||
|
});
|
||||||
|
|
||||||
const dynamicSecretLeases = await dynamicSecretLeaseDAL.find({ dynamicSecretId: dynamicSecretCfgId });
|
const dynamicSecretLeases = await dynamicSecretLeaseDAL.find({ dynamicSecretId: dynamicSecretCfgId });
|
||||||
if (dynamicSecretLeases.length) {
|
if (dynamicSecretLeases.length) {
|
||||||
const selectedProvider = dynamicSecretProviders[dynamicSecretCfg.type as DynamicSecretProviders];
|
const selectedProvider = dynamicSecretProviders[dynamicSecretCfg.type as DynamicSecretProviders];
|
||||||
const decryptedStoredInput = JSON.parse(
|
const decryptedStoredInput = JSON.parse(
|
||||||
infisicalSymmetricDecrypt({
|
secretManagerDecryptor({ cipherTextBlob: dynamicSecretCfg.encryptedInput }).toString()
|
||||||
keyEncoding: dynamicSecretCfg.keyEncoding as SecretKeyEncoding,
|
|
||||||
ciphertext: dynamicSecretCfg.inputCiphertext,
|
|
||||||
tag: dynamicSecretCfg.inputTag,
|
|
||||||
iv: dynamicSecretCfg.inputIV
|
|
||||||
})
|
|
||||||
) as object;
|
) as object;
|
||||||
|
|
||||||
await Promise.all(dynamicSecretLeases.map(({ id }) => unsetLeaseRevocation(id)));
|
await Promise.all(dynamicSecretLeases.map(({ id }) => unsetLeaseRevocation(id)));
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { ForbiddenError, subject } from "@casl/ability";
|
import { ForbiddenError, subject } from "@casl/ability";
|
||||||
import ms from "ms";
|
import ms from "ms";
|
||||||
|
|
||||||
import { ActionProjectType, SecretKeyEncoding } from "@app/db/schemas";
|
import { ActionProjectType } from "@app/db/schemas";
|
||||||
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
||||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||||
import {
|
import {
|
||||||
@@ -9,9 +9,10 @@ import {
|
|||||||
ProjectPermissionSub
|
ProjectPermissionSub
|
||||||
} from "@app/ee/services/permission/project-permission";
|
} from "@app/ee/services/permission/project-permission";
|
||||||
import { getConfig } from "@app/lib/config/env";
|
import { getConfig } from "@app/lib/config/env";
|
||||||
import { infisicalSymmetricDecrypt } from "@app/lib/crypto/encryption";
|
|
||||||
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
||||||
import { logger } from "@app/lib/logger";
|
import { logger } from "@app/lib/logger";
|
||||||
|
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
|
||||||
|
import { KmsDataKey } from "@app/services/kms/kms-types";
|
||||||
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
||||||
import { TSecretFolderDALFactory } from "@app/services/secret-folder/secret-folder-dal";
|
import { TSecretFolderDALFactory } from "@app/services/secret-folder/secret-folder-dal";
|
||||||
|
|
||||||
@@ -37,6 +38,7 @@ type TDynamicSecretLeaseServiceFactoryDep = {
|
|||||||
folderDAL: Pick<TSecretFolderDALFactory, "findBySecretPath">;
|
folderDAL: Pick<TSecretFolderDALFactory, "findBySecretPath">;
|
||||||
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
|
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
|
||||||
projectDAL: Pick<TProjectDALFactory, "findProjectBySlug">;
|
projectDAL: Pick<TProjectDALFactory, "findProjectBySlug">;
|
||||||
|
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TDynamicSecretLeaseServiceFactory = ReturnType<typeof dynamicSecretLeaseServiceFactory>;
|
export type TDynamicSecretLeaseServiceFactory = ReturnType<typeof dynamicSecretLeaseServiceFactory>;
|
||||||
@@ -49,7 +51,8 @@ export const dynamicSecretLeaseServiceFactory = ({
|
|||||||
permissionService,
|
permissionService,
|
||||||
dynamicSecretQueueService,
|
dynamicSecretQueueService,
|
||||||
projectDAL,
|
projectDAL,
|
||||||
licenseService
|
licenseService,
|
||||||
|
kmsService
|
||||||
}: TDynamicSecretLeaseServiceFactoryDep) => {
|
}: TDynamicSecretLeaseServiceFactoryDep) => {
|
||||||
const create = async ({
|
const create = async ({
|
||||||
environmentSlug,
|
environmentSlug,
|
||||||
@@ -104,13 +107,14 @@ export const dynamicSecretLeaseServiceFactory = ({
|
|||||||
throw new BadRequestError({ message: `Max lease limit reached. Limit: ${appCfg.MAX_LEASE_LIMIT}` });
|
throw new BadRequestError({ message: `Max lease limit reached. Limit: ${appCfg.MAX_LEASE_LIMIT}` });
|
||||||
|
|
||||||
const selectedProvider = dynamicSecretProviders[dynamicSecretCfg.type as DynamicSecretProviders];
|
const selectedProvider = dynamicSecretProviders[dynamicSecretCfg.type as DynamicSecretProviders];
|
||||||
|
|
||||||
|
const { decryptor: secretManagerDecryptor } = await kmsService.createCipherPairWithDataKey({
|
||||||
|
type: KmsDataKey.SecretManager,
|
||||||
|
projectId
|
||||||
|
});
|
||||||
|
|
||||||
const decryptedStoredInput = JSON.parse(
|
const decryptedStoredInput = JSON.parse(
|
||||||
infisicalSymmetricDecrypt({
|
secretManagerDecryptor({ cipherTextBlob: Buffer.from(dynamicSecretCfg.encryptedInput) }).toString()
|
||||||
keyEncoding: dynamicSecretCfg.keyEncoding as SecretKeyEncoding,
|
|
||||||
ciphertext: dynamicSecretCfg.inputCiphertext,
|
|
||||||
tag: dynamicSecretCfg.inputTag,
|
|
||||||
iv: dynamicSecretCfg.inputIV
|
|
||||||
})
|
|
||||||
) as object;
|
) as object;
|
||||||
|
|
||||||
const selectedTTL = ttl || dynamicSecretCfg.defaultTTL;
|
const selectedTTL = ttl || dynamicSecretCfg.defaultTTL;
|
||||||
@@ -160,6 +164,11 @@ export const dynamicSecretLeaseServiceFactory = ({
|
|||||||
subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path })
|
subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path })
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const { decryptor: secretManagerDecryptor } = await kmsService.createCipherPairWithDataKey({
|
||||||
|
type: KmsDataKey.SecretManager,
|
||||||
|
projectId
|
||||||
|
});
|
||||||
|
|
||||||
const plan = await licenseService.getPlan(actorOrgId);
|
const plan = await licenseService.getPlan(actorOrgId);
|
||||||
if (!plan?.dynamicSecret) {
|
if (!plan?.dynamicSecret) {
|
||||||
throw new BadRequestError({
|
throw new BadRequestError({
|
||||||
@@ -181,12 +190,7 @@ export const dynamicSecretLeaseServiceFactory = ({
|
|||||||
const dynamicSecretCfg = dynamicSecretLease.dynamicSecret;
|
const dynamicSecretCfg = dynamicSecretLease.dynamicSecret;
|
||||||
const selectedProvider = dynamicSecretProviders[dynamicSecretCfg.type as DynamicSecretProviders];
|
const selectedProvider = dynamicSecretProviders[dynamicSecretCfg.type as DynamicSecretProviders];
|
||||||
const decryptedStoredInput = JSON.parse(
|
const decryptedStoredInput = JSON.parse(
|
||||||
infisicalSymmetricDecrypt({
|
secretManagerDecryptor({ cipherTextBlob: Buffer.from(dynamicSecretCfg.encryptedInput) }).toString()
|
||||||
keyEncoding: dynamicSecretCfg.keyEncoding as SecretKeyEncoding,
|
|
||||||
ciphertext: dynamicSecretCfg.inputCiphertext,
|
|
||||||
tag: dynamicSecretCfg.inputTag,
|
|
||||||
iv: dynamicSecretCfg.inputIV
|
|
||||||
})
|
|
||||||
) as object;
|
) as object;
|
||||||
|
|
||||||
const selectedTTL = ttl || dynamicSecretCfg.defaultTTL;
|
const selectedTTL = ttl || dynamicSecretCfg.defaultTTL;
|
||||||
@@ -240,6 +244,11 @@ export const dynamicSecretLeaseServiceFactory = ({
|
|||||||
subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path })
|
subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path })
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const { decryptor: secretManagerDecryptor } = await kmsService.createCipherPairWithDataKey({
|
||||||
|
type: KmsDataKey.SecretManager,
|
||||||
|
projectId
|
||||||
|
});
|
||||||
|
|
||||||
const folder = await folderDAL.findBySecretPath(projectId, environmentSlug, path);
|
const folder = await folderDAL.findBySecretPath(projectId, environmentSlug, path);
|
||||||
if (!folder)
|
if (!folder)
|
||||||
throw new NotFoundError({
|
throw new NotFoundError({
|
||||||
@@ -253,12 +262,7 @@ export const dynamicSecretLeaseServiceFactory = ({
|
|||||||
const dynamicSecretCfg = dynamicSecretLease.dynamicSecret;
|
const dynamicSecretCfg = dynamicSecretLease.dynamicSecret;
|
||||||
const selectedProvider = dynamicSecretProviders[dynamicSecretCfg.type as DynamicSecretProviders];
|
const selectedProvider = dynamicSecretProviders[dynamicSecretCfg.type as DynamicSecretProviders];
|
||||||
const decryptedStoredInput = JSON.parse(
|
const decryptedStoredInput = JSON.parse(
|
||||||
infisicalSymmetricDecrypt({
|
secretManagerDecryptor({ cipherTextBlob: Buffer.from(dynamicSecretCfg.encryptedInput) }).toString()
|
||||||
keyEncoding: dynamicSecretCfg.keyEncoding as SecretKeyEncoding,
|
|
||||||
ciphertext: dynamicSecretCfg.inputCiphertext,
|
|
||||||
tag: dynamicSecretCfg.inputTag,
|
|
||||||
iv: dynamicSecretCfg.inputIV
|
|
||||||
})
|
|
||||||
) as object;
|
) as object;
|
||||||
|
|
||||||
const revokeResponse = await selectedProvider
|
const revokeResponse = await selectedProvider
|
||||||
|
|||||||
@@ -1,15 +1,16 @@
|
|||||||
import { ForbiddenError, subject } from "@casl/ability";
|
import { ForbiddenError, subject } from "@casl/ability";
|
||||||
|
|
||||||
import { ActionProjectType, SecretKeyEncoding } from "@app/db/schemas";
|
import { ActionProjectType } from "@app/db/schemas";
|
||||||
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
||||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||||
import {
|
import {
|
||||||
ProjectPermissionDynamicSecretActions,
|
ProjectPermissionDynamicSecretActions,
|
||||||
ProjectPermissionSub
|
ProjectPermissionSub
|
||||||
} from "@app/ee/services/permission/project-permission";
|
} from "@app/ee/services/permission/project-permission";
|
||||||
import { infisicalSymmetricDecrypt, infisicalSymmetricEncypt } from "@app/lib/crypto/encryption";
|
|
||||||
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
||||||
import { OrderByDirection, OrgServiceActor } from "@app/lib/types";
|
import { OrderByDirection, OrgServiceActor } from "@app/lib/types";
|
||||||
|
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
|
||||||
|
import { KmsDataKey } from "@app/services/kms/kms-types";
|
||||||
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
||||||
import { TSecretFolderDALFactory } from "@app/services/secret-folder/secret-folder-dal";
|
import { TSecretFolderDALFactory } from "@app/services/secret-folder/secret-folder-dal";
|
||||||
|
|
||||||
@@ -42,6 +43,7 @@ type TDynamicSecretServiceFactoryDep = {
|
|||||||
folderDAL: Pick<TSecretFolderDALFactory, "findBySecretPath" | "findBySecretPathMultiEnv">;
|
folderDAL: Pick<TSecretFolderDALFactory, "findBySecretPath" | "findBySecretPathMultiEnv">;
|
||||||
projectDAL: Pick<TProjectDALFactory, "findProjectBySlug">;
|
projectDAL: Pick<TProjectDALFactory, "findProjectBySlug">;
|
||||||
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
|
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
|
||||||
|
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TDynamicSecretServiceFactory = ReturnType<typeof dynamicSecretServiceFactory>;
|
export type TDynamicSecretServiceFactory = ReturnType<typeof dynamicSecretServiceFactory>;
|
||||||
@@ -54,7 +56,8 @@ export const dynamicSecretServiceFactory = ({
|
|||||||
dynamicSecretProviders,
|
dynamicSecretProviders,
|
||||||
permissionService,
|
permissionService,
|
||||||
dynamicSecretQueueService,
|
dynamicSecretQueueService,
|
||||||
projectDAL
|
projectDAL,
|
||||||
|
kmsService
|
||||||
}: TDynamicSecretServiceFactoryDep) => {
|
}: TDynamicSecretServiceFactoryDep) => {
|
||||||
const create = async ({
|
const create = async ({
|
||||||
path,
|
path,
|
||||||
@@ -108,16 +111,15 @@ export const dynamicSecretServiceFactory = ({
|
|||||||
const isConnected = await selectedProvider.validateConnection(provider.inputs);
|
const isConnected = await selectedProvider.validateConnection(provider.inputs);
|
||||||
if (!isConnected) throw new BadRequestError({ message: "Provider connection failed" });
|
if (!isConnected) throw new BadRequestError({ message: "Provider connection failed" });
|
||||||
|
|
||||||
const encryptedInput = infisicalSymmetricEncypt(JSON.stringify(inputs));
|
const { encryptor: secretManagerEncryptor } = await kmsService.createCipherPairWithDataKey({
|
||||||
|
type: KmsDataKey.SecretManager,
|
||||||
|
projectId
|
||||||
|
});
|
||||||
|
|
||||||
const dynamicSecretCfg = await dynamicSecretDAL.create({
|
const dynamicSecretCfg = await dynamicSecretDAL.create({
|
||||||
type: provider.type,
|
type: provider.type,
|
||||||
version: 1,
|
version: 1,
|
||||||
inputIV: encryptedInput.iv,
|
encryptedInput: secretManagerEncryptor({ plainText: Buffer.from(JSON.stringify(inputs)) }).cipherTextBlob,
|
||||||
inputTag: encryptedInput.tag,
|
|
||||||
inputCiphertext: encryptedInput.ciphertext,
|
|
||||||
algorithm: encryptedInput.algorithm,
|
|
||||||
keyEncoding: encryptedInput.encoding,
|
|
||||||
maxTTL,
|
maxTTL,
|
||||||
defaultTTL,
|
defaultTTL,
|
||||||
folderId: folder.id,
|
folderId: folder.id,
|
||||||
@@ -180,15 +182,15 @@ export const dynamicSecretServiceFactory = ({
|
|||||||
if (existingDynamicSecret)
|
if (existingDynamicSecret)
|
||||||
throw new BadRequestError({ message: "Provided dynamic secret already exist under the folder" });
|
throw new BadRequestError({ message: "Provided dynamic secret already exist under the folder" });
|
||||||
}
|
}
|
||||||
|
const { encryptor: secretManagerEncryptor, decryptor: secretManagerDecryptor } =
|
||||||
|
await kmsService.createCipherPairWithDataKey({
|
||||||
|
type: KmsDataKey.SecretManager,
|
||||||
|
projectId
|
||||||
|
});
|
||||||
|
|
||||||
const selectedProvider = dynamicSecretProviders[dynamicSecretCfg.type as DynamicSecretProviders];
|
const selectedProvider = dynamicSecretProviders[dynamicSecretCfg.type as DynamicSecretProviders];
|
||||||
const decryptedStoredInput = JSON.parse(
|
const decryptedStoredInput = JSON.parse(
|
||||||
infisicalSymmetricDecrypt({
|
secretManagerDecryptor({ cipherTextBlob: dynamicSecretCfg.encryptedInput }).toString()
|
||||||
keyEncoding: dynamicSecretCfg.keyEncoding as SecretKeyEncoding,
|
|
||||||
ciphertext: dynamicSecretCfg.inputCiphertext,
|
|
||||||
tag: dynamicSecretCfg.inputTag,
|
|
||||||
iv: dynamicSecretCfg.inputIV
|
|
||||||
})
|
|
||||||
) as object;
|
) as object;
|
||||||
const newInput = { ...decryptedStoredInput, ...(inputs || {}) };
|
const newInput = { ...decryptedStoredInput, ...(inputs || {}) };
|
||||||
const updatedInput = await selectedProvider.validateProviderInputs(newInput);
|
const updatedInput = await selectedProvider.validateProviderInputs(newInput);
|
||||||
@@ -196,13 +198,8 @@ export const dynamicSecretServiceFactory = ({
|
|||||||
const isConnected = await selectedProvider.validateConnection(newInput);
|
const isConnected = await selectedProvider.validateConnection(newInput);
|
||||||
if (!isConnected) throw new BadRequestError({ message: "Provider connection failed" });
|
if (!isConnected) throw new BadRequestError({ message: "Provider connection failed" });
|
||||||
|
|
||||||
const encryptedInput = infisicalSymmetricEncypt(JSON.stringify(updatedInput));
|
|
||||||
const updatedDynamicCfg = await dynamicSecretDAL.updateById(dynamicSecretCfg.id, {
|
const updatedDynamicCfg = await dynamicSecretDAL.updateById(dynamicSecretCfg.id, {
|
||||||
inputIV: encryptedInput.iv,
|
encryptedInput: secretManagerEncryptor({ plainText: Buffer.from(JSON.stringify(updatedInput)) }).cipherTextBlob,
|
||||||
inputTag: encryptedInput.tag,
|
|
||||||
inputCiphertext: encryptedInput.ciphertext,
|
|
||||||
algorithm: encryptedInput.algorithm,
|
|
||||||
keyEncoding: encryptedInput.encoding,
|
|
||||||
maxTTL,
|
maxTTL,
|
||||||
defaultTTL,
|
defaultTTL,
|
||||||
name: newName ?? name,
|
name: newName ?? name,
|
||||||
@@ -315,13 +312,13 @@ export const dynamicSecretServiceFactory = ({
|
|||||||
if (!dynamicSecretCfg) {
|
if (!dynamicSecretCfg) {
|
||||||
throw new NotFoundError({ message: `Dynamic secret with name '${name} in folder '${path}' not found` });
|
throw new NotFoundError({ message: `Dynamic secret with name '${name} in folder '${path}' not found` });
|
||||||
}
|
}
|
||||||
|
const { decryptor: secretManagerDecryptor } = await kmsService.createCipherPairWithDataKey({
|
||||||
|
type: KmsDataKey.SecretManager,
|
||||||
|
projectId
|
||||||
|
});
|
||||||
|
|
||||||
const decryptedStoredInput = JSON.parse(
|
const decryptedStoredInput = JSON.parse(
|
||||||
infisicalSymmetricDecrypt({
|
secretManagerDecryptor({ cipherTextBlob: dynamicSecretCfg.encryptedInput }).toString()
|
||||||
keyEncoding: dynamicSecretCfg.keyEncoding as SecretKeyEncoding,
|
|
||||||
ciphertext: dynamicSecretCfg.inputCiphertext,
|
|
||||||
tag: dynamicSecretCfg.inputTag,
|
|
||||||
iv: dynamicSecretCfg.inputIV
|
|
||||||
})
|
|
||||||
) as object;
|
) as object;
|
||||||
const selectedProvider = dynamicSecretProviders[dynamicSecretCfg.type as DynamicSecretProviders];
|
const selectedProvider = dynamicSecretProviders[dynamicSecretCfg.type as DynamicSecretProviders];
|
||||||
const providerInputs = (await selectedProvider.validateProviderInputs(decryptedStoredInput)) as object;
|
const providerInputs = (await selectedProvider.validateProviderInputs(decryptedStoredInput)) as object;
|
||||||
|
|||||||
+15
-34
@@ -5,13 +5,9 @@ import {
|
|||||||
IAMClient
|
IAMClient
|
||||||
} from "@aws-sdk/client-iam";
|
} from "@aws-sdk/client-iam";
|
||||||
|
|
||||||
import { SecretKeyEncoding, SecretType } from "@app/db/schemas";
|
import { SecretType } from "@app/db/schemas";
|
||||||
import { getConfig } from "@app/lib/config/env";
|
import { getConfig } from "@app/lib/config/env";
|
||||||
import {
|
import { encryptSymmetric128BitHexKeyUTF8 } from "@app/lib/crypto/encryption";
|
||||||
encryptSymmetric128BitHexKeyUTF8,
|
|
||||||
infisicalSymmetricDecrypt,
|
|
||||||
infisicalSymmetricEncypt
|
|
||||||
} from "@app/lib/crypto/encryption";
|
|
||||||
import { daysToMillisecond, secondsToMillis } from "@app/lib/dates";
|
import { daysToMillisecond, secondsToMillis } from "@app/lib/dates";
|
||||||
import { NotFoundError } from "@app/lib/errors";
|
import { NotFoundError } from "@app/lib/errors";
|
||||||
import { logger } from "@app/lib/logger";
|
import { logger } from "@app/lib/logger";
|
||||||
@@ -135,20 +131,15 @@ export const secretRotationQueueFactory = ({
|
|||||||
|
|
||||||
// deep copy
|
// deep copy
|
||||||
const provider = JSON.parse(JSON.stringify(rotationProvider)) as TSecretRotationProviderTemplate;
|
const provider = JSON.parse(JSON.stringify(rotationProvider)) as TSecretRotationProviderTemplate;
|
||||||
|
const { encryptor: secretManagerEncryptor, decryptor: secretManagerDecryptor } =
|
||||||
|
await kmsService.createCipherPairWithDataKey({
|
||||||
|
type: KmsDataKey.SecretManager,
|
||||||
|
projectId: secretRotation.projectId
|
||||||
|
});
|
||||||
|
|
||||||
// now get the encrypted variable values
|
const decryptedData = secretManagerDecryptor({
|
||||||
// in includes the inputs, the previous outputs
|
cipherTextBlob: secretRotation.encryptedRotationData
|
||||||
// internal mapping variables etc
|
}).toString();
|
||||||
const { encryptedDataTag, encryptedDataIV, encryptedData, keyEncoding } = secretRotation;
|
|
||||||
if (!encryptedDataTag || !encryptedDataIV || !encryptedData || !keyEncoding) {
|
|
||||||
throw new DisableRotationErrors({ message: "No inputs found" });
|
|
||||||
}
|
|
||||||
const decryptedData = infisicalSymmetricDecrypt({
|
|
||||||
keyEncoding: keyEncoding as SecretKeyEncoding,
|
|
||||||
ciphertext: encryptedData,
|
|
||||||
iv: encryptedDataIV,
|
|
||||||
tag: encryptedDataTag
|
|
||||||
});
|
|
||||||
|
|
||||||
const variables = JSON.parse(decryptedData) as TSecretRotationEncData;
|
const variables = JSON.parse(decryptedData) as TSecretRotationEncData;
|
||||||
// rotation set cycle
|
// rotation set cycle
|
||||||
@@ -303,11 +294,9 @@ export const secretRotationQueueFactory = ({
|
|||||||
outputs: newCredential.outputs,
|
outputs: newCredential.outputs,
|
||||||
internal: newCredential.internal
|
internal: newCredential.internal
|
||||||
});
|
});
|
||||||
const encVarData = infisicalSymmetricEncypt(JSON.stringify(variables));
|
const encryptedRotationData = secretManagerEncryptor({
|
||||||
const { encryptor: secretManagerEncryptor } = await kmsService.createCipherPairWithDataKey({
|
plainText: Buffer.from(JSON.stringify(variables))
|
||||||
type: KmsDataKey.SecretManager,
|
}).cipherTextBlob;
|
||||||
projectId: secretRotation.projectId
|
|
||||||
});
|
|
||||||
|
|
||||||
const numberOfSecretsRotated = rotationOutputs.length;
|
const numberOfSecretsRotated = rotationOutputs.length;
|
||||||
if (shouldUseSecretV2Bridge) {
|
if (shouldUseSecretV2Bridge) {
|
||||||
@@ -323,11 +312,7 @@ export const secretRotationQueueFactory = ({
|
|||||||
await secretRotationDAL.updateById(
|
await secretRotationDAL.updateById(
|
||||||
rotationId,
|
rotationId,
|
||||||
{
|
{
|
||||||
encryptedData: encVarData.ciphertext,
|
encryptedRotationData,
|
||||||
encryptedDataIV: encVarData.iv,
|
|
||||||
encryptedDataTag: encVarData.tag,
|
|
||||||
keyEncoding: encVarData.encoding,
|
|
||||||
algorithm: encVarData.algorithm,
|
|
||||||
lastRotatedAt: new Date(),
|
lastRotatedAt: new Date(),
|
||||||
statusMessage: "Rotated successfull",
|
statusMessage: "Rotated successfull",
|
||||||
status: "success"
|
status: "success"
|
||||||
@@ -371,11 +356,7 @@ export const secretRotationQueueFactory = ({
|
|||||||
await secretRotationDAL.updateById(
|
await secretRotationDAL.updateById(
|
||||||
rotationId,
|
rotationId,
|
||||||
{
|
{
|
||||||
encryptedData: encVarData.ciphertext,
|
encryptedRotationData,
|
||||||
encryptedDataIV: encVarData.iv,
|
|
||||||
encryptedDataTag: encVarData.tag,
|
|
||||||
keyEncoding: encVarData.encoding,
|
|
||||||
algorithm: encVarData.algorithm,
|
|
||||||
lastRotatedAt: new Date(),
|
lastRotatedAt: new Date(),
|
||||||
statusMessage: "Rotated successfull",
|
statusMessage: "Rotated successfull",
|
||||||
status: "success"
|
status: "success"
|
||||||
|
|||||||
@@ -2,9 +2,11 @@ import { ForbiddenError, subject } from "@casl/ability";
|
|||||||
import Ajv from "ajv";
|
import Ajv from "ajv";
|
||||||
|
|
||||||
import { ActionProjectType, ProjectVersion, TableName } from "@app/db/schemas";
|
import { ActionProjectType, ProjectVersion, TableName } from "@app/db/schemas";
|
||||||
import { decryptSymmetric128BitHexKeyUTF8, infisicalSymmetricEncypt } from "@app/lib/crypto/encryption";
|
import { decryptSymmetric128BitHexKeyUTF8 } from "@app/lib/crypto/encryption";
|
||||||
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
||||||
import { TProjectPermission } from "@app/lib/types";
|
import { TProjectPermission } from "@app/lib/types";
|
||||||
|
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
|
||||||
|
import { KmsDataKey } from "@app/services/kms/kms-types";
|
||||||
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
||||||
import { TProjectBotServiceFactory } from "@app/services/project-bot/project-bot-service";
|
import { TProjectBotServiceFactory } from "@app/services/project-bot/project-bot-service";
|
||||||
import { TSecretDALFactory } from "@app/services/secret/secret-dal";
|
import { TSecretDALFactory } from "@app/services/secret/secret-dal";
|
||||||
@@ -30,6 +32,7 @@ type TSecretRotationServiceFactoryDep = {
|
|||||||
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
|
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
|
||||||
secretRotationQueue: TSecretRotationQueueFactory;
|
secretRotationQueue: TSecretRotationQueueFactory;
|
||||||
projectBotService: Pick<TProjectBotServiceFactory, "getBotKey">;
|
projectBotService: Pick<TProjectBotServiceFactory, "getBotKey">;
|
||||||
|
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TSecretRotationServiceFactory = ReturnType<typeof secretRotationServiceFactory>;
|
export type TSecretRotationServiceFactory = ReturnType<typeof secretRotationServiceFactory>;
|
||||||
@@ -44,7 +47,8 @@ export const secretRotationServiceFactory = ({
|
|||||||
folderDAL,
|
folderDAL,
|
||||||
secretDAL,
|
secretDAL,
|
||||||
projectBotService,
|
projectBotService,
|
||||||
secretV2BridgeDAL
|
secretV2BridgeDAL,
|
||||||
|
kmsService
|
||||||
}: TSecretRotationServiceFactoryDep) => {
|
}: TSecretRotationServiceFactoryDep) => {
|
||||||
const getProviderTemplates = async ({
|
const getProviderTemplates = async ({
|
||||||
actor,
|
actor,
|
||||||
@@ -156,7 +160,11 @@ export const secretRotationServiceFactory = ({
|
|||||||
inputs: formattedInputs,
|
inputs: formattedInputs,
|
||||||
creds: []
|
creds: []
|
||||||
};
|
};
|
||||||
const encData = infisicalSymmetricEncypt(JSON.stringify(unencryptedData));
|
const { encryptor: secretManagerEncryptor } = await kmsService.createCipherPairWithDataKey({
|
||||||
|
type: KmsDataKey.SecretManager,
|
||||||
|
projectId
|
||||||
|
});
|
||||||
|
|
||||||
const secretRotation = await secretRotationDAL.transaction(async (tx) => {
|
const secretRotation = await secretRotationDAL.transaction(async (tx) => {
|
||||||
const doc = await secretRotationDAL.create(
|
const doc = await secretRotationDAL.create(
|
||||||
{
|
{
|
||||||
@@ -164,11 +172,8 @@ export const secretRotationServiceFactory = ({
|
|||||||
secretPath,
|
secretPath,
|
||||||
interval,
|
interval,
|
||||||
envId: folder.envId,
|
envId: folder.envId,
|
||||||
encryptedDataTag: encData.tag,
|
encryptedRotationData: secretManagerEncryptor({ plainText: Buffer.from(JSON.stringify(unencryptedData)) })
|
||||||
encryptedDataIV: encData.iv,
|
.cipherTextBlob
|
||||||
encryptedData: encData.ciphertext,
|
|
||||||
algorithm: encData.algorithm,
|
|
||||||
keyEncoding: encData.encoding
|
|
||||||
},
|
},
|
||||||
tx
|
tx
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -973,7 +973,8 @@ export const registerRoutes = async (
|
|||||||
permissionService,
|
permissionService,
|
||||||
webhookDAL,
|
webhookDAL,
|
||||||
projectEnvDAL,
|
projectEnvDAL,
|
||||||
projectDAL
|
projectDAL,
|
||||||
|
kmsService
|
||||||
});
|
});
|
||||||
|
|
||||||
const secretTagService = secretTagServiceFactory({ secretTagDAL, permissionService });
|
const secretTagService = secretTagServiceFactory({ secretTagDAL, permissionService });
|
||||||
@@ -1153,7 +1154,8 @@ export const registerRoutes = async (
|
|||||||
secretDAL,
|
secretDAL,
|
||||||
folderDAL,
|
folderDAL,
|
||||||
projectBotService,
|
projectBotService,
|
||||||
secretV2BridgeDAL
|
secretV2BridgeDAL,
|
||||||
|
kmsService
|
||||||
});
|
});
|
||||||
|
|
||||||
const integrationService = integrationServiceFactory({
|
const integrationService = integrationServiceFactory({
|
||||||
@@ -1293,7 +1295,9 @@ export const registerRoutes = async (
|
|||||||
queueService,
|
queueService,
|
||||||
dynamicSecretLeaseDAL,
|
dynamicSecretLeaseDAL,
|
||||||
dynamicSecretProviders,
|
dynamicSecretProviders,
|
||||||
dynamicSecretDAL
|
dynamicSecretDAL,
|
||||||
|
folderDAL,
|
||||||
|
kmsService
|
||||||
});
|
});
|
||||||
const dynamicSecretService = dynamicSecretServiceFactory({
|
const dynamicSecretService = dynamicSecretServiceFactory({
|
||||||
projectDAL,
|
projectDAL,
|
||||||
@@ -1303,7 +1307,8 @@ export const registerRoutes = async (
|
|||||||
dynamicSecretProviders,
|
dynamicSecretProviders,
|
||||||
folderDAL,
|
folderDAL,
|
||||||
permissionService,
|
permissionService,
|
||||||
licenseService
|
licenseService,
|
||||||
|
kmsService
|
||||||
});
|
});
|
||||||
const dynamicSecretLeaseService = dynamicSecretLeaseServiceFactory({
|
const dynamicSecretLeaseService = dynamicSecretLeaseServiceFactory({
|
||||||
projectDAL,
|
projectDAL,
|
||||||
@@ -1313,7 +1318,8 @@ export const registerRoutes = async (
|
|||||||
dynamicSecretLeaseDAL,
|
dynamicSecretLeaseDAL,
|
||||||
dynamicSecretProviders,
|
dynamicSecretProviders,
|
||||||
folderDAL,
|
folderDAL,
|
||||||
licenseService
|
licenseService,
|
||||||
|
kmsService
|
||||||
});
|
});
|
||||||
const dailyResourceCleanUp = dailyResourceCleanUpQueueServiceFactory({
|
const dailyResourceCleanUp = dailyResourceCleanUpQueueServiceFactory({
|
||||||
auditLogDAL,
|
auditLogDAL,
|
||||||
|
|||||||
@@ -201,11 +201,7 @@ export const SanitizedRoleSchemaV1 = ProjectRolesSchema.extend({
|
|||||||
});
|
});
|
||||||
|
|
||||||
export const SanitizedDynamicSecretSchema = DynamicSecretsSchema.omit({
|
export const SanitizedDynamicSecretSchema = DynamicSecretsSchema.omit({
|
||||||
inputIV: true,
|
encryptedInput: true
|
||||||
inputTag: true,
|
|
||||||
inputCiphertext: true,
|
|
||||||
keyEncoding: true,
|
|
||||||
algorithm: true
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export const SanitizedAuditLogStreamSchema = z.object({
|
export const SanitizedAuditLogStreamSchema = z.object({
|
||||||
|
|||||||
@@ -1488,7 +1488,18 @@ export const secretQueueFactory = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
queueService.start(QueueName.SecretWebhook, async (job) => {
|
queueService.start(QueueName.SecretWebhook, async (job) => {
|
||||||
await fnTriggerWebhook({ ...job.data, projectEnvDAL, webhookDAL, projectDAL });
|
const { decryptor: secretManagerDecryptor } = await kmsService.createCipherPairWithDataKey({
|
||||||
|
type: KmsDataKey.SecretManager,
|
||||||
|
projectId: job.data.projectId
|
||||||
|
});
|
||||||
|
|
||||||
|
await fnTriggerWebhook({
|
||||||
|
...job.data,
|
||||||
|
projectEnvDAL,
|
||||||
|
webhookDAL,
|
||||||
|
projectDAL,
|
||||||
|
secretManagerDecryptor: (value) => secretManagerDecryptor({ cipherTextBlob: value }).toString()
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -3,9 +3,8 @@ import crypto from "node:crypto";
|
|||||||
import { AxiosError } from "axios";
|
import { AxiosError } from "axios";
|
||||||
import picomatch from "picomatch";
|
import picomatch from "picomatch";
|
||||||
|
|
||||||
import { SecretKeyEncoding, TWebhooks } from "@app/db/schemas";
|
import { TWebhooks } from "@app/db/schemas";
|
||||||
import { request } from "@app/lib/config/request";
|
import { request } from "@app/lib/config/request";
|
||||||
import { infisicalSymmetricDecrypt } from "@app/lib/crypto/encryption";
|
|
||||||
import { NotFoundError } from "@app/lib/errors";
|
import { NotFoundError } from "@app/lib/errors";
|
||||||
import { logger } from "@app/lib/logger";
|
import { logger } from "@app/lib/logger";
|
||||||
|
|
||||||
@@ -16,28 +15,14 @@ import { WebhookType } from "./webhook-types";
|
|||||||
|
|
||||||
const WEBHOOK_TRIGGER_TIMEOUT = 15 * 1000;
|
const WEBHOOK_TRIGGER_TIMEOUT = 15 * 1000;
|
||||||
|
|
||||||
export const decryptWebhookDetails = (webhook: TWebhooks) => {
|
export const decryptWebhookDetails = (webhook: TWebhooks, decryptor: (value: Buffer) => string) => {
|
||||||
const { keyEncoding, iv, encryptedSecretKey, tag, urlCipherText, urlIV, urlTag, url } = webhook;
|
const { encryptedPassKey, encryptedUrl } = webhook;
|
||||||
|
|
||||||
|
const decryptedUrl = decryptor(encryptedUrl);
|
||||||
|
|
||||||
let decryptedSecretKey = "";
|
let decryptedSecretKey = "";
|
||||||
let decryptedUrl = url;
|
if (encryptedPassKey) {
|
||||||
|
decryptedSecretKey = decryptor(encryptedPassKey);
|
||||||
if (encryptedSecretKey) {
|
|
||||||
decryptedSecretKey = infisicalSymmetricDecrypt({
|
|
||||||
keyEncoding: keyEncoding as SecretKeyEncoding,
|
|
||||||
ciphertext: encryptedSecretKey,
|
|
||||||
iv: iv as string,
|
|
||||||
tag: tag as string
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (urlCipherText) {
|
|
||||||
decryptedUrl = infisicalSymmetricDecrypt({
|
|
||||||
keyEncoding: keyEncoding as SecretKeyEncoding,
|
|
||||||
ciphertext: urlCipherText,
|
|
||||||
iv: urlIV as string,
|
|
||||||
tag: urlTag as string
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -46,10 +31,14 @@ export const decryptWebhookDetails = (webhook: TWebhooks) => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const triggerWebhookRequest = async (webhook: TWebhooks, data: Record<string, unknown>) => {
|
export const triggerWebhookRequest = async (
|
||||||
|
webhook: TWebhooks,
|
||||||
|
decryptor: (value: Buffer) => string,
|
||||||
|
data: Record<string, unknown>
|
||||||
|
) => {
|
||||||
const headers: Record<string, string> = {};
|
const headers: Record<string, string> = {};
|
||||||
const payload = { ...data, timestamp: Date.now() };
|
const payload = { ...data, timestamp: Date.now() };
|
||||||
const { secretKey, url } = decryptWebhookDetails(webhook);
|
const { secretKey, url } = decryptWebhookDetails(webhook, decryptor);
|
||||||
|
|
||||||
if (secretKey) {
|
if (secretKey) {
|
||||||
const webhookSign = crypto.createHmac("sha256", secretKey).update(JSON.stringify(payload)).digest("hex");
|
const webhookSign = crypto.createHmac("sha256", secretKey).update(JSON.stringify(payload)).digest("hex");
|
||||||
@@ -124,6 +113,7 @@ export type TFnTriggerWebhookDTO = {
|
|||||||
webhookDAL: Pick<TWebhookDALFactory, "findAllWebhooks" | "transaction" | "update" | "bulkUpdate">;
|
webhookDAL: Pick<TWebhookDALFactory, "findAllWebhooks" | "transaction" | "update" | "bulkUpdate">;
|
||||||
projectEnvDAL: Pick<TProjectEnvDALFactory, "findOne">;
|
projectEnvDAL: Pick<TProjectEnvDALFactory, "findOne">;
|
||||||
projectDAL: Pick<TProjectDALFactory, "findById">;
|
projectDAL: Pick<TProjectDALFactory, "findById">;
|
||||||
|
secretManagerDecryptor: (value: Buffer) => string;
|
||||||
};
|
};
|
||||||
|
|
||||||
// this is reusable function
|
// this is reusable function
|
||||||
@@ -134,7 +124,8 @@ export const fnTriggerWebhook = async ({
|
|||||||
projectId,
|
projectId,
|
||||||
webhookDAL,
|
webhookDAL,
|
||||||
projectEnvDAL,
|
projectEnvDAL,
|
||||||
projectDAL
|
projectDAL,
|
||||||
|
secretManagerDecryptor
|
||||||
}: TFnTriggerWebhookDTO) => {
|
}: TFnTriggerWebhookDTO) => {
|
||||||
const webhooks = await webhookDAL.findAllWebhooks(projectId, environment);
|
const webhooks = await webhookDAL.findAllWebhooks(projectId, environment);
|
||||||
const toBeTriggeredHooks = webhooks.filter(
|
const toBeTriggeredHooks = webhooks.filter(
|
||||||
@@ -148,6 +139,7 @@ export const fnTriggerWebhook = async ({
|
|||||||
toBeTriggeredHooks.map((hook) =>
|
toBeTriggeredHooks.map((hook) =>
|
||||||
triggerWebhookRequest(
|
triggerWebhookRequest(
|
||||||
hook,
|
hook,
|
||||||
|
secretManagerDecryptor,
|
||||||
getWebhookPayload("secrets.modified", {
|
getWebhookPayload("secrets.modified", {
|
||||||
workspaceName: project.name,
|
workspaceName: project.name,
|
||||||
workspaceId: projectId,
|
workspaceId: projectId,
|
||||||
|
|||||||
@@ -3,9 +3,10 @@ import { ForbiddenError } from "@casl/ability";
|
|||||||
import { ActionProjectType, TWebhooksInsert } from "@app/db/schemas";
|
import { ActionProjectType, TWebhooksInsert } from "@app/db/schemas";
|
||||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||||
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
||||||
import { infisicalSymmetricEncypt } from "@app/lib/crypto/encryption";
|
|
||||||
import { NotFoundError } from "@app/lib/errors";
|
import { NotFoundError } from "@app/lib/errors";
|
||||||
|
|
||||||
|
import { TKmsServiceFactory } from "../kms/kms-service";
|
||||||
|
import { KmsDataKey } from "../kms/kms-types";
|
||||||
import { TProjectDALFactory } from "../project/project-dal";
|
import { TProjectDALFactory } from "../project/project-dal";
|
||||||
import { TProjectEnvDALFactory } from "../project-env/project-env-dal";
|
import { TProjectEnvDALFactory } from "../project-env/project-env-dal";
|
||||||
import { TWebhookDALFactory } from "./webhook-dal";
|
import { TWebhookDALFactory } from "./webhook-dal";
|
||||||
@@ -23,6 +24,7 @@ type TWebhookServiceFactoryDep = {
|
|||||||
projectEnvDAL: TProjectEnvDALFactory;
|
projectEnvDAL: TProjectEnvDALFactory;
|
||||||
projectDAL: Pick<TProjectDALFactory, "findById">;
|
projectDAL: Pick<TProjectDALFactory, "findById">;
|
||||||
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
|
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
|
||||||
|
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TWebhookServiceFactory = ReturnType<typeof webhookServiceFactory>;
|
export type TWebhookServiceFactory = ReturnType<typeof webhookServiceFactory>;
|
||||||
@@ -31,7 +33,8 @@ export const webhookServiceFactory = ({
|
|||||||
webhookDAL,
|
webhookDAL,
|
||||||
projectEnvDAL,
|
projectEnvDAL,
|
||||||
permissionService,
|
permissionService,
|
||||||
projectDAL
|
projectDAL,
|
||||||
|
kmsService
|
||||||
}: TWebhookServiceFactoryDep) => {
|
}: TWebhookServiceFactoryDep) => {
|
||||||
const createWebhook = async ({
|
const createWebhook = async ({
|
||||||
actor,
|
actor,
|
||||||
@@ -60,30 +63,20 @@ export const webhookServiceFactory = ({
|
|||||||
message: `Environment with slug '${environment}' in project with ID '${projectId}' not found`
|
message: `Environment with slug '${environment}' in project with ID '${projectId}' not found`
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const { encryptor: secretManagerEncryptor } = await kmsService.createCipherPairWithDataKey({
|
||||||
|
type: KmsDataKey.SecretManager,
|
||||||
|
projectId
|
||||||
|
});
|
||||||
const insertDoc: TWebhooksInsert = {
|
const insertDoc: TWebhooksInsert = {
|
||||||
url: "", // deprecated - we are moving away from plaintext URLs
|
|
||||||
envId: env.id,
|
envId: env.id,
|
||||||
isDisabled: false,
|
isDisabled: false,
|
||||||
secretPath: secretPath || "/",
|
secretPath: secretPath || "/",
|
||||||
type
|
type,
|
||||||
|
encryptedUrl: secretManagerEncryptor({ plainText: Buffer.from(webhookUrl) }).cipherTextBlob
|
||||||
};
|
};
|
||||||
|
|
||||||
if (webhookSecretKey) {
|
if (webhookSecretKey) {
|
||||||
const { ciphertext, iv, tag, algorithm, encoding } = infisicalSymmetricEncypt(webhookSecretKey);
|
insertDoc.encryptedPassKey = secretManagerEncryptor({ plainText: Buffer.from(webhookSecretKey) }).cipherTextBlob;
|
||||||
insertDoc.encryptedSecretKey = ciphertext;
|
|
||||||
insertDoc.iv = iv;
|
|
||||||
insertDoc.tag = tag;
|
|
||||||
insertDoc.algorithm = algorithm;
|
|
||||||
insertDoc.keyEncoding = encoding;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (webhookUrl) {
|
|
||||||
const { ciphertext, iv, tag, algorithm, encoding } = infisicalSymmetricEncypt(webhookUrl);
|
|
||||||
insertDoc.urlCipherText = ciphertext;
|
|
||||||
insertDoc.urlIV = iv;
|
|
||||||
insertDoc.urlTag = tag;
|
|
||||||
insertDoc.algorithm = algorithm;
|
|
||||||
insertDoc.keyEncoding = encoding;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const webhook = await webhookDAL.create(insertDoc);
|
const webhook = await webhookDAL.create(insertDoc);
|
||||||
@@ -140,12 +133,17 @@ export const webhookServiceFactory = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const project = await projectDAL.findById(webhook.projectId);
|
const project = await projectDAL.findById(webhook.projectId);
|
||||||
|
const { decryptor: secretManagerDecryptor } = await kmsService.createCipherPairWithDataKey({
|
||||||
|
type: KmsDataKey.SecretManager,
|
||||||
|
projectId: project.id
|
||||||
|
});
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Webhooks);
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Webhooks);
|
||||||
let webhookError: string | undefined;
|
let webhookError: string | undefined;
|
||||||
try {
|
try {
|
||||||
await triggerWebhookRequest(
|
await triggerWebhookRequest(
|
||||||
webhook,
|
webhook,
|
||||||
|
(value) => secretManagerDecryptor({ cipherTextBlob: value }).toString(),
|
||||||
getWebhookPayload("test", {
|
getWebhookPayload("test", {
|
||||||
workspaceName: project.name,
|
workspaceName: project.name,
|
||||||
workspaceId: webhook.projectId,
|
workspaceId: webhook.projectId,
|
||||||
@@ -185,8 +183,13 @@ export const webhookServiceFactory = ({
|
|||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Webhooks);
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Webhooks);
|
||||||
|
|
||||||
const webhooks = await webhookDAL.findAllWebhooks(projectId, environment, secretPath);
|
const webhooks = await webhookDAL.findAllWebhooks(projectId, environment, secretPath);
|
||||||
|
const { decryptor: secretManagerDecryptor } = await kmsService.createCipherPairWithDataKey({
|
||||||
|
type: KmsDataKey.SecretManager,
|
||||||
|
projectId
|
||||||
|
});
|
||||||
|
|
||||||
return webhooks.map((w) => {
|
return webhooks.map((w) => {
|
||||||
const { url } = decryptWebhookDetails(w);
|
const { url } = decryptWebhookDetails(w, (value) => secretManagerDecryptor({ cipherTextBlob: value }).toString());
|
||||||
return {
|
return {
|
||||||
...w,
|
...w,
|
||||||
url
|
url
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
{
|
{
|
||||||
"ts-node": {
|
"ts-node": {
|
||||||
// Do not forget to `npm i -D tsconfig-paths`
|
// Do not forget to `npm i -D tsconfig-paths`
|
||||||
"require": ["tsconfig-paths/register"]
|
"require": ["tsconfig-paths/register"],
|
||||||
|
"files": true
|
||||||
},
|
},
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "esnext",
|
"target": "esnext",
|
||||||
@@ -19,6 +20,7 @@
|
|||||||
"experimentalDecorators": true,
|
"experimentalDecorators": true,
|
||||||
"emitDecoratorMetadata": true,
|
"emitDecoratorMetadata": true,
|
||||||
"moduleResolution": "Node",
|
"moduleResolution": "Node",
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
"paths": {
|
"paths": {
|
||||||
|
|||||||
Reference in New Issue
Block a user