mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-14 23:52:09 +00:00
MVP preliminary idea for initial sync behavior
This commit is contained in:
@@ -11,6 +11,7 @@ import { groupBy, pick } from "@app/lib/fn";
|
||||
import { logger } from "@app/lib/logger";
|
||||
|
||||
import { ActorType } from "../auth/auth-type";
|
||||
import { TProjectDALFactory } from "../project/project-dal";
|
||||
import { TProjectBotServiceFactory } from "../project-bot/project-bot-service";
|
||||
import { TSecretBlindIndexDALFactory } from "../secret-blind-index/secret-blind-index-dal";
|
||||
import { TSecretFolderDALFactory } from "../secret-folder/secret-folder-dal";
|
||||
@@ -18,19 +19,22 @@ import { TSecretImportDALFactory } from "../secret-import/secret-import-dal";
|
||||
import { fnSecretsFromImports } from "../secret-import/secret-import-fns";
|
||||
import { TSecretTagDALFactory } from "../secret-tag/secret-tag-dal";
|
||||
import { TSecretDALFactory } from "./secret-dal";
|
||||
import { decryptSecretRaw, generateSecretBlindIndexBySalt } from "./secret-fns";
|
||||
import {
|
||||
decryptSecretRaw,
|
||||
fnSecretBlindIndexCheck,
|
||||
fnSecretBulkInsert
|
||||
} from "./secret-fns";
|
||||
import { TSecretQueueFactory } from "./secret-queue";
|
||||
import {
|
||||
TCreateManySecretsRawHelper,
|
||||
TCreateBulkSecretDTO,
|
||||
TCreateSecretDTO,
|
||||
TCreateSecretRawDTO,
|
||||
TDeleteBulkSecretDTO,
|
||||
TDeleteSecretDTO,
|
||||
TDeleteSecretRawDTO,
|
||||
TFnSecretBlindIndexCheck,
|
||||
TFnSecretBlindIndexCheckV2,
|
||||
TFnSecretBulkDelete,
|
||||
TFnSecretBulkInsert,
|
||||
TFnSecretBulkUpdate,
|
||||
TGetASecretDTO,
|
||||
TGetASecretRawDTO,
|
||||
@@ -49,6 +53,7 @@ type TSecretServiceFactoryDep = {
|
||||
secretTagDAL: TSecretTagDALFactory;
|
||||
secretVersionDAL: TSecretVersionDALFactory;
|
||||
folderDAL: Pick<TSecretFolderDALFactory, "findBySecretPath" | "updateById" | "findById" | "findByManySecretPath">;
|
||||
projectDAL: Pick<TProjectDALFactory, "checkProjectUpgradeStatus">;
|
||||
secretBlindIndexDAL: TSecretBlindIndexDALFactory;
|
||||
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
|
||||
snapshotService: Pick<TSecretSnapshotServiceFactory, "performSnapshot">;
|
||||
@@ -68,6 +73,7 @@ export const secretServiceFactory = ({
|
||||
permissionService,
|
||||
snapshotService,
|
||||
secretQueueService,
|
||||
projectDAL,
|
||||
projectBotService,
|
||||
secretImportDAL,
|
||||
secretVersionTagDAL
|
||||
@@ -92,41 +98,6 @@ export const secretServiceFactory = ({
|
||||
return secretBlindIndex;
|
||||
};
|
||||
|
||||
// these functions are special functions shared by a couple of resources
|
||||
// used by secret approval, rotation or anywhere in which secret needs to modified
|
||||
const fnSecretBulkInsert = async ({ folderId, inputSecrets, tx }: TFnSecretBulkInsert) => {
|
||||
const newSecrets = await secretDAL.insertMany(
|
||||
inputSecrets.map(({ tags, ...el }) => ({ ...el, folderId })),
|
||||
tx
|
||||
);
|
||||
const newSecretGroupByBlindIndex = groupBy(newSecrets, (item) => item.secretBlindIndex as string);
|
||||
const newSecretTags = inputSecrets.flatMap(({ tags: secretTags = [], secretBlindIndex }) =>
|
||||
secretTags.map((tag) => ({
|
||||
[`${TableName.SecretTag}Id` as const]: tag,
|
||||
[`${TableName.Secret}Id` as const]: newSecretGroupByBlindIndex[secretBlindIndex as string][0].id
|
||||
}))
|
||||
);
|
||||
const secretVersions = await secretVersionDAL.insertMany(
|
||||
inputSecrets.map(({ tags, ...el }) => ({
|
||||
...el,
|
||||
folderId,
|
||||
secretId: newSecretGroupByBlindIndex[el.secretBlindIndex as string][0].id
|
||||
})),
|
||||
tx
|
||||
);
|
||||
if (newSecretTags.length) {
|
||||
const secTags = await secretTagDAL.saveTagsToSecret(newSecretTags, tx);
|
||||
const secVersionsGroupBySecId = groupBy(secretVersions, (i) => i.secretId);
|
||||
const newSecretVersionTags = secTags.flatMap(({ secretsId, secret_tagsId }) => ({
|
||||
[`${TableName.SecretVersion}Id` as const]: secVersionsGroupBySecId[secretsId][0].id,
|
||||
[`${TableName.SecretTag}Id` as const]: secret_tagsId
|
||||
}));
|
||||
await secretVersionTagDAL.insertMany(newSecretVersionTags, tx);
|
||||
}
|
||||
|
||||
return newSecrets.map((secret) => ({ ...secret, _id: secret.id }));
|
||||
};
|
||||
|
||||
const fnSecretBulkUpdate = async ({ tx, inputSecrets, folderId, projectId }: TFnSecretBulkUpdate) => {
|
||||
const newSecrets = await secretDAL.bulkUpdate(
|
||||
inputSecrets.map(({ filter, data: { tags, ...data } }) => ({
|
||||
@@ -199,54 +170,6 @@ export const secretServiceFactory = ({
|
||||
return deletedSecrets;
|
||||
};
|
||||
|
||||
// this is a utility function for secret modification
|
||||
// this will check given secret name blind index exist or not
|
||||
// if its a created secret set isNew to true
|
||||
// thus if these blindindex exist it will throw an error
|
||||
// vice versa when u need to check for updated secret
|
||||
// this will also return the blind index grouped by secretName
|
||||
const fnSecretBlindIndexCheck = async ({
|
||||
inputSecrets,
|
||||
folderId,
|
||||
isNew,
|
||||
userId,
|
||||
blindIndexCfg
|
||||
}: TFnSecretBlindIndexCheck) => {
|
||||
const blindIndex2KeyName: Record<string, string> = {}; // used at audit log point
|
||||
const keyName2BlindIndex = await Promise.all(
|
||||
inputSecrets.map(({ secretName }) => generateSecretBlindIndexBySalt(secretName, blindIndexCfg))
|
||||
).then((blindIndexes) =>
|
||||
blindIndexes.reduce<Record<string, string>>((prev, curr, i) => {
|
||||
// eslint-disable-next-line
|
||||
prev[inputSecrets[i].secretName] = curr;
|
||||
blindIndex2KeyName[curr] = inputSecrets[i].secretName;
|
||||
return prev;
|
||||
}, {})
|
||||
);
|
||||
|
||||
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(({ secretName, type }) => ({
|
||||
blindIndex: keyName2BlindIndex[secretName],
|
||||
type: type || SecretType.Shared
|
||||
})),
|
||||
userId
|
||||
);
|
||||
|
||||
if (isNew) {
|
||||
if (secrets.length) throw new BadRequestError({ message: "Secret already exist" });
|
||||
} else if (secrets.length !== inputSecrets.length)
|
||||
throw new BadRequestError({
|
||||
message: `Secret not found: blind index ${JSON.stringify(keyName2BlindIndex)}`
|
||||
});
|
||||
|
||||
return { blindIndex2KeyName, keyName2BlindIndex, secrets };
|
||||
};
|
||||
|
||||
// this is used when secret blind index already exist
|
||||
// mainly for secret approval
|
||||
const fnSecretBlindIndexCheckV2 = async ({ inputSecrets, folderId, userId }: TFnSecretBlindIndexCheckV2) => {
|
||||
@@ -281,6 +204,8 @@ export const secretServiceFactory = ({
|
||||
subject(ProjectPermissionSub.Secrets, { environment, secretPath: path })
|
||||
);
|
||||
|
||||
await projectDAL.checkProjectUpgradeStatus(projectId);
|
||||
|
||||
const folder = await folderDAL.findBySecretPath(projectId, environment, path);
|
||||
if (!folder) throw new BadRequestError({ message: "Folder not found", name: "Create secret" });
|
||||
const folderId = folder.id;
|
||||
@@ -297,7 +222,8 @@ export const secretServiceFactory = ({
|
||||
folderId,
|
||||
isNew: true,
|
||||
userId: actorId,
|
||||
blindIndexCfg
|
||||
blindIndexCfg,
|
||||
secretDAL
|
||||
});
|
||||
|
||||
// if user creating personal check its shared also exist
|
||||
@@ -334,6 +260,10 @@ export const secretServiceFactory = ({
|
||||
tags: inputSecret.tags
|
||||
}
|
||||
],
|
||||
secretDAL,
|
||||
secretVersionDAL,
|
||||
secretTagDAL,
|
||||
secretVersionTagDAL,
|
||||
tx
|
||||
})
|
||||
);
|
||||
@@ -359,6 +289,12 @@ export const secretServiceFactory = ({
|
||||
subject(ProjectPermissionSub.Secrets, { environment, secretPath: path })
|
||||
);
|
||||
|
||||
await projectDAL.checkProjectUpgradeStatus(projectId);
|
||||
|
||||
if (inputSecret.newSecretName === "") {
|
||||
throw new BadRequestError({ message: "New secret name cannot be empty" });
|
||||
}
|
||||
|
||||
const folder = await folderDAL.findBySecretPath(projectId, environment, path);
|
||||
if (!folder) throw new BadRequestError({ message: "Folder not found", name: "Create secret" });
|
||||
const folderId = folder.id;
|
||||
@@ -375,7 +311,8 @@ export const secretServiceFactory = ({
|
||||
folderId,
|
||||
isNew: false,
|
||||
blindIndexCfg,
|
||||
userId: actorId
|
||||
userId: actorId,
|
||||
secretDAL
|
||||
});
|
||||
if (inputSecret.newSecretName && inputSecret.type === SecretType.Personal) {
|
||||
throw new BadRequestError({ message: "Personal secret cannot change the key name" });
|
||||
@@ -387,7 +324,8 @@ export const secretServiceFactory = ({
|
||||
inputSecrets: [{ secretName: inputSecret.newSecretName }],
|
||||
folderId,
|
||||
isNew: true,
|
||||
blindIndexCfg
|
||||
blindIndexCfg,
|
||||
secretDAL
|
||||
});
|
||||
newSecretNameBlindIndex = kN2NewBlindIndex[inputSecret.newSecretName];
|
||||
}
|
||||
@@ -459,6 +397,8 @@ export const secretServiceFactory = ({
|
||||
subject(ProjectPermissionSub.Secrets, { environment, secretPath: path })
|
||||
);
|
||||
|
||||
await projectDAL.checkProjectUpgradeStatus(projectId);
|
||||
|
||||
const folder = await folderDAL.findBySecretPath(projectId, environment, path);
|
||||
if (!folder) throw new BadRequestError({ message: "Folder not found", name: "Create secret" });
|
||||
const folderId = folder.id;
|
||||
@@ -474,7 +414,8 @@ export const secretServiceFactory = ({
|
||||
inputSecrets: [{ secretName: inputSecret.secretName }],
|
||||
folderId,
|
||||
isNew: false,
|
||||
blindIndexCfg
|
||||
blindIndexCfg,
|
||||
secretDAL
|
||||
});
|
||||
|
||||
const deletedSecret = await secretDAL.transaction(async (tx) =>
|
||||
@@ -650,6 +591,8 @@ export const secretServiceFactory = ({
|
||||
subject(ProjectPermissionSub.Secrets, { environment, secretPath: path })
|
||||
);
|
||||
|
||||
await projectDAL.checkProjectUpgradeStatus(projectId);
|
||||
|
||||
const folder = await folderDAL.findBySecretPath(projectId, environment, path);
|
||||
if (!folder) throw new BadRequestError({ message: "Folder not found", name: "Create secret" });
|
||||
const folderId = folder.id;
|
||||
@@ -661,7 +604,8 @@ export const secretServiceFactory = ({
|
||||
inputSecrets,
|
||||
folderId,
|
||||
isNew: true,
|
||||
blindIndexCfg
|
||||
blindIndexCfg,
|
||||
secretDAL
|
||||
});
|
||||
|
||||
// get all tags
|
||||
@@ -680,6 +624,10 @@ export const secretServiceFactory = ({
|
||||
keyEncoding: SecretKeyEncoding.UTF8
|
||||
})),
|
||||
folderId,
|
||||
secretDAL,
|
||||
secretVersionDAL,
|
||||
secretTagDAL,
|
||||
secretVersionTagDAL,
|
||||
tx
|
||||
})
|
||||
);
|
||||
@@ -705,6 +653,8 @@ export const secretServiceFactory = ({
|
||||
subject(ProjectPermissionSub.Secrets, { environment, secretPath: path })
|
||||
);
|
||||
|
||||
await projectDAL.checkProjectUpgradeStatus(projectId);
|
||||
|
||||
const folder = await folderDAL.findBySecretPath(projectId, environment, path);
|
||||
if (!folder) throw new BadRequestError({ message: "Folder not found", name: "Create secret" });
|
||||
const folderId = folder.id;
|
||||
@@ -716,7 +666,8 @@ export const secretServiceFactory = ({
|
||||
inputSecrets,
|
||||
folderId,
|
||||
isNew: false,
|
||||
blindIndexCfg
|
||||
blindIndexCfg,
|
||||
secretDAL
|
||||
});
|
||||
|
||||
// now find any secret that needs to update its name
|
||||
@@ -726,7 +677,8 @@ export const secretServiceFactory = ({
|
||||
inputSecrets: nameUpdatedSecrets,
|
||||
folderId,
|
||||
isNew: true,
|
||||
blindIndexCfg
|
||||
blindIndexCfg,
|
||||
secretDAL
|
||||
});
|
||||
|
||||
// get all tags
|
||||
@@ -776,6 +728,8 @@ export const secretServiceFactory = ({
|
||||
subject(ProjectPermissionSub.Secrets, { environment, secretPath: path })
|
||||
);
|
||||
|
||||
await projectDAL.checkProjectUpgradeStatus(projectId);
|
||||
|
||||
const folder = await folderDAL.findBySecretPath(projectId, environment, path);
|
||||
if (!folder) throw new BadRequestError({ message: "Folder not found", name: "Create secret" });
|
||||
const folderId = folder.id;
|
||||
@@ -787,7 +741,8 @@ export const secretServiceFactory = ({
|
||||
inputSecrets,
|
||||
folderId,
|
||||
isNew: false,
|
||||
blindIndexCfg
|
||||
blindIndexCfg,
|
||||
secretDAL
|
||||
});
|
||||
|
||||
const secretsDeleted = await secretDAL.transaction(async (tx) =>
|
||||
@@ -871,6 +826,98 @@ export const secretServiceFactory = ({
|
||||
});
|
||||
return decryptSecretRaw(secret, botKey);
|
||||
};
|
||||
|
||||
const createManySecretsRawHelper = async ({ // TODO: place on top
|
||||
projectId,
|
||||
environment,
|
||||
path,
|
||||
secrets,
|
||||
userId
|
||||
}: TCreateManySecretsRawHelper) => {
|
||||
const botKey = await projectBotService.getBotKey(projectId);
|
||||
if (!botKey) throw new BadRequestError({ message: "Project bot not found", name: "bot_not_found_error" });
|
||||
|
||||
await projectDAL.checkProjectUpgradeStatus(projectId);
|
||||
|
||||
const folder = await folderDAL.findBySecretPath(projectId, environment, path);
|
||||
if (!folder) throw new BadRequestError({ message: "Folder not found", name: "Create secret" });
|
||||
const folderId = folder.id;
|
||||
|
||||
const blindIndexCfg = await secretBlindIndexDAL.findOne({ projectId });
|
||||
if (!blindIndexCfg) throw new BadRequestError({ message: "Blind index not found", name: "Update secret" });
|
||||
|
||||
const inputSecrets = await Promise.all(
|
||||
secrets.map(async (secret) => {
|
||||
const secretKeyEncrypted = encryptSymmetric128BitHexKeyUTF8(secret.secretName, botKey);
|
||||
const secretValueEncrypted = encryptSymmetric128BitHexKeyUTF8(secret.secretValue || "", botKey);
|
||||
const secretCommentEncrypted = encryptSymmetric128BitHexKeyUTF8(secret.secretComment || "", botKey);
|
||||
|
||||
if (secret.type === SecretType.Personal) {
|
||||
const sharedExist = await secretDAL.findOne({
|
||||
secretBlindIndex: keyName2BlindIndex[secret.secretName],
|
||||
folderId,
|
||||
type: SecretType.Shared
|
||||
});
|
||||
|
||||
if (!sharedExist)
|
||||
throw new BadRequestError({
|
||||
message: "Failed to create personal secret override for no corresponding shared secret"
|
||||
});
|
||||
|
||||
if (!userId) throw new BadRequestError({ message: "Missing user id for personal secret" });
|
||||
}
|
||||
|
||||
const tags = secret.tags ? await secretTagDAL.findManyTagsById(projectId, secret.tags) : [];
|
||||
if ((secret.tags || []).length !== tags.length) throw new BadRequestError({ message: "Tag not found" });
|
||||
|
||||
return ({
|
||||
type: secret.type,
|
||||
userId: secret.type === SecretType.Personal ? userId : null,
|
||||
secretName: secret.secretName,
|
||||
secretKeyCiphertext: secretKeyEncrypted.ciphertext,
|
||||
secretKeyIV: secretKeyEncrypted.iv,
|
||||
secretKeyTag: secretKeyEncrypted.tag,
|
||||
secretValueCiphertext: secretValueEncrypted.ciphertext,
|
||||
secretValueIV: secretValueEncrypted.iv,
|
||||
secretValueTag: secretValueEncrypted.tag,
|
||||
secretCommentCiphertext: secretCommentEncrypted.ciphertext,
|
||||
secretCommentIV: secretCommentEncrypted.iv,
|
||||
secretCommentTag: secretCommentEncrypted.tag,
|
||||
skipMultilineEncoding: secret.skipMultilineEncoding,
|
||||
tags: secret.tags
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
// insert operation
|
||||
const { keyName2BlindIndex } = await fnSecretBlindIndexCheck({
|
||||
inputSecrets,
|
||||
folderId,
|
||||
isNew: true,
|
||||
blindIndexCfg,
|
||||
secretDAL
|
||||
});
|
||||
|
||||
const newSecrets = await secretDAL.transaction(async (tx) =>
|
||||
fnSecretBulkInsert({
|
||||
inputSecrets: inputSecrets.map(({ secretName, ...el }) => ({
|
||||
...el,
|
||||
version: 0,
|
||||
secretBlindIndex: keyName2BlindIndex[secretName],
|
||||
algorithm: SecretEncryptionAlgo.AES_256_GCM,
|
||||
keyEncoding: SecretKeyEncoding.UTF8
|
||||
})),
|
||||
folderId,
|
||||
secretDAL,
|
||||
secretVersionDAL,
|
||||
secretTagDAL,
|
||||
secretVersionTagDAL,
|
||||
tx
|
||||
})
|
||||
);
|
||||
|
||||
return newSecrets;
|
||||
}
|
||||
|
||||
const createSecretRaw = async ({
|
||||
secretName,
|
||||
|
||||
Reference in New Issue
Block a user