Merge pull request #2584 from akhilmhdh/fix/upgrade-v1-to-v2

feat: added auto ghost user creation and fixed ghost user creation in v1
This commit is contained in:
Maidul Islam
2024-10-14 13:55:31 -07:00
committed by GitHub
5 changed files with 166 additions and 32 deletions

View File

@@ -493,6 +493,9 @@ export const registerRoutes = async (
authDAL,
userDAL
});
const projectBotService = projectBotServiceFactory({ permissionService, projectBotDAL, projectDAL });
const orgService = orgServiceFactory({
userAliasDAL,
identityMetadataDAL,
@@ -515,7 +518,8 @@ export const registerRoutes = async (
userDAL,
groupDAL,
orgBotDAL,
oidcConfigDAL
oidcConfigDAL,
projectBotService
});
const signupService = authSignupServiceFactory({
tokenService,
@@ -574,7 +578,6 @@ export const registerRoutes = async (
secretScanningDAL,
secretScanningQueue
});
const projectBotService = projectBotServiceFactory({ permissionService, projectBotDAL, projectDAL });
const projectMembershipService = projectMembershipServiceFactory({
projectMembershipDAL,
@@ -838,7 +841,10 @@ export const registerRoutes = async (
integrationAuthDAL,
snapshotDAL,
snapshotSecretV2BridgeDAL,
secretApprovalRequestDAL
secretApprovalRequestDAL,
projectKeyDAL,
projectUserMembershipRoleDAL,
orgService
});
const secretImportService = secretImportServiceFactory({
licenseService,

View File

@@ -41,8 +41,9 @@ import { TAuthTokenServiceFactory } from "../auth-token/auth-token-service";
import { TokenType } from "../auth-token/auth-token-types";
import { TIdentityMetadataDALFactory } from "../identity/identity-metadata-dal";
import { TProjectDALFactory } from "../project/project-dal";
import { assignWorkspaceKeysToMembers } from "../project/project-fns";
import { assignWorkspaceKeysToMembers, createProjectKey } from "../project/project-fns";
import { TProjectBotDALFactory } from "../project-bot/project-bot-dal";
import { TProjectBotServiceFactory } from "../project-bot/project-bot-service";
import { TProjectKeyDALFactory } from "../project-key/project-key-dal";
import { TProjectMembershipDALFactory } from "../project-membership/project-membership-dal";
import { TProjectUserMembershipRoleDALFactory } from "../project-membership/project-user-membership-role-dal";
@@ -80,7 +81,7 @@ type TOrgServiceFactoryDep = {
TProjectMembershipDALFactory,
"findProjectMembershipsByUserId" | "delete" | "create" | "find" | "insertMany" | "transaction"
>;
projectKeyDAL: Pick<TProjectKeyDALFactory, "find" | "delete" | "insertMany" | "findLatestProjectKey">;
projectKeyDAL: Pick<TProjectKeyDALFactory, "find" | "delete" | "insertMany" | "findLatestProjectKey" | "create">;
orgMembershipDAL: Pick<TOrgMembershipDALFactory, "findOrgMembershipById" | "findOne" | "findById">;
incidentContactDAL: TIncidentContactsDALFactory;
samlConfigDAL: Pick<TSamlConfigDALFactory, "findOne" | "findEnforceableSamlCfg">;
@@ -94,8 +95,9 @@ type TOrgServiceFactoryDep = {
>;
projectUserAdditionalPrivilegeDAL: Pick<TProjectUserAdditionalPrivilegeDALFactory, "delete">;
projectRoleDAL: Pick<TProjectRoleDALFactory, "find">;
projectBotDAL: Pick<TProjectBotDALFactory, "findOne">;
projectUserMembershipRoleDAL: Pick<TProjectUserMembershipRoleDALFactory, "insertMany">;
projectBotDAL: Pick<TProjectBotDALFactory, "findOne" | "updateById">;
projectUserMembershipRoleDAL: Pick<TProjectUserMembershipRoleDALFactory, "insertMany" | "create">;
projectBotService: Pick<TProjectBotServiceFactory, "getBotKey">;
};
export type TOrgServiceFactory = ReturnType<typeof orgServiceFactory>;
@@ -122,7 +124,8 @@ export const orgServiceFactory = ({
oidcConfigDAL,
projectBotDAL,
projectUserMembershipRoleDAL,
identityMetadataDAL
identityMetadataDAL,
projectBotService
}: TOrgServiceFactoryDep) => {
/*
* Get organization details by the organization id
@@ -718,20 +721,67 @@ export const orgServiceFactory = ({
const customRolesGroupBySlug = groupBy(customRoles, ({ slug }) => slug);
const ghostUser = await projectDAL.findProjectGhostUser(projectId, tx);
if (!ghostUser) {
throw new NotFoundError({
name: "InviteUser",
message: "Failed to find project owner"
});
}
// this will auto generate bot
const { botKey, bot: autoGeneratedBot } = await projectBotService.getBotKey(projectId, true);
const ghostUserLatestKey = await projectKeyDAL.findLatestProjectKey(ghostUser.id, projectId, tx);
if (!ghostUserLatestKey) {
throw new NotFoundError({
name: "InviteUser",
message: "Failed to find project owner's latest key"
const ghostUser = await projectDAL.findProjectGhostUser(projectId, tx);
let ghostUserId = ghostUser?.id;
// backfill missing ghost user
if (!ghostUserId) {
const newGhostUser = await addGhostUser(project.orgId, tx);
const projectMembership = await projectMembershipDAL.create(
{
userId: newGhostUser.user.id,
projectId: project.id
},
tx
);
await projectUserMembershipRoleDAL.create(
{ projectMembershipId: projectMembership.id, role: ProjectMembershipRole.Admin },
tx
);
const { key: encryptedProjectKey, iv: encryptedProjectKeyIv } = createProjectKey({
publicKey: newGhostUser.keys.publicKey,
privateKey: newGhostUser.keys.plainPrivateKey,
plainProjectKey: botKey
});
// 4. Save the project key for the ghost user.
await projectKeyDAL.create(
{
projectId: project.id,
receiverId: newGhostUser.user.id,
encryptedKey: encryptedProjectKey,
nonce: encryptedProjectKeyIv,
senderId: newGhostUser.user.id
},
tx
);
const { iv, tag, ciphertext, encoding, algorithm } = infisicalSymmetricEncypt(
newGhostUser.keys.plainPrivateKey
);
if (autoGeneratedBot) {
await projectBotDAL.updateById(
autoGeneratedBot.id,
{
tag,
iv,
encryptedProjectKey,
encryptedProjectKeyNonce: encryptedProjectKeyIv,
encryptedPrivateKey: ciphertext,
isActive: true,
publicKey: newGhostUser.keys.publicKey,
senderId: newGhostUser.user.id,
algorithm,
keyEncoding: encoding
},
tx
);
}
ghostUserId = newGhostUser.user.id;
}
const bot = await projectBotDAL.findOne({ projectId }, tx);
@@ -742,6 +792,14 @@ export const orgServiceFactory = ({
});
}
const ghostUserLatestKey = await projectKeyDAL.findLatestProjectKey(ghostUserId, projectId, tx);
if (!ghostUserLatestKey) {
throw new NotFoundError({
name: "InviteUser",
message: "Failed to find project owner's latest key"
});
}
const botPrivateKey = infisicalSymmetricDecrypt({
keyEncoding: bot.keyEncoding as SecretKeyEncoding,
iv: bot.iv,
@@ -785,7 +843,7 @@ export const orgServiceFactory = ({
newWsMembers.map((el) => ({
encryptedKey: el.workspaceEncryptedKey,
nonce: el.workspaceEncryptedNonce,
senderId: ghostUser.id,
senderId: ghostUserId,
receiverId: el.orgMembershipId,
projectId
})),

View File

@@ -24,14 +24,14 @@ export const getBotKeyFnFactory = (
projectBotDAL: TProjectBotDALFactory,
projectDAL: Pick<TProjectDALFactory, "findById">
) => {
const getBotKeyFn = async (projectId: string) => {
const getBotKeyFn = async (projectId: string, shouldGetBotKey?: boolean) => {
const project = await projectDAL.findById(projectId);
if (!project)
throw new NotFoundError({
message: "Project not found during bot lookup. Are you sure you are using the correct project ID?"
});
if (project.version === 3) {
if (project.version === 3 && !shouldGetBotKey) {
return { project, shouldUseSecretV2Bridge: true };
}
@@ -65,8 +65,9 @@ export const getBotKeyFnFactory = (
const { iv, tag, ciphertext, encoding, algorithm } = infisicalSymmetricEncypt(botKey.privateKey);
const encryptedWorkspaceKey = encryptAsymmetric(workspaceKey, botKey.publicKey, userPrivateKey);
let botId;
if (!bot) {
await projectBotDAL.create({
const newBot = await projectBotDAL.create({
name: "Infisical Bot (Ghost)",
projectId,
isActive: true,
@@ -80,8 +81,9 @@ export const getBotKeyFnFactory = (
encryptedProjectKeyNonce: encryptedWorkspaceKey.nonce,
senderId: projectV1Keys.userId
});
botId = newBot.id;
} else {
await projectBotDAL.updateById(bot.id, {
const updatedBot = await projectBotDAL.updateById(bot.id, {
isActive: true,
tag,
iv,
@@ -93,8 +95,10 @@ export const getBotKeyFnFactory = (
encryptedProjectKeyNonce: encryptedWorkspaceKey.nonce,
senderId: projectV1Keys.userId
});
botId = updatedBot.id;
}
return { botKey: workspaceKey, project, shouldUseSecretV2Bridge: false };
return { botKey: workspaceKey, project, shouldUseSecretV2Bridge: false, bot: { id: botId } };
}
const botPrivateKey = getBotPrivateKey({ bot });
@@ -104,7 +108,7 @@ export const getBotKeyFnFactory = (
nonce: bot.encryptedProjectKeyNonce,
publicKey: bot.sender.publicKey
});
return { botKey, project, shouldUseSecretV2Bridge: false };
return { botKey, project, shouldUseSecretV2Bridge: false, bot: { id: bot.id } };
};
return getBotKeyFn;

View File

@@ -27,8 +27,8 @@ export const projectBotServiceFactory = ({
}: TProjectBotServiceFactoryDep) => {
const getBotKeyFn = getBotKeyFnFactory(projectBotDAL, projectDAL);
const getBotKey = async (projectId: string) => {
return getBotKeyFn(projectId);
const getBotKey = async (projectId: string, shouldGetBotKey?: boolean) => {
return getBotKeyFn(projectId, shouldGetBotKey);
};
const findBotByProjectId = async ({

View File

@@ -17,6 +17,7 @@ import { TSnapshotSecretV2DALFactory } from "@app/ee/services/secret-snapshot/sn
import { KeyStorePrefixes, KeyStoreTtls, TKeyStoreFactory } from "@app/keystore/keystore";
import { getConfig } from "@app/lib/config/env";
import { decryptSymmetric128BitHexKeyUTF8 } from "@app/lib/crypto";
import { infisicalSymmetricEncypt } from "@app/lib/crypto/encryption";
import { daysToMillisecond, secondsToMillis } from "@app/lib/dates";
import { BadRequestError, NotFoundError } from "@app/lib/errors";
import { getTimeDifferenceInSeconds, groupBy, isSamePath, unique } from "@app/lib/fn";
@@ -37,10 +38,14 @@ import { syncIntegrationSecrets } from "../integration-auth/integration-sync-sec
import { TKmsServiceFactory } from "../kms/kms-service";
import { KmsDataKey } from "../kms/kms-types";
import { TOrgDALFactory } from "../org/org-dal";
import { TOrgServiceFactory } from "../org/org-service";
import { TProjectDALFactory } from "../project/project-dal";
import { createProjectKey } from "../project/project-fns";
import { TProjectBotServiceFactory } from "../project-bot/project-bot-service";
import { TProjectEnvDALFactory } from "../project-env/project-env-dal";
import { TProjectKeyDALFactory } from "../project-key/project-key-dal";
import { TProjectMembershipDALFactory } from "../project-membership/project-membership-dal";
import { TProjectUserMembershipRoleDALFactory } from "../project-membership/project-user-membership-role-dal";
import { TSecretFolderDALFactory } from "../secret-folder/secret-folder-dal";
import { TSecretImportDALFactory } from "../secret-import/secret-import-dal";
import { fnSecretsV2FromImports } from "../secret-import/secret-import-fns";
@@ -77,7 +82,8 @@ type TSecretQueueFactoryDep = {
projectEnvDAL: Pick<TProjectEnvDALFactory, "findOne" | "find">;
projectDAL: TProjectDALFactory;
projectBotDAL: TProjectBotDALFactory;
projectMembershipDAL: Pick<TProjectMembershipDALFactory, "findAllProjectMembers">;
projectKeyDAL: Pick<TProjectKeyDALFactory, "create">;
projectMembershipDAL: Pick<TProjectMembershipDALFactory, "findAllProjectMembers" | "create">;
smtpService: TSmtpService;
orgDAL: Pick<TOrgDALFactory, "findOrgByProjectId">;
secretVersionDAL: TSecretVersionDALFactory;
@@ -95,6 +101,8 @@ type TSecretQueueFactoryDep = {
snapshotSecretV2BridgeDAL: Pick<TSnapshotSecretV2DALFactory, "insertMany" | "batchInsert">;
keyStore: Pick<TKeyStoreFactory, "acquireLock" | "setItemWithExpiry" | "getItem">;
auditLogService: Pick<TAuditLogServiceFactory, "createAuditLog">;
orgService: Pick<TOrgServiceFactory, "addGhostUser">;
projectUserMembershipRoleDAL: Pick<TProjectUserMembershipRoleDALFactory, "create">;
};
export type TGetSecrets = {
@@ -111,6 +119,8 @@ type TIntegrationSecret = Record<
string,
{ value: string; comment?: string; skipMultilineEncoding?: boolean | null | undefined }
>;
// TODO(akhilmhdh): split this into multiple queue
export const secretQueueFactory = ({
queueService,
integrationDAL,
@@ -141,7 +151,10 @@ export const secretQueueFactory = ({
snapshotSecretV2BridgeDAL,
secretApprovalRequestDAL,
keyStore,
auditLogService
auditLogService,
orgService,
projectUserMembershipRoleDAL,
projectKeyDAL
}: TSecretQueueFactoryDep) => {
const removeSecretReminder = async (dto: TRemoveSecretReminderDTO) => {
const appCfg = getConfig();
@@ -1028,11 +1041,13 @@ export const secretQueueFactory = ({
const {
botKey,
shouldUseSecretV2Bridge: isProjectUpgradedToV3,
project
project,
bot
} = await projectBotService.getBotKey(projectId);
if (isProjectUpgradedToV3 || project.upgradeStatus === ProjectUpgradeStatus.InProgress) {
return;
}
if (!botKey) throw new NotFoundError({ message: "Project bot not found" });
await projectDAL.updateById(projectId, { upgradeStatus: ProjectUpgradeStatus.InProgress });
@@ -1044,6 +1059,57 @@ export const secretQueueFactory = ({
const folders = await folderDAL.findByProjectId(projectId);
// except secret version and snapshot migrate rest of everything first in a transaction
await secretDAL.transaction(async (tx) => {
// if project v1 create the project ghost user
if (project.version === ProjectVersion.V1) {
const ghostUser = await orgService.addGhostUser(project.orgId, tx);
const projectMembership = await projectMembershipDAL.create(
{
userId: ghostUser.user.id,
projectId: project.id
},
tx
);
await projectUserMembershipRoleDAL.create(
{ projectMembershipId: projectMembership.id, role: ProjectMembershipRole.Admin },
tx
);
const { key: encryptedProjectKey, iv: encryptedProjectKeyIv } = createProjectKey({
publicKey: ghostUser.keys.publicKey,
privateKey: ghostUser.keys.plainPrivateKey,
plainProjectKey: botKey
});
// 4. Save the project key for the ghost user.
await projectKeyDAL.create(
{
projectId: project.id,
receiverId: ghostUser.user.id,
encryptedKey: encryptedProjectKey,
nonce: encryptedProjectKeyIv,
senderId: ghostUser.user.id
},
tx
);
const { iv, tag, ciphertext, encoding, algorithm } = infisicalSymmetricEncypt(ghostUser.keys.plainPrivateKey);
await projectBotDAL.updateById(
bot.id,
{
tag,
iv,
encryptedProjectKey,
encryptedProjectKeyNonce: encryptedProjectKeyIv,
encryptedPrivateKey: ciphertext,
isActive: true,
publicKey: ghostUser.keys.publicKey,
senderId: ghostUser.user.id,
algorithm,
keyEncoding: encoding
},
tx
);
}
for (const folder of folders) {
const folderId = folder.id;
/*