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
+9 -3
View File
@@ -493,6 +493,9 @@ export const registerRoutes = async (
authDAL, authDAL,
userDAL userDAL
}); });
const projectBotService = projectBotServiceFactory({ permissionService, projectBotDAL, projectDAL });
const orgService = orgServiceFactory({ const orgService = orgServiceFactory({
userAliasDAL, userAliasDAL,
identityMetadataDAL, identityMetadataDAL,
@@ -515,7 +518,8 @@ export const registerRoutes = async (
userDAL, userDAL,
groupDAL, groupDAL,
orgBotDAL, orgBotDAL,
oidcConfigDAL oidcConfigDAL,
projectBotService
}); });
const signupService = authSignupServiceFactory({ const signupService = authSignupServiceFactory({
tokenService, tokenService,
@@ -574,7 +578,6 @@ export const registerRoutes = async (
secretScanningDAL, secretScanningDAL,
secretScanningQueue secretScanningQueue
}); });
const projectBotService = projectBotServiceFactory({ permissionService, projectBotDAL, projectDAL });
const projectMembershipService = projectMembershipServiceFactory({ const projectMembershipService = projectMembershipServiceFactory({
projectMembershipDAL, projectMembershipDAL,
@@ -838,7 +841,10 @@ export const registerRoutes = async (
integrationAuthDAL, integrationAuthDAL,
snapshotDAL, snapshotDAL,
snapshotSecretV2BridgeDAL, snapshotSecretV2BridgeDAL,
secretApprovalRequestDAL secretApprovalRequestDAL,
projectKeyDAL,
projectUserMembershipRoleDAL,
orgService
}); });
const secretImportService = secretImportServiceFactory({ const secretImportService = secretImportServiceFactory({
licenseService, licenseService,
+76 -18
View File
@@ -41,8 +41,9 @@ import { TAuthTokenServiceFactory } from "../auth-token/auth-token-service";
import { TokenType } from "../auth-token/auth-token-types"; import { TokenType } from "../auth-token/auth-token-types";
import { TIdentityMetadataDALFactory } from "../identity/identity-metadata-dal"; import { TIdentityMetadataDALFactory } from "../identity/identity-metadata-dal";
import { TProjectDALFactory } from "../project/project-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 { TProjectBotDALFactory } from "../project-bot/project-bot-dal";
import { TProjectBotServiceFactory } from "../project-bot/project-bot-service";
import { TProjectKeyDALFactory } from "../project-key/project-key-dal"; import { TProjectKeyDALFactory } from "../project-key/project-key-dal";
import { TProjectMembershipDALFactory } from "../project-membership/project-membership-dal"; import { TProjectMembershipDALFactory } from "../project-membership/project-membership-dal";
import { TProjectUserMembershipRoleDALFactory } from "../project-membership/project-user-membership-role-dal"; import { TProjectUserMembershipRoleDALFactory } from "../project-membership/project-user-membership-role-dal";
@@ -80,7 +81,7 @@ type TOrgServiceFactoryDep = {
TProjectMembershipDALFactory, TProjectMembershipDALFactory,
"findProjectMembershipsByUserId" | "delete" | "create" | "find" | "insertMany" | "transaction" "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">; orgMembershipDAL: Pick<TOrgMembershipDALFactory, "findOrgMembershipById" | "findOne" | "findById">;
incidentContactDAL: TIncidentContactsDALFactory; incidentContactDAL: TIncidentContactsDALFactory;
samlConfigDAL: Pick<TSamlConfigDALFactory, "findOne" | "findEnforceableSamlCfg">; samlConfigDAL: Pick<TSamlConfigDALFactory, "findOne" | "findEnforceableSamlCfg">;
@@ -94,8 +95,9 @@ type TOrgServiceFactoryDep = {
>; >;
projectUserAdditionalPrivilegeDAL: Pick<TProjectUserAdditionalPrivilegeDALFactory, "delete">; projectUserAdditionalPrivilegeDAL: Pick<TProjectUserAdditionalPrivilegeDALFactory, "delete">;
projectRoleDAL: Pick<TProjectRoleDALFactory, "find">; projectRoleDAL: Pick<TProjectRoleDALFactory, "find">;
projectBotDAL: Pick<TProjectBotDALFactory, "findOne">; projectBotDAL: Pick<TProjectBotDALFactory, "findOne" | "updateById">;
projectUserMembershipRoleDAL: Pick<TProjectUserMembershipRoleDALFactory, "insertMany">; projectUserMembershipRoleDAL: Pick<TProjectUserMembershipRoleDALFactory, "insertMany" | "create">;
projectBotService: Pick<TProjectBotServiceFactory, "getBotKey">;
}; };
export type TOrgServiceFactory = ReturnType<typeof orgServiceFactory>; export type TOrgServiceFactory = ReturnType<typeof orgServiceFactory>;
@@ -122,7 +124,8 @@ export const orgServiceFactory = ({
oidcConfigDAL, oidcConfigDAL,
projectBotDAL, projectBotDAL,
projectUserMembershipRoleDAL, projectUserMembershipRoleDAL,
identityMetadataDAL identityMetadataDAL,
projectBotService
}: TOrgServiceFactoryDep) => { }: TOrgServiceFactoryDep) => {
/* /*
* Get organization details by the organization id * Get organization details by the organization id
@@ -718,20 +721,67 @@ export const orgServiceFactory = ({
const customRolesGroupBySlug = groupBy(customRoles, ({ slug }) => slug); const customRolesGroupBySlug = groupBy(customRoles, ({ slug }) => slug);
const ghostUser = await projectDAL.findProjectGhostUser(projectId, tx); // this will auto generate bot
if (!ghostUser) { const { botKey, bot: autoGeneratedBot } = await projectBotService.getBotKey(projectId, true);
throw new NotFoundError({
name: "InviteUser",
message: "Failed to find project owner"
});
}
const ghostUserLatestKey = await projectKeyDAL.findLatestProjectKey(ghostUser.id, projectId, tx); const ghostUser = await projectDAL.findProjectGhostUser(projectId, tx);
if (!ghostUserLatestKey) { let ghostUserId = ghostUser?.id;
throw new NotFoundError({
name: "InviteUser", // backfill missing ghost user
message: "Failed to find project owner's latest key" 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); 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({ const botPrivateKey = infisicalSymmetricDecrypt({
keyEncoding: bot.keyEncoding as SecretKeyEncoding, keyEncoding: bot.keyEncoding as SecretKeyEncoding,
iv: bot.iv, iv: bot.iv,
@@ -785,7 +843,7 @@ export const orgServiceFactory = ({
newWsMembers.map((el) => ({ newWsMembers.map((el) => ({
encryptedKey: el.workspaceEncryptedKey, encryptedKey: el.workspaceEncryptedKey,
nonce: el.workspaceEncryptedNonce, nonce: el.workspaceEncryptedNonce,
senderId: ghostUser.id, senderId: ghostUserId,
receiverId: el.orgMembershipId, receiverId: el.orgMembershipId,
projectId projectId
})), })),
@@ -24,14 +24,14 @@ export const getBotKeyFnFactory = (
projectBotDAL: TProjectBotDALFactory, projectBotDAL: TProjectBotDALFactory,
projectDAL: Pick<TProjectDALFactory, "findById"> projectDAL: Pick<TProjectDALFactory, "findById">
) => { ) => {
const getBotKeyFn = async (projectId: string) => { const getBotKeyFn = async (projectId: string, shouldGetBotKey?: boolean) => {
const project = await projectDAL.findById(projectId); const project = await projectDAL.findById(projectId);
if (!project) if (!project)
throw new NotFoundError({ throw new NotFoundError({
message: "Project not found during bot lookup. Are you sure you are using the correct project ID?" 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 }; return { project, shouldUseSecretV2Bridge: true };
} }
@@ -65,8 +65,9 @@ export const getBotKeyFnFactory = (
const { iv, tag, ciphertext, encoding, algorithm } = infisicalSymmetricEncypt(botKey.privateKey); const { iv, tag, ciphertext, encoding, algorithm } = infisicalSymmetricEncypt(botKey.privateKey);
const encryptedWorkspaceKey = encryptAsymmetric(workspaceKey, botKey.publicKey, userPrivateKey); const encryptedWorkspaceKey = encryptAsymmetric(workspaceKey, botKey.publicKey, userPrivateKey);
let botId;
if (!bot) { if (!bot) {
await projectBotDAL.create({ const newBot = await projectBotDAL.create({
name: "Infisical Bot (Ghost)", name: "Infisical Bot (Ghost)",
projectId, projectId,
isActive: true, isActive: true,
@@ -80,8 +81,9 @@ export const getBotKeyFnFactory = (
encryptedProjectKeyNonce: encryptedWorkspaceKey.nonce, encryptedProjectKeyNonce: encryptedWorkspaceKey.nonce,
senderId: projectV1Keys.userId senderId: projectV1Keys.userId
}); });
botId = newBot.id;
} else { } else {
await projectBotDAL.updateById(bot.id, { const updatedBot = await projectBotDAL.updateById(bot.id, {
isActive: true, isActive: true,
tag, tag,
iv, iv,
@@ -93,8 +95,10 @@ export const getBotKeyFnFactory = (
encryptedProjectKeyNonce: encryptedWorkspaceKey.nonce, encryptedProjectKeyNonce: encryptedWorkspaceKey.nonce,
senderId: projectV1Keys.userId 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 }); const botPrivateKey = getBotPrivateKey({ bot });
@@ -104,7 +108,7 @@ export const getBotKeyFnFactory = (
nonce: bot.encryptedProjectKeyNonce, nonce: bot.encryptedProjectKeyNonce,
publicKey: bot.sender.publicKey publicKey: bot.sender.publicKey
}); });
return { botKey, project, shouldUseSecretV2Bridge: false }; return { botKey, project, shouldUseSecretV2Bridge: false, bot: { id: bot.id } };
}; };
return getBotKeyFn; return getBotKeyFn;
@@ -27,8 +27,8 @@ export const projectBotServiceFactory = ({
}: TProjectBotServiceFactoryDep) => { }: TProjectBotServiceFactoryDep) => {
const getBotKeyFn = getBotKeyFnFactory(projectBotDAL, projectDAL); const getBotKeyFn = getBotKeyFnFactory(projectBotDAL, projectDAL);
const getBotKey = async (projectId: string) => { const getBotKey = async (projectId: string, shouldGetBotKey?: boolean) => {
return getBotKeyFn(projectId); return getBotKeyFn(projectId, shouldGetBotKey);
}; };
const findBotByProjectId = async ({ const findBotByProjectId = async ({
+69 -3
View File
@@ -17,6 +17,7 @@ import { TSnapshotSecretV2DALFactory } from "@app/ee/services/secret-snapshot/sn
import { KeyStorePrefixes, KeyStoreTtls, TKeyStoreFactory } from "@app/keystore/keystore"; import { KeyStorePrefixes, KeyStoreTtls, TKeyStoreFactory } from "@app/keystore/keystore";
import { getConfig } from "@app/lib/config/env"; import { getConfig } from "@app/lib/config/env";
import { decryptSymmetric128BitHexKeyUTF8 } from "@app/lib/crypto"; import { decryptSymmetric128BitHexKeyUTF8 } from "@app/lib/crypto";
import { infisicalSymmetricEncypt } from "@app/lib/crypto/encryption";
import { daysToMillisecond, secondsToMillis } from "@app/lib/dates"; import { daysToMillisecond, secondsToMillis } from "@app/lib/dates";
import { BadRequestError, NotFoundError } from "@app/lib/errors"; import { BadRequestError, NotFoundError } from "@app/lib/errors";
import { getTimeDifferenceInSeconds, groupBy, isSamePath, unique } from "@app/lib/fn"; 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 { TKmsServiceFactory } from "../kms/kms-service";
import { KmsDataKey } from "../kms/kms-types"; import { KmsDataKey } from "../kms/kms-types";
import { TOrgDALFactory } from "../org/org-dal"; import { TOrgDALFactory } from "../org/org-dal";
import { TOrgServiceFactory } from "../org/org-service";
import { TProjectDALFactory } from "../project/project-dal"; import { TProjectDALFactory } from "../project/project-dal";
import { createProjectKey } from "../project/project-fns";
import { TProjectBotServiceFactory } from "../project-bot/project-bot-service"; import { TProjectBotServiceFactory } from "../project-bot/project-bot-service";
import { TProjectEnvDALFactory } from "../project-env/project-env-dal"; 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 { 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 { TSecretFolderDALFactory } from "../secret-folder/secret-folder-dal";
import { TSecretImportDALFactory } from "../secret-import/secret-import-dal"; import { TSecretImportDALFactory } from "../secret-import/secret-import-dal";
import { fnSecretsV2FromImports } from "../secret-import/secret-import-fns"; import { fnSecretsV2FromImports } from "../secret-import/secret-import-fns";
@@ -77,7 +82,8 @@ type TSecretQueueFactoryDep = {
projectEnvDAL: Pick<TProjectEnvDALFactory, "findOne" | "find">; projectEnvDAL: Pick<TProjectEnvDALFactory, "findOne" | "find">;
projectDAL: TProjectDALFactory; projectDAL: TProjectDALFactory;
projectBotDAL: TProjectBotDALFactory; projectBotDAL: TProjectBotDALFactory;
projectMembershipDAL: Pick<TProjectMembershipDALFactory, "findAllProjectMembers">; projectKeyDAL: Pick<TProjectKeyDALFactory, "create">;
projectMembershipDAL: Pick<TProjectMembershipDALFactory, "findAllProjectMembers" | "create">;
smtpService: TSmtpService; smtpService: TSmtpService;
orgDAL: Pick<TOrgDALFactory, "findOrgByProjectId">; orgDAL: Pick<TOrgDALFactory, "findOrgByProjectId">;
secretVersionDAL: TSecretVersionDALFactory; secretVersionDAL: TSecretVersionDALFactory;
@@ -95,6 +101,8 @@ type TSecretQueueFactoryDep = {
snapshotSecretV2BridgeDAL: Pick<TSnapshotSecretV2DALFactory, "insertMany" | "batchInsert">; snapshotSecretV2BridgeDAL: Pick<TSnapshotSecretV2DALFactory, "insertMany" | "batchInsert">;
keyStore: Pick<TKeyStoreFactory, "acquireLock" | "setItemWithExpiry" | "getItem">; keyStore: Pick<TKeyStoreFactory, "acquireLock" | "setItemWithExpiry" | "getItem">;
auditLogService: Pick<TAuditLogServiceFactory, "createAuditLog">; auditLogService: Pick<TAuditLogServiceFactory, "createAuditLog">;
orgService: Pick<TOrgServiceFactory, "addGhostUser">;
projectUserMembershipRoleDAL: Pick<TProjectUserMembershipRoleDALFactory, "create">;
}; };
export type TGetSecrets = { export type TGetSecrets = {
@@ -111,6 +119,8 @@ type TIntegrationSecret = Record<
string, string,
{ value: string; comment?: string; skipMultilineEncoding?: boolean | null | undefined } { value: string; comment?: string; skipMultilineEncoding?: boolean | null | undefined }
>; >;
// TODO(akhilmhdh): split this into multiple queue
export const secretQueueFactory = ({ export const secretQueueFactory = ({
queueService, queueService,
integrationDAL, integrationDAL,
@@ -141,7 +151,10 @@ export const secretQueueFactory = ({
snapshotSecretV2BridgeDAL, snapshotSecretV2BridgeDAL,
secretApprovalRequestDAL, secretApprovalRequestDAL,
keyStore, keyStore,
auditLogService auditLogService,
orgService,
projectUserMembershipRoleDAL,
projectKeyDAL
}: TSecretQueueFactoryDep) => { }: TSecretQueueFactoryDep) => {
const removeSecretReminder = async (dto: TRemoveSecretReminderDTO) => { const removeSecretReminder = async (dto: TRemoveSecretReminderDTO) => {
const appCfg = getConfig(); const appCfg = getConfig();
@@ -1028,11 +1041,13 @@ export const secretQueueFactory = ({
const { const {
botKey, botKey,
shouldUseSecretV2Bridge: isProjectUpgradedToV3, shouldUseSecretV2Bridge: isProjectUpgradedToV3,
project project,
bot
} = await projectBotService.getBotKey(projectId); } = await projectBotService.getBotKey(projectId);
if (isProjectUpgradedToV3 || project.upgradeStatus === ProjectUpgradeStatus.InProgress) { if (isProjectUpgradedToV3 || project.upgradeStatus === ProjectUpgradeStatus.InProgress) {
return; return;
} }
if (!botKey) throw new NotFoundError({ message: "Project bot not found" }); if (!botKey) throw new NotFoundError({ message: "Project bot not found" });
await projectDAL.updateById(projectId, { upgradeStatus: ProjectUpgradeStatus.InProgress }); await projectDAL.updateById(projectId, { upgradeStatus: ProjectUpgradeStatus.InProgress });
@@ -1044,6 +1059,57 @@ export const secretQueueFactory = ({
const folders = await folderDAL.findByProjectId(projectId); const folders = await folderDAL.findByProjectId(projectId);
// except secret version and snapshot migrate rest of everything first in a transaction // except secret version and snapshot migrate rest of everything first in a transaction
await secretDAL.transaction(async (tx) => { 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) { for (const folder of folders) {
const folderId = folder.id; const folderId = folder.id;
/* /*