mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-07 12:27:28 +00:00
feat: removed all the migration done for webhook and dynamic secret towards kms encryption
This commit is contained in:
@@ -1,178 +1,8 @@
|
|||||||
/* eslint-disable @typescript-eslint/ban-ts-comment */
|
/* eslint-disable @typescript-eslint/ban-ts-comment */
|
||||||
import { Knex } from "knex";
|
import { Knex } from "knex";
|
||||||
|
|
||||||
import { infisicalSymmetricDecrypt } from "@app/lib/crypto/encryption";
|
import { SecretType, TableName } from "../schemas";
|
||||||
import { selectAllTableCols } from "@app/lib/knex/select";
|
|
||||||
|
|
||||||
import { SecretKeyEncoding, SecretType, TableName } from "../schemas";
|
|
||||||
import { createJunctionTable, createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils";
|
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> {
|
export async function up(knex: Knex): Promise<void> {
|
||||||
const doesSecretV2TableExist = await knex.schema.hasTable(TableName.SecretV2);
|
const doesSecretV2TableExist = await knex.schema.hasTable(TableName.SecretV2);
|
||||||
@@ -314,14 +144,6 @@ export async function up(knex: Knex): Promise<void> {
|
|||||||
t.foreign("rotationId").references("id").inTable(TableName.SecretRotation).onDelete("CASCADE");
|
t.foreign("rotationId").references("id").inTable(TableName.SecretRotation).onDelete("CASCADE");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (await knex.schema.hasTable(TableName.Webhook)) {
|
|
||||||
await backfillWebhooks(knex);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (await knex.schema.hasTable(TableName.DynamicSecret)) {
|
|
||||||
await backfillDynamicSecretConfigs(knex);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function down(knex: Knex): Promise<void> {
|
export async function down(knex: Knex): Promise<void> {
|
||||||
@@ -356,49 +178,4 @@ export async function down(knex: Knex): Promise<void> {
|
|||||||
if (hasEncryptedAwsIamAssumRole) t.dropColumn("encryptedAwsAssumeIamRoleArn");
|
if (hasEncryptedAwsIamAssumRole) t.dropColumn("encryptedAwsAssumeIamRoleArn");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
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");
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,8 +5,6 @@
|
|||||||
|
|
||||||
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({
|
||||||
@@ -16,12 +14,16 @@ export const DynamicSecretsSchema = z.object({
|
|||||||
type: z.string(),
|
type: z.string(),
|
||||||
defaultTTL: z.string(),
|
defaultTTL: z.string(),
|
||||||
maxTTL: z.string().nullable().optional(),
|
maxTTL: z.string().nullable().optional(),
|
||||||
|
inputIV: z.string(),
|
||||||
|
inputCiphertext: z.string(),
|
||||||
|
inputTag: z.string(),
|
||||||
|
algorithm: z.string().default("aes-256-gcm"),
|
||||||
|
keyEncoding: z.string().default("utf8"),
|
||||||
folderId: z.string().uuid(),
|
folderId: z.string().uuid(),
|
||||||
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()
|
||||||
encryptedConfig: zodBuffer
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TDynamicSecrets = z.infer<typeof DynamicSecretsSchema>;
|
export type TDynamicSecrets = z.infer<typeof DynamicSecretsSchema>;
|
||||||
|
|||||||
@@ -5,22 +5,27 @@
|
|||||||
|
|
||||||
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({
|
||||||
id: z.string().uuid(),
|
id: z.string().uuid(),
|
||||||
secretPath: z.string().default("/"),
|
secretPath: z.string().default("/"),
|
||||||
|
url: z.string(),
|
||||||
lastStatus: z.string().nullable().optional(),
|
lastStatus: z.string().nullable().optional(),
|
||||||
lastRunErrorMessage: z.string().nullable().optional(),
|
lastRunErrorMessage: z.string().nullable().optional(),
|
||||||
isDisabled: z.boolean().default(false),
|
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(),
|
createdAt: z.date(),
|
||||||
updatedAt: z.date(),
|
updatedAt: z.date(),
|
||||||
envId: z.string().uuid(),
|
envId: z.string().uuid(),
|
||||||
type: z.string().default("general").nullable().optional(),
|
urlCipherText: z.string().nullable().optional(),
|
||||||
encryptedSecretKeyWithKms: zodBuffer.nullable().optional(),
|
urlIV: z.string().nullable().optional(),
|
||||||
encryptedUrl: zodBuffer
|
urlTag: z.string().nullable().optional(),
|
||||||
|
type: z.string().default("general").nullable().optional()
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TWebhooks = z.infer<typeof WebhooksSchema>;
|
export type TWebhooks = z.infer<typeof WebhooksSchema>;
|
||||||
|
|||||||
@@ -12,10 +12,7 @@ export const dynamicSecretLeaseDALFactory = (db: TDbClient) => {
|
|||||||
|
|
||||||
const countLeasesForDynamicSecret = async (dynamicSecretId: string, tx?: Knex) => {
|
const countLeasesForDynamicSecret = async (dynamicSecretId: string, tx?: Knex) => {
|
||||||
try {
|
try {
|
||||||
const doc = await (tx || db.replicaNode())(TableName.DynamicSecretLease)
|
const doc = await (tx || db)(TableName.DynamicSecretLease).count("*").where({ dynamicSecretId }).first();
|
||||||
.count("*")
|
|
||||||
.where({ dynamicSecretId })
|
|
||||||
.first();
|
|
||||||
return parseInt(doc || "0", 10);
|
return parseInt(doc || "0", 10);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new DatabaseError({ error, name: "DynamicSecretCountLeases" });
|
throw new DatabaseError({ error, name: "DynamicSecretCountLeases" });
|
||||||
@@ -24,7 +21,7 @@ export const dynamicSecretLeaseDALFactory = (db: TDbClient) => {
|
|||||||
|
|
||||||
const findById = async (id: string, tx?: Knex) => {
|
const findById = async (id: string, tx?: Knex) => {
|
||||||
try {
|
try {
|
||||||
const doc = await (tx || db.replicaNode())(TableName.DynamicSecretLease)
|
const doc = await (tx || db)(TableName.DynamicSecretLease)
|
||||||
.where({ [`${TableName.DynamicSecretLease}.id` as "id"]: id })
|
.where({ [`${TableName.DynamicSecretLease}.id` as "id"]: id })
|
||||||
.first()
|
.first()
|
||||||
.join(
|
.join(
|
||||||
@@ -40,10 +37,14 @@ 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("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"),
|
||||||
db.ref("encryptedConfig").withSchema(TableName.DynamicSecret).as("dynEncryptedConfig"),
|
|
||||||
db.ref("createdAt").withSchema(TableName.DynamicSecret).as("dynCreatedAt"),
|
db.ref("createdAt").withSchema(TableName.DynamicSecret).as("dynCreatedAt"),
|
||||||
db.ref("updatedAt").withSchema(TableName.DynamicSecret).as("dynUpdatedAt")
|
db.ref("updatedAt").withSchema(TableName.DynamicSecret).as("dynUpdatedAt")
|
||||||
);
|
);
|
||||||
@@ -58,12 +59,16 @@ 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,
|
||||||
|
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,
|
||||||
createdAt: doc.dynCreatedAt,
|
createdAt: doc.dynCreatedAt,
|
||||||
updatedAt: doc.dynUpdatedAt,
|
updatedAt: doc.dynUpdatedAt
|
||||||
encryptedConfig: doc.dynEncryptedConfig
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
|
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 { 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";
|
||||||
@@ -15,8 +14,6 @@ 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>;
|
||||||
@@ -25,9 +22,7 @@ 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(
|
||||||
@@ -82,20 +77,15 @@ export const dynamicSecretLeaseQueueServiceFactory = ({
|
|||||||
if (!dynamicSecretLease) throw new DisableRotationErrors({ message: "Dynamic secret lease not found" });
|
if (!dynamicSecretLease) throw new DisableRotationErrors({ message: "Dynamic secret lease not found" });
|
||||||
|
|
||||||
const dynamicSecretCfg = dynamicSecretLease.dynamicSecret;
|
const dynamicSecretCfg = dynamicSecretLease.dynamicSecret;
|
||||||
const folder = await folderDAL.findById(dynamicSecretCfg.folderId);
|
|
||||||
if (!folder) throw new DisableRotationErrors({ message: "Folder not found" });
|
|
||||||
const { projectId } = folder;
|
|
||||||
|
|
||||||
const { decryptor: secretManagerDecryptor } = await kmsService.createCipherPairWithDataKey({
|
|
||||||
type: KmsDataKey.SecretManager,
|
|
||||||
projectId
|
|
||||||
});
|
|
||||||
|
|
||||||
const dynamicSecretInputConfig = secretManagerDecryptor({
|
|
||||||
cipherTextBlob: dynamicSecretCfg.encryptedConfig
|
|
||||||
}).toString();
|
|
||||||
const selectedProvider = dynamicSecretProviders[dynamicSecretCfg.type as DynamicSecretProviders];
|
const selectedProvider = dynamicSecretProviders[dynamicSecretCfg.type as DynamicSecretProviders];
|
||||||
const decryptedStoredInput = JSON.parse(dynamicSecretInputConfig) as object;
|
const decryptedStoredInput = JSON.parse(
|
||||||
|
infisicalSymmetricDecrypt({
|
||||||
|
keyEncoding: dynamicSecretCfg.keyEncoding as SecretKeyEncoding,
|
||||||
|
ciphertext: dynamicSecretCfg.inputCiphertext,
|
||||||
|
tag: dynamicSecretCfg.inputTag,
|
||||||
|
iv: dynamicSecretCfg.inputIV
|
||||||
|
})
|
||||||
|
) as object;
|
||||||
|
|
||||||
await selectedProvider.revoke(decryptedStoredInput, dynamicSecretLease.externalEntityId);
|
await selectedProvider.revoke(decryptedStoredInput, dynamicSecretLease.externalEntityId);
|
||||||
await dynamicSecretLeaseDAL.deleteById(dynamicSecretLease.id);
|
await dynamicSecretLeaseDAL.deleteById(dynamicSecretLease.id);
|
||||||
@@ -110,22 +100,17 @@ 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 DisableRotationErrors({ message: "Folder not found" });
|
|
||||||
const { projectId } = folder;
|
|
||||||
const { decryptor: secretManagerDecryptor } = await kmsService.createCipherPairWithDataKey({
|
|
||||||
type: KmsDataKey.SecretManager,
|
|
||||||
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 dynamicSecretInputConfig = secretManagerDecryptor({
|
infisicalSymmetricDecrypt({
|
||||||
cipherTextBlob: dynamicSecretCfg.encryptedConfig
|
keyEncoding: dynamicSecretCfg.keyEncoding as SecretKeyEncoding,
|
||||||
}).toString();
|
ciphertext: dynamicSecretCfg.inputCiphertext,
|
||||||
const decryptedStoredInput = JSON.parse(dynamicSecretInputConfig) as object;
|
tag: dynamicSecretCfg.inputTag,
|
||||||
|
iv: dynamicSecretCfg.inputIV
|
||||||
|
})
|
||||||
|
) as object;
|
||||||
|
|
||||||
await Promise.all(dynamicSecretLeases.map(({ id }) => unsetLeaseRevocation(id)));
|
await Promise.all(dynamicSecretLeases.map(({ id }) => unsetLeaseRevocation(id)));
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
import { ForbiddenError, subject } from "@casl/ability";
|
import { ForbiddenError, subject } from "@casl/ability";
|
||||||
import ms from "ms";
|
import ms from "ms";
|
||||||
|
|
||||||
|
import { SecretKeyEncoding } 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 { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
import { ProjectPermissionActions, ProjectPermissionSub } 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 } from "@app/lib/errors";
|
import { BadRequestError } 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";
|
||||||
|
|
||||||
@@ -34,7 +34,6 @@ 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>;
|
||||||
@@ -47,8 +46,7 @@ export const dynamicSecretLeaseServiceFactory = ({
|
|||||||
permissionService,
|
permissionService,
|
||||||
dynamicSecretQueueService,
|
dynamicSecretQueueService,
|
||||||
projectDAL,
|
projectDAL,
|
||||||
licenseService,
|
licenseService
|
||||||
kmsService
|
|
||||||
}: TDynamicSecretLeaseServiceFactoryDep) => {
|
}: TDynamicSecretLeaseServiceFactoryDep) => {
|
||||||
const create = async ({
|
const create = async ({
|
||||||
environmentSlug,
|
environmentSlug,
|
||||||
@@ -96,12 +94,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: kmsDecryptor } = await kmsService.createCipherPairWithDataKey({
|
const decryptedStoredInput = JSON.parse(
|
||||||
type: KmsDataKey.SecretManager,
|
infisicalSymmetricDecrypt({
|
||||||
projectId
|
keyEncoding: dynamicSecretCfg.keyEncoding as SecretKeyEncoding,
|
||||||
});
|
ciphertext: dynamicSecretCfg.inputCiphertext,
|
||||||
const decryptedStoredInputJson = kmsDecryptor({ cipherTextBlob: dynamicSecretCfg.encryptedConfig }).toString();
|
tag: dynamicSecretCfg.inputTag,
|
||||||
const decryptedStoredInput = JSON.parse(decryptedStoredInputJson) as object;
|
iv: dynamicSecretCfg.inputIV
|
||||||
|
})
|
||||||
|
) as object;
|
||||||
|
|
||||||
const selectedTTL = ttl ?? dynamicSecretCfg.defaultTTL;
|
const selectedTTL = ttl ?? dynamicSecretCfg.defaultTTL;
|
||||||
const { maxTTL } = dynamicSecretCfg;
|
const { maxTTL } = dynamicSecretCfg;
|
||||||
@@ -164,12 +164,14 @@ 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 { decryptor: kmsDecryptor } = await kmsService.createCipherPairWithDataKey({
|
const decryptedStoredInput = JSON.parse(
|
||||||
type: KmsDataKey.SecretManager,
|
infisicalSymmetricDecrypt({
|
||||||
projectId
|
keyEncoding: dynamicSecretCfg.keyEncoding as SecretKeyEncoding,
|
||||||
});
|
ciphertext: dynamicSecretCfg.inputCiphertext,
|
||||||
const decryptedStoredInputJson = kmsDecryptor({ cipherTextBlob: dynamicSecretCfg.encryptedConfig }).toString();
|
tag: dynamicSecretCfg.inputTag,
|
||||||
const decryptedStoredInput = JSON.parse(decryptedStoredInputJson) as object;
|
iv: dynamicSecretCfg.inputIV
|
||||||
|
})
|
||||||
|
) as object;
|
||||||
|
|
||||||
const selectedTTL = ttl ?? dynamicSecretCfg.defaultTTL;
|
const selectedTTL = ttl ?? dynamicSecretCfg.defaultTTL;
|
||||||
const { maxTTL } = dynamicSecretCfg;
|
const { maxTTL } = dynamicSecretCfg;
|
||||||
@@ -229,12 +231,14 @@ 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 { decryptor: kmsDecryptor } = await kmsService.createCipherPairWithDataKey({
|
const decryptedStoredInput = JSON.parse(
|
||||||
type: KmsDataKey.SecretManager,
|
infisicalSymmetricDecrypt({
|
||||||
projectId
|
keyEncoding: dynamicSecretCfg.keyEncoding as SecretKeyEncoding,
|
||||||
});
|
ciphertext: dynamicSecretCfg.inputCiphertext,
|
||||||
const decryptedStoredInputJson = kmsDecryptor({ cipherTextBlob: dynamicSecretCfg.encryptedConfig }).toString();
|
tag: dynamicSecretCfg.inputTag,
|
||||||
const decryptedStoredInput = JSON.parse(decryptedStoredInputJson) as object;
|
iv: dynamicSecretCfg.inputIV
|
||||||
|
})
|
||||||
|
) as object;
|
||||||
|
|
||||||
const revokeResponse = await selectedProvider
|
const revokeResponse = await selectedProvider
|
||||||
.revoke(decryptedStoredInput, dynamicSecretLease.externalEntityId)
|
.revoke(decryptedStoredInput, dynamicSecretLease.externalEntityId)
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import { ForbiddenError, subject } from "@casl/ability";
|
import { ForbiddenError, subject } from "@casl/ability";
|
||||||
|
|
||||||
|
import { SecretKeyEncoding } 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 { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
||||||
|
import { infisicalSymmetricDecrypt, infisicalSymmetricEncypt } from "@app/lib/crypto/encryption";
|
||||||
import { BadRequestError } from "@app/lib/errors";
|
import { BadRequestError } from "@app/lib/errors";
|
||||||
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";
|
||||||
|
|
||||||
@@ -34,7 +34,6 @@ type TDynamicSecretServiceFactoryDep = {
|
|||||||
folderDAL: Pick<TSecretFolderDALFactory, "findBySecretPath">;
|
folderDAL: Pick<TSecretFolderDALFactory, "findBySecretPath">;
|
||||||
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>;
|
||||||
@@ -47,8 +46,7 @@ export const dynamicSecretServiceFactory = ({
|
|||||||
dynamicSecretProviders,
|
dynamicSecretProviders,
|
||||||
permissionService,
|
permissionService,
|
||||||
dynamicSecretQueueService,
|
dynamicSecretQueueService,
|
||||||
projectDAL,
|
projectDAL
|
||||||
kmsService
|
|
||||||
}: TDynamicSecretServiceFactoryDep) => {
|
}: TDynamicSecretServiceFactoryDep) => {
|
||||||
const create = async ({
|
const create = async ({
|
||||||
path,
|
path,
|
||||||
@@ -98,16 +96,16 @@ 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 { encryptor: secretManagerEncryptor } = await kmsService.createCipherPairWithDataKey({
|
|
||||||
type: KmsDataKey.SecretManager,
|
|
||||||
projectId
|
|
||||||
});
|
|
||||||
|
|
||||||
const encryptedConfig = secretManagerEncryptor({ plainText: Buffer.from(JSON.stringify(inputs)) }).cipherTextBlob;
|
const encryptedInput = infisicalSymmetricEncypt(JSON.stringify(inputs));
|
||||||
const dynamicSecretCfg = await dynamicSecretDAL.create({
|
const dynamicSecretCfg = await dynamicSecretDAL.create({
|
||||||
type: provider.type,
|
type: provider.type,
|
||||||
version: 1,
|
version: 1,
|
||||||
encryptedConfig,
|
inputIV: encryptedInput.iv,
|
||||||
|
inputTag: encryptedInput.tag,
|
||||||
|
inputCiphertext: encryptedInput.ciphertext,
|
||||||
|
algorithm: encryptedInput.algorithm,
|
||||||
|
keyEncoding: encryptedInput.encoding,
|
||||||
maxTTL,
|
maxTTL,
|
||||||
defaultTTL,
|
defaultTTL,
|
||||||
folderId: folder.id,
|
folderId: folder.id,
|
||||||
@@ -167,28 +165,27 @@ export const dynamicSecretServiceFactory = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const selectedProvider = dynamicSecretProviders[dynamicSecretCfg.type as DynamicSecretProviders];
|
const selectedProvider = dynamicSecretProviders[dynamicSecretCfg.type as DynamicSecretProviders];
|
||||||
const { encryptor: secretManagerEncryptor, decryptor: secretManagerDecryptor } =
|
const decryptedStoredInput = JSON.parse(
|
||||||
await kmsService.createCipherPairWithDataKey({
|
infisicalSymmetricDecrypt({
|
||||||
type: KmsDataKey.SecretManager,
|
keyEncoding: dynamicSecretCfg.keyEncoding as SecretKeyEncoding,
|
||||||
projectId
|
ciphertext: dynamicSecretCfg.inputCiphertext,
|
||||||
});
|
tag: dynamicSecretCfg.inputTag,
|
||||||
const dynamicSecretInputConfig = secretManagerDecryptor({
|
iv: dynamicSecretCfg.inputIV
|
||||||
cipherTextBlob: dynamicSecretCfg.encryptedConfig
|
})
|
||||||
}).toString();
|
) as object;
|
||||||
|
|
||||||
const decryptedStoredInput = JSON.parse(dynamicSecretInputConfig) as object;
|
|
||||||
const newInput = { ...decryptedStoredInput, ...(inputs || {}) };
|
const newInput = { ...decryptedStoredInput, ...(inputs || {}) };
|
||||||
const updatedInput = await selectedProvider.validateProviderInputs(newInput);
|
const updatedInput = await selectedProvider.validateProviderInputs(newInput);
|
||||||
|
|
||||||
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 encryptedConfig = secretManagerEncryptor({
|
const encryptedInput = infisicalSymmetricEncypt(JSON.stringify(updatedInput));
|
||||||
plainText: Buffer.from(JSON.stringify(updatedInput))
|
|
||||||
}).cipherTextBlob;
|
|
||||||
|
|
||||||
const updatedDynamicCfg = await dynamicSecretDAL.updateById(dynamicSecretCfg.id, {
|
const updatedDynamicCfg = await dynamicSecretDAL.updateById(dynamicSecretCfg.id, {
|
||||||
encryptedConfig,
|
inputIV: encryptedInput.iv,
|
||||||
|
inputTag: encryptedInput.tag,
|
||||||
|
inputCiphertext: encryptedInput.ciphertext,
|
||||||
|
algorithm: encryptedInput.algorithm,
|
||||||
|
keyEncoding: encryptedInput.encoding,
|
||||||
maxTTL,
|
maxTTL,
|
||||||
defaultTTL,
|
defaultTTL,
|
||||||
name: newName ?? name,
|
name: newName ?? name,
|
||||||
@@ -289,16 +286,14 @@ export const dynamicSecretServiceFactory = ({
|
|||||||
|
|
||||||
const dynamicSecretCfg = await dynamicSecretDAL.findOne({ name, folderId: folder.id });
|
const dynamicSecretCfg = await dynamicSecretDAL.findOne({ name, folderId: folder.id });
|
||||||
if (!dynamicSecretCfg) throw new BadRequestError({ message: "Dynamic secret not found" });
|
if (!dynamicSecretCfg) throw new BadRequestError({ message: "Dynamic secret not found" });
|
||||||
const { decryptor: secretManagerDecryptor } = await kmsService.createCipherPairWithDataKey({
|
const decryptedStoredInput = JSON.parse(
|
||||||
type: KmsDataKey.SecretManager,
|
infisicalSymmetricDecrypt({
|
||||||
projectId
|
keyEncoding: dynamicSecretCfg.keyEncoding as SecretKeyEncoding,
|
||||||
});
|
ciphertext: dynamicSecretCfg.inputCiphertext,
|
||||||
|
tag: dynamicSecretCfg.inputTag,
|
||||||
const dynamicSecretInputConfig = secretManagerDecryptor({
|
iv: dynamicSecretCfg.inputIV
|
||||||
cipherTextBlob: dynamicSecretCfg.encryptedConfig
|
})
|
||||||
}).toString();
|
) as object;
|
||||||
|
|
||||||
const decryptedStoredInput = JSON.parse(dynamicSecretInputConfig) 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;
|
||||||
return { ...dynamicSecretCfg, inputs: providerInputs };
|
return { ...dynamicSecretCfg, inputs: providerInputs };
|
||||||
|
|||||||
@@ -677,8 +677,7 @@ export const registerRoutes = async (
|
|||||||
permissionService,
|
permissionService,
|
||||||
webhookDAL,
|
webhookDAL,
|
||||||
projectEnvDAL,
|
projectEnvDAL,
|
||||||
projectDAL,
|
projectDAL
|
||||||
kmsService
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const secretTagService = secretTagServiceFactory({ secretTagDAL, permissionService });
|
const secretTagService = secretTagServiceFactory({ secretTagDAL, permissionService });
|
||||||
@@ -988,9 +987,7 @@ export const registerRoutes = async (
|
|||||||
queueService,
|
queueService,
|
||||||
dynamicSecretLeaseDAL,
|
dynamicSecretLeaseDAL,
|
||||||
dynamicSecretProviders,
|
dynamicSecretProviders,
|
||||||
dynamicSecretDAL,
|
dynamicSecretDAL
|
||||||
kmsService,
|
|
||||||
folderDAL
|
|
||||||
});
|
});
|
||||||
const dynamicSecretService = dynamicSecretServiceFactory({
|
const dynamicSecretService = dynamicSecretServiceFactory({
|
||||||
projectDAL,
|
projectDAL,
|
||||||
@@ -1000,8 +997,7 @@ export const registerRoutes = async (
|
|||||||
dynamicSecretProviders,
|
dynamicSecretProviders,
|
||||||
folderDAL,
|
folderDAL,
|
||||||
permissionService,
|
permissionService,
|
||||||
licenseService,
|
licenseService
|
||||||
kmsService
|
|
||||||
});
|
});
|
||||||
const dynamicSecretLeaseService = dynamicSecretLeaseServiceFactory({
|
const dynamicSecretLeaseService = dynamicSecretLeaseServiceFactory({
|
||||||
projectDAL,
|
projectDAL,
|
||||||
@@ -1011,8 +1007,7 @@ export const registerRoutes = async (
|
|||||||
dynamicSecretLeaseDAL,
|
dynamicSecretLeaseDAL,
|
||||||
dynamicSecretProviders,
|
dynamicSecretProviders,
|
||||||
folderDAL,
|
folderDAL,
|
||||||
licenseService,
|
licenseService
|
||||||
kmsService
|
|
||||||
});
|
});
|
||||||
const dailyResourceCleanUp = dailyResourceCleanUpQueueServiceFactory({
|
const dailyResourceCleanUp = dailyResourceCleanUpQueueServiceFactory({
|
||||||
auditLogDAL,
|
auditLogDAL,
|
||||||
|
|||||||
@@ -129,7 +129,11 @@ export const SanitizedRoleSchema = ProjectRolesSchema.extend({
|
|||||||
});
|
});
|
||||||
|
|
||||||
export const SanitizedDynamicSecretSchema = DynamicSecretsSchema.omit({
|
export const SanitizedDynamicSecretSchema = DynamicSecretsSchema.omit({
|
||||||
encryptedConfig: true
|
inputIV: true,
|
||||||
|
inputTag: true,
|
||||||
|
inputCiphertext: true,
|
||||||
|
keyEncoding: true,
|
||||||
|
algorithm: true
|
||||||
});
|
});
|
||||||
|
|
||||||
export const SanitizedAuditLogStreamSchema = z.object({
|
export const SanitizedAuditLogStreamSchema = z.object({
|
||||||
|
|||||||
@@ -1133,7 +1133,7 @@ export const secretQueueFactory = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
queueService.start(QueueName.SecretWebhook, async (job) => {
|
queueService.start(QueueName.SecretWebhook, async (job) => {
|
||||||
await fnTriggerWebhook({ ...job.data, projectEnvDAL, webhookDAL, projectDAL, kmsService });
|
await fnTriggerWebhook({ ...job.data, projectEnvDAL, webhookDAL, projectDAL });
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -3,12 +3,12 @@ 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 { request } from "@app/lib/config/request";
|
import { request } from "@app/lib/config/request";
|
||||||
|
import { infisicalSymmetricDecrypt } from "@app/lib/crypto/encryption";
|
||||||
import { BadRequestError } from "@app/lib/errors";
|
import { BadRequestError } from "@app/lib/errors";
|
||||||
import { logger } from "@app/lib/logger";
|
import { logger } from "@app/lib/logger";
|
||||||
|
|
||||||
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";
|
||||||
@@ -16,12 +16,40 @@ import { WebhookType } from "./webhook-types";
|
|||||||
|
|
||||||
const WEBHOOK_TRIGGER_TIMEOUT = 15 * 1000;
|
const WEBHOOK_TRIGGER_TIMEOUT = 15 * 1000;
|
||||||
|
|
||||||
export const triggerWebhookRequest = async (
|
export const decryptWebhookDetails = (webhook: TWebhooks) => {
|
||||||
{ webhookSecretKey: secretKey, webhookUrl: url }: { webhookSecretKey?: string; webhookUrl: string },
|
const { keyEncoding, iv, encryptedSecretKey, tag, urlCipherText, urlIV, urlTag, url } = webhook;
|
||||||
data: Record<string, unknown>
|
|
||||||
) => {
|
let decryptedSecretKey = "";
|
||||||
|
let decryptedUrl = url;
|
||||||
|
|
||||||
|
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 {
|
||||||
|
secretKey: decryptedSecretKey,
|
||||||
|
url: decryptedUrl
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const triggerWebhookRequest = async (webhook: TWebhooks, 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);
|
||||||
|
|
||||||
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");
|
||||||
@@ -96,7 +124,6 @@ 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">;
|
||||||
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// this is reusable function
|
// this is reusable function
|
||||||
@@ -107,8 +134,7 @@ export const fnTriggerWebhook = async ({
|
|||||||
projectId,
|
projectId,
|
||||||
webhookDAL,
|
webhookDAL,
|
||||||
projectEnvDAL,
|
projectEnvDAL,
|
||||||
projectDAL,
|
projectDAL
|
||||||
kmsService
|
|
||||||
}: TFnTriggerWebhookDTO) => {
|
}: TFnTriggerWebhookDTO) => {
|
||||||
const webhooks = await webhookDAL.findAllWebhooks(projectId, environment);
|
const webhooks = await webhookDAL.findAllWebhooks(projectId, environment);
|
||||||
const toBeTriggeredHooks = webhooks.filter(
|
const toBeTriggeredHooks = webhooks.filter(
|
||||||
@@ -118,20 +144,10 @@ export const fnTriggerWebhook = async ({
|
|||||||
if (!toBeTriggeredHooks.length) return;
|
if (!toBeTriggeredHooks.length) return;
|
||||||
logger.info("Secret webhook job started", { environment, secretPath, projectId });
|
logger.info("Secret webhook job started", { environment, secretPath, projectId });
|
||||||
const project = await projectDAL.findById(projectId);
|
const project = await projectDAL.findById(projectId);
|
||||||
const { decryptor: kmsDataKeyDecryptor } = await kmsService.createCipherPairWithDataKey({
|
|
||||||
projectId,
|
|
||||||
type: KmsDataKey.SecretManager
|
|
||||||
});
|
|
||||||
|
|
||||||
const webhooksTriggered = await Promise.allSettled(
|
const webhooksTriggered = await Promise.allSettled(
|
||||||
toBeTriggeredHooks.map((hook) => {
|
toBeTriggeredHooks.map((hook) =>
|
||||||
const webhookUrl = kmsDataKeyDecryptor({ cipherTextBlob: hook.encryptedUrl }).toString();
|
triggerWebhookRequest(
|
||||||
const webhookSecretKey = hook.encryptedSecretKeyWithKms
|
hook,
|
||||||
? kmsDataKeyDecryptor({ cipherTextBlob: hook.encryptedSecretKeyWithKms }).toString()
|
|
||||||
: undefined;
|
|
||||||
|
|
||||||
return triggerWebhookRequest(
|
|
||||||
{ webhookUrl, webhookSecretKey },
|
|
||||||
getWebhookPayload("secrets.modified", {
|
getWebhookPayload("secrets.modified", {
|
||||||
workspaceName: project.name,
|
workspaceName: project.name,
|
||||||
workspaceId: projectId,
|
workspaceId: projectId,
|
||||||
@@ -139,8 +155,8 @@ export const fnTriggerWebhook = async ({
|
|||||||
secretPath,
|
secretPath,
|
||||||
type: hook.type
|
type: hook.type
|
||||||
})
|
})
|
||||||
);
|
)
|
||||||
})
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
// filter hooks by status
|
// filter hooks by status
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
import { ForbiddenError } from "@casl/ability";
|
import { ForbiddenError } from "@casl/ability";
|
||||||
|
|
||||||
|
import { 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 { BadRequestError } from "@app/lib/errors";
|
import { BadRequestError } 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";
|
||||||
import { getWebhookPayload, triggerWebhookRequest } from "./webhook-fns";
|
import { decryptWebhookDetails, getWebhookPayload, triggerWebhookRequest } from "./webhook-fns";
|
||||||
import {
|
import {
|
||||||
TCreateWebhookDTO,
|
TCreateWebhookDTO,
|
||||||
TDeleteWebhookDTO,
|
TDeleteWebhookDTO,
|
||||||
@@ -23,7 +23,6 @@ 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>;
|
||||||
@@ -32,8 +31,7 @@ export const webhookServiceFactory = ({
|
|||||||
webhookDAL,
|
webhookDAL,
|
||||||
projectEnvDAL,
|
projectEnvDAL,
|
||||||
permissionService,
|
permissionService,
|
||||||
projectDAL,
|
projectDAL
|
||||||
kmsService
|
|
||||||
}: TWebhookServiceFactoryDep) => {
|
}: TWebhookServiceFactoryDep) => {
|
||||||
const createWebhook = async ({
|
const createWebhook = async ({
|
||||||
actor,
|
actor,
|
||||||
@@ -58,28 +56,33 @@ export const webhookServiceFactory = ({
|
|||||||
const env = await projectEnvDAL.findOne({ projectId, slug: environment });
|
const env = await projectEnvDAL.findOne({ projectId, slug: environment });
|
||||||
if (!env) throw new BadRequestError({ message: "Env not found" });
|
if (!env) throw new BadRequestError({ message: "Env not found" });
|
||||||
|
|
||||||
const { encryptor: secretManagerEncryptor } = await kmsService.createCipherPairWithDataKey({
|
const insertDoc: TWebhooksInsert = {
|
||||||
projectId,
|
url: "", // deprecated - we are moving away from plaintext URLs
|
||||||
type: KmsDataKey.SecretManager
|
|
||||||
});
|
|
||||||
|
|
||||||
const encryptedSecretKeyWithKms = webhookSecretKey
|
|
||||||
? secretManagerEncryptor({
|
|
||||||
plainText: Buffer.from(webhookSecretKey)
|
|
||||||
}).cipherTextBlob
|
|
||||||
: null;
|
|
||||||
const encryptedUrl = secretManagerEncryptor({
|
|
||||||
plainText: Buffer.from(webhookUrl)
|
|
||||||
}).cipherTextBlob;
|
|
||||||
|
|
||||||
const webhook = await webhookDAL.create({
|
|
||||||
encryptedUrl,
|
|
||||||
encryptedSecretKeyWithKms,
|
|
||||||
envId: env.id,
|
envId: env.id,
|
||||||
isDisabled: false,
|
isDisabled: false,
|
||||||
secretPath: secretPath || "/",
|
secretPath: secretPath || "/",
|
||||||
type
|
type
|
||||||
});
|
};
|
||||||
|
|
||||||
|
if (webhookSecretKey) {
|
||||||
|
const { ciphertext, iv, tag, algorithm, encoding } = infisicalSymmetricEncypt(webhookSecretKey);
|
||||||
|
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);
|
||||||
return { ...webhook, projectId, environment: env };
|
return { ...webhook, projectId, environment: env };
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -133,18 +136,9 @@ export const webhookServiceFactory = ({
|
|||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Webhooks);
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Webhooks);
|
||||||
let webhookError: string | undefined;
|
let webhookError: string | undefined;
|
||||||
const { decryptor: kmsDataKeyDecryptor } = await kmsService.createCipherPairWithDataKey({
|
|
||||||
projectId: project.id,
|
|
||||||
type: KmsDataKey.SecretManager
|
|
||||||
});
|
|
||||||
const webhookUrl = kmsDataKeyDecryptor({ cipherTextBlob: webhook.encryptedUrl }).toString();
|
|
||||||
const webhookSecretKey = webhook.encryptedSecretKeyWithKms
|
|
||||||
? kmsDataKeyDecryptor({ cipherTextBlob: webhook.encryptedSecretKeyWithKms }).toString()
|
|
||||||
: undefined;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await triggerWebhookRequest(
|
await triggerWebhookRequest(
|
||||||
{ webhookUrl, webhookSecretKey },
|
webhook,
|
||||||
getWebhookPayload("test", {
|
getWebhookPayload("test", {
|
||||||
workspaceName: project.name,
|
workspaceName: project.name,
|
||||||
workspaceId: webhook.projectId,
|
workspaceId: webhook.projectId,
|
||||||
@@ -183,15 +177,11 @@ 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: kmsDataKeyDecryptor } = await kmsService.createCipherPairWithDataKey({
|
|
||||||
type: KmsDataKey.SecretManager,
|
|
||||||
projectId
|
|
||||||
});
|
|
||||||
return webhooks.map((w) => {
|
return webhooks.map((w) => {
|
||||||
const decryptedUrl = kmsDataKeyDecryptor({ cipherTextBlob: w.encryptedUrl }).toString();
|
const { url } = decryptWebhookDetails(w);
|
||||||
return {
|
return {
|
||||||
...w,
|
...w,
|
||||||
url: decryptedUrl
|
url
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user