mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-08 09:27:50 +00:00
feat: added secret approval policy in replication
This commit is contained in:
@@ -25,6 +25,9 @@ export const mockKeyStore = (): TKeyStoreFactory => {
|
|||||||
},
|
},
|
||||||
incrementBy: async () => {
|
incrementBy: async () => {
|
||||||
return 1;
|
return 1;
|
||||||
|
},
|
||||||
|
acquireLock: () => {
|
||||||
|
throw new Error("Not implemented");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -20,6 +20,12 @@ export async function up(knex: Knex): Promise<void> {
|
|||||||
t.boolean("isReplicated");
|
t.boolean("isReplicated");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (await knex.schema.hasTable(TableName.SecretApprovalRequestSecret)) {
|
||||||
|
await knex.schema.alterTable(TableName.SecretApprovalRequestSecret, (t) => {
|
||||||
|
t.boolean("isReplicated");
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function down(knex: Knex): Promise<void> {
|
export async function down(knex: Knex): Promise<void> {
|
||||||
@@ -36,7 +42,13 @@ export async function down(knex: Knex): Promise<void> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (await knex.schema.hasTable(TableName.SecretVersion)) {
|
if (await knex.schema.hasTable(TableName.SecretVersion)) {
|
||||||
await knex.schema.alterTable(TableName.Secret, (t) => {
|
await knex.schema.alterTable(TableName.SecretVersion, (t) => {
|
||||||
|
t.dropColumns("isReplicated");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (await knex.schema.hasTable(TableName.SecretApprovalRequestSecret)) {
|
||||||
|
await knex.schema.alterTable(TableName.SecretApprovalRequestSecret, (t) => {
|
||||||
t.dropColumns("isReplicated");
|
t.dropColumns("isReplicated");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,7 +31,8 @@ export const SecretApprovalRequestsSecretsSchema = z.object({
|
|||||||
requestId: z.string().uuid(),
|
requestId: z.string().uuid(),
|
||||||
op: z.string(),
|
op: z.string(),
|
||||||
secretId: z.string().uuid().nullable().optional(),
|
secretId: z.string().uuid().nullable().optional(),
|
||||||
secretVersion: z.string().uuid().nullable().optional()
|
secretVersion: z.string().uuid().nullable().optional(),
|
||||||
|
isReplicated: z.boolean().nullable().optional()
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TSecretApprovalRequestsSecrets = z.infer<typeof SecretApprovalRequestsSecretsSchema>;
|
export type TSecretApprovalRequestsSecrets = z.infer<typeof SecretApprovalRequestsSecretsSchema>;
|
||||||
|
|||||||
+64
-40
@@ -16,8 +16,15 @@ 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";
|
||||||
import { getAllNestedSecretReferences } from "@app/services/secret/secret-fns";
|
import { getAllNestedSecretReferences } from "@app/services/secret/secret-fns";
|
||||||
|
import {
|
||||||
|
fnSecretBlindIndexCheck,
|
||||||
|
fnSecretBlindIndexCheckV2,
|
||||||
|
fnSecretBulkDelete,
|
||||||
|
fnSecretBulkInsert,
|
||||||
|
fnSecretBulkUpdate
|
||||||
|
} from "@app/services/secret/secret-fns";
|
||||||
import { TSecretQueueFactory } from "@app/services/secret/secret-queue";
|
import { TSecretQueueFactory } from "@app/services/secret/secret-queue";
|
||||||
import { TSecretServiceFactory } from "@app/services/secret/secret-service";
|
import { SecretOperations } from "@app/services/secret/secret-types";
|
||||||
import { TSecretVersionDALFactory } from "@app/services/secret/secret-version-dal";
|
import { TSecretVersionDALFactory } from "@app/services/secret/secret-version-dal";
|
||||||
import { TSecretVersionTagDALFactory } from "@app/services/secret/secret-version-tag-dal";
|
import { TSecretVersionTagDALFactory } from "@app/services/secret/secret-version-tag-dal";
|
||||||
import { TSecretBlindIndexDALFactory } from "@app/services/secret-blind-index/secret-blind-index-dal";
|
import { TSecretBlindIndexDALFactory } from "@app/services/secret-blind-index/secret-blind-index-dal";
|
||||||
@@ -32,7 +39,6 @@ import { TSecretApprovalRequestReviewerDALFactory } from "./secret-approval-requ
|
|||||||
import { TSecretApprovalRequestSecretDALFactory } from "./secret-approval-request-secret-dal";
|
import { TSecretApprovalRequestSecretDALFactory } from "./secret-approval-request-secret-dal";
|
||||||
import {
|
import {
|
||||||
ApprovalStatus,
|
ApprovalStatus,
|
||||||
CommitType,
|
|
||||||
RequestState,
|
RequestState,
|
||||||
TApprovalRequestCountDTO,
|
TApprovalRequestCountDTO,
|
||||||
TGenerateSecretApprovalRequestDTO,
|
TGenerateSecretApprovalRequestDTO,
|
||||||
@@ -48,7 +54,7 @@ type TSecretApprovalRequestServiceFactoryDep = {
|
|||||||
secretApprovalRequestDAL: TSecretApprovalRequestDALFactory;
|
secretApprovalRequestDAL: TSecretApprovalRequestDALFactory;
|
||||||
secretApprovalRequestSecretDAL: TSecretApprovalRequestSecretDALFactory;
|
secretApprovalRequestSecretDAL: TSecretApprovalRequestSecretDALFactory;
|
||||||
secretApprovalRequestReviewerDAL: TSecretApprovalRequestReviewerDALFactory;
|
secretApprovalRequestReviewerDAL: TSecretApprovalRequestReviewerDALFactory;
|
||||||
folderDAL: Pick<TSecretFolderDALFactory, "findBySecretPath" | "findById" | "findSecretPathByFolderIds">;
|
folderDAL: Pick<TSecretFolderDALFactory, "findBySecretPath" | "findSecretPathByFolderIds">;
|
||||||
secretDAL: TSecretDALFactory;
|
secretDAL: TSecretDALFactory;
|
||||||
secretTagDAL: Pick<TSecretTagDALFactory, "findManyTagsById" | "saveTagsToSecret" | "deleteTagsManySecret">;
|
secretTagDAL: Pick<TSecretTagDALFactory, "findManyTagsById" | "saveTagsToSecret" | "deleteTagsManySecret">;
|
||||||
secretBlindIndexDAL: Pick<TSecretBlindIndexDALFactory, "findOne">;
|
secretBlindIndexDAL: Pick<TSecretBlindIndexDALFactory, "findOne">;
|
||||||
@@ -56,16 +62,7 @@ type TSecretApprovalRequestServiceFactoryDep = {
|
|||||||
secretVersionDAL: Pick<TSecretVersionDALFactory, "findLatestVersionMany" | "insertMany">;
|
secretVersionDAL: Pick<TSecretVersionDALFactory, "findLatestVersionMany" | "insertMany">;
|
||||||
secretVersionTagDAL: Pick<TSecretVersionTagDALFactory, "insertMany">;
|
secretVersionTagDAL: Pick<TSecretVersionTagDALFactory, "insertMany">;
|
||||||
projectDAL: Pick<TProjectDALFactory, "checkProjectUpgradeStatus">;
|
projectDAL: Pick<TProjectDALFactory, "checkProjectUpgradeStatus">;
|
||||||
projectBotService: Pick<TProjectBotServiceFactory, "getBotKey">;
|
secretQueueService: Pick<TSecretQueueFactory, "syncSecrets" | "removeSecretReminder">;
|
||||||
secretService: Pick<
|
|
||||||
TSecretServiceFactory,
|
|
||||||
| "fnSecretBulkInsert"
|
|
||||||
| "fnSecretBulkUpdate"
|
|
||||||
| "fnSecretBlindIndexCheck"
|
|
||||||
| "fnSecretBulkDelete"
|
|
||||||
| "fnSecretBlindIndexCheckV2"
|
|
||||||
>;
|
|
||||||
secretQueueService: Pick<TSecretQueueFactory, "syncSecrets">;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TSecretApprovalRequestServiceFactory = ReturnType<typeof secretApprovalRequestServiceFactory>;
|
export type TSecretApprovalRequestServiceFactory = ReturnType<typeof secretApprovalRequestServiceFactory>;
|
||||||
@@ -82,7 +79,6 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
projectDAL,
|
projectDAL,
|
||||||
permissionService,
|
permissionService,
|
||||||
snapshotService,
|
snapshotService,
|
||||||
secretService,
|
|
||||||
secretVersionDAL,
|
secretVersionDAL,
|
||||||
secretQueueService,
|
secretQueueService,
|
||||||
projectBotService
|
projectBotService
|
||||||
@@ -302,11 +298,12 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
const secretApprovalSecrets = await secretApprovalRequestSecretDAL.findByRequestId(secretApprovalRequest.id);
|
const secretApprovalSecrets = await secretApprovalRequestSecretDAL.findByRequestId(secretApprovalRequest.id);
|
||||||
if (!secretApprovalSecrets) throw new BadRequestError({ message: "No secrets found" });
|
if (!secretApprovalSecrets) throw new BadRequestError({ message: "No secrets found" });
|
||||||
|
|
||||||
const conflicts: Array<{ secretId: string; op: CommitType }> = [];
|
const conflicts: Array<{ secretId: string; op: SecretOperations }> = [];
|
||||||
let secretCreationCommits = secretApprovalSecrets.filter(({ op }) => op === CommitType.Create);
|
let secretCreationCommits = secretApprovalSecrets.filter(({ op }) => op === SecretOperations.Create);
|
||||||
if (secretCreationCommits.length) {
|
if (secretCreationCommits.length) {
|
||||||
const { secsGroupedByBlindIndex: conflictGroupByBlindIndex } = await secretService.fnSecretBlindIndexCheckV2({
|
const { secsGroupedByBlindIndex: conflictGroupByBlindIndex } = await fnSecretBlindIndexCheckV2({
|
||||||
folderId,
|
folderId,
|
||||||
|
secretDAL,
|
||||||
inputSecrets: secretCreationCommits.map(({ secretBlindIndex }) => {
|
inputSecrets: secretCreationCommits.map(({ secretBlindIndex }) => {
|
||||||
if (!secretBlindIndex) {
|
if (!secretBlindIndex) {
|
||||||
throw new BadRequestError({
|
throw new BadRequestError({
|
||||||
@@ -319,17 +316,19 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
secretCreationCommits
|
secretCreationCommits
|
||||||
.filter(({ secretBlindIndex }) => conflictGroupByBlindIndex[secretBlindIndex || ""])
|
.filter(({ secretBlindIndex }) => conflictGroupByBlindIndex[secretBlindIndex || ""])
|
||||||
.forEach((el) => {
|
.forEach((el) => {
|
||||||
conflicts.push({ op: CommitType.Create, secretId: el.id });
|
conflicts.push({ op: SecretOperations.Create, secretId: el.id });
|
||||||
});
|
});
|
||||||
secretCreationCommits = secretCreationCommits.filter(
|
secretCreationCommits = secretCreationCommits.filter(
|
||||||
({ secretBlindIndex }) => !conflictGroupByBlindIndex[secretBlindIndex || ""]
|
({ secretBlindIndex }) => !conflictGroupByBlindIndex[secretBlindIndex || ""]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let secretUpdationCommits = secretApprovalSecrets.filter(({ op }) => op === CommitType.Update);
|
let secretUpdationCommits = secretApprovalSecrets.filter(({ op }) => op === SecretOperations.Update);
|
||||||
if (secretUpdationCommits.length) {
|
if (secretUpdationCommits.length) {
|
||||||
const { secsGroupedByBlindIndex: conflictGroupByBlindIndex } = await secretService.fnSecretBlindIndexCheckV2({
|
const { secsGroupedByBlindIndex: conflictGroupByBlindIndex } = await fnSecretBlindIndexCheckV2({
|
||||||
folderId,
|
folderId,
|
||||||
|
secretDAL,
|
||||||
|
userId: "",
|
||||||
inputSecrets: secretUpdationCommits
|
inputSecrets: secretUpdationCommits
|
||||||
.filter(({ secretBlindIndex, secret }) => secret && secret.secretBlindIndex !== secretBlindIndex)
|
.filter(({ secretBlindIndex, secret }) => secret && secret.secretBlindIndex !== secretBlindIndex)
|
||||||
.map(({ secretBlindIndex }) => {
|
.map(({ secretBlindIndex }) => {
|
||||||
@@ -347,7 +346,7 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
(secretBlindIndex && conflictGroupByBlindIndex[secretBlindIndex]) || !secretId
|
(secretBlindIndex && conflictGroupByBlindIndex[secretBlindIndex]) || !secretId
|
||||||
)
|
)
|
||||||
.forEach((el) => {
|
.forEach((el) => {
|
||||||
conflicts.push({ op: CommitType.Update, secretId: el.id });
|
conflicts.push({ op: SecretOperations.Update, secretId: el.id });
|
||||||
});
|
});
|
||||||
|
|
||||||
secretUpdationCommits = secretUpdationCommits.filter(
|
secretUpdationCommits = secretUpdationCommits.filter(
|
||||||
@@ -356,11 +355,11 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const secretDeletionCommits = secretApprovalSecrets.filter(({ op }) => op === CommitType.Delete);
|
const secretDeletionCommits = secretApprovalSecrets.filter(({ op }) => op === SecretOperations.Delete);
|
||||||
const botKey = await projectBotService.getBotKey(projectId).catch(() => null);
|
|
||||||
const mergeStatus = await secretApprovalRequestDAL.transaction(async (tx) => {
|
const mergeStatus = await secretApprovalRequestDAL.transaction(async (tx) => {
|
||||||
const newSecrets = secretCreationCommits.length
|
const newSecrets = secretCreationCommits.length
|
||||||
? await secretService.fnSecretBulkInsert({
|
? await fnSecretBulkInsert({
|
||||||
tx,
|
tx,
|
||||||
folderId,
|
folderId,
|
||||||
inputSecrets: secretCreationCommits.map((el) => ({
|
inputSecrets: secretCreationCommits.map((el) => ({
|
||||||
@@ -380,7 +379,8 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
"secretReminderRepeatDays",
|
"secretReminderRepeatDays",
|
||||||
"algorithm",
|
"algorithm",
|
||||||
"keyEncoding",
|
"keyEncoding",
|
||||||
"secretBlindIndex"
|
"secretBlindIndex",
|
||||||
|
"isReplicated"
|
||||||
]),
|
]),
|
||||||
tags: el?.tags.map(({ id }) => id),
|
tags: el?.tags.map(({ id }) => id),
|
||||||
version: 1,
|
version: 1,
|
||||||
@@ -403,7 +403,7 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
})
|
})
|
||||||
: [];
|
: [];
|
||||||
const updatedSecrets = secretUpdationCommits.length
|
const updatedSecrets = secretUpdationCommits.length
|
||||||
? await secretService.fnSecretBulkUpdate({
|
? await fnSecretBulkUpdate({
|
||||||
folderId,
|
folderId,
|
||||||
projectId,
|
projectId,
|
||||||
tx,
|
tx,
|
||||||
@@ -425,6 +425,7 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
"secretKeyTag",
|
"secretKeyTag",
|
||||||
"secretKeyIV",
|
"secretKeyIV",
|
||||||
"metadata",
|
"metadata",
|
||||||
|
"isReplicated",
|
||||||
"skipMultilineEncoding",
|
"skipMultilineEncoding",
|
||||||
"secretReminderNote",
|
"secretReminderNote",
|
||||||
"secretReminderRepeatDays",
|
"secretReminderRepeatDays",
|
||||||
@@ -449,11 +450,13 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
})
|
})
|
||||||
: [];
|
: [];
|
||||||
const deletedSecret = secretDeletionCommits.length
|
const deletedSecret = secretDeletionCommits.length
|
||||||
? await secretService.fnSecretBulkDelete({
|
? await fnSecretBulkDelete({
|
||||||
projectId,
|
projectId,
|
||||||
folderId,
|
folderId,
|
||||||
tx,
|
tx,
|
||||||
actorId: "",
|
actorId: "",
|
||||||
|
secretDAL,
|
||||||
|
secretQueueService,
|
||||||
inputSecrets: secretDeletionCommits.map(({ secretBlindIndex }) => {
|
inputSecrets: secretDeletionCommits.map(({ secretBlindIndex }) => {
|
||||||
if (!secretBlindIndex) {
|
if (!secretBlindIndex) {
|
||||||
throw new BadRequestError({
|
throw new BadRequestError({
|
||||||
@@ -480,12 +483,33 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
await snapshotService.performSnapshot(folderId);
|
await snapshotService.performSnapshot(folderId);
|
||||||
const folder = await folderDAL.findById(folderId);
|
const [folder] = await folderDAL.findSecretPathByFolderIds(projectId, [folderId]);
|
||||||
// TODO(akhilmhdh-pg): change query to do secret path from folder
|
// TODO(akhilmhdh-pg): change query to do secret path from folder
|
||||||
await secretQueueService.syncSecrets({
|
await secretQueueService.syncSecrets({
|
||||||
projectId,
|
projectId,
|
||||||
secretPath: "/",
|
secretPath: folder.path,
|
||||||
environment: folder?.environment.envSlug as string
|
environmentSlug: folder.environmentSlug,
|
||||||
|
folderId: folder.id,
|
||||||
|
membershipId: membership.id,
|
||||||
|
environmentId: folder.envId,
|
||||||
|
secrets: mergeStatus.secrets.created
|
||||||
|
.map(({ id, version }) => ({
|
||||||
|
operation: SecretOperations.Create,
|
||||||
|
version,
|
||||||
|
id
|
||||||
|
}))
|
||||||
|
.concat(
|
||||||
|
mergeStatus.secrets.updated.map(({ id, version }) => ({
|
||||||
|
operation: SecretOperations.Update,
|
||||||
|
version,
|
||||||
|
id
|
||||||
|
})),
|
||||||
|
mergeStatus.secrets.deleted.map(({ id, version }) => ({
|
||||||
|
operation: SecretOperations.Delete,
|
||||||
|
version,
|
||||||
|
id
|
||||||
|
}))
|
||||||
|
)
|
||||||
});
|
});
|
||||||
return mergeStatus;
|
return mergeStatus;
|
||||||
};
|
};
|
||||||
@@ -533,9 +557,9 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
const commits: Omit<TSecretApprovalRequestsSecretsInsert, "requestId">[] = [];
|
const commits: Omit<TSecretApprovalRequestsSecretsInsert, "requestId">[] = [];
|
||||||
const commitTagIds: Record<string, string[]> = {};
|
const commitTagIds: Record<string, string[]> = {};
|
||||||
// for created secret approval change
|
// for created secret approval change
|
||||||
const createdSecrets = data[CommitType.Create];
|
const createdSecrets = data[SecretOperations.Create];
|
||||||
if (createdSecrets && createdSecrets?.length) {
|
if (createdSecrets && createdSecrets?.length) {
|
||||||
const { keyName2BlindIndex } = await secretService.fnSecretBlindIndexCheck({
|
const { keyName2BlindIndex } = await fnSecretBlindIndexCheck({
|
||||||
inputSecrets: createdSecrets,
|
inputSecrets: createdSecrets,
|
||||||
folderId,
|
folderId,
|
||||||
isNew: true,
|
isNew: true,
|
||||||
@@ -546,7 +570,7 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
commits.push(
|
commits.push(
|
||||||
...createdSecrets.map(({ secretName, ...el }) => ({
|
...createdSecrets.map(({ secretName, ...el }) => ({
|
||||||
...el,
|
...el,
|
||||||
op: CommitType.Create as const,
|
op: SecretOperations.Create as const,
|
||||||
version: 1,
|
version: 1,
|
||||||
secretBlindIndex: keyName2BlindIndex[secretName],
|
secretBlindIndex: keyName2BlindIndex[secretName],
|
||||||
algorithm: SecretEncryptionAlgo.AES_256_GCM,
|
algorithm: SecretEncryptionAlgo.AES_256_GCM,
|
||||||
@@ -558,12 +582,12 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
// not secret approval for update operations
|
// not secret approval for update operations
|
||||||
const updatedSecrets = data[CommitType.Update];
|
const updatedSecrets = data[SecretOperations.Update];
|
||||||
if (updatedSecrets && updatedSecrets?.length) {
|
if (updatedSecrets && updatedSecrets?.length) {
|
||||||
// get all blind index
|
// get all blind index
|
||||||
// Find all those secrets
|
// Find all those secrets
|
||||||
// if not throw not found
|
// if not throw not found
|
||||||
const { keyName2BlindIndex, secrets: secretsToBeUpdated } = await secretService.fnSecretBlindIndexCheck({
|
const { keyName2BlindIndex, secrets: secretsToBeUpdated } = await fnSecretBlindIndexCheck({
|
||||||
inputSecrets: updatedSecrets,
|
inputSecrets: updatedSecrets,
|
||||||
folderId,
|
folderId,
|
||||||
isNew: false,
|
isNew: false,
|
||||||
@@ -574,7 +598,7 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
// now find any secret that needs to update its name
|
// now find any secret that needs to update its name
|
||||||
// same process as above
|
// same process as above
|
||||||
const nameUpdatedSecrets = updatedSecrets.filter(({ newSecretName }) => Boolean(newSecretName));
|
const nameUpdatedSecrets = updatedSecrets.filter(({ newSecretName }) => Boolean(newSecretName));
|
||||||
const { keyName2BlindIndex: newKeyName2BlindIndex } = await secretService.fnSecretBlindIndexCheck({
|
const { keyName2BlindIndex: newKeyName2BlindIndex } = await fnSecretBlindIndexCheck({
|
||||||
inputSecrets: nameUpdatedSecrets,
|
inputSecrets: nameUpdatedSecrets,
|
||||||
folderId,
|
folderId,
|
||||||
isNew: true,
|
isNew: true,
|
||||||
@@ -599,7 +623,7 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
return {
|
return {
|
||||||
...latestSecretVersions[secretId],
|
...latestSecretVersions[secretId],
|
||||||
...el,
|
...el,
|
||||||
op: CommitType.Update as const,
|
op: SecretOperations.Update as const,
|
||||||
secret: secretId,
|
secret: secretId,
|
||||||
secretVersion: latestSecretVersions[secretId].id,
|
secretVersion: latestSecretVersions[secretId].id,
|
||||||
secretBlindIndex,
|
secretBlindIndex,
|
||||||
@@ -609,12 +633,12 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
// deleted secrets
|
// deleted secrets
|
||||||
const deletedSecrets = data[CommitType.Delete];
|
const deletedSecrets = data[SecretOperations.Delete];
|
||||||
if (deletedSecrets && deletedSecrets.length) {
|
if (deletedSecrets && deletedSecrets.length) {
|
||||||
// get all blind index
|
// get all blind index
|
||||||
// Find all those secrets
|
// Find all those secrets
|
||||||
// if not throw not found
|
// if not throw not found
|
||||||
const { keyName2BlindIndex, secrets } = await secretService.fnSecretBlindIndexCheck({
|
const { keyName2BlindIndex, secrets } = await fnSecretBlindIndexCheck({
|
||||||
inputSecrets: deletedSecrets,
|
inputSecrets: deletedSecrets,
|
||||||
folderId,
|
folderId,
|
||||||
isNew: false,
|
isNew: false,
|
||||||
@@ -635,7 +659,7 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
if (!latestSecretVersions[secretId].secretBlindIndex)
|
if (!latestSecretVersions[secretId].secretBlindIndex)
|
||||||
throw new BadRequestError({ message: "Failed to find secret blind index" });
|
throw new BadRequestError({ message: "Failed to find secret blind index" });
|
||||||
return {
|
return {
|
||||||
op: CommitType.Delete as const,
|
op: SecretOperations.Delete as const,
|
||||||
...latestSecretVersions[secretId],
|
...latestSecretVersions[secretId],
|
||||||
secretBlindIndex: latestSecretVersions[secretId].secretBlindIndex as string,
|
secretBlindIndex: latestSecretVersions[secretId].secretBlindIndex as string,
|
||||||
secret: secretId,
|
secret: secretId,
|
||||||
|
|||||||
@@ -1,11 +1,6 @@
|
|||||||
import { TImmutableDBKeys, TSecretApprovalPolicies, TSecretApprovalRequestsSecrets } from "@app/db/schemas";
|
import { TImmutableDBKeys, TSecretApprovalPolicies, TSecretApprovalRequestsSecrets } from "@app/db/schemas";
|
||||||
import { TProjectPermission } from "@app/lib/types";
|
import { TProjectPermission } from "@app/lib/types";
|
||||||
|
import { SecretOperations } from "@app/services/secret/secret-types";
|
||||||
export enum CommitType {
|
|
||||||
Create = "create",
|
|
||||||
Update = "update",
|
|
||||||
Delete = "delete"
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum RequestState {
|
export enum RequestState {
|
||||||
Open = "open",
|
Open = "open",
|
||||||
@@ -18,14 +13,14 @@ export enum ApprovalStatus {
|
|||||||
REJECTED = "rejected"
|
REJECTED = "rejected"
|
||||||
}
|
}
|
||||||
|
|
||||||
type TApprovalCreateSecret = Omit<
|
export type TApprovalCreateSecret = Omit<
|
||||||
TSecretApprovalRequestsSecrets,
|
TSecretApprovalRequestsSecrets,
|
||||||
TImmutableDBKeys | "version" | "algorithm" | "keyEncoding" | "requestId" | "op" | "secretVersion" | "secretBlindIndex"
|
TImmutableDBKeys | "version" | "algorithm" | "keyEncoding" | "requestId" | "op" | "secretVersion" | "secretBlindIndex"
|
||||||
> & {
|
> & {
|
||||||
secretName: string;
|
secretName: string;
|
||||||
tagIds?: string[];
|
tagIds?: string[];
|
||||||
};
|
};
|
||||||
type TApprovalUpdateSecret = Partial<TApprovalCreateSecret> & {
|
export type TApprovalUpdateSecret = Partial<TApprovalCreateSecret> & {
|
||||||
secretName: string;
|
secretName: string;
|
||||||
newSecretName?: string;
|
newSecretName?: string;
|
||||||
tagIds?: string[];
|
tagIds?: string[];
|
||||||
@@ -36,9 +31,9 @@ export type TGenerateSecretApprovalRequestDTO = {
|
|||||||
secretPath: string;
|
secretPath: string;
|
||||||
policy: TSecretApprovalPolicies;
|
policy: TSecretApprovalPolicies;
|
||||||
data: {
|
data: {
|
||||||
[CommitType.Create]?: TApprovalCreateSecret[];
|
[SecretOperations.Create]?: TApprovalCreateSecret[];
|
||||||
[CommitType.Update]?: TApprovalUpdateSecret[];
|
[SecretOperations.Update]?: TApprovalUpdateSecret[];
|
||||||
[CommitType.Delete]?: { secretName: string }[];
|
[SecretOperations.Delete]?: { secretName: string }[];
|
||||||
};
|
};
|
||||||
} & TProjectPermission;
|
} & TProjectPermission;
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import {
|
|||||||
TScanFullRepoEventPayload,
|
TScanFullRepoEventPayload,
|
||||||
TScanPushEventPayload
|
TScanPushEventPayload
|
||||||
} from "@app/ee/services/secret-scanning/secret-scanning-queue/secret-scanning-queue-types";
|
} from "@app/ee/services/secret-scanning/secret-scanning-queue/secret-scanning-queue-types";
|
||||||
import { TSyncSecretReplicationDTO } from "@app/services/secret-replication/secret-replication-types";
|
import { TSyncSecretsDTO } from "@app/services/secret/secret-types";
|
||||||
|
|
||||||
export enum QueueName {
|
export enum QueueName {
|
||||||
SecretRotation = "secret-rotation",
|
SecretRotation = "secret-rotation",
|
||||||
@@ -23,7 +23,8 @@ export enum QueueName {
|
|||||||
SecretPushEventScan = "secret-push-event-scan",
|
SecretPushEventScan = "secret-push-event-scan",
|
||||||
UpgradeProjectToGhost = "upgrade-project-to-ghost",
|
UpgradeProjectToGhost = "upgrade-project-to-ghost",
|
||||||
DynamicSecretRevocation = "dynamic-secret-revocation",
|
DynamicSecretRevocation = "dynamic-secret-revocation",
|
||||||
SecretReplication = "secret-replication"
|
SecretReplication = "secret-replication",
|
||||||
|
SecretSync = "secret-sync" // parent queue to push integration sync, webhook, and secret replication
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum QueueJobs {
|
export enum QueueJobs {
|
||||||
@@ -40,7 +41,8 @@ export enum QueueJobs {
|
|||||||
UpgradeProjectToGhost = "upgrade-project-to-ghost-job",
|
UpgradeProjectToGhost = "upgrade-project-to-ghost-job",
|
||||||
DynamicSecretRevocation = "dynamic-secret-revocation",
|
DynamicSecretRevocation = "dynamic-secret-revocation",
|
||||||
DynamicSecretPruning = "dynamic-secret-pruning",
|
DynamicSecretPruning = "dynamic-secret-pruning",
|
||||||
SecretReplication = "secret-replication"
|
SecretReplication = "secret-replication",
|
||||||
|
SecretSync = "secret-sync" // parent queue to push integration sync, webhook, and secret replication
|
||||||
}
|
}
|
||||||
|
|
||||||
export type TQueueJobTypes = {
|
export type TQueueJobTypes = {
|
||||||
@@ -121,7 +123,11 @@ export type TQueueJobTypes = {
|
|||||||
};
|
};
|
||||||
[QueueName.SecretReplication]: {
|
[QueueName.SecretReplication]: {
|
||||||
name: QueueJobs.SecretReplication;
|
name: QueueJobs.SecretReplication;
|
||||||
payload: TSyncSecretReplicationDTO;
|
payload: Omit<TSyncSecretsDTO, "environmentSlug">;
|
||||||
|
};
|
||||||
|
[QueueName.SecretSync]: {
|
||||||
|
name: QueueJobs.SecretSync;
|
||||||
|
payload: TSyncSecretsDTO;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -243,8 +243,8 @@ export const registerRoutes = async (
|
|||||||
const sapApproverDAL = secretApprovalPolicyApproverDALFactory(db);
|
const sapApproverDAL = secretApprovalPolicyApproverDALFactory(db);
|
||||||
const secretApprovalPolicyDAL = secretApprovalPolicyDALFactory(db);
|
const secretApprovalPolicyDAL = secretApprovalPolicyDALFactory(db);
|
||||||
const secretApprovalRequestDAL = secretApprovalRequestDALFactory(db);
|
const secretApprovalRequestDAL = secretApprovalRequestDALFactory(db);
|
||||||
const sarReviewerDAL = secretApprovalRequestReviewerDALFactory(db);
|
const secretApprovalRequestReviewerDAL = secretApprovalRequestReviewerDALFactory(db);
|
||||||
const sarSecretDAL = secretApprovalRequestSecretDALFactory(db);
|
const secretApprovalRequestSecretDAL = secretApprovalRequestSecretDALFactory(db);
|
||||||
|
|
||||||
const secretRotationDAL = secretRotationDALFactory(db);
|
const secretRotationDAL = secretRotationDALFactory(db);
|
||||||
const snapshotDAL = snapshotDALFactory(db);
|
const snapshotDAL = snapshotDALFactory(db);
|
||||||
@@ -492,7 +492,7 @@ export const registerRoutes = async (
|
|||||||
projectBotDAL,
|
projectBotDAL,
|
||||||
projectMembershipDAL,
|
projectMembershipDAL,
|
||||||
secretApprovalRequestDAL,
|
secretApprovalRequestDAL,
|
||||||
secretApprovalSecretDAL: sarSecretDAL,
|
secretApprovalSecretDAL: secretApprovalRequestSecretDAL,
|
||||||
projectUserMembershipRoleDAL
|
projectUserMembershipRoleDAL
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -603,17 +603,6 @@ export const registerRoutes = async (
|
|||||||
secretDAL,
|
secretDAL,
|
||||||
secretBlindIndexDAL
|
secretBlindIndexDAL
|
||||||
});
|
});
|
||||||
const secretReplicationService = secretReplicationServiceFactory({
|
|
||||||
secretTagDAL,
|
|
||||||
secretVersionTagDAL,
|
|
||||||
secretDAL,
|
|
||||||
secretVersionDAL,
|
|
||||||
secretImportDAL,
|
|
||||||
keyStore,
|
|
||||||
queueService,
|
|
||||||
secretReplicationDAL,
|
|
||||||
folderDAL
|
|
||||||
});
|
|
||||||
const secretService = secretServiceFactory({
|
const secretService = secretServiceFactory({
|
||||||
folderDAL,
|
folderDAL,
|
||||||
secretVersionDAL,
|
secretVersionDAL,
|
||||||
@@ -625,7 +614,6 @@ export const registerRoutes = async (
|
|||||||
secretTagDAL,
|
secretTagDAL,
|
||||||
snapshotService,
|
snapshotService,
|
||||||
secretQueueService,
|
secretQueueService,
|
||||||
secretReplicationService,
|
|
||||||
secretImportDAL,
|
secretImportDAL,
|
||||||
projectEnvDAL,
|
projectEnvDAL,
|
||||||
projectBotService
|
projectBotService
|
||||||
@@ -636,19 +624,18 @@ export const registerRoutes = async (
|
|||||||
secretSharingDAL
|
secretSharingDAL
|
||||||
});
|
});
|
||||||
|
|
||||||
const sarService = secretApprovalRequestServiceFactory({
|
const secretApprovalRequestService = secretApprovalRequestServiceFactory({
|
||||||
permissionService,
|
permissionService,
|
||||||
projectBotService,
|
projectBotService,
|
||||||
folderDAL,
|
folderDAL,
|
||||||
secretDAL,
|
secretDAL,
|
||||||
secretTagDAL,
|
secretTagDAL,
|
||||||
secretApprovalRequestSecretDAL: sarSecretDAL,
|
secretApprovalRequestSecretDAL,
|
||||||
secretApprovalRequestReviewerDAL: sarReviewerDAL,
|
secretApprovalRequestReviewerDAL,
|
||||||
projectDAL,
|
projectDAL,
|
||||||
secretVersionDAL,
|
secretVersionDAL,
|
||||||
secretBlindIndexDAL,
|
secretBlindIndexDAL,
|
||||||
secretApprovalRequestDAL,
|
secretApprovalRequestDAL,
|
||||||
secretService,
|
|
||||||
snapshotService,
|
snapshotService,
|
||||||
secretVersionTagDAL,
|
secretVersionTagDAL,
|
||||||
secretQueueService
|
secretQueueService
|
||||||
@@ -677,6 +664,21 @@ export const registerRoutes = async (
|
|||||||
accessApprovalPolicyApproverDAL
|
accessApprovalPolicyApproverDAL
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const secretReplicationService = secretReplicationServiceFactory({
|
||||||
|
secretTagDAL,
|
||||||
|
secretVersionTagDAL,
|
||||||
|
secretDAL,
|
||||||
|
secretVersionDAL,
|
||||||
|
secretImportDAL,
|
||||||
|
keyStore,
|
||||||
|
queueService,
|
||||||
|
secretReplicationDAL,
|
||||||
|
folderDAL,
|
||||||
|
secretApprovalPolicyService,
|
||||||
|
secretBlindIndexDAL,
|
||||||
|
secretApprovalRequestDAL,
|
||||||
|
secretApprovalRequestSecretDAL
|
||||||
|
});
|
||||||
const secretRotationQueue = secretRotationQueueFactory({
|
const secretRotationQueue = secretRotationQueueFactory({
|
||||||
telemetryService,
|
telemetryService,
|
||||||
secretRotationDAL,
|
secretRotationDAL,
|
||||||
@@ -858,10 +860,10 @@ export const registerRoutes = async (
|
|||||||
identityGcpAuth: identityGcpAuthService,
|
identityGcpAuth: identityGcpAuthService,
|
||||||
identityAwsAuth: identityAwsAuthService,
|
identityAwsAuth: identityAwsAuthService,
|
||||||
identityAzureAuth: identityAzureAuthService,
|
identityAzureAuth: identityAzureAuthService,
|
||||||
secretApprovalPolicy: sapService,
|
|
||||||
accessApprovalPolicy: accessApprovalPolicyService,
|
accessApprovalPolicy: accessApprovalPolicyService,
|
||||||
accessApprovalRequest: accessApprovalRequestService,
|
accessApprovalRequest: accessApprovalRequestService,
|
||||||
secretApprovalRequest: sarService,
|
secretApprovalPolicy: secretApprovalPolicyService,
|
||||||
|
secretApprovalRequest: secretApprovalRequestService,
|
||||||
secretRotation: secretRotationService,
|
secretRotation: secretRotationService,
|
||||||
dynamicSecret: dynamicSecretService,
|
dynamicSecret: dynamicSecretService,
|
||||||
dynamicSecretLease: dynamicSecretLeaseService,
|
dynamicSecretLease: dynamicSecretLeaseService,
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import {
|
|||||||
ServiceTokenScopes
|
ServiceTokenScopes
|
||||||
} from "@app/db/schemas";
|
} from "@app/db/schemas";
|
||||||
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
||||||
import { CommitType } from "@app/ee/services/secret-approval-request/secret-approval-request-types";
|
|
||||||
import { RAW_SECRETS, SECRETS } from "@app/lib/api-docs";
|
import { RAW_SECRETS, SECRETS } from "@app/lib/api-docs";
|
||||||
import { BadRequestError } from "@app/lib/errors";
|
import { BadRequestError } from "@app/lib/errors";
|
||||||
import { removeTrailingSlash } from "@app/lib/fn";
|
import { removeTrailingSlash } from "@app/lib/fn";
|
||||||
@@ -19,6 +18,7 @@ import { getUserAgentType } from "@app/server/plugins/audit-log";
|
|||||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
import { ActorType, AuthMode } from "@app/services/auth/auth-type";
|
import { ActorType, AuthMode } from "@app/services/auth/auth-type";
|
||||||
import { ProjectFilterType } from "@app/services/project/project-types";
|
import { ProjectFilterType } from "@app/services/project/project-types";
|
||||||
|
import { SecretOperations } from "@app/services/secret/secret-types";
|
||||||
import { PostHogEventTypes } from "@app/services/telemetry/telemetry-types";
|
import { PostHogEventTypes } from "@app/services/telemetry/telemetry-types";
|
||||||
|
|
||||||
import { secretRawSchema } from "../sanitizedSchemas";
|
import { secretRawSchema } from "../sanitizedSchemas";
|
||||||
@@ -902,7 +902,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
|||||||
projectId,
|
projectId,
|
||||||
policy,
|
policy,
|
||||||
data: {
|
data: {
|
||||||
[CommitType.Create]: [
|
[SecretOperations.Create]: [
|
||||||
{
|
{
|
||||||
secretName: req.params.secretName,
|
secretName: req.params.secretName,
|
||||||
secretValueCiphertext,
|
secretValueCiphertext,
|
||||||
@@ -1084,7 +1084,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
|||||||
projectId,
|
projectId,
|
||||||
policy,
|
policy,
|
||||||
data: {
|
data: {
|
||||||
[CommitType.Update]: [
|
[SecretOperations.Update]: [
|
||||||
{
|
{
|
||||||
secretName: req.params.secretName,
|
secretName: req.params.secretName,
|
||||||
newSecretName,
|
newSecretName,
|
||||||
@@ -1234,7 +1234,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
|||||||
projectId,
|
projectId,
|
||||||
policy,
|
policy,
|
||||||
data: {
|
data: {
|
||||||
[CommitType.Delete]: [
|
[SecretOperations.Delete]: [
|
||||||
{
|
{
|
||||||
secretName: req.params.secretName
|
secretName: req.params.secretName
|
||||||
}
|
}
|
||||||
@@ -1364,7 +1364,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
|||||||
projectId,
|
projectId,
|
||||||
policy,
|
policy,
|
||||||
data: {
|
data: {
|
||||||
[CommitType.Create]: inputSecrets
|
[SecretOperations.Create]: inputSecrets
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1491,7 +1491,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
|||||||
projectId,
|
projectId,
|
||||||
policy,
|
policy,
|
||||||
data: {
|
data: {
|
||||||
[CommitType.Update]: inputSecrets.filter(({ type }) => type === "shared")
|
[SecretOperations.Update]: inputSecrets.filter(({ type }) => type === "shared")
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1606,7 +1606,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
|||||||
projectId,
|
projectId,
|
||||||
policy,
|
policy,
|
||||||
data: {
|
data: {
|
||||||
[CommitType.Delete]: inputSecrets.filter(({ type }) => type === "shared")
|
[SecretOperations.Delete]: inputSecrets.filter(({ type }) => type === "shared")
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
await server.services.auditLog.createAuditLog({
|
await server.services.auditLog.createAuditLog({
|
||||||
|
|||||||
@@ -111,7 +111,8 @@ export const secretImportServiceFactory = ({
|
|||||||
await secretQueueService.syncSecrets({
|
await secretQueueService.syncSecrets({
|
||||||
secretPath: secImport.importPath,
|
secretPath: secImport.importPath,
|
||||||
projectId,
|
projectId,
|
||||||
environment: importEnv.slug
|
environmentSlug: importEnv.slug,
|
||||||
|
excludeReplication: true
|
||||||
});
|
});
|
||||||
|
|
||||||
return { ...secImport, importEnv };
|
return { ...secImport, importEnv };
|
||||||
@@ -223,7 +224,8 @@ export const secretImportServiceFactory = ({
|
|||||||
await secretQueueService.syncSecrets({
|
await secretQueueService.syncSecrets({
|
||||||
secretPath: path,
|
secretPath: path,
|
||||||
projectId,
|
projectId,
|
||||||
environment
|
environmentSlug: environment,
|
||||||
|
excludeReplication: true
|
||||||
});
|
});
|
||||||
|
|
||||||
return secImport;
|
return secImport;
|
||||||
|
|||||||
@@ -1,28 +1,40 @@
|
|||||||
|
import { TSecretApprovalPolicyServiceFactory } from "@app/ee/services/secret-approval-policy/secret-approval-policy-service";
|
||||||
|
import { TSecretApprovalRequestDALFactory } from "@app/ee/services/secret-approval-request/secret-approval-request-dal";
|
||||||
|
import { TSecretApprovalRequestSecretDALFactory } from "@app/ee/services/secret-approval-request/secret-approval-request-secret-dal";
|
||||||
import { TKeyStoreFactory } from "@app/keystore/keystore";
|
import { TKeyStoreFactory } from "@app/keystore/keystore";
|
||||||
import { groupBy } from "@app/lib/fn";
|
import { groupBy } from "@app/lib/fn";
|
||||||
import { logger } from "@app/lib/logger";
|
import { logger } from "@app/lib/logger";
|
||||||
|
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
||||||
import { QueueJobs, QueueName, TQueueServiceFactory } from "@app/queue";
|
import { QueueJobs, QueueName, TQueueServiceFactory } from "@app/queue";
|
||||||
|
|
||||||
import { TSecretDALFactory } from "../secret/secret-dal";
|
import { TSecretDALFactory } from "../secret/secret-dal";
|
||||||
import { fnSecretBulkInsert, fnSecretBulkUpdate } from "../secret/secret-fns";
|
import { fnSecretBulkInsert, fnSecretBulkUpdate } from "../secret/secret-fns";
|
||||||
|
import { SecretOperations, TSyncSecretsDTO } from "../secret/secret-types";
|
||||||
import { TSecretVersionDALFactory } from "../secret/secret-version-dal";
|
import { TSecretVersionDALFactory } from "../secret/secret-version-dal";
|
||||||
import { TSecretVersionTagDALFactory } from "../secret/secret-version-tag-dal";
|
import { TSecretVersionTagDALFactory } from "../secret/secret-version-tag-dal";
|
||||||
|
import { TSecretBlindIndexDALFactory } from "../secret-blind-index/secret-blind-index-dal";
|
||||||
import { TSecretFolderDALFactory } from "../secret-folder/secret-folder-dal";
|
import { TSecretFolderDALFactory } from "../secret-folder/secret-folder-dal";
|
||||||
import { TSecretImportDALFactory } from "../secret-import/secret-import-dal";
|
import { TSecretImportDALFactory } from "../secret-import/secret-import-dal";
|
||||||
import { TSecretTagDALFactory } from "../secret-tag/secret-tag-dal";
|
import { TSecretTagDALFactory } from "../secret-tag/secret-tag-dal";
|
||||||
import { TSecretReplicationDALFactory } from "./secret-replication-dal";
|
import { TSecretReplicationDALFactory } from "./secret-replication-dal";
|
||||||
import { SecretReplicationOperations, TSyncSecretReplicationDTO } from "./secret-replication-types";
|
|
||||||
|
|
||||||
type TSecretReplicationServiceFactoryDep = {
|
type TSecretReplicationServiceFactoryDep = {
|
||||||
secretReplicationDAL: TSecretReplicationDALFactory;
|
secretReplicationDAL: TSecretReplicationDALFactory;
|
||||||
secretDAL: Pick<TSecretDALFactory, "find" | "findByBlindIndexes" | "insertMany" | "bulkUpdate" | "delete">;
|
secretDAL: Pick<TSecretDALFactory, "find" | "findByBlindIndexes" | "insertMany" | "bulkUpdate" | "delete">;
|
||||||
|
secretVersionDAL: Pick<TSecretVersionDALFactory, "find" | "insertMany" | "update" | "findLatestVersionMany">;
|
||||||
secretImportDAL: Pick<TSecretImportDALFactory, "find">;
|
secretImportDAL: Pick<TSecretImportDALFactory, "find">;
|
||||||
folderDAL: Pick<TSecretFolderDALFactory, "findSecretPathByFolderIds">;
|
folderDAL: Pick<TSecretFolderDALFactory, "findSecretPathByFolderIds" | "findBySecretPath">;
|
||||||
secretVersionDAL: Pick<TSecretVersionDALFactory, "find" | "insertMany" | "update">;
|
|
||||||
secretTagDAL: Pick<TSecretTagDALFactory, "find" | "saveTagsToSecret" | "deleteTagsManySecret">;
|
|
||||||
secretVersionTagDAL: Pick<TSecretVersionTagDALFactory, "find" | "insertMany">;
|
secretVersionTagDAL: Pick<TSecretVersionTagDALFactory, "find" | "insertMany">;
|
||||||
queueService: Pick<TQueueServiceFactory, "start" | "listen" | "queue" | "stopJobById">;
|
queueService: Pick<TQueueServiceFactory, "start" | "listen" | "queue" | "stopJobById">;
|
||||||
|
secretApprovalPolicyService: Pick<TSecretApprovalPolicyServiceFactory, "getSecretApprovalPolicy">;
|
||||||
keyStore: Pick<TKeyStoreFactory, "acquireLock">;
|
keyStore: Pick<TKeyStoreFactory, "acquireLock">;
|
||||||
|
secretBlindIndexDAL: Pick<TSecretBlindIndexDALFactory, "findOne">;
|
||||||
|
secretTagDAL: Pick<TSecretTagDALFactory, "findManyTagsById" | "saveTagsToSecret" | "deleteTagsManySecret" | "find">;
|
||||||
|
secretApprovalRequestDAL: Pick<TSecretApprovalRequestDALFactory, "create" | "transaction">;
|
||||||
|
secretApprovalRequestSecretDAL: Pick<
|
||||||
|
TSecretApprovalRequestSecretDALFactory,
|
||||||
|
"insertMany" | "insertApprovalSecretTags"
|
||||||
|
>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TSecretReplicationServiceFactory = ReturnType<typeof secretReplicationServiceFactory>;
|
export type TSecretReplicationServiceFactory = ReturnType<typeof secretReplicationServiceFactory>;
|
||||||
@@ -44,18 +56,21 @@ export const secretReplicationServiceFactory = ({
|
|||||||
keyStore,
|
keyStore,
|
||||||
secretVersionTagDAL,
|
secretVersionTagDAL,
|
||||||
secretTagDAL,
|
secretTagDAL,
|
||||||
folderDAL
|
folderDAL,
|
||||||
|
secretApprovalPolicyService,
|
||||||
|
secretApprovalRequestSecretDAL,
|
||||||
|
secretApprovalRequestDAL
|
||||||
}: TSecretReplicationServiceFactoryDep) => {
|
}: TSecretReplicationServiceFactoryDep) => {
|
||||||
queueService.start(QueueName.SecretReplication, async (job) => {
|
queueService.start(QueueName.SecretReplication, async (job) => {
|
||||||
logger.info(job.data, "Replication started");
|
logger.info(job.data, "Replication started");
|
||||||
const { secrets, folderId, secretPath, environmentId, projectId } = job.data;
|
const { secrets, folderId, secretPath, environmentId, projectId, membershipId } = job.data;
|
||||||
const secretImports = await secretImportDAL.find({
|
const secretImports = await secretImportDAL.find({
|
||||||
importPath: secretPath,
|
importPath: secretPath,
|
||||||
importEnv: environmentId,
|
importEnv: environmentId,
|
||||||
isReplication: true
|
isReplication: true
|
||||||
});
|
});
|
||||||
console.log(">>>> Secret Imports replics ", secretImports.length, secretPath, environmentId);
|
console.log(">>>> Secret Imports replics ", secretImports.length, secretPath, environmentId);
|
||||||
if (!secretImports.length) return;
|
if (!secretImports.length || !secrets.length) return;
|
||||||
|
|
||||||
// unfiltered secrets to be replicated
|
// unfiltered secrets to be replicated
|
||||||
console.log(secrets.length);
|
console.log(secrets.length);
|
||||||
@@ -75,7 +90,8 @@ export const secretReplicationServiceFactory = ({
|
|||||||
try {
|
try {
|
||||||
/* eslint-disable no-await-in-loop */
|
/* eslint-disable no-await-in-loop */
|
||||||
for (const secretImport of secretImports) {
|
for (const secretImport of secretImports) {
|
||||||
const importFolderId = secretImport.folderId;
|
const [importedFolder] = await folderDAL.findSecretPathByFolderIds(projectId, [secretImport.folderId]);
|
||||||
|
const importFolderId = importedFolder.id;
|
||||||
|
|
||||||
const localSecrets = await secretDAL.find({
|
const localSecrets = await secretDAL.find({
|
||||||
$in: { secretBlindIndex: replicatedSecrets.map(({ secretBlindIndex }) => secretBlindIndex) },
|
$in: { secretBlindIndex: replicatedSecrets.map(({ secretBlindIndex }) => secretBlindIndex) },
|
||||||
@@ -86,14 +102,14 @@ export const secretReplicationServiceFactory = ({
|
|||||||
|
|
||||||
const locallyCreatedSecrets = secrets.filter(({ operation, id }) => {
|
const locallyCreatedSecrets = secrets.filter(({ operation, id }) => {
|
||||||
return (
|
return (
|
||||||
(operation === SecretReplicationOperations.Create || operation === SecretReplicationOperations.Update) &&
|
(operation === SecretOperations.Create || operation === SecretOperations.Update) &&
|
||||||
!localSecretsGroupedByBlindIndex[replicatedSecretsGroupBySecretId[id][0].secretBlindIndex as string]?.[0]
|
!localSecretsGroupedByBlindIndex[replicatedSecretsGroupBySecretId[id][0].secretBlindIndex as string]?.[0]
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
const locallyUpdatedSecrets = secrets.filter(
|
const locallyUpdatedSecrets = secrets.filter(
|
||||||
({ operation, id }) =>
|
({ operation, id }) =>
|
||||||
(operation === SecretReplicationOperations.Create || operation === SecretReplicationOperations.Update) &&
|
(operation === SecretOperations.Create || operation === SecretOperations.Update) &&
|
||||||
localSecretsGroupedByBlindIndex[replicatedSecretsGroupBySecretId[id][0].secretBlindIndex as string]?.[0]
|
localSecretsGroupedByBlindIndex[replicatedSecretsGroupBySecretId[id][0].secretBlindIndex as string]?.[0]
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -102,35 +118,48 @@ export const secretReplicationServiceFactory = ({
|
|||||||
console.log("update", locallyUpdatedSecrets);
|
console.log("update", locallyUpdatedSecrets);
|
||||||
console.log("local board", localSecrets);
|
console.log("local board", localSecrets);
|
||||||
|
|
||||||
const locallyDeletedSecrets = secrets
|
const locallyDeletedSecrets = secrets.filter(
|
||||||
.filter(
|
({ operation, id }) =>
|
||||||
({ operation, id }) =>
|
operation === SecretOperations.Delete &&
|
||||||
operation === SecretReplicationOperations.Delete &&
|
Boolean(replicatedSecretsGroupBySecretId[id]?.[0]?.secretBlindIndex) &&
|
||||||
Boolean(replicatedSecretsGroupBySecretId[id]?.[0]?.secretBlindIndex) &&
|
localSecretsGroupedByBlindIndex[replicatedSecretsGroupBySecretId[id][0].secretBlindIndex as string]?.[0]
|
||||||
localSecretsGroupedByBlindIndex[replicatedSecretsGroupBySecretId[id][0].secretBlindIndex as string]?.[0]
|
);
|
||||||
)
|
|
||||||
.map(
|
|
||||||
({ id }) =>
|
|
||||||
localSecretsGroupedByBlindIndex[replicatedSecretsGroupBySecretId[id][0].secretBlindIndex as string][0]
|
|
||||||
);
|
|
||||||
|
|
||||||
let nestedImportSecrets: TSyncSecretReplicationDTO["secrets"] = [];
|
const policy = await secretApprovalPolicyService.getSecretApprovalPolicy(
|
||||||
await secretReplicationDAL.transaction(async (tx) => {
|
projectId,
|
||||||
if (locallyCreatedSecrets.length) {
|
importedFolder.environmentSlug,
|
||||||
const newSecrets = await fnSecretBulkInsert({
|
importedFolder.path
|
||||||
folderId: importFolderId,
|
);
|
||||||
secretVersionDAL,
|
// this means it should be a approval request rather than direct replication
|
||||||
secretDAL,
|
if (policy) {
|
||||||
tx,
|
const localSecretsLatestVersions = localSecrets.map(({ id }) => id);
|
||||||
secretTagDAL,
|
const latestSecretVersions = await secretVersionDAL.findLatestVersionMany(
|
||||||
secretVersionTagDAL,
|
importFolderId,
|
||||||
inputSecrets: locallyCreatedSecrets.map(({ id }) => {
|
localSecretsLatestVersions
|
||||||
|
);
|
||||||
|
await secretApprovalRequestDAL.transaction(async (tx) => {
|
||||||
|
const approvalRequestDoc = await secretApprovalRequestDAL.create(
|
||||||
|
{
|
||||||
|
folderId: importFolderId,
|
||||||
|
slug: alphaNumericNanoId(),
|
||||||
|
policyId: policy.id,
|
||||||
|
status: "open",
|
||||||
|
hasMerged: false,
|
||||||
|
committerId: membershipId
|
||||||
|
},
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
const commits = locallyCreatedSecrets
|
||||||
|
.concat(locallyUpdatedSecrets)
|
||||||
|
.concat(locallyDeletedSecrets)
|
||||||
|
.map(({ id, operation }) => {
|
||||||
const doc = replicatedSecretsGroupBySecretId[id][0];
|
const doc = replicatedSecretsGroupBySecretId[id][0];
|
||||||
|
const localSecret = localSecretsGroupedByBlindIndex[doc.secretBlindIndex as string]?.[0];
|
||||||
return {
|
return {
|
||||||
|
op: operation,
|
||||||
keyEncoding: doc.keyEncoding,
|
keyEncoding: doc.keyEncoding,
|
||||||
algorithm: doc.algorithm,
|
algorithm: doc.algorithm,
|
||||||
folderId,
|
requestId: approvalRequestDoc.id,
|
||||||
type: doc.type,
|
|
||||||
metadata: doc.metadata,
|
metadata: doc.metadata,
|
||||||
secretKeyIV: doc.secretKeyIV,
|
secretKeyIV: doc.secretKeyIV,
|
||||||
secretKeyTag: doc.secretKeyTag,
|
secretKeyTag: doc.secretKeyTag,
|
||||||
@@ -143,31 +172,31 @@ export const secretReplicationServiceFactory = ({
|
|||||||
secretCommentTag: doc.secretCommentTag,
|
secretCommentTag: doc.secretCommentTag,
|
||||||
secretCommentCiphertext: doc.secretCommentCiphertext,
|
secretCommentCiphertext: doc.secretCommentCiphertext,
|
||||||
isReplicated: true,
|
isReplicated: true,
|
||||||
skipMultilineEncoding: doc.skipMultilineEncoding
|
skipMultilineEncoding: doc.skipMultilineEncoding,
|
||||||
|
// except create operation other two needs the secret id and version id
|
||||||
|
...(operation !== SecretOperations.Create
|
||||||
|
? { secretId: localSecret.id, secretVersion: latestSecretVersions[localSecret.id].id }
|
||||||
|
: {})
|
||||||
};
|
};
|
||||||
})
|
});
|
||||||
});
|
const approvalCommits = await secretApprovalRequestSecretDAL.insertMany(commits, tx);
|
||||||
nestedImportSecrets = nestedImportSecrets.concat(
|
|
||||||
...newSecrets.map(({ id, version }) => ({ operation: SecretReplicationOperations.Create, version, id }))
|
return { ...approvalRequestDoc, commits: approvalCommits };
|
||||||
);
|
});
|
||||||
}
|
} else {
|
||||||
if (locallyUpdatedSecrets.length) {
|
let nestedImportSecrets: TSyncSecretsDTO["secrets"] = [];
|
||||||
const newSecrets = await fnSecretBulkUpdate({
|
await secretReplicationDAL.transaction(async (tx) => {
|
||||||
projectId,
|
if (locallyCreatedSecrets.length) {
|
||||||
folderId: importFolderId,
|
const newSecrets = await fnSecretBulkInsert({
|
||||||
secretVersionDAL,
|
folderId: importFolderId,
|
||||||
secretDAL,
|
secretVersionDAL,
|
||||||
tx,
|
secretDAL,
|
||||||
secretTagDAL,
|
tx,
|
||||||
secretVersionTagDAL,
|
secretTagDAL,
|
||||||
inputSecrets: locallyUpdatedSecrets.map(({ id }) => {
|
secretVersionTagDAL,
|
||||||
const doc = replicatedSecretsGroupBySecretId[id][0];
|
inputSecrets: locallyCreatedSecrets.map(({ id }) => {
|
||||||
return {
|
const doc = replicatedSecretsGroupBySecretId[id][0];
|
||||||
filter: {
|
return {
|
||||||
folderId: importFolderId,
|
|
||||||
id: localSecretsGroupedByBlindIndex[doc.secretBlindIndex as string][0].id
|
|
||||||
},
|
|
||||||
data: {
|
|
||||||
keyEncoding: doc.keyEncoding,
|
keyEncoding: doc.keyEncoding,
|
||||||
algorithm: doc.algorithm,
|
algorithm: doc.algorithm,
|
||||||
type: doc.type,
|
type: doc.type,
|
||||||
@@ -184,39 +213,80 @@ export const secretReplicationServiceFactory = ({
|
|||||||
secretCommentCiphertext: doc.secretCommentCiphertext,
|
secretCommentCiphertext: doc.secretCommentCiphertext,
|
||||||
isReplicated: true,
|
isReplicated: true,
|
||||||
skipMultilineEncoding: doc.skipMultilineEncoding
|
skipMultilineEncoding: doc.skipMultilineEncoding
|
||||||
}
|
};
|
||||||
};
|
})
|
||||||
})
|
});
|
||||||
});
|
nestedImportSecrets = nestedImportSecrets.concat(
|
||||||
nestedImportSecrets = nestedImportSecrets.concat(
|
newSecrets.map(({ id, version }) => ({ operation: SecretOperations.Create, version, id }))
|
||||||
...newSecrets.map(({ id, version }) => ({ operation: SecretReplicationOperations.Update, version, id }))
|
);
|
||||||
);
|
}
|
||||||
}
|
if (locallyUpdatedSecrets.length) {
|
||||||
if (locallyDeletedSecrets.length) {
|
const newSecrets = await fnSecretBulkUpdate({
|
||||||
const newSecrets = await secretDAL.delete(
|
projectId,
|
||||||
{
|
folderId: importFolderId,
|
||||||
$in: {
|
secretVersionDAL,
|
||||||
id: locallyDeletedSecrets.map(({ id }) => id)
|
secretDAL,
|
||||||
|
tx,
|
||||||
|
secretTagDAL,
|
||||||
|
secretVersionTagDAL,
|
||||||
|
inputSecrets: locallyUpdatedSecrets.map(({ id }) => {
|
||||||
|
const doc = replicatedSecretsGroupBySecretId[id][0];
|
||||||
|
return {
|
||||||
|
filter: {
|
||||||
|
folderId: importFolderId,
|
||||||
|
id: localSecretsGroupedByBlindIndex[doc.secretBlindIndex as string][0].id
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
keyEncoding: doc.keyEncoding,
|
||||||
|
algorithm: doc.algorithm,
|
||||||
|
type: doc.type,
|
||||||
|
metadata: doc.metadata,
|
||||||
|
secretKeyIV: doc.secretKeyIV,
|
||||||
|
secretKeyTag: doc.secretKeyTag,
|
||||||
|
secretKeyCiphertext: doc.secretKeyCiphertext,
|
||||||
|
secretValueIV: doc.secretValueIV,
|
||||||
|
secretValueTag: doc.secretValueTag,
|
||||||
|
secretValueCiphertext: doc.secretValueCiphertext,
|
||||||
|
secretBlindIndex: doc.secretBlindIndex,
|
||||||
|
secretCommentIV: doc.secretCommentIV,
|
||||||
|
secretCommentTag: doc.secretCommentTag,
|
||||||
|
secretCommentCiphertext: doc.secretCommentCiphertext,
|
||||||
|
isReplicated: true,
|
||||||
|
skipMultilineEncoding: doc.skipMultilineEncoding
|
||||||
|
}
|
||||||
|
};
|
||||||
|
})
|
||||||
|
});
|
||||||
|
nestedImportSecrets = nestedImportSecrets.concat(
|
||||||
|
newSecrets.map(({ id, version }) => ({ operation: SecretOperations.Update, version, id }))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (locallyDeletedSecrets.length) {
|
||||||
|
const newSecrets = await secretDAL.delete(
|
||||||
|
{
|
||||||
|
$in: {
|
||||||
|
id: locallyDeletedSecrets.map(({ id }) => id)
|
||||||
|
},
|
||||||
|
isReplicated: true,
|
||||||
|
folderId: importFolderId
|
||||||
},
|
},
|
||||||
isReplicated: true,
|
tx
|
||||||
folderId: importFolderId
|
);
|
||||||
},
|
nestedImportSecrets = nestedImportSecrets.concat(
|
||||||
tx
|
newSecrets.map(({ id, version }) => ({ operation: SecretOperations.Delete, version, id }))
|
||||||
);
|
);
|
||||||
nestedImportSecrets = nestedImportSecrets.concat(
|
}
|
||||||
...newSecrets.map(({ id, version }) => ({ operation: SecretReplicationOperations.Delete, version, id }))
|
});
|
||||||
);
|
console.log("Environment ID -> slug", importedFolder.envId, importedFolder.environmentSlug);
|
||||||
}
|
await queueService.queue(QueueName.SecretReplication, QueueJobs.SecretReplication, {
|
||||||
});
|
folderId: importedFolder.id,
|
||||||
const [folder] = await folderDAL.findSecretPathByFolderIds(projectId, [secretImport.folderId]);
|
projectId,
|
||||||
console.log("Environment ID -> slug", folder.envId, folder.environmentSlug);
|
secrets: nestedImportSecrets,
|
||||||
await queueService.queue(QueueName.SecretReplication, QueueJobs.SecretReplication, {
|
secretPath: importedFolder.path,
|
||||||
folderId: folder.id,
|
environmentId: importedFolder.envId,
|
||||||
projectId,
|
membershipId
|
||||||
secrets: nestedImportSecrets,
|
});
|
||||||
secretPath: folder.path,
|
}
|
||||||
environmentId: folder.envId
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
await secretVersionDAL.update({ $in: { id: replicatedSecrets.map(({ id }) => id) } }, { isReplicated: true });
|
await secretVersionDAL.update({ $in: { id: replicatedSecrets.map(({ id }) => id) } }, { isReplicated: true });
|
||||||
/* eslint-enable no-await-in-loop */
|
/* eslint-enable no-await-in-loop */
|
||||||
@@ -228,20 +298,4 @@ export const secretReplicationServiceFactory = ({
|
|||||||
queueService.listen(QueueName.SecretReplication, "failed", async (job, err) => {
|
queueService.listen(QueueName.SecretReplication, "failed", async (job, err) => {
|
||||||
logger.error(err, "Failed to replicate secret", job?.data);
|
logger.error(err, "Failed to replicate secret", job?.data);
|
||||||
});
|
});
|
||||||
|
|
||||||
const replicate = async (data: TSyncSecretReplicationDTO) => {
|
|
||||||
await queueService.queue(QueueName.SecretReplication, QueueJobs.SecretReplication, data, {
|
|
||||||
attempts: 3,
|
|
||||||
backoff: {
|
|
||||||
type: "exponential",
|
|
||||||
delay: 1000
|
|
||||||
},
|
|
||||||
removeOnComplete: true,
|
|
||||||
removeOnFail: true
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
|
||||||
replicate
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,17 +1,3 @@
|
|||||||
export enum SecretReplicationOperations {
|
|
||||||
Create = "create",
|
|
||||||
Update = "update",
|
|
||||||
Delete = "delete"
|
|
||||||
}
|
|
||||||
|
|
||||||
export type TSyncSecretReplicationDTO = {
|
export type TSyncSecretReplicationDTO = {
|
||||||
secretPath: string;
|
id: string;
|
||||||
projectId: string;
|
|
||||||
environmentId: string;
|
|
||||||
folderId: string;
|
|
||||||
secrets: {
|
|
||||||
operation: SecretReplicationOperations;
|
|
||||||
id: string;
|
|
||||||
version: number;
|
|
||||||
}[];
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -32,6 +32,8 @@ import {
|
|||||||
TCreateManySecretsRawFn,
|
TCreateManySecretsRawFn,
|
||||||
TCreateManySecretsRawFnFactory,
|
TCreateManySecretsRawFnFactory,
|
||||||
TFnSecretBlindIndexCheck,
|
TFnSecretBlindIndexCheck,
|
||||||
|
TFnSecretBlindIndexCheckV2,
|
||||||
|
TFnSecretBulkDelete,
|
||||||
TFnSecretBulkInsert,
|
TFnSecretBulkInsert,
|
||||||
TFnSecretBulkUpdate,
|
TFnSecretBulkUpdate,
|
||||||
TUpdateManySecretsRawFn,
|
TUpdateManySecretsRawFn,
|
||||||
@@ -396,6 +398,30 @@ export const decryptSecretRaw = (
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// this is used when secret blind index already exist
|
||||||
|
// mainly for secret approval
|
||||||
|
export const fnSecretBlindIndexCheckV2 = async ({
|
||||||
|
inputSecrets,
|
||||||
|
folderId,
|
||||||
|
userId,
|
||||||
|
secretDAL
|
||||||
|
}: TFnSecretBlindIndexCheckV2) => {
|
||||||
|
if (inputSecrets.some(({ type }) => type === SecretType.Personal) && !userId) {
|
||||||
|
throw new BadRequestError({ message: "Missing user id for personal secret" });
|
||||||
|
}
|
||||||
|
const secrets = await secretDAL.findByBlindIndexes(
|
||||||
|
folderId,
|
||||||
|
inputSecrets.map(({ secretBlindIndex, type }) => ({
|
||||||
|
blindIndex: secretBlindIndex,
|
||||||
|
type: type || SecretType.Shared
|
||||||
|
})),
|
||||||
|
userId
|
||||||
|
);
|
||||||
|
const secsGroupedByBlindIndex = groupBy(secrets, (i) => i.secretBlindIndex as string);
|
||||||
|
|
||||||
|
return { secsGroupedByBlindIndex, secrets };
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Grabs and processes nested secret references from a string
|
* Grabs and processes nested secret references from a string
|
||||||
*
|
*
|
||||||
@@ -598,6 +624,35 @@ export const fnSecretBulkUpdate = async ({
|
|||||||
return newSecrets.map((secret) => ({ ...secret, _id: secret.id }));
|
return newSecrets.map((secret) => ({ ...secret, _id: secret.id }));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const fnSecretBulkDelete = async ({
|
||||||
|
folderId,
|
||||||
|
inputSecrets,
|
||||||
|
tx,
|
||||||
|
actorId,
|
||||||
|
secretDAL,
|
||||||
|
secretQueueService
|
||||||
|
}: TFnSecretBulkDelete) => {
|
||||||
|
const deletedSecrets = await secretDAL.deleteMany(
|
||||||
|
inputSecrets.map(({ type, secretBlindIndex }) => ({
|
||||||
|
blindIndex: secretBlindIndex,
|
||||||
|
type
|
||||||
|
})),
|
||||||
|
folderId,
|
||||||
|
actorId,
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
|
||||||
|
await Promise.allSettled(
|
||||||
|
deletedSecrets
|
||||||
|
.filter(({ secretReminderRepeatDays }) => Boolean(secretReminderRepeatDays))
|
||||||
|
.map(({ id, secretReminderRepeatDays }) =>
|
||||||
|
secretQueueService.removeSecretReminder({ secretId: id, repeatDays: secretReminderRepeatDays as number })
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
return deletedSecrets;
|
||||||
|
};
|
||||||
|
|
||||||
export const createManySecretsRawFnFactory = ({
|
export const createManySecretsRawFnFactory = ({
|
||||||
projectDAL,
|
projectDAL,
|
||||||
projectBotDAL,
|
projectBotDAL,
|
||||||
|
|||||||
@@ -28,7 +28,12 @@ import { TWebhookDALFactory } from "../webhook/webhook-dal";
|
|||||||
import { fnTriggerWebhook } from "../webhook/webhook-fns";
|
import { fnTriggerWebhook } from "../webhook/webhook-fns";
|
||||||
import { TSecretDALFactory } from "./secret-dal";
|
import { TSecretDALFactory } from "./secret-dal";
|
||||||
import { interpolateSecrets } from "./secret-fns";
|
import { interpolateSecrets } from "./secret-fns";
|
||||||
import { TCreateSecretReminderDTO, THandleReminderDTO, TRemoveSecretReminderDTO } from "./secret-types";
|
import {
|
||||||
|
TCreateSecretReminderDTO,
|
||||||
|
THandleReminderDTO,
|
||||||
|
TRemoveSecretReminderDTO,
|
||||||
|
TSyncSecretsDTO
|
||||||
|
} from "./secret-types";
|
||||||
|
|
||||||
export type TSecretQueueFactory = ReturnType<typeof secretQueueFactory>;
|
export type TSecretQueueFactory = ReturnType<typeof secretQueueFactory>;
|
||||||
type TSecretQueueFactoryDep = {
|
type TSecretQueueFactoryDep = {
|
||||||
@@ -116,21 +121,17 @@ export const secretQueueFactory = ({
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const syncSecrets = async ({
|
const syncSecrets = async <T extends boolean = false>({_deDupeQueue:deDupeQueue = {},_depth = 0, ...dto}: TSyncSecretsDTO<T>) => {
|
||||||
deDupeQueue = {},
|
logger.info(
|
||||||
...dto
|
`syncSecrets: syncing project secrets where [projectId=${dto.projectId}] [environment=${dto.environmentSlug}] [path=${dto.secretPath}]`
|
||||||
}: TGetSecrets & { depth?: number; deDupeQueue?: Record<string, boolean> }) => {
|
);
|
||||||
const deDuplicationKey = uniqueIntegrationKey(dto.environment, dto.secretPath);
|
const deDuplicationKey = uniqueIntegrationKey(dto.environmentSlug, dto.secretPath);
|
||||||
if (deDupeQueue?.[deDuplicationKey]) {
|
if (deDupeQueue?.[deDuplicationKey]) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
deDupeQueue[deDuplicationKey] = true;
|
deDupeQueue[deDuplicationKey] = true;
|
||||||
logger.info(
|
await queueService.queue(QueueName.SecretSync, QueueJobs.SecretSync, dto as TSyncSecretsDTO, {
|
||||||
`syncSecrets: syncing project secrets where [projectId=${dto.projectId}] [environment=${dto.environment}] [path=${dto.secretPath}]`
|
|
||||||
);
|
|
||||||
await queueService.queue(QueueName.SecretWebhook, QueueJobs.SecWebhook, dto, {
|
|
||||||
jobId: `secret-webhook-${dto.environment}-${dto.projectId}-${dto.secretPath}`,
|
|
||||||
removeOnFail: true,
|
removeOnFail: true,
|
||||||
removeOnComplete: true,
|
removeOnComplete: true,
|
||||||
delay: 1000,
|
delay: 1000,
|
||||||
@@ -140,7 +141,6 @@ export const secretQueueFactory = ({
|
|||||||
delay: 3000
|
delay: 3000
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
await syncIntegrations({ ...dto, deDupeQueue });
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const removeSecretReminder = async (dto: TRemoveSecretReminderDTO) => {
|
const removeSecretReminder = async (dto: TRemoveSecretReminderDTO) => {
|
||||||
@@ -333,6 +333,52 @@ export const secretQueueFactory = ({
|
|||||||
return content;
|
return content;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
queueService.start(QueueName.SecretSync, async (job) => {
|
||||||
|
const {
|
||||||
|
secretPath,
|
||||||
|
environmentId,
|
||||||
|
projectId,
|
||||||
|
environmentSlug: environment,
|
||||||
|
secrets,
|
||||||
|
folderId,
|
||||||
|
excludeReplication,
|
||||||
|
membershipId
|
||||||
|
} = job.data;
|
||||||
|
await queueService.queue(
|
||||||
|
QueueName.SecretWebhook,
|
||||||
|
QueueJobs.SecWebhook,
|
||||||
|
{ environment, projectId, secretPath },
|
||||||
|
{
|
||||||
|
jobId: `secret-webhook-${environmentId}-${projectId}-${secretPath}`,
|
||||||
|
removeOnFail: { count: 5 },
|
||||||
|
removeOnComplete: true,
|
||||||
|
delay: 1000,
|
||||||
|
attempts: 5,
|
||||||
|
backoff: {
|
||||||
|
type: "exponential",
|
||||||
|
delay: 3000
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
await syncIntegrations({ secretPath, projectId, environment });
|
||||||
|
if (!excludeReplication) {
|
||||||
|
await queueService.queue(
|
||||||
|
QueueName.SecretReplication,
|
||||||
|
QueueJobs.SecretReplication,
|
||||||
|
{ environmentId, projectId, secretPath, folderId, secrets, membershipId },
|
||||||
|
{
|
||||||
|
attempts: 3,
|
||||||
|
backoff: {
|
||||||
|
type: "exponential",
|
||||||
|
delay: 1000
|
||||||
|
},
|
||||||
|
removeOnComplete: true,
|
||||||
|
removeOnFail: true
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
queueService.start(QueueName.IntegrationSync, async (job) => {
|
queueService.start(QueueName.IntegrationSync, async (job) => {
|
||||||
const { environment, projectId, secretPath, depth = 1, deDupeQueue = {} } = job.data;
|
const { environment, projectId, secretPath, depth = 1, deDupeQueue = {} } = job.data;
|
||||||
|
|
||||||
@@ -375,11 +421,12 @@ export const secretQueueFactory = ({
|
|||||||
)
|
)
|
||||||
.map(({ folderId }) =>
|
.map(({ folderId }) =>
|
||||||
syncSecrets({
|
syncSecrets({
|
||||||
depth: depth + 1,
|
_depth: depth + 1,
|
||||||
projectId,
|
projectId,
|
||||||
secretPath: foldersGroupedById[folderId][0].path,
|
secretPath: foldersGroupedById[folderId][0].path,
|
||||||
environment: foldersGroupedById[folderId][0].environmentSlug,
|
environmentSlug: foldersGroupedById[folderId][0].environmentSlug,
|
||||||
deDupeQueue
|
_deDupeQueue: deDupeQueue,
|
||||||
|
excludeReplication: true
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
@@ -412,11 +459,12 @@ export const secretQueueFactory = ({
|
|||||||
)
|
)
|
||||||
.map(({ folderId }) =>
|
.map(({ folderId }) =>
|
||||||
syncSecrets({
|
syncSecrets({
|
||||||
depth: depth + 1,
|
_depth: depth + 1,
|
||||||
projectId,
|
projectId,
|
||||||
secretPath: referencedFoldersGroupedById[folderId][0].path,
|
secretPath: referencedFoldersGroupedById[folderId][0].path,
|
||||||
environment: referencedFoldersGroupedById[folderId][0].environmentSlug,
|
environmentSlug: referencedFoldersGroupedById[folderId][0].environmentSlug,
|
||||||
deDupeQueue
|
_deDupeQueue: deDupeQueue,
|
||||||
|
excludeReplication: true
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
@@ -546,7 +594,7 @@ export const secretQueueFactory = ({
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
// depth is internal only field thus no need to make it available outside
|
// depth is internal only field thus no need to make it available outside
|
||||||
syncSecrets: (dto: TGetSecrets) => syncSecrets(dto),
|
syncSecrets,
|
||||||
syncIntegrations,
|
syncIntegrations,
|
||||||
addSecretReminder,
|
addSecretReminder,
|
||||||
removeSecretReminder,
|
removeSecretReminder,
|
||||||
|
|||||||
@@ -30,13 +30,12 @@ import { TSecretBlindIndexDALFactory } from "../secret-blind-index/secret-blind-
|
|||||||
import { TSecretFolderDALFactory } from "../secret-folder/secret-folder-dal";
|
import { TSecretFolderDALFactory } from "../secret-folder/secret-folder-dal";
|
||||||
import { TSecretImportDALFactory } from "../secret-import/secret-import-dal";
|
import { TSecretImportDALFactory } from "../secret-import/secret-import-dal";
|
||||||
import { fnSecretsFromImports } from "../secret-import/secret-import-fns";
|
import { fnSecretsFromImports } from "../secret-import/secret-import-fns";
|
||||||
import { TSecretReplicationServiceFactory } from "../secret-replication/secret-replication-service";
|
|
||||||
import { SecretReplicationOperations } from "../secret-replication/secret-replication-types";
|
|
||||||
import { TSecretTagDALFactory } from "../secret-tag/secret-tag-dal";
|
import { TSecretTagDALFactory } from "../secret-tag/secret-tag-dal";
|
||||||
import { TSecretDALFactory } from "./secret-dal";
|
import { TSecretDALFactory } from "./secret-dal";
|
||||||
import {
|
import {
|
||||||
decryptSecretRaw,
|
decryptSecretRaw,
|
||||||
fnSecretBlindIndexCheck,
|
fnSecretBlindIndexCheck,
|
||||||
|
fnSecretBulkDelete,
|
||||||
fnSecretBulkInsert,
|
fnSecretBulkInsert,
|
||||||
fnSecretBulkUpdate,
|
fnSecretBulkUpdate,
|
||||||
getAllNestedSecretReferences,
|
getAllNestedSecretReferences,
|
||||||
@@ -45,6 +44,7 @@ import {
|
|||||||
} from "./secret-fns";
|
} from "./secret-fns";
|
||||||
import { TSecretQueueFactory } from "./secret-queue";
|
import { TSecretQueueFactory } from "./secret-queue";
|
||||||
import {
|
import {
|
||||||
|
SecretOperations,
|
||||||
TAttachSecretTagsDTO,
|
TAttachSecretTagsDTO,
|
||||||
TBackFillSecretReferencesDTO,
|
TBackFillSecretReferencesDTO,
|
||||||
TCreateBulkSecretDTO,
|
TCreateBulkSecretDTO,
|
||||||
@@ -55,8 +55,6 @@ import {
|
|||||||
TDeleteManySecretRawDTO,
|
TDeleteManySecretRawDTO,
|
||||||
TDeleteSecretDTO,
|
TDeleteSecretDTO,
|
||||||
TDeleteSecretRawDTO,
|
TDeleteSecretRawDTO,
|
||||||
TFnSecretBlindIndexCheckV2,
|
|
||||||
TFnSecretBulkDelete,
|
|
||||||
TGetASecretDTO,
|
TGetASecretDTO,
|
||||||
TGetASecretRawDTO,
|
TGetASecretRawDTO,
|
||||||
TGetSecretsDTO,
|
TGetSecretsDTO,
|
||||||
@@ -87,7 +85,6 @@ type TSecretServiceFactoryDep = {
|
|||||||
projectBotService: Pick<TProjectBotServiceFactory, "getBotKey">;
|
projectBotService: Pick<TProjectBotServiceFactory, "getBotKey">;
|
||||||
secretImportDAL: Pick<TSecretImportDALFactory, "find" | "findByFolderIds">;
|
secretImportDAL: Pick<TSecretImportDALFactory, "find" | "findByFolderIds">;
|
||||||
secretVersionTagDAL: Pick<TSecretVersionTagDALFactory, "insertMany">;
|
secretVersionTagDAL: Pick<TSecretVersionTagDALFactory, "insertMany">;
|
||||||
secretReplicationService: Pick<TSecretReplicationServiceFactory, "replicate">;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TSecretServiceFactory = ReturnType<typeof secretServiceFactory>;
|
export type TSecretServiceFactory = ReturnType<typeof secretServiceFactory>;
|
||||||
@@ -104,8 +101,7 @@ export const secretServiceFactory = ({
|
|||||||
projectDAL,
|
projectDAL,
|
||||||
projectBotService,
|
projectBotService,
|
||||||
secretImportDAL,
|
secretImportDAL,
|
||||||
secretVersionTagDAL,
|
secretVersionTagDAL
|
||||||
secretReplicationService
|
|
||||||
}: TSecretServiceFactoryDep) => {
|
}: TSecretServiceFactoryDep) => {
|
||||||
const getSecretReference = async (projectId: string) => {
|
const getSecretReference = async (projectId: string) => {
|
||||||
// if bot key missing means e2e still exist
|
// if bot key missing means e2e still exist
|
||||||
@@ -143,53 +139,6 @@ export const secretServiceFactory = ({
|
|||||||
return secretBlindIndex;
|
return secretBlindIndex;
|
||||||
};
|
};
|
||||||
|
|
||||||
const fnSecretBulkDelete = async ({ folderId, inputSecrets, tx, actorId }: TFnSecretBulkDelete) => {
|
|
||||||
const deletedSecrets = await secretDAL.deleteMany(
|
|
||||||
inputSecrets.map(({ type, secretBlindIndex }) => ({
|
|
||||||
blindIndex: secretBlindIndex,
|
|
||||||
type
|
|
||||||
})),
|
|
||||||
folderId,
|
|
||||||
actorId,
|
|
||||||
tx
|
|
||||||
);
|
|
||||||
|
|
||||||
for (const s of deletedSecrets) {
|
|
||||||
if (s.secretReminderRepeatDays) {
|
|
||||||
// eslint-disable-next-line no-await-in-loop
|
|
||||||
await secretQueueService
|
|
||||||
.removeSecretReminder({
|
|
||||||
secretId: s.id,
|
|
||||||
repeatDays: s.secretReminderRepeatDays
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
logger.error(err, `Failed to delete secret reminder for secret with ID ${s?.id}`);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return deletedSecrets;
|
|
||||||
};
|
|
||||||
|
|
||||||
// this is used when secret blind index already exist
|
|
||||||
// mainly for secret approval
|
|
||||||
const fnSecretBlindIndexCheckV2 = async ({ inputSecrets, folderId, userId }: TFnSecretBlindIndexCheckV2) => {
|
|
||||||
if (inputSecrets.some(({ type }) => type === SecretType.Personal) && !userId) {
|
|
||||||
throw new BadRequestError({ message: "Missing user id for personal secret" });
|
|
||||||
}
|
|
||||||
const secrets = await secretDAL.findByBlindIndexes(
|
|
||||||
folderId,
|
|
||||||
inputSecrets.map(({ secretBlindIndex, type }) => ({
|
|
||||||
blindIndex: secretBlindIndex,
|
|
||||||
type: type || SecretType.Shared
|
|
||||||
})),
|
|
||||||
userId
|
|
||||||
);
|
|
||||||
const secsGroupedByBlindIndex = groupBy(secrets, (i) => i.secretBlindIndex as string);
|
|
||||||
|
|
||||||
return { secsGroupedByBlindIndex, secrets };
|
|
||||||
};
|
|
||||||
|
|
||||||
const createSecret = async ({
|
const createSecret = async ({
|
||||||
path,
|
path,
|
||||||
actor,
|
actor,
|
||||||
@@ -200,7 +149,7 @@ export const secretServiceFactory = ({
|
|||||||
projectId,
|
projectId,
|
||||||
...inputSecret
|
...inputSecret
|
||||||
}: TCreateSecretDTO) => {
|
}: TCreateSecretDTO) => {
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
const { permission, membership } = await permissionService.getProjectPermission(
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
projectId,
|
projectId,
|
||||||
@@ -287,16 +236,17 @@ export const secretServiceFactory = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
await snapshotService.performSnapshot(folderId);
|
await snapshotService.performSnapshot(folderId);
|
||||||
await secretQueueService.syncSecrets({ secretPath: path, projectId, environment });
|
await secretQueueService.syncSecrets({
|
||||||
// TODO(akhilmhdh-pg): licence check, posthog service and snapshot
|
|
||||||
await secretReplicationService.replicate({
|
|
||||||
folderId,
|
|
||||||
projectId,
|
|
||||||
environmentId: folder.envId,
|
|
||||||
secretPath: path,
|
secretPath: path,
|
||||||
|
// if secret service reached means there was no secret policy
|
||||||
|
// TODO(akhilmhdh): The policy based replication will fail if machine identity is used.
|
||||||
|
membershipId: membership?.id as string,
|
||||||
|
projectId,
|
||||||
|
environmentSlug: folder.environment.slug,
|
||||||
|
environmentId: folder.envId,
|
||||||
secrets: [
|
secrets: [
|
||||||
{
|
{
|
||||||
operation: SecretReplicationOperations.Create,
|
operation: SecretOperations.Create,
|
||||||
id: secret[0].id,
|
id: secret[0].id,
|
||||||
version: 1
|
version: 1
|
||||||
}
|
}
|
||||||
@@ -315,7 +265,7 @@ export const secretServiceFactory = ({
|
|||||||
projectId,
|
projectId,
|
||||||
...inputSecret
|
...inputSecret
|
||||||
}: TUpdateSecretDTO) => {
|
}: TUpdateSecretDTO) => {
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
const { permission, membership } = await permissionService.getProjectPermission(
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
projectId,
|
projectId,
|
||||||
@@ -430,15 +380,18 @@ export const secretServiceFactory = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
await snapshotService.performSnapshot(folderId);
|
await snapshotService.performSnapshot(folderId);
|
||||||
await secretQueueService.syncSecrets({ secretPath: path, projectId, environment });
|
await secretQueueService.syncSecrets({
|
||||||
await secretReplicationService.replicate({
|
// if secret service reached means there was no secret policy
|
||||||
folderId,
|
// TODO(akhilmhdh): The policy based replication will fail if machine identity is used.
|
||||||
projectId,
|
membershipId: membership?.id as string,
|
||||||
environmentId: folder.envId,
|
|
||||||
secretPath: path,
|
secretPath: path,
|
||||||
|
folderId: folder.id,
|
||||||
|
projectId,
|
||||||
|
environmentSlug: folder.environment.slug,
|
||||||
|
environmentId: folder.envId,
|
||||||
secrets: [
|
secrets: [
|
||||||
{
|
{
|
||||||
operation: SecretReplicationOperations.Update,
|
operation: SecretOperations.Update,
|
||||||
id: updatedSecret[0].id,
|
id: updatedSecret[0].id,
|
||||||
version: updatedSecret[0].version
|
version: updatedSecret[0].version
|
||||||
}
|
}
|
||||||
@@ -457,7 +410,7 @@ export const secretServiceFactory = ({
|
|||||||
projectId,
|
projectId,
|
||||||
...inputSecret
|
...inputSecret
|
||||||
}: TDeleteSecretDTO) => {
|
}: TDeleteSecretDTO) => {
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
const { permission, membership } = await permissionService.getProjectPermission(
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
projectId,
|
projectId,
|
||||||
@@ -499,6 +452,8 @@ export const secretServiceFactory = ({
|
|||||||
projectId,
|
projectId,
|
||||||
folderId,
|
folderId,
|
||||||
actorId,
|
actorId,
|
||||||
|
secretDAL,
|
||||||
|
secretQueueService,
|
||||||
inputSecrets: [
|
inputSecrets: [
|
||||||
{
|
{
|
||||||
type: inputSecret.type as SecretType,
|
type: inputSecret.type as SecretType,
|
||||||
@@ -510,15 +465,18 @@ export const secretServiceFactory = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
await snapshotService.performSnapshot(folderId);
|
await snapshotService.performSnapshot(folderId);
|
||||||
await secretQueueService.syncSecrets({ secretPath: path, projectId, environment });
|
await secretQueueService.syncSecrets({
|
||||||
await secretReplicationService.replicate({
|
// if secret service reached means there was no secret policy
|
||||||
folderId,
|
// TODO(akhilmhdh): The policy based replication will fail if machine identity is used.
|
||||||
projectId,
|
membershipId: membership?.id as string,
|
||||||
environmentId: folder.envId,
|
|
||||||
secretPath: path,
|
secretPath: path,
|
||||||
|
folderId: folder.id,
|
||||||
|
projectId,
|
||||||
|
environmentSlug: folder.environment.slug,
|
||||||
|
environmentId: folder.envId,
|
||||||
secrets: [
|
secrets: [
|
||||||
{
|
{
|
||||||
operation: SecretReplicationOperations.Delete,
|
operation: SecretOperations.Delete,
|
||||||
id: deletedSecret[0].id,
|
id: deletedSecret[0].id,
|
||||||
version: deletedSecret[0].version
|
version: deletedSecret[0].version
|
||||||
}
|
}
|
||||||
@@ -744,7 +702,7 @@ export const secretServiceFactory = ({
|
|||||||
projectId,
|
projectId,
|
||||||
secrets: inputSecrets
|
secrets: inputSecrets
|
||||||
}: TCreateBulkSecretDTO) => {
|
}: TCreateBulkSecretDTO) => {
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
const { permission, membership } = await permissionService.getProjectPermission(
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
projectId,
|
projectId,
|
||||||
@@ -808,13 +766,16 @@ export const secretServiceFactory = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
await snapshotService.performSnapshot(folderId);
|
await snapshotService.performSnapshot(folderId);
|
||||||
await secretQueueService.syncSecrets({ secretPath: path, projectId, environment });
|
await secretQueueService.syncSecrets({
|
||||||
await secretReplicationService.replicate({
|
// if secret service reached means there was no secret policy
|
||||||
folderId,
|
// TODO(akhilmhdh): The policy based replication will fail if machine identity is used.
|
||||||
projectId,
|
membershipId: membership?.id as string,
|
||||||
environmentId: folder.envId,
|
|
||||||
secretPath: path,
|
secretPath: path,
|
||||||
secrets: newSecrets.map(({ id, version }) => ({ id, version, operation: SecretReplicationOperations.Create }))
|
folderId: folder.id,
|
||||||
|
projectId,
|
||||||
|
environmentSlug: folder.environment.slug,
|
||||||
|
environmentId: folder.envId,
|
||||||
|
secrets: newSecrets.map(({ id, version }) => ({ id, version, operation: SecretOperations.Create }))
|
||||||
});
|
});
|
||||||
|
|
||||||
return newSecrets;
|
return newSecrets;
|
||||||
@@ -830,7 +791,7 @@ export const secretServiceFactory = ({
|
|||||||
projectId,
|
projectId,
|
||||||
secrets: inputSecrets
|
secrets: inputSecrets
|
||||||
}: TUpdateBulkSecretDTO) => {
|
}: TUpdateBulkSecretDTO) => {
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
const { permission, membership } = await permissionService.getProjectPermission(
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
projectId,
|
projectId,
|
||||||
@@ -915,13 +876,16 @@ export const secretServiceFactory = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
await snapshotService.performSnapshot(folderId);
|
await snapshotService.performSnapshot(folderId);
|
||||||
await secretQueueService.syncSecrets({ secretPath: path, projectId, environment });
|
await secretQueueService.syncSecrets({
|
||||||
await secretReplicationService.replicate({
|
// if secret service reached means there was no secret policy
|
||||||
folderId,
|
// TODO(akhilmhdh): The policy based replication will fail if machine identity is used.
|
||||||
projectId,
|
membershipId: membership?.id as string,
|
||||||
environmentId: folder.envId,
|
|
||||||
secretPath: path,
|
secretPath: path,
|
||||||
secrets: secrets.map(({ id, version }) => ({ id, version, operation: SecretReplicationOperations.Update }))
|
folderId: folder.id,
|
||||||
|
projectId,
|
||||||
|
environmentSlug: folder.environment.slug,
|
||||||
|
environmentId: folder.envId,
|
||||||
|
secrets: secrets.map(({ id, version }) => ({ id, version, operation: SecretOperations.Update }))
|
||||||
});
|
});
|
||||||
|
|
||||||
return secrets;
|
return secrets;
|
||||||
@@ -937,7 +901,7 @@ export const secretServiceFactory = ({
|
|||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId
|
||||||
}: TDeleteBulkSecretDTO) => {
|
}: TDeleteBulkSecretDTO) => {
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
const { permission, membership } = await permissionService.getProjectPermission(
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
projectId,
|
projectId,
|
||||||
@@ -972,6 +936,8 @@ export const secretServiceFactory = ({
|
|||||||
|
|
||||||
const secretsDeleted = await secretDAL.transaction(async (tx) =>
|
const secretsDeleted = await secretDAL.transaction(async (tx) =>
|
||||||
fnSecretBulkDelete({
|
fnSecretBulkDelete({
|
||||||
|
secretDAL,
|
||||||
|
secretQueueService,
|
||||||
inputSecrets: inputSecrets.map(({ type, secretName }) => ({
|
inputSecrets: inputSecrets.map(({ type, secretName }) => ({
|
||||||
secretBlindIndex: keyName2BlindIndex[secretName],
|
secretBlindIndex: keyName2BlindIndex[secretName],
|
||||||
type
|
type
|
||||||
@@ -984,13 +950,16 @@ export const secretServiceFactory = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
await snapshotService.performSnapshot(folderId);
|
await snapshotService.performSnapshot(folderId);
|
||||||
await secretQueueService.syncSecrets({ secretPath: path, projectId, environment });
|
await secretQueueService.syncSecrets({
|
||||||
await secretReplicationService.replicate({
|
// if secret service reached means there was no secret policy
|
||||||
folderId,
|
// TODO(akhilmhdh): The policy based replication will fail if machine identity is used.
|
||||||
projectId,
|
membershipId: membership?.id as string,
|
||||||
environmentId: folder.envId,
|
|
||||||
secretPath: path,
|
secretPath: path,
|
||||||
secrets: secretsDeleted.map(({ id, version }) => ({ id, version, operation: SecretReplicationOperations.Delete }))
|
folderId: folder.id,
|
||||||
|
projectId,
|
||||||
|
environmentSlug: folder.environment.slug,
|
||||||
|
environmentId: folder.envId,
|
||||||
|
secrets: secretsDeleted.map(({ id, version }) => ({ id, version, operation: SecretOperations.Delete }))
|
||||||
});
|
});
|
||||||
|
|
||||||
return secretsDeleted;
|
return secretsDeleted;
|
||||||
@@ -1171,9 +1140,6 @@ export const secretServiceFactory = ({
|
|||||||
skipMultilineEncoding
|
skipMultilineEncoding
|
||||||
});
|
});
|
||||||
|
|
||||||
await snapshotService.performSnapshot(secret.folderId);
|
|
||||||
await secretQueueService.syncSecrets({ secretPath, projectId, environment });
|
|
||||||
|
|
||||||
return decryptSecretRaw(secret, botKey);
|
return decryptSecretRaw(secret, botKey);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1212,8 +1178,6 @@ export const secretServiceFactory = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
await snapshotService.performSnapshot(secret.folderId);
|
await snapshotService.performSnapshot(secret.folderId);
|
||||||
await secretQueueService.syncSecrets({ secretPath, projectId, environment });
|
|
||||||
|
|
||||||
return decryptSecretRaw(secret, botKey);
|
return decryptSecretRaw(secret, botKey);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1243,9 +1207,6 @@ export const secretServiceFactory = ({
|
|||||||
actorAuthMethod
|
actorAuthMethod
|
||||||
});
|
});
|
||||||
|
|
||||||
await snapshotService.performSnapshot(secret.folderId);
|
|
||||||
await secretQueueService.syncSecrets({ secretPath, projectId, environment });
|
|
||||||
|
|
||||||
return decryptSecretRaw(secret, botKey);
|
return decryptSecretRaw(secret, botKey);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1294,12 +1255,7 @@ export const secretServiceFactory = ({
|
|||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
await snapshotService.performSnapshot(secrets[0].folderId);
|
return secrets.map((secret) => decryptSecretRaw({ ...secret, workspace: projectId, environment }, botKey));
|
||||||
await secretQueueService.syncSecrets({ secretPath, projectId, environment });
|
|
||||||
|
|
||||||
return secrets.map((secret) =>
|
|
||||||
decryptSecretRaw({ ...secret, workspace: projectId, environment, secretPath }, botKey)
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateManySecretsRaw = async ({
|
const updateManySecretsRaw = async ({
|
||||||
@@ -1348,12 +1304,7 @@ export const secretServiceFactory = ({
|
|||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
await snapshotService.performSnapshot(secrets[0].folderId);
|
return secrets.map((secret) => decryptSecretRaw({ ...secret, workspace: projectId, environment }, botKey));
|
||||||
await secretQueueService.syncSecrets({ secretPath, projectId, environment });
|
|
||||||
|
|
||||||
return secrets.map((secret) =>
|
|
||||||
decryptSecretRaw({ ...secret, workspace: projectId, environment, secretPath }, botKey)
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const deleteManySecretsRaw = async ({
|
const deleteManySecretsRaw = async ({
|
||||||
@@ -1384,12 +1335,7 @@ export const secretServiceFactory = ({
|
|||||||
secrets: inputSecrets.map(({ secretKey }) => ({ secretName: secretKey, type: SecretType.Shared }))
|
secrets: inputSecrets.map(({ secretKey }) => ({ secretName: secretKey, type: SecretType.Shared }))
|
||||||
});
|
});
|
||||||
|
|
||||||
await snapshotService.performSnapshot(secrets[0].folderId);
|
return secrets.map((secret) => decryptSecretRaw({ ...secret, workspace: projectId, environment }, botKey));
|
||||||
await secretQueueService.syncSecrets({ secretPath, projectId, environment });
|
|
||||||
|
|
||||||
return secrets.map((secret) =>
|
|
||||||
decryptSecretRaw({ ...secret, workspace: projectId, environment, secretPath }, botKey)
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const getSecretVersions = async ({
|
const getSecretVersions = async ({
|
||||||
@@ -1510,7 +1456,12 @@ export const secretServiceFactory = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
await snapshotService.performSnapshot(folder.id);
|
await snapshotService.performSnapshot(folder.id);
|
||||||
await secretQueueService.syncSecrets({ secretPath, projectId: project.id, environment });
|
await secretQueueService.syncSecrets({
|
||||||
|
secretPath,
|
||||||
|
projectId: project.id,
|
||||||
|
environmentSlug: environment,
|
||||||
|
excludeReplication: true
|
||||||
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...updatedSecret[0],
|
...updatedSecret[0],
|
||||||
@@ -1612,7 +1563,12 @@ export const secretServiceFactory = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
await snapshotService.performSnapshot(folder.id);
|
await snapshotService.performSnapshot(folder.id);
|
||||||
await secretQueueService.syncSecrets({ secretPath, projectId: project.id, environment });
|
await secretQueueService.syncSecrets({
|
||||||
|
secretPath,
|
||||||
|
projectId: project.id,
|
||||||
|
environmentSlug: environment,
|
||||||
|
excludeReplication: true
|
||||||
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...updatedSecret[0],
|
...updatedSecret[0],
|
||||||
@@ -1685,13 +1641,6 @@ export const secretServiceFactory = ({
|
|||||||
createManySecretsRaw,
|
createManySecretsRaw,
|
||||||
updateManySecretsRaw,
|
updateManySecretsRaw,
|
||||||
deleteManySecretsRaw,
|
deleteManySecretsRaw,
|
||||||
getSecretVersions,
|
getSecretVersions
|
||||||
backfillSecretReferences,
|
|
||||||
// external services function
|
|
||||||
fnSecretBulkDelete,
|
|
||||||
fnSecretBulkUpdate,
|
|
||||||
fnSecretBlindIndexCheck,
|
|
||||||
fnSecretBulkInsert,
|
|
||||||
fnSecretBlindIndexCheckV2
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -264,6 +264,10 @@ export type TFnSecretBulkDelete = {
|
|||||||
inputSecrets: Array<{ type: SecretType; secretBlindIndex: string }>;
|
inputSecrets: Array<{ type: SecretType; secretBlindIndex: string }>;
|
||||||
actorId: string;
|
actorId: string;
|
||||||
tx?: Knex;
|
tx?: Knex;
|
||||||
|
secretDAL: Pick<TSecretDALFactory, "deleteMany">;
|
||||||
|
secretQueueService: {
|
||||||
|
removeSecretReminder: (data: TRemoveSecretReminderDTO) => Promise<void>;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TFnSecretBlindIndexCheck = {
|
export type TFnSecretBlindIndexCheck = {
|
||||||
@@ -277,6 +281,7 @@ export type TFnSecretBlindIndexCheck = {
|
|||||||
|
|
||||||
// when blind index is already present
|
// when blind index is already present
|
||||||
export type TFnSecretBlindIndexCheckV2 = {
|
export type TFnSecretBlindIndexCheckV2 = {
|
||||||
|
secretDAL: Pick<TSecretDALFactory, "findByBlindIndexes">;
|
||||||
folderId: string;
|
folderId: string;
|
||||||
userId?: string;
|
userId?: string;
|
||||||
inputSecrets: Array<{ secretBlindIndex: string; type?: SecretType }>;
|
inputSecrets: Array<{ secretBlindIndex: string; type?: SecretType }>;
|
||||||
@@ -363,3 +368,30 @@ export type TUpdateManySecretsRawFn = {
|
|||||||
}[];
|
}[];
|
||||||
userId?: string;
|
userId?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export enum SecretOperations {
|
||||||
|
Create = "create",
|
||||||
|
Update = "update",
|
||||||
|
Delete = "delete"
|
||||||
|
}
|
||||||
|
|
||||||
|
export type TSyncSecretsDTO<T extends boolean = false> = {
|
||||||
|
_depth?: number;
|
||||||
|
_deDupeQueue?: Record<string, boolean>;
|
||||||
|
secretPath: string;
|
||||||
|
projectId: string;
|
||||||
|
environmentSlug: string;
|
||||||
|
// cases for just doing sync integration and webhook
|
||||||
|
excludeReplication?: T;
|
||||||
|
} & (T extends true
|
||||||
|
? object
|
||||||
|
: {
|
||||||
|
environmentId: string;
|
||||||
|
folderId: string;
|
||||||
|
membershipId: string;
|
||||||
|
secrets: {
|
||||||
|
operation: SecretOperations;
|
||||||
|
id: string;
|
||||||
|
version: number;
|
||||||
|
}[];
|
||||||
|
});
|
||||||
|
|||||||
@@ -89,6 +89,7 @@ export const secretVersionDALFactory = (db: TDbClient) => {
|
|||||||
|
|
||||||
const findLatestVersionMany = async (folderId: string, secretIds: string[], tx?: Knex) => {
|
const findLatestVersionMany = async (folderId: string, secretIds: string[], tx?: Knex) => {
|
||||||
try {
|
try {
|
||||||
|
if (!secretIds.length) return {};
|
||||||
const docs: Array<TSecretVersions & { max: number }> = await (tx || db)(TableName.SecretVersion)
|
const docs: Array<TSecretVersions & { max: number }> = await (tx || db)(TableName.SecretVersion)
|
||||||
.where("folderId", folderId)
|
.where("folderId", folderId)
|
||||||
.whereIn(`${TableName.SecretVersion}.secretId`, secretIds)
|
.whereIn(`${TableName.SecretVersion}.secretId`, secretIds)
|
||||||
|
|||||||
Reference in New Issue
Block a user